Commit c28bfbc5 authored by Andrew Eikum's avatar Andrew Eikum Committed by Alexandre Julliard

xaudio2: Support looping buffers.

Also shorten up the test runtime.
parent 031af2ca
......@@ -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");
......
......@@ -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;
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment