From 9672475f70f5420897a47d51b989b41a185a3056 Mon Sep 17 00:00:00 2001 From: "Azamat H. Hackimov" Date: Mon, 1 Jul 2024 03:59:23 +0300 Subject: [PATCH] Update MVE playback on Windows There only one problem still persist on playback - slow filling sound buffer from std::deque on MSVC Debug mode due extremely slow performance of STL containers on that mode. On Release and RelWithDebInfo playback is fine. --- libmve/CMakeLists.txt | 4 +- libmve/lnx_sound.cpp | 21 +++-- libmve/lnx_sound.h | 2 +- libmve/mveplay.cpp | 21 ++--- libmve/win_sound.cpp | 155 ++++++++++++++++++++++++++++++++ libmve/win_sound.h | 55 ++++++++++++ movie/d3movie.cpp | 203 ++---------------------------------------- 7 files changed, 239 insertions(+), 222 deletions(-) create mode 100644 libmve/win_sound.cpp create mode 100644 libmve/win_sound.h diff --git a/libmve/CMakeLists.txt b/libmve/CMakeLists.txt index aed3b840..fb8bfec3 100644 --- a/libmve/CMakeLists.txt +++ b/libmve/CMakeLists.txt @@ -2,12 +2,12 @@ set(CPPS # mveasm.cpp # mvelibl.cpp # platform.cpp - lnxdsound.cpp - lnx_sound.cpp +# lnxdsound.cpp # d2x implementation decoder8.cpp decoder16.cpp + lnx_sound.cpp mve_audio.cpp mvelib.cpp mveplay.cpp diff --git a/libmve/lnx_sound.cpp b/libmve/lnx_sound.cpp index 4ccb77d5..104e021b 100644 --- a/libmve/lnx_sound.cpp +++ b/libmve/lnx_sound.cpp @@ -26,23 +26,22 @@ void MovieSoundDevice::SDLAudioCallback(void *userdata, unsigned char *stream, i for (int i = 0; i < len; i += 2) { int16_t sample = device->m_sound_buffer->front(); device->m_sound_buffer->pop_front(); - stream[i] = sample & 0xff; - stream[i + 1] = sample >> 8; + memcpy(&stream[i], &sample, 2); } } MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size, bool is_compressed) { SDL_AudioFormat format = (sample_size == 2) ? AUDIO_S16LSB : AUDIO_U8; - SDL_AudioSpec spec{ - .freq = sample_rate, - .format = format, - .channels = channels, - .size = buf_size, - .callback = &MovieSoundDevice::SDLAudioCallback, - .userdata = this, - }; - m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, SDL_AUDIO_ALLOW_ANY_CHANGE); + SDL_AudioSpec spec; + spec.freq = sample_rate; + spec.format = format; + spec.channels = channels; + spec.size = 4096; + spec.callback = &MovieSoundDevice::SDLAudioCallback; + spec.userdata = this; + + m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0); m_is_compressed = is_compressed; }; diff --git a/libmve/lnx_sound.h b/libmve/lnx_sound.h index d56d7a2e..d59aee33 100644 --- a/libmve/lnx_sound.h +++ b/libmve/lnx_sound.h @@ -33,7 +33,7 @@ public: MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size, bool is_compressed); ~MovieSoundDevice(); - [[nodiscard]] bool IsInitialized() const { return m_device_id > 0 ? true : false; } + [[nodiscard]] bool IsInitialized() const { return m_device_id > 0; } void static SDLAudioCallback(void *userdata, unsigned char *stream, int len); diff --git a/libmve/mveplay.cpp b/libmve/mveplay.cpp index 8f07fe4a..f22323f9 100644 --- a/libmve/mveplay.cpp +++ b/libmve/mveplay.cpp @@ -30,12 +30,8 @@ #include "mve_audio.h" #ifdef AUDIO -#ifdef _WIN32 - -#else #include "lnx_sound.h" #endif -#endif #define MVE_OPCODE_ENDOFSTREAM 0x00 // mcmd_end #define MVE_OPCODE_ENDOFCHUNK 0x01 // mcmd_next @@ -140,6 +136,13 @@ static void timer_start() { timer_started = 1; } +#ifdef AUDIO +static std::unique_ptr snd_ds; +static int mve_audio_enabled = 1; +#else +static int mve_audio_enabled = 0; +#endif + static void do_timer_wait() { uint64_t ts; uint64_t tv; @@ -153,6 +156,7 @@ static void do_timer_wait() { ts = timer_expire - tv; + //mprintf(0,"SLEEP for %llu us, buffer size is %d\n", ts, snd_ds->GetBuffer()->size()); timer_sleepmicroseconds(ts); end: @@ -163,13 +167,6 @@ end: * audio handlers *************************/ -#ifdef AUDIO -static std::unique_ptr snd_ds; -static int mve_audio_enabled = 1; -#else -static int mve_audio_enabled = 0; -#endif - static int create_audiobuf_handler(unsigned char major, unsigned char minor, unsigned char *data, int len, void *context) { #ifdef AUDIO @@ -188,7 +185,7 @@ static int create_audiobuf_handler(unsigned char major, unsigned char minor, uns is_compressed = true; } - snd_ds = std::make_unique(sample_rate, sample_size, channels, 4096, is_compressed); + snd_ds = std::make_unique(sample_rate, sample_size, channels, desired_buffer, is_compressed); #endif return 1; diff --git a/libmve/win_sound.cpp b/libmve/win_sound.cpp new file mode 100644 index 00000000..014d6cc4 --- /dev/null +++ b/libmve/win_sound.cpp @@ -0,0 +1,155 @@ +/* + * Descent 3 + * Copyright (C) 2024 Descent Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "win_sound.h" + +namespace D3 { + +MovieSoundDevice::MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size, + bool is_compressed) { + m_is_compressed = is_compressed; + + m_lpDS = nullptr; + m_lpDSB = nullptr; + + m_WFE.wFormatTag = WAVE_FORMAT_PCM; + m_WFE.nChannels = channels; + m_WFE.nSamplesPerSec = sample_rate; + m_WFE.nAvgBytesPerSec = sample_rate * channels * sample_size; + m_WFE.nBlockAlign = channels * sample_size; + m_WFE.wBitsPerSample = sample_size * 8; + + // Initialize DSound + if (FAILED(DirectSoundCreate(nullptr, &m_lpDS, nullptr))) { + return; + } + // Set Cooperative Level + HWND hWnd = GetForegroundWindow(); + if (hWnd == nullptr) { + hWnd = GetDesktopWindow(); + } + if (FAILED(m_lpDS->SetCooperativeLevel(hWnd, DSSCL_EXCLUSIVE))) { + return; + } + + // Create Primary Buffer + DSBUFFERDESC dsbd; + ZeroMemory(&dsbd, sizeof(dsbd)); + dsbd.dwSize = sizeof(DSBUFFERDESC); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = nullptr; + + LPDIRECTSOUNDBUFFER lpDSB = nullptr; + if (FAILED(m_lpDS->CreateSoundBuffer(&dsbd, &lpDSB, nullptr))) { + return; + } + + // Set Primary Buffer Format + if (FAILED(lpDSB->SetFormat(&m_WFE))) { + return; + } + + // Create Second Sound Buffer + dsbd.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS; + dsbd.dwBufferBytes = (buf_size + (buf_size >> 1)) & ~3; + dsbd.lpwfxFormat = &m_WFE; + + if (FAILED(m_lpDS->CreateSoundBuffer(&dsbd, &m_lpDSB, nullptr))) { + return; + } +} + +MovieSoundDevice::~MovieSoundDevice() { + if (m_lpDSB != nullptr) { + m_lpDSB->Stop(); + m_lpDSB->Release(); + } + m_sound_buffer.reset(); +} + +void MovieSoundDevice::Play() { + if (!m_lpDSB) + return; + + // Check if the DirectSound was created successfully + if (m_lpDS == nullptr) { + return; + } + + LPBYTE lpvAudio1 = nullptr, lpvAudio2 = nullptr; + DWORD dwBytesAudio1 = 0, dwBytesAudio2 = 0; + + if (FAILED(m_lpDSB->Lock(0, (DWORD)GetBuffer()->size() * 2, reinterpret_cast(&lpvAudio1), &dwBytesAudio1, + reinterpret_cast(&lpvAudio2), &dwBytesAudio2, DSBLOCK_FROMWRITECURSOR))) + return; + + if (dwBytesAudio1 > 0) { + memset(lpvAudio1, 0, dwBytesAudio1); + auto limit = std::min(dwBytesAudio1 / 2, (DWORD)GetBuffer()->size()); + for (int i = 0; i < limit; i += 2) { + int16_t sample = GetBuffer()->front(); + GetBuffer()->pop_front(); + lpvAudio1[i] = sample & 0xff; + lpvAudio1[i + 1] = sample >> 8; + } + } + + if (dwBytesAudio2 > 0) { + memset(lpvAudio2, 0, dwBytesAudio2); + auto limit = std::min(dwBytesAudio2 / 2, (DWORD)GetBuffer()->size()); + for (int i = 0; i < limit; i += 2) { + int16_t sample = GetBuffer()->front(); + GetBuffer()->pop_front(); + lpvAudio2[i] = sample & 0xff; + lpvAudio2[i + 1] = sample >> 8; + } + } + + m_lpDSB->Unlock(lpvAudio1, dwBytesAudio1, lpvAudio2, dwBytesAudio2); + + // Begin Play + m_lpDSB->Play(0, 0, DSBPLAY_LOOPING); + +} + +void MovieSoundDevice::Stop() { + if (m_lpDSB != nullptr) { + + m_lpDSB->Stop(); + + // Empty the buffer + LPVOID lpvAudio1 = nullptr; + DWORD dwBytesAudio1 = 0; + HRESULT hr = m_lpDSB->Lock(0, 0, &lpvAudio1, &dwBytesAudio1, nullptr, nullptr, DSBLOCK_ENTIREBUFFER); + if (FAILED(hr)) { + return; + } + memset(lpvAudio1, 0, dwBytesAudio1); + m_lpDSB->Unlock(lpvAudio1, dwBytesAudio1, nullptr, 0); + + // Move the current play position to begin + m_lpDSB->SetCurrentPosition(0); + } +} + +void MovieSoundDevice::Lock(){} + +void MovieSoundDevice::Unlock(){} + +} // namespace D3 diff --git a/libmve/win_sound.h b/libmve/win_sound.h new file mode 100644 index 00000000..5b06792c --- /dev/null +++ b/libmve/win_sound.h @@ -0,0 +1,55 @@ +/* + * Descent 3 + * Copyright (C) 2024 Descent Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef WIN_SOUND_H +#define WIN_SOUND_H + +#include +#include + +#include "win/DirectX/dsound.h" + +#include "sound_interface.h" + +namespace D3 { + +class MovieSoundDevice : ISoundDevice { +public: + MovieSoundDevice(int sample_rate, uint16_t sample_size, uint8_t channels, uint32_t buf_size, bool is_compressed); + ~MovieSoundDevice(); + + [[nodiscard]] bool IsInitialized() const { return m_lpDS != nullptr; } + + void Play() override; + void Stop() override; + void Lock() override; + void Unlock() override; + + using ISoundDevice::GetBuffer; + using ISoundDevice::IsCompressed; + +private: + WAVEFORMATEX m_WFE{}; + LPDIRECTSOUND m_lpDS; + LPDIRECTSOUNDBUFFER m_lpDSB; + +}; + +} // namespace D3 + +#endif // WIN_SOUND_H diff --git a/movie/d3movie.cpp b/movie/d3movie.cpp index 6fd7ea8a..317f2093 100644 --- a/movie/d3movie.cpp +++ b/movie/d3movie.cpp @@ -53,183 +53,6 @@ uint16_t CurrentPalette[256]; int Movie_bm_handle = -1; uint32_t Movie_current_framenum = 0; bool Movie_looping = false; - -#ifndef NO_MOVIES - -class MovieSoundBuffer : public ISysSoundBuffer { -private: - LnxSoundBuffer *m_pBuffer; - -public: - LnxSoundBuffer *GetLnxBuffer() { return m_pBuffer; } - MovieSoundBuffer(LnxSoundBuffer *buffer) : m_pBuffer(buffer) {} - - //////////////////////////// - // Release - //////////////////////////// - // Releases the memory associated with a sound buffer. This pointer is - // no longer valid after return. - // - // Returns: - // -1 : Invalid Parameter - // 0 : Ok! - int Release() { - return LnxSoundBuffer_Release(m_pBuffer); - // m_pBuffer->Release(); - delete this; - return 0; - } - - ////////////////////////////// - // SetVolume - ////////////////////////////// - // Sets the volume of a buffer. - // - // Returns: - // 0 : no error - // -1 : Cannot set volume - // -2 : Invalid parameters - int SetVolume(int32_t vol) { return LnxSoundBuffer_SetVolume(m_pBuffer, vol); } - - /////////////////////////// - // SetPan - /////////////////////////// - // Sets the pan of a buffer. - // - // Returns: - // 0 : no error - // -1 : Cannot set pan - // -2 : Invalid parameters - int SetPan(int32_t pan) { return LnxSoundBuffer_SetPan(m_pBuffer, pan); } - - ///////////////////////// - // Stop - ///////////////////////// - // Stops a buffer from playing - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int Stop() { return LnxSoundBuffer_Stop(m_pBuffer); } - - ///////////////////////// - // Play - ///////////////////////// - // Starts a buffer playing (or changes the flags for a buffer currently - // playing). - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int Play(uint32_t flags) { - uint32_t dsFlags = (flags & LNXSND_LOOPING) ? LNXSND_LOOPING : 0; - return LnxSoundBuffer_Play(m_pBuffer, dsFlags); - } - - //////////////////////////// - // GetCaps - //////////////////////////// - // Get the capabilities of a sound buffer - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int GetCaps(SysSoundCaps *caps) { return LnxSoundBuffer_GetCaps(m_pBuffer, (LinuxSoundCaps *)caps); } - - ////////////////////////////// - // GetStatus - ////////////////////////////// - // Returns the status of a buffer - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int GetStatus(uint32_t *status) { return LnxSoundBuffer_GetStatus(m_pBuffer, status); } - - /////////////////////////////////////// - // GetCurrentPosition - /////////////////////////////////////// - // Returns the current play and write positions of the buffer - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int GetCurrentPosition(uint32_t *ppos, uint32_t *wpos) { - - return LnxSoundBuffer_GetCurrentPosition(m_pBuffer, ppos, wpos); - } - - /////////////////////////////////////// - // SetCurrentPosition - /////////////////////////////////////// - // Sets the current play position of the buffer - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int SetCurrentPosition(uint32_t pos) { return LnxSoundBuffer_SetCurrentPosition(m_pBuffer, pos); } - - ///////////////////////// - // Lock - ///////////////////////// - // Locks the given buffer, returning pointer(s) to the buffer(s) along with - // available the size of the buffer(s) for writing. - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int Lock(uint32_t pos, uint32_t numbytes, void **ptr1, uint32_t *numbytes1, void **ptr2, - uint32_t *numbytes2, uint32_t flags) { - return LnxSoundBuffer_Lock(m_pBuffer, pos, numbytes, ptr1, numbytes1, ptr2, numbytes2, flags); - } - - /////////////////////////// - // Unlock - /////////////////////////// - // Unlocks a buffer. - // - // Returns: - // 0 : no error - // -1 : invalid parameters - int Unlock(void *ptr1, uint32_t num1, void *ptr2, uint32_t num2) { - return LnxSoundBuffer_Unlock(m_pBuffer, ptr1, num1, ptr2, num2); - } -}; -class MovieSoundDevice : public ISoundDevice { -private: - LnxSoundDevice m_ds; - -public: - MovieSoundDevice() {} - - void SetDirectSound(LnxSoundDevice ds) { m_ds = ds; } - - LnxSoundDevice GetDirectSound() { return m_ds; } - - /////////////////////////////// - // CreateSoundBuffer - /////////////////////////////// - // Creates a sound buffer to be used with mixing and output. - // - // Returns: - // -1 : Invalid Parameter - // -2 : Out of memory - // 0 : Ok! - int CreateSoundBuffer(SysSoundBufferDesc *lbdesc, ISysSoundBuffer **lsndb) { - LnxSoundBuffer *sb = NULL; - int ret = LnxSound_CreateSoundBuffer(&m_ds, (LnxBufferDesc *)lbdesc, (LnxSoundBuffer **)&sb); - if (ret == 0) { - ISysSoundBuffer *p = (ISysSoundBuffer *)new MovieSoundBuffer(sb); - *lsndb = p; - } else { - lsndb = nullptr; - } - return ret; - } -}; - - -#endif } // namespace static void *CallbackAlloc(uint32_t size); @@ -242,8 +65,8 @@ static void CallbackShowFrame(uint8_t *buf, uint32_t bufw, uint32_t bufh, uint32 uint32_t hicolor); #ifndef NO_MOVIES -static bool mve_InitSound(oeApplication *app, MovieSoundDevice &device); -static void mve_CloseSound(MovieSoundDevice &device); +static bool mve_InitSound(); +static void mve_CloseSound(); #endif // sets the directory where movies are stored @@ -329,8 +152,7 @@ int mve_PlayMovie(const char *pMovieName, oeApplication *pApp) { InitializePalette(); Movie_bm_handle = -1; - MovieSoundDevice soundDevice; - if (!mve_InitSound(pApp, soundDevice)) { + if (!mve_InitSound()) { mprintf(0, "Failed to initialize sound\n"); fclose(hFile); return MVELIB_INIT_ERROR; @@ -340,7 +162,7 @@ int mve_PlayMovie(const char *pMovieName, oeApplication *pApp) { if (mve == nullptr) { mprintf(0, "Failed to prepMovie %s\n", pMovieName); fclose(hFile); - mve_CloseSound(soundDevice); + mve_CloseSound(); return MVELIB_INIT_ERROR; } @@ -380,7 +202,7 @@ int mve_PlayMovie(const char *pMovieName, oeApplication *pApp) { MVE_rmEndMovie(mve); // reset sound - mve_CloseSound(soundDevice); + mve_CloseSound(); // return out return err; @@ -680,24 +502,13 @@ void mve_ClearRect(int16_t x1, int16_t y1, int16_t x2, int16_t y2) { } #ifndef NO_MOVIES -bool mve_InitSound(oeApplication *app, MovieSoundDevice &device) { - - LnxSoundDevice snddev; - bool use_22k_sound = false; - snddev.freq = use_22k_sound ? 22050 : 44100; - snddev.bit_depth = 16; - snddev.channels = 2; - snddev.bps = snddev.freq * snddev.channels * snddev.bit_depth / 8; - - device.SetDirectSound(snddev); - - MVE_sndInit(&device); +bool mve_InitSound() { MVE_sndInit(1); return true; } -void mve_CloseSound(MovieSoundDevice &device) { +void mve_CloseSound() { // TODO: close the driver out }