From c28bfbc5b274e9009060fafa6ffaab7375c46014 Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Fri, 11 Sep 2015 10:55:58 -0500 Subject: [PATCH] xaudio2: Support looping buffers. Also shorten up the test runtime. --- dlls/xaudio2_7/tests/xaudio2.c | 265 ++++++++++++++++++++++++++++++++- dlls/xaudio2_7/xaudio_dll.c | 132 ++++++++++++++-- 2 files changed, 382 insertions(+), 15 deletions(-) diff --git a/dlls/xaudio2_7/tests/xaudio2.c b/dlls/xaudio2_7/tests/xaudio2.c index 0538f80c072..aabb70eceef 100644 --- a/dlls/xaudio2_7/tests/xaudio2.c +++ b/dlls/xaudio2_7/tests/xaudio2.c @@ -209,9 +209,9 @@ static void test_simple_streaming(IXAudio2 *xa) ok(hr == S_OK, "CreateSourceVoice failed: %08x\n", hr); memset(&buf, 0, sizeof(buf)); - buf.AudioBytes = 44100 * fmt.nBlockAlign; + buf.AudioBytes = 22050 * fmt.nBlockAlign; buf.pAudioData = HeapAlloc(GetProcessHeap(), 0, buf.AudioBytes); - fill_buf((float*)buf.pAudioData, &fmt, 440, 44100); + fill_buf((float*)buf.pAudioData, &fmt, 440, 22050); hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); @@ -224,9 +224,9 @@ static void test_simple_streaming(IXAudio2 *xa) ok(hr == S_OK, "CreateSourceVoice failed: %08x\n", hr); memset(&buf2, 0, sizeof(buf2)); - buf2.AudioBytes = 44100 * fmt.nBlockAlign; + buf2.AudioBytes = 22050 * fmt.nBlockAlign; buf2.pAudioData = HeapAlloc(GetProcessHeap(), 0, buf2.AudioBytes); - fill_buf((float*)buf2.pAudioData, &fmt, 220, 44100); + fill_buf((float*)buf2.pAudioData, &fmt, 220, 22050); hr = IXAudio2SourceVoice_SubmitSourceBuffer(src2, &buf2, NULL); ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); @@ -264,12 +264,12 @@ static void test_simple_streaming(IXAudio2 *xa) IXAudio27SourceVoice_GetState((IXAudio27SourceVoice*)src, &state); else IXAudio2SourceVoice_GetState(src, &state, 0); - if(state.SamplesPlayed >= 44100) + if(state.SamplesPlayed >= 22050) break; Sleep(100); } - ok(state.SamplesPlayed == 44100, "Got wrong samples played\n"); + ok(state.SamplesPlayed == 22050, "Got wrong samples played\n"); HeapFree(GetProcessHeap(), 0, (void*)buf.pAudioData); HeapFree(GetProcessHeap(), 0, (void*)buf2.pAudioData); @@ -373,6 +373,45 @@ static const IXAudio2VoiceCallbackVtbl vcb_buf_vtbl = { static IXAudio2VoiceCallback vcb_buf = { &vcb_buf_vtbl }; +static int nloopends = 0; + +static void WINAPI loop_buf_OnStreamEnd(IXAudio2VoiceCallback *This) +{ +} + +static void WINAPI loop_buf_OnBufferStart(IXAudio2VoiceCallback *This, + void *pBufferContext) +{ +} + +static void WINAPI loop_buf_OnBufferEnd(IXAudio2VoiceCallback *This, + void *pBufferContext) +{ +} + +static void WINAPI loop_buf_OnLoopEnd(IXAudio2VoiceCallback *This, + void *pBufferContext) +{ + ++nloopends; +} + +static void WINAPI loop_buf_OnVoiceError(IXAudio2VoiceCallback *This, + void *pBuffercontext, HRESULT Error) +{ +} + +static const IXAudio2VoiceCallbackVtbl loop_buf_vtbl = { + vcb_buf_OnVoiceProcessingPassStart, + vcb_buf_OnVoiceProcessingPassEnd, + loop_buf_OnStreamEnd, + loop_buf_OnBufferStart, + loop_buf_OnBufferEnd, + loop_buf_OnLoopEnd, + loop_buf_OnVoiceError +}; + +static IXAudio2VoiceCallback loop_buf = { &loop_buf_vtbl }; + static void test_buffer_callbacks(IXAudio2 *xa) { HRESULT hr; @@ -450,6 +489,218 @@ static void test_buffer_callbacks(IXAudio2 *xa) IXAudio2MasteringVoice_DestroyVoice(master); } +static UINT32 play_to_completion(IXAudio2SourceVoice *src, UINT32 max_samples) +{ + XAUDIO2_VOICE_STATE state; + HRESULT hr; + + nloopends = 0; + + hr = IXAudio2SourceVoice_Start(src, 0, XAUDIO2_COMMIT_NOW); + ok(hr == S_OK, "Start failed: %08x\n", hr); + + while(1){ + if(xaudio27) + IXAudio27SourceVoice_GetState((IXAudio27SourceVoice*)src, &state); + else + IXAudio2SourceVoice_GetState(src, &state, 0); + if(state.BuffersQueued == 0) + break; + if(state.SamplesPlayed >= max_samples){ + if(xaudio27) + IXAudio27SourceVoice_ExitLoop((IXAudio27SourceVoice*)src, XAUDIO2_COMMIT_NOW); + else + IXAudio2SourceVoice_ExitLoop(src, XAUDIO2_COMMIT_NOW); + } + Sleep(100); + } + + hr = IXAudio2SourceVoice_Stop(src, 0, XAUDIO2_COMMIT_NOW); + ok(hr == S_OK, "Start failed: %08x\n", hr); + + return state.SamplesPlayed; +} + +static void test_looping(IXAudio2 *xa) +{ + HRESULT hr; + IXAudio2MasteringVoice *master; + IXAudio2SourceVoice *src; + WAVEFORMATEX fmt; + XAUDIO2_BUFFER buf; + UINT32 played, running_total = 0; + + XA2CALL_0V(StopEngine); + + if(xaudio27) + hr = IXAudio27_CreateMasteringVoice((IXAudio27*)xa, &master, 2, 44100, 0, 0, NULL); + else + hr = IXAudio2_CreateMasteringVoice(xa, &master, 2, 44100, 0, NULL, NULL, AudioCategory_GameEffects); + ok(hr == S_OK, "CreateMasteringVoice failed: %08x\n", hr); + + + fmt.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + fmt.nChannels = 2; + fmt.nSamplesPerSec = 44100; + fmt.wBitsPerSample = 32; + fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8; + fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign; + fmt.cbSize = 0; + + XA2CALL(CreateSourceVoice, &src, &fmt, 0, 1.f, &loop_buf, NULL, NULL); + ok(hr == S_OK, "CreateSourceVoice failed: %08x\n", hr); + + memset(&buf, 0, sizeof(buf)); + + buf.AudioBytes = 44100 * fmt.nBlockAlign; + buf.pAudioData = HeapAlloc(GetProcessHeap(), 0, buf.AudioBytes); + fill_buf((float*)buf.pAudioData, &fmt, 440, 44100); + + XA2CALL_0(StartEngine); + ok(hr == S_OK, "StartEngine failed: %08x\n", hr); + + /* play from middle to end */ + buf.PlayBegin = 22050; + buf.PlayLength = 0; + buf.LoopBegin = 0; + buf.LoopLength = 0; + buf.LoopCount = 0; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 22050, "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 0, "Got wrong OnLoopEnd calls: %u\n", nloopends); + + /* play 4410 samples from middle */ + buf.PlayBegin = 22050; + buf.PlayLength = 4410; + buf.LoopBegin = 0; + buf.LoopLength = 0; + buf.LoopCount = 0; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 4410, "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 0, "Got wrong OnLoopEnd calls: %u\n", nloopends); + + /* loop 4410 samples in middle */ + buf.PlayBegin = 0; + buf.PlayLength = 0; + buf.LoopBegin = 22050; + buf.LoopLength = 4410; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 44100 + 4410, "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 1, "Got wrong OnLoopEnd calls: %u\n", nloopends); + + /* play last half, then loop the whole buffer */ + buf.PlayBegin = 22050; + buf.PlayLength = 0; + buf.LoopBegin = 0; + buf.LoopLength = 0; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 22050 + 44100, "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 1, "Got wrong OnLoopEnd calls: %u\n", nloopends); + + /* play short segment from middle, loop to the beginning, and end at PlayEnd */ + buf.PlayBegin = 22050; + buf.PlayLength = 4410; + buf.LoopBegin = 0; + buf.LoopLength = 0; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 4410 + (22050 + 4410), "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 1, "Got wrong OnLoopEnd calls: %u\n", nloopends); + + /* invalid: LoopEnd must be <= PlayEnd + * xaudio27: play until LoopEnd, loop to beginning, play until PlayEnd */ + buf.PlayBegin = 22050; + buf.PlayLength = 4410; + buf.LoopBegin = 0; + buf.LoopLength = 22050 + 4410 * 2; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + if(xaudio27){ + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 4410 + (22050 + 4410 * 2), "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 1, "Got wrong OnLoopEnd calls: %u\n", nloopends); + }else + ok(hr == XAUDIO2_E_INVALID_CALL, "SubmitSourceBuffer should have failed: %08x\n", hr); + + /* invalid: LoopEnd must be within play range + * xaudio27: plays only play range */ + buf.PlayBegin = 22050; + buf.PlayLength = 0; /* == until end of buffer */ + buf.LoopBegin = 0; + buf.LoopLength = 22050; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + if(xaudio27){ + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, -1); + ok(played - running_total == 22050, "Got wrong samples played: %u\n", played - running_total); + running_total = played; + ok(nloopends == 0, "Got wrong OnLoopEnd calls: %u\n", nloopends); + }else + ok(hr == XAUDIO2_E_INVALID_CALL, "SubmitSourceBuffer should have failed: %08x\n", hr); + + /* invalid: LoopBegin must be before PlayEnd + * xaudio27: crashes */ + if(!xaudio27){ + buf.PlayBegin = 0; + buf.PlayLength = 4410; + buf.LoopBegin = 22050; + buf.LoopLength = 4410; + buf.LoopCount = 1; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == XAUDIO2_E_INVALID_CALL, "SubmitSourceBuffer should have failed: %08x\n", hr); + } + + /* infinite looping buffer */ + buf.PlayBegin = 22050; + buf.PlayLength = 0; + buf.LoopBegin = 0; + buf.LoopLength = 0; + buf.LoopCount = 255; + + hr = IXAudio2SourceVoice_SubmitSourceBuffer(src, &buf, NULL); + ok(hr == S_OK, "SubmitSourceBuffer failed: %08x\n", hr); + + played = play_to_completion(src, running_total + 88200); + ok(played - running_total == 22050 + 44100 * 2, "Got wrong samples played: %u\n", played - running_total); + ok(nloopends == (played - running_total) / 88200 + 1, "Got wrong OnLoopEnd calls: %u\n", nloopends); + running_total = played; +} + static UINT32 test_DeviceDetails(IXAudio27 *xa) { HRESULT hr; @@ -519,6 +770,7 @@ START_TEST(xaudio2) test_DeviceDetails(xa27); test_simple_streaming((IXAudio2*)xa27); test_buffer_callbacks((IXAudio2*)xa27); + test_looping((IXAudio2*)xa27); }else skip("No audio devices available\n"); @@ -537,6 +789,7 @@ START_TEST(xaudio2) if(has_devices){ test_simple_streaming(xa); test_buffer_callbacks(xa); + test_looping(xa); }else skip("No audio devices available\n"); diff --git a/dlls/xaudio2_7/xaudio_dll.c b/dlls/xaudio2_7/xaudio_dll.c index e68f1b68df1..ccbcd21fbf8 100644 --- a/dlls/xaudio2_7/xaudio_dll.c +++ b/dlls/xaudio2_7/xaudio_dll.c @@ -80,6 +80,9 @@ static void dump_fmt(const WAVEFORMATEX *fmt) TRACE("dwChannelMask: %08x\n", fmtex->dwChannelMask); TRACE("Samples: %04x\n", fmtex->Samples.wReserved); TRACE("SubFormat: %s\n", wine_dbgstr_guid(&fmtex->SubFormat)); + }else if(fmt->wFormatTag == WAVE_FORMAT_ADPCM){ + ADPCMWAVEFORMAT *fmtadpcm = (void*)fmt; + TRACE("wSamplesPerBlock: %u\n", fmtadpcm->wSamplesPerBlock); } } @@ -127,7 +130,7 @@ HRESULT WINAPI DllUnregisterServer(void) typedef struct _XA2Buffer { XAUDIO2_BUFFER xa2buffer; DWORD offs_bytes; - UINT32 latest_al_buf; + UINT32 latest_al_buf, looped, loop_end_bytes, play_end_bytes, cur_end_bytes; } XA2Buffer; typedef struct _IXAudio2Impl IXAudio2Impl; @@ -625,7 +628,67 @@ static HRESULT WINAPI XA2SRC_SubmitSourceBuffer(IXAudio2SourceVoice *iface, * but pBuffer itself may be reused immediately */ memcpy(&buf->xa2buffer, pBuffer, sizeof(*pBuffer)); - buf->offs_bytes = 0; + /* convert samples offsets to bytes */ + if(This->fmt->wFormatTag == WAVE_FORMAT_ADPCM){ + /* ADPCM gives us a number of samples per block, so round down to + * nearest block and convert to bytes */ + buf->xa2buffer.PlayBegin = buf->xa2buffer.PlayBegin / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign; + buf->xa2buffer.PlayLength = buf->xa2buffer.PlayLength / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign; + buf->xa2buffer.LoopBegin = buf->xa2buffer.LoopBegin / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign; + buf->xa2buffer.LoopLength = buf->xa2buffer.LoopLength / ((ADPCMWAVEFORMAT*)This->fmt)->wSamplesPerBlock * This->fmt->nBlockAlign; + }else{ + buf->xa2buffer.PlayBegin *= This->fmt->nBlockAlign; + buf->xa2buffer.PlayLength *= This->fmt->nBlockAlign; + buf->xa2buffer.LoopBegin *= This->fmt->nBlockAlign; + buf->xa2buffer.LoopLength *= This->fmt->nBlockAlign; + } + + if(buf->xa2buffer.PlayLength == 0) + /* set to end of buffer */ + buf->xa2buffer.PlayLength = buf->xa2buffer.AudioBytes - buf->xa2buffer.PlayBegin; + + buf->play_end_bytes = buf->xa2buffer.PlayBegin + buf->xa2buffer.PlayLength; + + if(buf->xa2buffer.LoopCount){ + if(buf->xa2buffer.LoopLength == 0) + /* set to end of play range */ + buf->xa2buffer.LoopLength = buf->play_end_bytes - buf->xa2buffer.LoopBegin; + + if(buf->xa2buffer.LoopBegin >= buf->play_end_bytes){ + /* this actually crashes on native xaudio 2.7 */ + LeaveCriticalSection(&This->lock); + return XAUDIO2_E_INVALID_CALL; + } + + buf->loop_end_bytes = buf->xa2buffer.LoopBegin + buf->xa2buffer.LoopLength; + + /* xaudio 2.7 allows some invalid looping setups, but later versions + * return an error */ + if(This->xa2->version > 27){ + if(buf->loop_end_bytes > buf->play_end_bytes){ + LeaveCriticalSection(&This->lock); + return XAUDIO2_E_INVALID_CALL; + } + + if(buf->loop_end_bytes <= buf->xa2buffer.PlayBegin){ + LeaveCriticalSection(&This->lock); + return XAUDIO2_E_INVALID_CALL; + } + }else{ + if(buf->loop_end_bytes <= buf->xa2buffer.PlayBegin){ + buf->xa2buffer.LoopCount = 0; + buf->loop_end_bytes = buf->play_end_bytes; + } + } + }else{ + buf->xa2buffer.LoopLength = buf->xa2buffer.PlayLength; + buf->xa2buffer.LoopBegin = buf->xa2buffer.PlayBegin; + buf->loop_end_bytes = buf->play_end_bytes; + } + + buf->offs_bytes = buf->xa2buffer.PlayBegin; + buf->cur_end_bytes = buf->loop_end_bytes; + buf->latest_al_buf = -1; ++This->nbufs; @@ -692,7 +755,15 @@ static HRESULT WINAPI XA2SRC_Discontinuity(IXAudio2SourceVoice *iface) static HRESULT WINAPI XA2SRC_ExitLoop(IXAudio2SourceVoice *iface, UINT32 OperationSet) { XA2SourceImpl *This = impl_from_IXAudio2SourceVoice(iface); + TRACE("%p, 0x%x\n", This, OperationSet); + + EnterCriticalSection(&This->lock); + + This->buffers[This->cur_buf].looped = XAUDIO2_LOOP_INFINITE; + + LeaveCriticalSection(&This->lock); + return S_OK; } @@ -2908,12 +2979,12 @@ static BOOL xa2buffer_queue_period(XA2SourceImpl *src, XA2Buffer *buf, ALuint al UINT32 submit_bytes; const BYTE *submit_buf = NULL; - if(buf->offs_bytes >= buf->xa2buffer.AudioBytes){ + if(buf->offs_bytes >= buf->cur_end_bytes){ WARN("Shouldn't happen: Trying to push frames from a spent buffer?\n"); return FALSE; } - submit_bytes = min(src->xa2->period_frames * src->submit_blocksize, buf->xa2buffer.AudioBytes - buf->offs_bytes); + submit_bytes = min(src->xa2->period_frames * src->submit_blocksize, buf->cur_end_bytes - buf->offs_bytes); submit_buf = buf->xa2buffer.pAudioData + buf->offs_bytes; buf->offs_bytes += submit_bytes; @@ -2929,9 +3000,32 @@ static BOOL xa2buffer_queue_period(XA2SourceImpl *src, XA2Buffer *buf, ALuint al TRACE("queueing %u bytes, now %u in AL\n", submit_bytes, src->in_al_bytes); - return buf->offs_bytes < buf->xa2buffer.AudioBytes; + return buf->offs_bytes < buf->cur_end_bytes; } +/* Looping: + * + * The looped section of a buffer is a subset of the play area which is looped + * LoopCount times. + * + * v PlayBegin + * vvvvvvvvvvvvvvvvvv PlayLength + * v (PlayEnd) + * [-----PPPLLLLLLLLPPPPPPP------] + * ^ LoopBegin + * ^^^^^^^^ LoopLength + * ^ (LoopEnd) + * + * In the simple case, playback will start at PlayBegin. At LoopEnd, playback + * will move to LoopBegin and repeat that loop LoopCount times. Then, playback + * will cease at LoopEnd. + * + * If PlayLength is zero, then PlayEnd is the end of the buffer. + * + * If LoopLength is zero, then LoopEnd is PlayEnd. + * + * For corner cases and version differences, see tests. + */ static void update_source_state(XA2SourceImpl *src) { int i; @@ -2985,15 +3079,35 @@ static void update_source_state(XA2SourceImpl *src) TRACE("%p: going to queue a period from buffer %u\n", src, src->cur_buf); /* starting from an empty buffer */ - if(src->cb && src->cur_buf == src->first_buf && src->buffers[src->cur_buf].offs_bytes == 0) + if(src->cb && src->cur_buf == src->first_buf && src->buffers[src->cur_buf].offs_bytes == 0 && !src->buffers[src->cur_buf].looped) IXAudio2VoiceCallback_OnBufferStart(src->cb, src->buffers[src->first_buf].xa2buffer.pContext); if(!xa2buffer_queue_period(src, &src->buffers[src->cur_buf], src->al_bufs[(src->first_al_buf + src->al_bufs_used) % XAUDIO2_MAX_QUEUED_BUFFERS])){ - /* buffer is spent, move on */ - src->cur_buf++; - src->cur_buf %= XAUDIO2_MAX_QUEUED_BUFFERS; + XA2Buffer *cur = &src->buffers[src->cur_buf]; + + if(cur->looped < cur->xa2buffer.LoopCount){ + if(cur->xa2buffer.LoopCount != XAUDIO2_LOOP_INFINITE) + ++cur->looped; + else + cur->looped = 1; /* indicate that we are executing a loop */ + + cur->offs_bytes = cur->xa2buffer.LoopBegin; + if(cur->looped == cur->xa2buffer.LoopCount) + cur->cur_end_bytes = cur->play_end_bytes; + else + cur->cur_end_bytes = cur->loop_end_bytes; + + if(src->cb) + IXAudio2VoiceCallback_OnLoopEnd(src->cb, + src->buffers[src->cur_buf].xa2buffer.pContext); + + }else{ + /* buffer is spent, move on */ + src->cur_buf++; + src->cur_buf %= XAUDIO2_MAX_QUEUED_BUFFERS; + } } } }