Merge pull request #1843 from RadWolfie/hotfix-audio-flush-async

Fix Audio Flush Async and Discontinuity
This commit is contained in:
Luke Usher 2020-03-16 17:06:36 +00:00 committed by GitHub
commit 5c6e808e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 57 deletions

View File

@ -152,12 +152,37 @@ static inline void DSStream_Packet_UploadPartial(
}
}
static inline void DSStream_Packet_Starved(
static inline void DSStream_Packet_Stop_Internal(
XTL::X_CDirectSoundStream* pThis
)
{
pThis->Host_isProcessing = false;
pThis->EmuDirectSoundBuffer8->Stop();
}
static inline void DSStream_Packet_Stop(
XTL::X_CDirectSoundStream* pThis
)
{
DSStream_Packet_Stop_Internal(pThis);
if (pThis->Host_BufferPacketArray.size() == 0) {
if ((pThis->EmuFlags & DSE_FLAG_ENVELOPE2) > 0) {
pThis->Xb_Status |= X_DSSSTATUS_ENVELOPECOMPLETE;
}
}
// We need to log this info since some titles may have stop/played audio often in middle of processing (relative to videos).
EmuLog(LOG_LEVEL::INFO, "Stopped: pThis = %08X; Remaining packet(s): %d",
pThis, pThis->Host_BufferPacketArray.size()
);
}
static inline void DSStream_Packet_Starved(
XTL::X_CDirectSoundStream* pThis
)
{
DSStream_Packet_Stop_Internal(pThis);
pThis->Xb_Status |= X_DSSSTATUS_STARVED | X_DSSSTATUS_PAUSED;
// We need to log this info since some titles may have stop/played audio often in middle of processing (relative to videos).
EmuLog(LOG_LEVEL::INFO, "Starved: pThis = %08X;",
pThis
@ -191,6 +216,11 @@ bool DSStream_Packet_Process(
)
{
// Do not allow to process if there is no packets.
if (pThis->Host_BufferPacketArray.size() == 0) {
return 0;
}
// If title want to pause, then don't process the packets.
// If media object is being used as playback synch, then don't process the packets.
if ((pThis->EmuFlags & DSE_FLAG_PAUSE) > 0 ||
@ -201,9 +231,14 @@ bool DSStream_Packet_Process(
return 0;
}
// Do not allow to process if there is no packets.
if (pThis->Host_BufferPacketArray.size() == 0) {
return 0;
if ((pThis->Xb_Status & X_DSSSTATUS_PAUSED) > 0) {
pThis->Xb_Status &= ~X_DSSSTATUS_PAUSED;
}
if (pThis->Host_isProcessing == false) {
if (!(pThis->EmuFlags & DSE_FLAG_IS_ACTIVATED)) {
pThis->EmuFlags |= DSE_FLAG_IS_ACTIVATED;
}
}
DWORD dwAudioBytes;
@ -218,7 +253,8 @@ bool DSStream_Packet_Process(
DWORD writePos = 0;
hRet = pThis->EmuDirectSoundBuffer8->GetCurrentPosition(nullptr, &writePos);
if (hRet == DS_OK) {
do {
// Disabled do/while loop since xbox thread will be processing as well.
//do {
int bufPlayed = writePos - packetCurrent->lastWritePos;
// Correct it if buffer was playing and is at beginning.
@ -253,10 +289,16 @@ bool DSStream_Packet_Process(
// Once bufPlayed is equal to dwMaxSize, we know the packet is completed.
if (packetCurrent->bufPlayed == packetCurrent->xmp_data.dwMaxSize) {
bool isStreamEnd = packetCurrent->isStreamEnd;
DSStream_Packet_Clear(packetCurrent, XMP_STATUS_SUCCESS, pThis->Xb_lpfnCallback, pThis->Xb_lpvContext, pThis);
if (pThis->Host_BufferPacketArray.size() == 0) {
DSStream_Packet_Starved(pThis);
if (isStreamEnd) {
DSStream_Packet_Stop(pThis);
}
else {
DSStream_Packet_Starved(pThis);
}
return 0;
}
#if 0 // Extend debug verification
@ -279,9 +321,9 @@ bool DSStream_Packet_Process(
// Otherwise, continue upload partial of the packet's data to host if there are any left.
else {
DSStream_Packet_UploadPartial(pThis, packetCurrent);
break; // Leave loop since there is more buffer haven't been process in the current packet.
//break; // Leave loop since there is more buffer haven't been process in the current packet.
}
} while (true);
//} while (true);
DSStream_Packet_Prefill(pThis, packetCurrent);
}
@ -292,17 +334,32 @@ bool DSStream_Packet_Process(
}
}
// The only place when Host_isProcessing is set to true to start play Host's audio buffer.
if (pThis->Host_isProcessing == false) {
pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags);
pThis->Host_isProcessing = true;
// Add debug log for verify if Host_isProcessing has set to true.
EmuLog(LOG_LEVEL::DEBUG, "pStream = %08X:Host_isProcessing is set to true.", pThis);
}
return 1;
}
void DSStream_Packet_FlushEx_Reset(
XTL::X_CDirectSoundStream* pThis
)
{
// Remove flags only (This is the only place it will remove other than FlushEx perform set/remove the flags.)
pThis->EmuFlags &= ~(DSE_FLAG_FLUSH_ASYNC | DSE_FLAG_ENVELOPE | DSE_FLAG_ENVELOPE2 | DSE_FLAG_PAUSE);
pThis->Xb_rtFlushEx = 0LL;
pThis->Xb_Status = 0;
}
bool DSStream_Packet_Flush(
XTL::X_CDirectSoundStream* pThis
)
{
DSStream_Packet_FlushEx_Reset(pThis);
// If host's audio is still playing then return busy-state until buffer has stop playing.
DWORD dwStatus;
pThis->EmuDirectSoundBuffer8->GetStatus(&dwStatus);

View File

@ -39,4 +39,6 @@ extern void DSStream_Packet_Clear(
extern bool DSStream_Packet_Process(XTL::X_CDirectSoundStream* pThis);
extern void DSStream_Packet_FlushEx_Reset(XTL::X_CDirectSoundStream* pThis);
extern bool DSStream_Packet_Flush(XTL::X_CDirectSoundStream* pThis);

View File

@ -390,15 +390,9 @@ static void dsound_thread_worker(LPVOID nullPtr)
{
DSoundMutexGuardLock;
vector_ds_stream::iterator ppDSStream = g_pDSoundStreamCache.begin();
for (; ppDSStream != g_pDSoundStreamCache.end(); ppDSStream++) {
if ((*ppDSStream)->Host_BufferPacketArray.size() == 0) {
continue;
}
if (((*ppDSStream)->EmuFlags & DSE_FLAG_FLUSH_ASYNC) > 0 && (*ppDSStream)->Xb_rtFlushEx == 0LL) {
DSStream_Packet_Process((*ppDSStream));
}
}
xboxkrnl::LARGE_INTEGER getTime;
xboxkrnl::KeQuerySystemTime(&getTime);
DirectSoundDoWork_Stream(getTime);
}
}
}
@ -895,7 +889,7 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSound_SynchPlayback)
if (((*ppDSBuffer)->EmuFlags & DSE_FLAG_SYNCHPLAYBACK_CONTROL) > 0) {
DSoundBufferSynchPlaybackFlagRemove((*ppDSBuffer)->EmuFlags);
EmuLog(LOG_LEVEL::DEBUG, "SynchPlayback - EmuPlayFlags: %08X", (*ppDSBuffer)->EmuPlayFlags);
EmuLog(LOG_LEVEL::DEBUG, "SynchPlayback - pDSBuffer: %08X; EmuPlayFlags: %08X", *ppDSBuffer, (*ppDSBuffer)->EmuPlayFlags);
(*ppDSBuffer)->EmuDirectSoundBuffer8->Play(0, 0, (*ppDSBuffer)->EmuPlayFlags);
}
}
@ -907,6 +901,7 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSound_SynchPlayback)
}
if (((*ppDSStream)->EmuFlags & DSE_FLAG_SYNCHPLAYBACK_CONTROL) > 0) {
DSoundBufferSynchPlaybackFlagRemove((*ppDSStream)->EmuFlags);
EmuLog(LOG_LEVEL::DEBUG, "SynchPlayback - pDSStream: %08X; EmuPlayFlags: %08X", *ppDSStream, (*ppDSStream)->EmuPlayFlags);
DSStream_Packet_Process((*ppDSStream));
}
}

View File

@ -122,6 +122,7 @@ struct X_CDirectSoundBuffer
#define DSE_FLAG_ENVELOPE (1 << 13)
#define DSE_FLAG_ENVELOPE2 (1 << 14) // NOTE: This flag is a requirement for GetStatus to return X_DSSSTATUS_ENVELOPECOMPLETE value.
#define DSE_FLAG_RECIEVEDATA (1 << 20)
#define DSE_FLAG_IS_ACTIVATED (1 << 21) // Only used for DirectSoundStream class, to acknowledge pause's no activate flag.
#define DSE_FLAG_DEBUG_MUTE (1 << 30) // Cxbx-R debugging usage only
#define DSE_FLAG_BUFFER_EXTERNAL (1 << 31)
#define DSE_FLAG_AUDIO_CODECS (DSE_FLAG_PCM | DSE_FLAG_XADPCM | DSE_FLAG_PCM_UNKNOWN)
@ -174,6 +175,7 @@ struct host_voice_packet {
DWORD bufPlayed;
DWORD bufWrittenBytes;
bool isPlayed;
bool isStreamEnd;
};
// ******************************************************************
@ -266,6 +268,7 @@ class X_CDirectSoundStream
DWORD Xb_Frequency;
DWORD Host_dwLastWritePos;
DWORD Xb_Flags;
DWORD Xb_Status;
};
// ******************************************************************

View File

@ -81,7 +81,7 @@ void DirectSoundDoWork_Buffer(xboxkrnl::LARGE_INTEGER &time)
// TODO: Do we need this in async thread loop?
if (pThis->Xb_rtPauseEx != 0LL && pThis->Xb_rtPauseEx <= time.QuadPart) {
pThis->Xb_rtPauseEx = 0LL;
pThis->EmuFlags ^= DSE_FLAG_PAUSE;
pThis->EmuFlags &= ~DSE_FLAG_PAUSE;
pThis->EmuDirectSoundBuffer8->Play(0, 0, pThis->EmuPlayFlags);
}

View File

@ -102,17 +102,17 @@ void DirectSoundDoWork_Stream(xboxkrnl::LARGE_INTEGER& time)
// TODO: Do we need this in async thread loop?
if (pThis->Xb_rtPauseEx != 0LL && pThis->Xb_rtPauseEx <= time.QuadPart) {
pThis->Xb_rtPauseEx = 0LL;
pThis->EmuFlags ^= DSE_FLAG_PAUSE;
pThis->EmuFlags &= ~DSE_FLAG_PAUSE;
// Don't call play here, let DSStream_Packet_Process deal with it.
}
if ((pThis->EmuFlags & DSE_FLAG_FLUSH_ASYNC) == 0) {
DSStream_Packet_Process(pThis);
} else {
// Confirmed flush packet must be done in DirectSoundDoWork only when title is ready.
if (pThis->Xb_rtFlushEx != 0LL && pThis->Xb_rtFlushEx <= time.QuadPart) {
pThis->Xb_rtFlushEx = 0LL;
DSStream_Packet_Flush(pThis);
// If has flush async requested then verify time has expired to perform flush process.
if ((pThis->EmuFlags & DSE_FLAG_FLUSH_ASYNC) > 0 && pThis->Xb_rtFlushEx <= time.QuadPart) {
if (pThis->Xb_rtFlushEx == 0LL) {
EmuLog(LOG_LEVEL::WARNING, "Attempted to flush without Xb_rtFlushEx set to non-zero");
}
while(DSStream_Packet_Flush(pThis));
} else {
DSStream_Packet_Process(pThis);
}
}
}
@ -251,6 +251,7 @@ HRESULT WINAPI XTL::EMUPATCH(DirectSoundCreateStream)
(*ppStream)->Host_isProcessing = false;
(*ppStream)->Xb_lpfnCallback = pdssd->lpfnCallback;
(*ppStream)->Xb_lpvContext = pdssd->lpvContext;
(*ppStream)->Xb_Status = 0;
//TODO: Implement mixbin variable support. Or just merge pdssd struct into DS Stream class.
EmuLog(LOG_LEVEL::DEBUG, "DirectSoundCreateStream, *ppStream := 0x%.08X", *ppStream);
@ -337,11 +338,9 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_Discontinuity)
// default ret = DSERR_GENERIC
// Perform check if has pending data. if so, clear pending packets.
if (pThis->Host_BufferPacketArray.size() > 1) {
for (auto buffer = pThis->Host_BufferPacketArray.begin() + 1; buffer != pThis->Host_BufferPacketArray.end();) {
DSStream_Packet_Clear(buffer, XMP_STATUS_FLUSHED, pThis->Xb_lpfnCallback, pThis->Xb_lpvContext, pThis);
}
// Perform check if packets exist, then mark the last submited packet as end of stream.
if (!pThis->Host_BufferPacketArray.empty()) {
pThis->Host_BufferPacketArray.back().isStreamEnd = true;
}
return DS_OK;
@ -360,10 +359,6 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_Flush)
DSoundBufferSynchPlaybackFlagRemove(pThis->EmuFlags);
// Remove flags only (This is the only place it will remove other than FlushEx perform set/remove the flags.)
pThis->EmuFlags &= ~(DSE_FLAG_FLUSH_ASYNC | DSE_FLAG_ENVELOPE | DSE_FLAG_ENVELOPE2);
pThis->Xb_rtFlushEx = 0LL;
while (DSStream_Packet_Flush(pThis));
return DS_OK;
@ -387,6 +382,8 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_FlushEx)
LOG_FUNC_END;
HRESULT hRet = DSERR_INVALIDPARAM;
// Reset flags here to reprocess dwFlags request.
DSStream_Packet_FlushEx_Reset(pThis);
// Cannot use rtTimeStamp here, it must be flush.
if (dwFlags == X_DSSFLUSHEX_IMMEDIATE) {
@ -464,32 +461,14 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_GetStatus)
LOG_FUNC_ARG_OUT(pdwStatus)
LOG_FUNC_END;
DWORD dwStatusXbox = 0, dwStatusHost;
DWORD dwStatusXbox = pThis->Xb_Status, dwStatusHost;
HRESULT hRet = pThis->EmuDirectSoundBuffer8->GetStatus(&dwStatusHost);
// Convert host to xbox status flag.
if (hRet == DS_OK) {
DWORD testSize = pThis->Host_BufferPacketArray.size();
if ((dwStatusHost & DSBSTATUS_PLAYING) > 0) {
if (pThis->Host_isProcessing && !(dwStatusXbox & X_DSSSTATUS_PAUSED)) {
dwStatusXbox |= X_DSSSTATUS_PLAYING;
} else {
if ((pThis->EmuFlags & DSE_FLAG_PAUSE) > 0) {
dwStatusXbox |= X_DSSSTATUS_PAUSED;
// Set to paused when has packet(s) queued and is not processing.
} else if (pThis->Host_BufferPacketArray.size() != 0 && pThis->Host_isProcessing == false) {
dwStatusXbox |= X_DSSSTATUS_PAUSED;
}
if (pThis->Host_BufferPacketArray.size() == 0) {
dwStatusXbox |= X_DSSSTATUS_STARVED;
if ((pThis->EmuFlags & DSE_FLAG_ENVELOPE2) > 0) {
dwStatusXbox |= X_DSSSTATUS_ENVELOPECOMPLETE;
}
}
}
if (pThis->Host_BufferPacketArray.size() != pThis->X_MaxAttachedPackets) {
dwStatusXbox |= X_DSSSTATUS_READY;
@ -554,6 +533,24 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_Pause)
HRESULT hRet = HybridDirectSoundBuffer_Pause(pThis->EmuDirectSoundBuffer8, dwPause, pThis->EmuFlags, pThis->EmuPlayFlags,
pThis->Host_isProcessing, 0LL, pThis->Xb_rtPauseEx);
if (dwPause == X_DSSPAUSE_PAUSENOACTIVATE) {
if (pThis->Host_BufferPacketArray.size() == 0 && !(pThis->EmuFlags & DSE_FLAG_IS_ACTIVATED)) {
pThis->EmuFlags |= DSE_FLAG_PAUSE;
}
}
if ((pThis->EmuFlags & DSE_FLAG_PAUSE) > 0) {
pThis->Host_isProcessing = false;
if ((pThis->EmuFlags & DSE_FLAG_IS_ACTIVATED) > 0) {
if (pThis->Host_BufferPacketArray.size() != 0) {
pThis->Xb_Status |= X_DSSSTATUS_PAUSED;
}
}
}
else if (!pThis->Host_isProcessing) {
DSStream_Packet_Process(pThis);
}
return hRet;
}
@ -628,6 +625,7 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_Process)
packet_input.bufWrittenBytes = 0;
packet_input.bufPlayed = 0;
packet_input.isPlayed = false;
packet_input.isStreamEnd = false;
DSoundBufferOutputXBtoHost(pThis->EmuFlags, pThis->EmuBufferDesc, pInputBuffer->pvBuffer, pInputBuffer->dwMaxSize, packet_input.pBuffer_data, packet_input.xmp_data.dwMaxSize);
@ -642,10 +640,18 @@ HRESULT WINAPI XTL::EMUPATCH(CDirectSoundStream_Process)
if (pThis->Host_isProcessing == false && pThis->Host_BufferPacketArray.size() == 1) {
pThis->EmuDirectSoundBuffer8->SetCurrentPosition(packet_input.nextWriteOffset);
}
if ((pThis->Xb_Status & X_DSSSTATUS_STARVED) > 0) {
pThis->Xb_Status &= ~X_DSSSTATUS_STARVED;
}
if ((pThis->EmuFlags & DSE_FLAG_IS_ACTIVATED) > 0 && (pThis->EmuFlags & DSE_FLAG_PAUSE) > 0) {
pThis->Xb_Status |= X_DSSSTATUS_PAUSED;
}
DSStream_Packet_Process(pThis);
// Once full it needs to change status to flushed when cannot hold any more packets.
} else {
if (pInputBuffer->pdwStatus != xbnullptr) {
(*pInputBuffer->pdwStatus) = XMP_STATUS_FLUSHED;
(*pInputBuffer->pdwStatus) = XMP_STATUS_FAILURE;
}
}
}