diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c3dd7dca..030b1c5df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,6 +141,7 @@ file (GLOB CXBXR_HEADER_EMU "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbState.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbVertexBuffer.h" "${CXBXR_ROOT_DIR}/src/core/hle/D3D8/XbVertexShader.h" + "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/common/windows/WFXformat.hpp" "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/DirectSound/DirectSound.hpp" "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/DirectSound/DirectSoundGlobal.hpp" "${CXBXR_ROOT_DIR}/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp" diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp index 16aafa102..70b3bc298 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.hpp @@ -109,6 +109,7 @@ struct X_CDirectSoundBuffer X_DSENVOLOPEDESC Xb_EnvolopeDesc; X_DSVOICEPROPS Xb_VoiceProperties; DWORD Xb_Frequency; + DWORD Xb_Flags; }; //Custom flags (4 bytes support up to 31 shifts,starting from 0) @@ -263,7 +264,8 @@ class X_CDirectSoundStream X_DSENVOLOPEDESC Xb_EnvolopeDesc; X_DSVOICEPROPS Xb_VoiceProperties; DWORD Xb_Frequency; - DWORD Host_dwLastWritePos; + DWORD Host_dwLastWritePos; + DWORD Xb_Flags; }; // ****************************************************************** diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp index c2de79481..12cfd4d98 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundBuffer.cpp @@ -205,14 +205,14 @@ HRESULT WINAPI XTL::EMUPATCH(DirectSoundCreateBuffer) // TODO: Garbage Collection *ppBuffer = new X_CDirectSoundBuffer(); - DSoundBufferSetDefault((*ppBuffer), 0); + DSoundBufferSetDefault((*ppBuffer), 0, pdsbd->dwFlags); (*ppBuffer)->Host_lock = { 0 }; (*ppBuffer)->Xb_rtStopEx = 0LL; DSoundBufferRegionSetDefault(*ppBuffer); // We have to set DSBufferDesc last due to EmuFlags must be either 0 or previously written value to preserve other flags. - GeneratePCMFormat(DSBufferDesc, pdsbd->lpwfxFormat, (*ppBuffer)->EmuFlags, pdsbd->dwBufferBytes, + GeneratePCMFormat(DSBufferDesc, pdsbd->lpwfxFormat, pdsbd->dwFlags, (*ppBuffer)->EmuFlags, pdsbd->dwBufferBytes, &(*ppBuffer)->X_BufferCache, (*ppBuffer)->X_BufferCacheSize, (*ppBuffer)->Xb_VoiceProperties, pdsbd->lpMixBinsOutput); (*ppBuffer)->EmuBufferDesc = DSBufferDesc; @@ -929,8 +929,8 @@ HRESULT WINAPI XTL::EMUPATCH(IDirectSoundBuffer_SetFormat) LOG_FUNC_ARG(pwfxFormat) LOG_FUNC_END; - HRESULT hRet = HybridDirectSoundBuffer_SetFormat(pThis->EmuDirectSoundBuffer8, pwfxFormat, - pThis->EmuBufferDesc, pThis->EmuFlags, + HRESULT hRet = HybridDirectSoundBuffer_SetFormat(pThis->EmuDirectSoundBuffer8, pwfxFormat, pThis->Xb_Flags, + pThis->EmuBufferDesc, pThis->EmuFlags, pThis->EmuPlayFlags, pThis->EmuDirectSound3DBuffer8, 0, pThis->X_BufferCache, pThis->X_BufferCacheSize, pThis->Xb_VoiceProperties, xbnullptr, pThis->Xb_Frequency); diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp index 502fd3a51..cc4f8f8b7 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundInline.hpp @@ -30,6 +30,7 @@ #include "common/XADPCM.h" #include "core/hle/DSOUND/XbDSoundTypes.h" +#include "core/hle/DSOUND/common/windows/WFXformat.hpp" #include @@ -85,75 +86,6 @@ static void DSoundBufferOutputXBtoHost(DWORD emuFlags, DSBUFFERDESC &DSBufferDes } } -// Convert XADPCM to PCM format helper function -static inline void XADPCM2PCMFormat(LPWAVEFORMATEX lpwfxFormat) -{ - -#if 0 //For testing purpose if XADPCM to PCM is not accurate. - EmuLog(LOG_LEVEL::DEBUG, - "EmuDSound: XADPCM WAVEFORMATEX\n" - "{\n" - " wFormatTag : 0x%.04hX\n" - " nChannels : 0x%.02hd\n" - " nSamplesPerSec : 0x%.08X\n" - " nAvgBytesPerSec : 0x%.08X\n" - " nBlockAlign : 0x%.02hd\n" - " wBitsPerSample : 0x%.04hX\n" - " cbSize : 0x%.04hX\n" - "}\n", - lpwfxFormat->wFormatTag, - lpwfxFormat->nChannels, - lpwfxFormat->nSamplesPerSec, - lpwfxFormat->nAvgBytesPerSec, - lpwfxFormat->nBlockAlign, - lpwfxFormat->wBitsPerSample, - lpwfxFormat->cbSize); -#endif - - lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM; - - //lpwfxFormat.wFormatTag; /* format type */ - //lpwfxFormat.nChannels; /* number of channels (i.e. mono, stereo...) */ NO CHANGE - //lpwfxFormat.nSamplesPerSec; /* sample rate */ NO CHANGE - //lpwfxFormat.nAvgBytesPerSec; /* for buffer estimation */ - //lpwfxFormat.nBlockAlign; /* block size of data */ - //lpwfxFormat.wBitsPerSample; /* number of bits per sample of mono data */ - //lpwfxFormat.cbSize; /* the count in bytes of the size of extra information (after cbSize) */ - - lpwfxFormat->wBitsPerSample = 16; - lpwfxFormat->nBlockAlign = 2 * lpwfxFormat->nChannels; - lpwfxFormat->nAvgBytesPerSec = lpwfxFormat->nSamplesPerSec * lpwfxFormat->nBlockAlign; - lpwfxFormat->cbSize = 0; - //Enable this only if you have Xbox ADPCM Codec installed on your PC, or else it will fail every time. - //This is just to verify format conversion is correct or not. -#if 0 - if (waveOutOpen(nullptr, WAVE_MAPPER, lpwfxFormat, NULL, NULL, WAVE_FORMAT_QUERY) != MMSYSERR_NOERROR) { - return DSERR_BADFORMAT; - } -#endif - -#if 0 //For testing purpose if XADPCM to PCM is not accurate. - EmuLog(LOG_LEVEL::DEBUG, - "EmuDSound: Converted to PCM WAVEFORMATEX\n" - "{\n" - " wFormatTag : 0x%.04hX\n" - " nChannels : 0x%.02hd\n" - " nSamplesPerSec : 0x%.08X\n" - " nAvgBytesPerSec : 0x%.08X\n" - " nBlockAlign : 0x%.02hd\n" - " wBitsPerSample : 0x%.04hX\n" - " cbSize : 0x%.04hX\n" - "}\n", - lpwfxFormat->wFormatTag, - lpwfxFormat->nChannels, - lpwfxFormat->nSamplesPerSec, - lpwfxFormat->nAvgBytesPerSec, - lpwfxFormat->nBlockAlign, - lpwfxFormat->wBitsPerSample, - lpwfxFormat->cbSize); -#endif -} - static inline void GenerateXboxBufferCache( DSBUFFERDESC &DSBufferDesc, DWORD &dwEmuFlags, @@ -312,7 +244,8 @@ static inline void GenerateMixBinDefault( static inline void GeneratePCMFormat( DSBUFFERDESC &DSBufferDesc, - LPCWAVEFORMATEX lpwfxFormat, + LPCWAVEFORMATEX Xb_lpwfxFormat, + DWORD &Xb_flags, DWORD &dwEmuFlags, DWORD X_BufferSizeRequest, LPVOID* X_BufferCache, @@ -321,186 +254,61 @@ static inline void GeneratePCMFormat( XTL::X_LPDSMIXBINS mixbins_output) { bool bIsSpecial = false; - DWORD checkAvgBps; - GenerateMixBinDefault(Xb_VoiceProperties, lpwfxFormat, mixbins_output, ((DSBufferDesc.dwFlags & DSBCAPS_CTRL3D) > 0)); + GenerateMixBinDefault(Xb_VoiceProperties, Xb_lpwfxFormat, mixbins_output, ((DSBufferDesc.dwFlags & DSBCAPS_CTRL3D) > 0)); // convert from Xbox to PC DSound { DSBufferDesc.dwReserved = 0; - if (lpwfxFormat != xbnullptr) { + // Allocate only once, does not need to re-allocate. + if (DSBufferDesc.lpwfxFormat == nullptr) { + // Only allocate extra value for setting extra values later on. WAVEFORMATEXTENSIBLE is the highest size I had seen. + DSBufferDesc.lpwfxFormat = (WAVEFORMATEX*)calloc(1, sizeof(WAVEFORMATEXTENSIBLE)); + } - //TODO: RadWolfie - Need implement support for WAVEFORMATEXTENSIBLE as stated in CDirectSoundStream_SetFormat function note below - // Do we need to convert it? Or just do the WAVEFORMATEX only? + if (DSBufferDesc.lpwfxFormat == nullptr) { + CxbxKrnlCleanup("Unable to allocate DSBufferDesc.Xb_lpwfxFormat"); + } - // NOTE: pwfxFormat is not always a WAVEFORMATEX structure, it can - // be WAVEFORMATEXTENSIBLE if that's what the programmer(s) wanted - // in the first place, FYI. + if (Xb_lpwfxFormat != xbnullptr) { - // Allocate only once, does not need to re-allocate. - if (DSBufferDesc.lpwfxFormat == nullptr) { - // Only allocate extra value for setting extra values later on. WAVEFORMATEXTENSIBLE is the highest size I had seen. - DSBufferDesc.lpwfxFormat = (WAVEFORMATEX*)calloc(1, sizeof(WAVEFORMATEXTENSIBLE)); - } - WAVEFORMATEX* lpwfxFormatHost = DSBufferDesc.lpwfxFormat; + PWAVEFORMATEXTENSIBLE lpwfxFormatHost = reinterpret_cast(DSBufferDesc.lpwfxFormat); - if (lpwfxFormat->wFormatTag == WAVE_FORMAT_PCM) { - // Test case: Hulk crash due to cbSize is not a valid size. - memcpy(lpwfxFormatHost, lpwfxFormat, sizeof(WAVEFORMATEX)); - lpwfxFormatHost->cbSize = 0; // Let's enforce this value to prevent any other exception later on. - } - else if (lpwfxFormat->wFormatTag == 0 && (DSBufferDesc.dwFlags & DSBCAPS_LOCDEFER) > 0) { - // NOTE: This is currently a hack for ability to create buffer class with DSBCAPS_LOCDEFER flag. - lpwfxFormatHost->wFormatTag = WAVE_FORMAT_PCM; - lpwfxFormatHost->nChannels = 2; - lpwfxFormatHost->nSamplesPerSec = 44100; - lpwfxFormatHost->wBitsPerSample = 8; - lpwfxFormatHost->nBlockAlign = lpwfxFormatHost->nChannels * static_cast(lpwfxFormatHost->wBitsPerSample) / 8; - lpwfxFormatHost->nAvgBytesPerSec = lpwfxFormatHost->nSamplesPerSec * lpwfxFormatHost->nBlockAlign; + if (Xb_lpwfxFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + memcpy(lpwfxFormatHost, Xb_lpwfxFormat, sizeof(WAVEFORMATEX) + Xb_lpwfxFormat->cbSize); } else { - memcpy(lpwfxFormatHost, lpwfxFormat, sizeof(WAVEFORMATEX) + lpwfxFormat->cbSize); - } - - // NOTE: Currently a workaround hack fix until custom management for buffer/stream can allow unallocated buffer. - // Without this fix, some titles wouldn't progress further. Plus no matter what values are set in Xbox's wfxFormat - // are approved. It does need further investigation which require LLE APU stubbed and a HLE verbose plugin. - if (X_BufferSizeRequest == 0 && - (lpwfxFormatHost->nSamplesPerSec == 0 || lpwfxFormatHost->nAvgBytesPerSec == 0)) { - // NOTE: When X_BufferSizeRequest is 0, creation is allow to be performed until allocated size is given from different API. - // Set dwBufferBytes is not needed as it is handled later on. - // Test case: Hobbit did not input nSamplesPerSec & nAvgBytesPerSec variable, yet isn't a requirement. - if (lpwfxFormatHost->nChannels == 0) { - lpwfxFormatHost->nChannels = 2; - } - if (lpwfxFormatHost->wBitsPerSample == 0) { - lpwfxFormatHost->wBitsPerSample = 8; - } - - if (lpwfxFormatHost->nBlockAlign == 0) { - lpwfxFormatHost->nBlockAlign = lpwfxFormatHost->nChannels * static_cast(lpwfxFormatHost->wBitsPerSample) / 8; - } - - if (lpwfxFormatHost->nSamplesPerSec == 0) { - lpwfxFormatHost->nSamplesPerSec = 44100; - lpwfxFormatHost->nAvgBytesPerSec = lpwfxFormatHost->nSamplesPerSec * lpwfxFormatHost->nBlockAlign; - } - - // TODO: I don't think we need WAVEFORMATEXTENSIBLE stub for this fix. - if (lpwfxFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { - EmuLog(LOG_LEVEL::WARNING, "Is WAVE_FORMAT_EXTENSIBLE having problem with creation?"); - } + // Other formats will enforce use WAVEFORMATEX size, we cannot allow corruption sneak into WAVEFORMATEXTENSIBLE's structure. + memcpy(lpwfxFormatHost, Xb_lpwfxFormat, sizeof(WAVEFORMATEX)); + lpwfxFormatHost->Format.cbSize = 0; } dwEmuFlags = dwEmuFlags & ~DSE_FLAG_AUDIO_CODECS; - switch (DSBufferDesc.lpwfxFormat->wFormatTag) { - case WAVE_FORMAT_PCM: + CODEC_FORMAT cf_audio = WFXformat_SyncHostFormat(DSBufferDesc.lpwfxFormat, Xb_lpwfxFormat, X_BufferSizeRequest, Xb_flags); + if (cf_audio == CF_PCM) { dwEmuFlags |= DSE_FLAG_PCM; - - //TODO: Phantasy Star Online Episode I & II made an attempt to use avg byte/second below sample/second requirement. - //In other word, this is a workaround to fix title mistake... - checkAvgBps = lpwfxFormat->nSamplesPerSec * lpwfxFormat->nBlockAlign; - if (lpwfxFormat->nAvgBytesPerSec < checkAvgBps) { - DSBufferDesc.lpwfxFormat->nAvgBytesPerSec = checkAvgBps; - } - break; - case WAVE_FORMAT_XBOX_ADPCM: - dwEmuFlags |= DSE_FLAG_XADPCM; - XADPCM2PCMFormat(DSBufferDesc.lpwfxFormat); - break; - default: - dwEmuFlags |= DSE_FLAG_PCM_UNKNOWN; - break; } + else if (cf_audio == CF_XADPCM) { + dwEmuFlags |= DSE_FLAG_XADPCM; + } + else { + dwEmuFlags |= DSE_FLAG_PCM_UNKNOWN; + } + } else { - bIsSpecial = true; dwEmuFlags |= DSE_FLAG_RECIEVEDATA; - EmuLog(LOG_LEVEL::WARNING, "Creating dummy WAVEFORMATEX (pdsbd->lpwfxFormat = xbnullptr)..."); - - // HACK: This is a special sound buffer, create dummy WAVEFORMATEX data. - // It's supposed to recieve data rather than generate it. Buffers created - // with flags DSBCAPS_MIXIN, DSBCAPS_FXIN, and DSBCAPS_FXIN2 will have no - // WAVEFORMATEX structure by default. - - // TODO: A better response to this scenario if possible. - - DSBufferDesc.lpwfxFormat = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEXTENSIBLE)); - - //memset(pDSBufferDescSpecial->lpwfxFormat, 0, sizeof(WAVEFORMATEX)); - //memset(pDSBufferDescSpecial, 0, sizeof(DSBUFFERDESC)); - - DSBufferDesc.lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM; - DSBufferDesc.lpwfxFormat->nChannels = 2; - DSBufferDesc.lpwfxFormat->nSamplesPerSec = 22050; - DSBufferDesc.lpwfxFormat->nBlockAlign = 4; - DSBufferDesc.lpwfxFormat->nAvgBytesPerSec = DSBufferDesc.lpwfxFormat->nSamplesPerSec * DSBufferDesc.lpwfxFormat->nBlockAlign; - DSBufferDesc.lpwfxFormat->wBitsPerSample = 16; - DSBufferDesc.lpwfxFormat->cbSize = 0; + (void)WFXformat_SyncHostFormat(DSBufferDesc.lpwfxFormat, Xb_lpwfxFormat, X_BufferSizeRequest, Xb_flags); DSBufferDesc.dwFlags = DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY; - DSBufferDesc.dwBufferBytes = 3 * DSBufferDesc.lpwfxFormat->nAvgBytesPerSec; - - // DSBufferDesc.lpwfxFormat = (WAVEFORMATEX*)malloc(sizeof(WAVEFORMATEX)/*+pdsbd->lpwfxFormat->cbSize*/); - - //// DSBufferDesc.lpwfxFormat->cbSize = sizeof( WAVEFORMATEX ); - // DSBufferDesc.lpwfxFormat->nChannels = 1; - // DSBufferDesc.lpwfxFormat->wFormatTag = WAVE_FORMAT_PCM; - // DSBufferDesc.lpwfxFormat->nSamplesPerSec = 22050; - // DSBufferDesc.lpwfxFormat->nBlockAlign = 4; - // DSBufferDesc.lpwfxFormat->nAvgBytesPerSec = 4 * 22050; - // DSBufferDesc.lpwfxFormat->wBitsPerSample = 16; - - // Give this buffer 3 seconds of data if needed - /*if(pdsbd->dwBufferBytes == 0) { - DSBufferDesc.dwBufferBytes = 3 * DSBufferDesc.lpwfxFormat->nAvgBytesPerSec; - }*/ + DSBufferDesc.dwBufferBytes = 5 * DSBufferDesc.lpwfxFormat->nAvgBytesPerSec; } DSBufferDesc.guid3DAlgorithm = DS3DALG_DEFAULT; } - // TODO: Still a requirement? Need to retest it again. Can't remember which title cause problem or had been resolved. - // sanity check - if (!bIsSpecial) { - if (DSBufferDesc.lpwfxFormat->nBlockAlign != DSBufferDesc.lpwfxFormat->nChannels* static_cast(DSBufferDesc.lpwfxFormat->wBitsPerSample) / 8) { - DSBufferDesc.lpwfxFormat->nBlockAlign = (2 * DSBufferDesc.lpwfxFormat->wBitsPerSample) / 8; - DSBufferDesc.lpwfxFormat->nAvgBytesPerSec = DSBufferDesc.lpwfxFormat->nSamplesPerSec * DSBufferDesc.lpwfxFormat->nBlockAlign; - } - } - - if (DSBufferDesc.lpwfxFormat != nullptr) { - - // NOTE: This process check for 2+ channels whenever XADPCM or PCM format from xbox titles input in nChannels. - // Since the host do not support 2+ channels on PCM only format. We must convert/update allocated wfxFormat to EXTENSIBLE - // format which had been allocated enough to update. - if (DSBufferDesc.lpwfxFormat->nChannels > 2 && DSBufferDesc.lpwfxFormat->wFormatTag != WAVE_FORMAT_EXTENSIBLE) { - PWAVEFORMATEXTENSIBLE lpwfxFormatExtensible = (PWAVEFORMATEXTENSIBLE)DSBufferDesc.lpwfxFormat; - lpwfxFormatExtensible->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - lpwfxFormatExtensible->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - lpwfxFormatExtensible->Samples.wValidBitsPerSample = lpwfxFormatExtensible->Format.wBitsPerSample; - lpwfxFormatExtensible->Samples.wSamplesPerBlock = 0; - lpwfxFormatExtensible->Samples.wReserved = 0; - lpwfxFormatExtensible->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; - - // Add rear speakers - if (lpwfxFormat->nChannels >= 4) { - lpwfxFormatExtensible->dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; - } - // Add center speaker - if (lpwfxFormat->nChannels >= 5) { - lpwfxFormatExtensible->dwChannelMask |= SPEAKER_FRONT_CENTER; - } - // Add subwoofer mask (pretty sure 3 channels is 2.1, not 3.0 for xbox purpose) - if (lpwfxFormat->nChannels == 6 || lpwfxFormat->nChannels == 3) { - lpwfxFormatExtensible->dwChannelMask |= SPEAKER_LOW_FREQUENCY; - } - lpwfxFormatExtensible->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - } - if (X_BufferSizeRequest < DSBSIZE_MIN) { X_BufferSizeRequest = DSBSIZE_MIN; } else if (X_BufferSizeRequest > DSBSIZE_MAX) { @@ -572,7 +380,7 @@ static inline void DSound3DBufferCreate(LPDIRECTSOUNDBUFFER8 pDSBuffer, LPDIRECT } } -#define DSoundBufferSetDefault(pThis, dwEmuPlayFlags) \ +#define DSoundBufferSetDefault(pThis, dwEmuPlayFlags, Xb_dwFlags) \ pThis->EmuDirectSoundBuffer8 = nullptr; \ pThis->EmuDirectSound3DBuffer8 = nullptr; \ pThis->X_BufferCache = xbnullptr; \ @@ -585,7 +393,8 @@ static inline void DSound3DBufferCreate(LPDIRECTSOUNDBUFFER8 pDSBuffer, LPDIRECT pThis->Xb_dwHeadroom = 600; /* default for 2D voice */ \ pThis->Xb_EnvolopeDesc = { 0 }; \ InitVoiceProperties(pThis->Xb_VoiceProperties); /* The rest will initialize in GeneratePCMFormat to GenerateMixBinDefault. */ \ - pThis->Xb_Frequency = XTL_DSXFREQUENCY_ORIGINAL; + pThis->Xb_Frequency = XTL_DSXFREQUENCY_ORIGINAL; \ + pThis->Xb_Flags = Xb_dwFlags; //pThis->EmuBufferDesc = { 0 }; // Enable this when become necessary. /* pThis->EmuLockPtr1 = xbnullptr; \ @@ -1259,7 +1068,8 @@ static inline HRESULT HybridDirectSoundBuffer_SetFilter( //IDirectSoundBuffer static inline HRESULT HybridDirectSoundBuffer_SetFormat( LPDIRECTSOUNDBUFFER8 &pDSBuffer, - LPCWAVEFORMATEX pwfxFormat, + LPCWAVEFORMATEX Xb_pwfxFormat, + DWORD Xb_flags, DSBUFFERDESC &BufferDesc, DWORD &dwEmuFlags, DWORD &dwPlayFlags, @@ -1274,10 +1084,10 @@ static inline HRESULT HybridDirectSoundBuffer_SetFormat( pDSBuffer->Stop(); if (X_BufferAllocate) { - GeneratePCMFormat(BufferDesc, pwfxFormat, dwEmuFlags, X_BufferCacheSize, xbnullptr, X_BufferCacheSize, Xb_VoiceProperties, mixbins_output); + GeneratePCMFormat(BufferDesc, Xb_pwfxFormat, Xb_flags, dwEmuFlags, X_BufferCacheSize, xbnullptr, X_BufferCacheSize, Xb_VoiceProperties, mixbins_output); // Don't allocate for DS Stream class, it is using straight from the source. } else { - GeneratePCMFormat(BufferDesc, pwfxFormat, dwEmuFlags, 0, xbnullptr, X_BufferCacheSize, Xb_VoiceProperties, mixbins_output); + GeneratePCMFormat(BufferDesc, Xb_pwfxFormat, Xb_flags, dwEmuFlags, 0, xbnullptr, X_BufferCacheSize, Xb_VoiceProperties, mixbins_output); } HRESULT hRet = DS_OK; if (g_pDSoundPrimaryBuffer == pDSBuffer) { diff --git a/src/core/hle/DSOUND/DirectSound/DirectSoundStream.cpp b/src/core/hle/DSOUND/DirectSound/DirectSoundStream.cpp index 3146846bd..6b999300f 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSoundStream.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSoundStream.cpp @@ -230,11 +230,11 @@ HRESULT WINAPI XTL::EMUPATCH(DirectSoundCreateStream) DSBufferDesc.dwFlags |= DSBCAPS_CTRLPAN; } - DSoundBufferSetDefault((*ppStream), DSBPLAY_LOOPING); + DSoundBufferSetDefault((*ppStream), DSBPLAY_LOOPING, pdssd->dwFlags); (*ppStream)->Xb_rtFlushEx = 0LL; // We have to set DSBufferDesc last due to EmuFlags must be either 0 or previously written value to preserve other flags. - GeneratePCMFormat(DSBufferDesc, pdssd->lpwfxFormat, (*ppStream)->EmuFlags, 0, + GeneratePCMFormat(DSBufferDesc, pdssd->lpwfxFormat, pdssd->dwFlags, (*ppStream)->EmuFlags, 0, xbnullptr, (*ppStream)->X_BufferCacheSize, (*ppStream)->Xb_VoiceProperties, pdssd->lpMixBinsOutput); // Test case: Star Wars: KotOR has one packet greater than 5 seconds worth. Increasing to 10 seconds allow stream to work until @@ -896,10 +896,11 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_SetFormat) while (DSStream_Packet_Flush(pThis)); - HRESULT hRet = HybridDirectSoundBuffer_SetFormat(pThis->EmuDirectSoundBuffer8, pwfxFormat, pThis->EmuBufferDesc, - pThis->EmuFlags, pThis->EmuPlayFlags, pThis->EmuDirectSound3DBuffer8, - 0, pThis->X_BufferCache, pThis->X_BufferCacheSize, - pThis->Xb_VoiceProperties, xbnullptr, pThis->Xb_Frequency); + HRESULT hRet = HybridDirectSoundBuffer_SetFormat(pThis->EmuDirectSoundBuffer8, pwfxFormat, pThis->Xb_Flags, + pThis->EmuBufferDesc, pThis->EmuFlags, pThis->EmuPlayFlags, + pThis->EmuDirectSound3DBuffer8, 0, pThis->X_BufferCache, + pThis->X_BufferCacheSize, pThis->Xb_VoiceProperties, + xbnullptr, pThis->Xb_Frequency); return hRet; } diff --git a/src/core/hle/DSOUND/common/windows/WFXformat.hpp b/src/core/hle/DSOUND/common/windows/WFXformat.hpp new file mode 100644 index 000000000..9118a996a --- /dev/null +++ b/src/core/hle/DSOUND/common/windows/WFXformat.hpp @@ -0,0 +1,336 @@ +// ****************************************************************** +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 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 recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2017-2020 RadWolfie +// * +// * All rights reserved +// * +// ****************************************************************** +#pragma once + +#include +#include +#include "core/kernel/init/CxbxKrnl.h" +#include "core/hle/DSOUND/XbDSoundTypes.h" + +#include "Logging.h" +#include "core/hle/DSOUND/DirectSound/DirectSoundLogging.hpp" +#include "core/hle/DSOUND/XbDSoundLogging.hpp" + +// Only use as last resort instead of alternative duplicates (an error occur between keyboard and chair) +static void WFXformat_GeneratePCMFormat( + uint16_t nChannels, + uint32_t nSamplesPerSec, + uint16_t wBitPerSample, + PWAVEFORMATEXTENSIBLE Host_wfxFormat) +{ + Host_wfxFormat->Format.wFormatTag = WAVE_FORMAT_PCM; + Host_wfxFormat->Format.nChannels = nChannels; + Host_wfxFormat->Format.nSamplesPerSec = nSamplesPerSec; + Host_wfxFormat->Format.wBitsPerSample = wBitPerSample; + Host_wfxFormat->Format.nBlockAlign = wBitPerSample / 8 * nChannels; + Host_wfxFormat->Format.nAvgBytesPerSec = nSamplesPerSec * Host_wfxFormat->Format.nBlockAlign; +} + +static void WFXformat_GenerateXADPCMFormat( + uint16_t nChannels, + uint32_t nSamplesPerSec, + PWAVEFORMATEXTENSIBLE Host_pwfxFormat) +{ + Host_pwfxFormat->Format.nChannels = nChannels; + Host_pwfxFormat->Format.nSamplesPerSec = nSamplesPerSec; + Host_pwfxFormat->Format.wBitsPerSample = 4; // Always 4 (even if they're mono or stereo) + Host_pwfxFormat->Format.nBlockAlign = 36 * nChannels; + // nAvgBytesPerSec's equation is confirmed. + Host_pwfxFormat->Format.nAvgBytesPerSec = (nSamplesPerSec / 64/*Always 64 samples per block*/) * Host_pwfxFormat->Format.nBlockAlign; +} + +// Convert XADPCM to PCM format helper function +static void XADPCM2PCMFormat(PWAVEFORMATEXTENSIBLE Host_pwfxFormat) +{ + +#if 0 //For testing purpose if XADPCM to PCM is not accurate. + EmuLog(LOG_LEVEL::DEBUG, + "EmuDSound: XADPCM WAVEFORMATEX\n" + "{\n" + " wFormatTag : 0x%.04hX\n" + " nChannels : 0x%.02hd\n" + " nSamplesPerSec : 0x%.08X\n" + " nAvgBytesPerSec : 0x%.08X\n" + " nBlockAlign : 0x%.02hd\n" + " wBitsPerSample : 0x%.04hX\n" + " cbSize : 0x%.04hX\n" + "}\n", + Host_pwfxFormat->Format.wFormatTag, + Host_pwfxFormat->Format.nChannels, + Host_pwfxFormat->Format.nSamplesPerSec, + Host_pwfxFormat->Format.nAvgBytesPerSec, + Host_pwfxFormat->Format.nBlockAlign, + Host_pwfxFormat->Format.wBitsPerSample, + Host_pwfxFormat->Format.cbSize); +#endif + + //Xb_lpwfxFormat.wFormatTag; /* format type */ + //Xb_lpwfxFormat.nChannels; /* number of channels (i.e. mono, stereo...) */ NO CHANGE + //Xb_lpwfxFormat.nSamplesPerSec; /* sample rate */ NO CHANGE + //Xb_lpwfxFormat.nAvgBytesPerSec; /* for buffer estimation */ + //Xb_lpwfxFormat.nBlockAlign; /* block size of data */ + //Xb_lpwfxFormat.wBitsPerSample; /* number of bits per sample of mono data */ + //Xb_lpwfxFormat.cbSize; /* the count in bytes of the size of extra information (after cbSize) */ + WFXformat_GeneratePCMFormat(Host_pwfxFormat->Format.nChannels, + Host_pwfxFormat->Format.nSamplesPerSec, + 16, + Host_pwfxFormat); + //Enable this only if you have Xbox ADPCM Codec installed on your PC, or else it will fail every time. + //This is just to verify format conversion is correct or not. +#if 0 + if (waveOutOpen(nullptr, WAVE_MAPPER, Xb_lpwfxFormat, NULL, NULL, WAVE_FORMAT_QUERY) != MMSYSERR_NOERROR) { + return DSERR_BADFORMAT; + } +#endif + +#if 0 //For testing purpose if XADPCM to PCM is not accurate. + EmuLog(LOG_LEVEL::DEBUG, + "EmuDSound: Converted to PCM WAVEFORMATEX\n" + "{\n" + " wFormatTag : 0x%.04hX\n" + " nChannels : 0x%.02hd\n" + " nSamplesPerSec : 0x%.08X\n" + " nAvgBytesPerSec : 0x%.08X\n" + " nBlockAlign : 0x%.02hd\n" + " wBitsPerSample : 0x%.04hX\n" + " cbSize : 0x%.04hX\n" + "}\n", + Host_pwfxFormat->Format.wFormatTag, + Host_pwfxFormat->Format.nChannels, + Host_pwfxFormat->Format.nSamplesPerSec, + Host_pwfxFormat->Format.nAvgBytesPerSec, + Host_pwfxFormat->Format.nBlockAlign, + Host_pwfxFormat->Format.wBitsPerSample, + Host_pwfxFormat->Format.cbSize); +#endif +} + +static void WFXformat_SyncPCMFormat( + PWAVEFORMATEXTENSIBLE Host_pwfxFormat) +{ + PWAVEFORMATEX ex_pwfxFormat = &Host_pwfxFormat->Format; + // NOTE: This process check for 2+ channels whenever XADPCM or PCM format from xbox titles input in nChannels. + // Since the host do not support 2+ channels on PCM only format. We must convert/update allocated wfxFormat to EXTENSIBLE + // format which had been allocated enough to update. + if (ex_pwfxFormat->nChannels > 2) { + Host_pwfxFormat->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + Host_pwfxFormat->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + Host_pwfxFormat->Samples.wValidBitsPerSample = ex_pwfxFormat->wBitsPerSample; + Host_pwfxFormat->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + Host_pwfxFormat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + + if (ex_pwfxFormat->nChannels >= 4) { + Host_pwfxFormat->dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; + } + if (ex_pwfxFormat->nChannels == 6) { + Host_pwfxFormat->dwChannelMask |= SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY; + } + } + else { + Host_pwfxFormat->Format.wFormatTag = WAVE_FORMAT_PCM; + // Test case: Hulk crash due to cbSize is not a valid size. + // If Xbox applications didn't enfore cbSize, we'll have to enforce from host side. + // Also, we're enforcing it anyway. + Host_pwfxFormat->Format.cbSize = 0; + } +} + +static void WFXformat_SyncXADPCMFormat( + PWAVEFORMATEXTENSIBLE Host_wfxFormat) +{ + XADPCM2PCMFormat(Host_wfxFormat); + WFXformat_SyncPCMFormat(Host_wfxFormat); +} + +typedef enum _CODEC_FORMAT { + CF_PCM=0, + CF_XADPCM, + CF_UNKNOWN +} CODEC_FORMAT; + +static void WFXformat_SanityFix( + PWAVEFORMATEXTENSIBLE Host_pwfxFormat, + CODEC_FORMAT codec_format, + uint32_t Xb_buffer_request_size, + uint32_t Xb_flags) +{ + WAVEFORMATEXTENSIBLE before_wfxFormat = *Host_pwfxFormat; + bool isNotSanity = false; + uint32_t nSamplesPerSec = Host_pwfxFormat->Format.nSamplesPerSec; + + // Generic enforcement + // If Xbox applications supply invalid total channels, enforce to use either mono or stereo channel + if (Host_pwfxFormat->Format.nChannels == 0 || Host_pwfxFormat->Format.nChannels > 6) { + Host_pwfxFormat->Format.nChannels = 2; + isNotSanity = true; + } + // If nSamplesPerSec is zero'd then use 44.1kHz by default + if (nSamplesPerSec == 0) { + Host_pwfxFormat->Format.nSamplesPerSec = 44100; + isNotSanity = true; + } + + if (Host_pwfxFormat->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + if (Host_pwfxFormat->Format.cbSize != sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) { + Host_pwfxFormat->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + //isNotSanity = true; + } + } + else { + // Test case: Hulk crash due to cbSize is not a valid size. + // If Xbox applications didn't enfore cbSize, we'll have to enforce from host side. + /* NOTE: cbSize is already handled in WFXformat_SyncPCMFormat function as enforcement. + if (Host_pwfxFormat->Format.cbSize != 0) { + Host_pwfxFormat->Format.cbSize = 0; + //isNotSanity = true; + } + */ + } + + // Generic handler + if (codec_format == CF_PCM || codec_format == CF_UNKNOWN) { + // If wBitsPerSample is zero'd then use 16 by default + if (Host_pwfxFormat->Format.wBitsPerSample == 0) { + isNotSanity = true; + } + + // testcase: Phantasy Star Online Episode I & II made an attempt to use avg byte/second below sample/second requirement. + // Defender is another title made an attempt to use avg byte/second way above the sample/second requirement. + // In other word, this is a workaround to fix titles' mistake... + uint32_t checkAvgBps = Host_pwfxFormat->Format.nSamplesPerSec * Host_pwfxFormat->Format.nBlockAlign; + + if (isNotSanity) { + WFXformat_GeneratePCMFormat(Host_pwfxFormat->Format.nChannels, Host_pwfxFormat->Format.nSamplesPerSec, 16, Host_pwfxFormat); + } + else if (Host_pwfxFormat->Format.nAvgBytesPerSec != checkAvgBps) { + Host_pwfxFormat->Format.nAvgBytesPerSec = checkAvgBps; + isNotSanity = true; + } + } + // CF_XADPCM + else if (codec_format == CF_XADPCM) { + // If wBitsPerSample is not equal to 4, then wfx format is not sane. + if (Host_pwfxFormat->Format.wBitsPerSample != 4) { + //Host_pwfxFormat->Format.wBitsPerSample = 4; + isNotSanity = true; + } + + if (isNotSanity) { + WFXformat_GenerateXADPCMFormat(Host_pwfxFormat->Format.nChannels, Host_pwfxFormat->Format.nSamplesPerSec, Host_pwfxFormat); + } + } + + if (isNotSanity) { + std::stringstream before, after; + before << before_wfxFormat; + after << Host_pwfxFormat; + EmuLog(LOG_LEVEL::WARNING, "One or more values were not set properly before: %s\nvs after: %s", before.str().c_str(), after.str().c_str()); + } +} + +static CODEC_FORMAT WFXformat_SyncHostFormat( + void* Host_wfx_ptr, + const void* Xb_wfx_ptr, + uint32_t Xb_buffer_request_size, + uint32_t Xb_flags) +{ + PWAVEFORMATEXTENSIBLE Xb_wfxFormat = (PWAVEFORMATEXTENSIBLE)Xb_wfx_ptr; + PWAVEFORMATEXTENSIBLE Host_wfxFormat = (PWAVEFORMATEXTENSIBLE)Host_wfx_ptr; + CODEC_FORMAT codec_format_ret = CF_PCM; + bool require_validate = true; + + // If no format is provided, then use default. + if (Xb_wfx_ptr == xbnullptr) { + WFXformat_GeneratePCMFormat(2, 44100, 16, Host_wfxFormat); + require_validate = false; + } + // HACK: This is a special sound buffer, create dummy WAVEFORMATEX data. + // It's supposed to recieve data rather than generate it. Buffers created + // with flags DSBCAPS_MIXIN, DSBCAPS_FXIN, and DSBCAPS_FXIN2 will have no + // WAVEFORMATEX structure by default. + else if ((Xb_flags & (XTL_DSBCAPS_MIXIN | XTL_DSBCAPS_FXIN | XTL_DSBCAPS_FXIN2)) > 0) { + EmuLog(LOG_LEVEL::WARNING, "Creating dummy WAVEFORMATEX (pdsbd->Xb_lpwfxFormat = xbnullptr)..."); + WFXformat_GeneratePCMFormat(2, 44100, 16, Host_wfxFormat); + require_validate = false; + } + // Otherwise, let's process given format. + else { + switch (Xb_wfxFormat->Format.wFormatTag) { + case WAVE_FORMAT_PCM: + // No further steps require here. + break; + case WAVE_FORMAT_XBOX_ADPCM: + codec_format_ret = CF_XADPCM; + break; + case WAVE_FORMAT_EXTENSIBLE: + if (Xb_wfxFormat->SubFormat.Data1 = WAVE_FORMAT_PCM) { + // No further steps require here. + } + else if (Xb_wfxFormat->SubFormat.Data1 == WAVE_FORMAT_XBOX_ADPCM) { + codec_format_ret = CF_XADPCM; + } + else { + codec_format_ret = CF_UNKNOWN; + require_validate = false; + std::stringstream oss; + oss << Xb_wfxFormat->SubFormat; + std::string msg = "Please report unknown extensible format : " + oss.str(); + LOG_TEST_CASE(msg.c_str()); + } + break; + // Both 0 and default will use static structure until given a valid one. + case 0: + // NOTE: This is currently a hack for ability to create buffer class with DSBCAPS_LOCDEFER flag. + WFXformat_GeneratePCMFormat(2, 44100, 16, Host_wfxFormat); + require_validate = false; + LOG_TEST_CASE("WAVE_FORMAT_(0) found"); + break; + default: + codec_format_ret = CF_UNKNOWN; + require_validate = false; + EmuLog(LOG_LEVEL::WARNING, "Unknown Xbox format: %08X", Xb_wfxFormat->Format.wFormatTag); + LOG_TEST_CASE("Unknown Xbox format, check your log output"); + break; + } + + if (require_validate) { + // First, perform sanity fix + WFXformat_SanityFix(Host_wfxFormat, codec_format_ret, Xb_buffer_request_size, Xb_flags); + } + + // Then re-convert to Host's PCM format. + if (codec_format_ret == CF_PCM) { + WFXformat_SyncPCMFormat(Host_wfxFormat); + } + else if (codec_format_ret == CF_XADPCM) { + WFXformat_SyncXADPCMFormat(Host_wfxFormat); + } + // Any unknown formats will be using default PCM format. + else { + WFXformat_GeneratePCMFormat(2, 44100, 16, Host_wfxFormat); + } + } + return codec_format_ret; +}