Fix XAudio2 playback when seeking audio start play location

This commit is contained in:
Wojtek Figat
2023-10-12 15:20:23 +02:00
parent 4ac65ee91c
commit 455b3f2446

View File

@@ -31,7 +31,7 @@
#define XAUDIO2_CHECK_ERROR(method) \ #define XAUDIO2_CHECK_ERROR(method) \
if (hr != 0) \ if (hr != 0) \
{ \ { \
LOG(Error, "XAudio2 method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), hr, __LINE__ - 1); \ LOG(Error, "XAudio2 method {0} failed with error 0x{1:X} (at line {2})", TEXT(#method), (uint32)hr, __LINE__ - 1); \
} }
#else #else
#define XAUDIO2_CHECK_ERROR(method) #define XAUDIO2_CHECK_ERROR(method)
@@ -131,7 +131,8 @@ namespace XAudio2
XAUDIO2_SEND_DESCRIPTOR Destination; XAUDIO2_SEND_DESCRIPTOR Destination;
float Pitch; float Pitch;
float Pan; float Pan;
float StartTime; float StartTimeForQueueBuffer;
float LastBufferStartTime;
float DopplerFactor; float DopplerFactor;
uint64 LastBufferStartSamplesPlayed; uint64 LastBufferStartSamplesPlayed;
int32 BuffersProcessed; int32 BuffersProcessed;
@@ -155,7 +156,8 @@ namespace XAudio2
Destination.pOutputVoice = nullptr; Destination.pOutputVoice = nullptr;
Pitch = 1.0f; Pitch = 1.0f;
Pan = 0.0f; Pan = 0.0f;
StartTime = 0.0f; StartTimeForQueueBuffer = 0.0f;
LastBufferStartTime = 0.0f;
IsDirty = false; IsDirty = false;
Is3D = false; Is3D = false;
IsPlaying = false; IsPlaying = false;
@@ -265,11 +267,14 @@ namespace XAudio2
buffer.pAudioData = aBuffer->Data.Get(); buffer.pAudioData = aBuffer->Data.Get();
buffer.AudioBytes = aBuffer->Data.Count(); buffer.AudioBytes = aBuffer->Data.Count();
if (aSource->StartTime > ZeroTolerance) if (aSource->StartTimeForQueueBuffer > ZeroTolerance)
{ {
buffer.PlayBegin = (UINT32)(aSource->StartTime * (aBuffer->Info.SampleRate * aBuffer->Info.NumChannels)); // Offset start position when playing buffer with a custom time offset
buffer.PlayLength = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels - buffer.PlayBegin; const uint32 bytesPerSample = aBuffer->Info.BitDepth / 8 * aBuffer->Info.NumChannels;
aSource->StartTime = 0; buffer.PlayBegin = (UINT32)(aSource->StartTimeForQueueBuffer * aBuffer->Info.SampleRate);
buffer.PlayLength = (buffer.AudioBytes / bytesPerSample) - buffer.PlayBegin;
aSource->LastBufferStartTime = aSource->StartTimeForQueueBuffer;
aSource->StartTimeForQueueBuffer = 0;
} }
const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer); const HRESULT hr = aSource->Voice->SubmitSourceBuffer(&buffer);
@@ -512,6 +517,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
hr = aSource->Voice->FlushSourceBuffers(); hr = aSource->Voice->FlushSourceBuffers();
XAUDIO2_CHECK_ERROR(FlushSourceBuffers); XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
aSource->LastBufferStartSamplesPlayed = 0; aSource->LastBufferStartSamplesPlayed = 0;
aSource->LastBufferStartTime = 0;
aSource->BuffersProcessed = 0; aSource->BuffersProcessed = 0;
XAUDIO2_BUFFER buffer = { 0 }; XAUDIO2_BUFFER buffer = { 0 };
@@ -524,7 +530,7 @@ void AudioBackendXAudio2::Source_IsLoopingChanged(AudioSource* source)
const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels; const UINT32 totalSamples = aBuffer->Info.NumSamples / aBuffer->Info.NumChannels;
buffer.PlayBegin = state.SamplesPlayed % totalSamples; buffer.PlayBegin = state.SamplesPlayed % totalSamples;
buffer.PlayLength = totalSamples - buffer.PlayBegin; buffer.PlayLength = totalSamples - buffer.PlayBegin;
aSource->StartTime = 0; aSource->StartTimeForQueueBuffer = 0;
XAudio2::QueueBuffer(aSource, source, bufferId, buffer); XAudio2::QueueBuffer(aSource, source, bufferId, buffer);
@@ -610,7 +616,8 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
auto aSource = XAudio2::GetSource(source); auto aSource = XAudio2::GetSource(source);
if (aSource && aSource->Voice) if (aSource && aSource->Voice)
{ {
aSource->StartTime = 0.0f; aSource->StartTimeForQueueBuffer = 0.0f;
aSource->LastBufferStartTime = 0.0f;
// Pause // Pause
HRESULT hr = aSource->Voice->Stop(); HRESULT hr = aSource->Voice->Stop();
@@ -620,6 +627,7 @@ void AudioBackendXAudio2::Source_Stop(AudioSource* source)
// Unset streaming buffers to rewind // Unset streaming buffers to rewind
hr = aSource->Voice->FlushSourceBuffers(); hr = aSource->Voice->FlushSourceBuffers();
XAUDIO2_CHECK_ERROR(FlushSourceBuffers); XAUDIO2_CHECK_ERROR(FlushSourceBuffers);
Platform::Sleep(10); // TODO: find a better way to handle case when VoiceCallback::OnBufferEnd is called after source was stopped thus BuffersProcessed != 0, probably via buffers contexts ptrs
aSource->BuffersProcessed = 0; aSource->BuffersProcessed = 0;
aSource->Callback.PeekSamples(); aSource->Callback.PeekSamples();
} }
@@ -631,7 +639,7 @@ void AudioBackendXAudio2::Source_SetCurrentBufferTime(AudioSource* source, float
if (aSource) if (aSource)
{ {
// Store start time so next buffer submitted will start from here (assumes audio is stopped) // Store start time so next buffer submitted will start from here (assumes audio is stopped)
aSource->StartTime = value; aSource->StartTimeForQueueBuffer = value;
} }
} }
@@ -647,8 +655,9 @@ float AudioBackendXAudio2::Source_GetCurrentBufferTime(const AudioSource* source
aSource->Voice->GetState(&state); aSource->Voice->GetState(&state);
const uint32 numChannels = clipInfo.NumChannels; const uint32 numChannels = clipInfo.NumChannels;
const uint32 totalSamples = clipInfo.NumSamples / numChannels; const uint32 totalSamples = clipInfo.NumSamples / numChannels;
const uint32 sampleRate = clipInfo.SampleRate;// / clipInfo.NumChannels;
state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin state.SamplesPlayed -= aSource->LastBufferStartSamplesPlayed % totalSamples; // Offset by the last buffer start to get time relative to its begin
time = aSource->StartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, clipInfo.SampleRate)); time = aSource->LastBufferStartTime + (state.SamplesPlayed % totalSamples) / static_cast<float>(Math::Max(1U, sampleRate));
} }
return time; return time;
} }
@@ -765,8 +774,7 @@ void AudioBackendXAudio2::Buffer_Write(uint32 bufferId, byte* samples, const Aud
XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1]; XAudio2::Buffer* aBuffer = XAudio2::Buffers[bufferId - 1];
XAudio2::Locker.Unlock(); XAudio2::Locker.Unlock();
const uint32 bytesPerSample = info.BitDepth / 8; const uint32 samplesLength = info.NumSamples * info.BitDepth / 8;
const int32 samplesLength = info.NumSamples * bytesPerSample;
aBuffer->Info = info; aBuffer->Info = info;
aBuffer->Data.Set(samples, samplesLength); aBuffer->Data.Set(samples, samplesLength);