Update XAudio2 support to handle overflow, underflow, and audio_latency properly.

This commit is contained in:
Bradley Hughes 2016-01-20 23:07:59 -05:00 committed by Brad Hughes
parent 2f36228306
commit 7b072ad174

View File

@ -43,7 +43,7 @@
//============================================================
#define INITIAL_BUFFER_COUNT 4
#define MIN_QUEUE_DEPTH 1
#define SUBMIT_FREQUENCY_TARGET_MS 20
//============================================================
// Macros
@ -231,6 +231,7 @@ private:
DWORD m_sample_bytes;
std::unique_ptr<BYTE[]> m_buffer;
DWORD m_buffer_size;
DWORD m_buffer_count;
DWORD m_writepos;
osd_lock_ptr m_buffer_lock;
HANDLE m_hEventBufferCompleted;
@ -241,6 +242,9 @@ private:
std::unique_ptr<bufferpool> m_buffer_pool;
HMODULE m_xaudio2_module;
PFN_XAUDIO2CREATE m_pfnxaudio2create;
UINT32 m_overflows;
UINT32 m_underflows;
BOOL m_in_underflow;
public:
sound_xaudio2() :
@ -252,13 +256,18 @@ public:
m_sample_bytes(0),
m_buffer(nullptr),
m_buffer_size(0),
m_buffer_count(0),
m_writepos(0),
m_buffer_lock(osd_lock_alloc()),
m_hEventBufferCompleted(NULL),
m_hEventDataAvailable(NULL),
m_hEventExiting(NULL),
m_buffer_pool(nullptr),
m_xaudio2_module(NULL)
m_xaudio2_module(NULL),
m_pfnxaudio2create(nullptr),
m_overflows(0),
m_underflows(0),
m_in_underflow(FALSE)
{
}
@ -270,7 +279,7 @@ public:
virtual void set_mastervolume(int attenuation) override;
// Xaudio callbacks
void OnVoiceProcessingPassStart(UINT32 bytes_required) override {}
void OnVoiceProcessingPassStart(UINT32 bytes_required) override;
void OnVoiceProcessingPassEnd() override {}
void OnStreamEnd() override {}
void OnBufferStart(void* pBufferContext) override {}
@ -279,11 +288,14 @@ public:
void OnBufferEnd(void *pBufferContext) override;
private:
void create_buffers(const WAVEFORMATEX &format);
HRESULT create_voices(const WAVEFORMATEX &format);
void process_audio();
void submit_buffer(std::unique_ptr<BYTE[]> audioData, DWORD audioLength);
void submit_needed();
HRESULT xaudio2_create(IXAudio2 ** xaudio2_interface);
void roll_buffer();
BOOL submit_next_queued();
};
//============================================================
@ -319,18 +331,6 @@ int sound_xaudio2::init(osd_options const &options)
m_xAudio2->SetDebugConfiguration(&debugConfig);
#endif
// Compute the buffer size
m_buffer_size = format.nSamplesPerSec * format.nBlockAlign * m_audio_latency / 10;
m_buffer_size = MAX(1024, (m_buffer_size / 1024) * 1024);
m_buffer_size = MIN(m_buffer_size, XAUDIO2_MAX_BUFFER_BYTES);
// Create the buffer pool
m_buffer_pool = std::make_unique<bufferpool>(INITIAL_BUFFER_COUNT, m_buffer_size);
// get our initial buffer
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
osd_printf_verbose("Sound: XAudio2 created initial buffer size: %u\n", (unsigned int)m_buffer_size);
// Initialize our events
m_hEventBufferCompleted = CreateEvent(NULL, FALSE, FALSE, NULL);
m_hEventDataAvailable = CreateEvent(NULL, FALSE, FALSE, NULL);
@ -368,6 +368,9 @@ void sound_xaudio2::exit()
m_buffer.reset();
m_buffer_pool.reset();
if (m_overflows != 0 || m_underflows != 0)
osd_printf_verbose("Sound: overflows=%u, underflows=%u\n", m_overflows, m_underflows);
osd_printf_verbose("Sound: XAudio2 deinitialized\n");
}
@ -387,24 +390,24 @@ void sound_xaudio2::update_audio_stream(
osd_scoped_lock scope_lock(m_buffer_lock.get());
// Ensure this is going to fit in the current buffer
if (m_writepos + bytes_this_frame > m_buffer_size)
UINT32 bytes_left = bytes_this_frame;
while (bytes_left > 0)
{
// Queue the current buffer
xaudio2_buffer buf;
buf.AudioData = std::move(m_buffer);
buf.AudioSize = m_writepos;
m_queue.push(std::move(buf));
UINT32 chunk = MIN(m_buffer_size, bytes_left);
// Get a new buffer
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
m_writepos = 0;
// Roll the buffer if needed
if (m_writepos + chunk >= m_buffer_size)
{
roll_buffer();
}
// Copy in the data
memcpy(m_buffer.get() + m_writepos, buffer, chunk);
m_writepos += chunk;
bytes_left -= chunk;
}
// Copy in the data
memcpy(m_buffer.get() + m_writepos, buffer, bytes_this_frame);
m_writepos += bytes_this_frame;
// Signal data available
SetEvent(m_hEventDataAvailable);
}
@ -444,12 +447,34 @@ void sound_xaudio2::OnBufferEnd(void *pBufferContext)
{
auto scoped_lock = osd_scoped_lock(m_buffer_lock.get());
m_buffer_pool->return_to_pool(completed_buffer);
completed_buffer = nullptr;
}
SetEvent(m_hEventBufferCompleted);
}
//============================================================
// IXAudio2VoiceCallback::OnVoiceProcessingPassStart
//============================================================
// The XAudio2 voice callback triggered on every pass
void sound_xaudio2::OnVoiceProcessingPassStart(UINT32 bytes_required)
{
if (bytes_required == 0)
{
// Reset underflow indicator if we're caught up
if (m_in_underflow) m_in_underflow = FALSE;
return;
}
// Since there are bytes required, we're going to be in underflow
if (!m_in_underflow)
{
m_underflows++;
m_in_underflow = TRUE;
}
}
//============================================================
// xaudio2_create
//============================================================
@ -484,6 +509,46 @@ HRESULT sound_xaudio2::xaudio2_create(IXAudio2 ** ppxaudio2_interface)
return S_OK;
}
//============================================================
// create_buffers
//============================================================
void sound_xaudio2::create_buffers(const WAVEFORMATEX &format)
{
// Compute the buffer size
// buffer size is equal to the bytes we need to hold in memory per X tenths of a second where X is audio_latency
float audio_latency_in_seconds = m_audio_latency / 10.0f;
UINT32 format_bytes_per_second = format.nSamplesPerSec * format.nBlockAlign;
UINT32 total_buffer_size = format_bytes_per_second * audio_latency_in_seconds;
// We want to be able to submit buffers every X milliseconds
// I want to divide these up into "packets" so figure out how many buffers we need
m_buffer_count = (audio_latency_in_seconds * 1000.0f) / SUBMIT_FREQUENCY_TARGET_MS;
// Now record the size of the individual buffers
m_buffer_size = MAX(1024, total_buffer_size / m_buffer_count);
// Make the buffer a multiple of the format size bytes (rounding up)
UINT32 remainder = m_buffer_size % format.nBlockAlign;
if (remainder != 0)
m_buffer_size += format.nBlockAlign - remainder;
// get our initial buffer pool and our first buffer
m_buffer_pool = std::make_unique<bufferpool>(m_buffer_count + 1, m_buffer_size);
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
osd_printf_verbose(
"Sound: XAudio2 created initial buffers. total size: %u, count %u, size each %u\n",
total_buffer_size,
m_buffer_count,
m_buffer_size);
// reset buffer states
m_writepos = 0;
m_overflows = 0;
m_underflows = 0;
}
//============================================================
// create_voices
//============================================================
@ -553,40 +618,19 @@ void sound_xaudio2::process_audio()
void sound_xaudio2::submit_needed()
{
XAUDIO2_VOICE_STATE state;
m_sourceVoice->GetState(&state);
m_sourceVoice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED);
if (state.BuffersQueued >= MIN_QUEUE_DEPTH)
// If we have a buffer on the queue, no reason to submit
if (state.BuffersQueued >= 1)
return;
osd_scoped_lock lock_scope(m_buffer_lock.get());
for (;;)
{
// Take buffers from the queue first
if (!m_queue.empty())
{
// Get a reference to the buffer
auto buf = &m_queue.front();
// Roll the buffer
roll_buffer();
// submit the buffer data
submit_buffer(std::move(buf->AudioData), buf->AudioSize);
// Remove it from the queue
m_queue.pop();
}
else
{
// submit the main buffer
submit_buffer(std::move(m_buffer), m_writepos);
// Get a new buffer since this one is gone
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
m_writepos = 0;
// break out, this was the last buffer to submit
break;
}
}
// Submit the next buffer
submit_next_queued();
}
//============================================================
@ -618,6 +662,67 @@ void sound_xaudio2::submit_buffer(std::unique_ptr<BYTE[]> audioData, DWORD audio
audioData.release();
}
//============================================================
// submit_next_queued
//============================================================
BOOL sound_xaudio2::submit_next_queued()
{
if (!m_queue.empty())
{
// Get a reference to the buffer
auto buf = &m_queue.front();
// submit the buffer data
submit_buffer(std::move(buf->AudioData), buf->AudioSize);
// Remove it from the queue
assert(buf->AudioSize > 0);
m_queue.pop();
return !m_queue.empty();
}
// queue was already empty
return FALSE;
}
//============================================================
// roll_buffer
//============================================================
// Queues the current buffer, and gets a new write buffer
void sound_xaudio2::roll_buffer()
{
// Don't queue a buffer if it is empty
if (m_writepos == 0)
return;
// Queue the current buffer
xaudio2_buffer buf;
buf.AudioData = std::move(m_buffer);
buf.AudioSize = m_writepos;
m_queue.push(std::move(buf));
// Get a new buffer
m_buffer = std::unique_ptr<BYTE[]>(m_buffer_pool->next());
m_writepos = 0;
// We only want to keep a maximum number of buffers at any given time
// so remove any from queue greater than MAX_QUEUED_BUFFERS
if (m_queue.size() > m_buffer_count)
{
xaudio2_buffer *next_buffer = &m_queue.front();
// return the oldest buffer to the pool, and remove it from queue
m_buffer_pool->return_to_pool(next_buffer->AudioData.release());
m_queue.pop();
m_overflows++;
}
}
#else
MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2")
#endif