- Fixed a few bugs with ringbuffer, and some cleanup.

- Reworked audio system to use semaphores instead of events for waiting.
  Should fix rare issues where the XAudio2 driver would run out of buffers
  even though it was supposed to be guarded against that.
This commit is contained in:
gibbed 2015-06-19 21:48:51 -05:00
parent f3547a832f
commit edbd724370
8 changed files with 118 additions and 111 deletions

View File

@ -62,18 +62,22 @@ AudioSystem::AudioSystem(Emulator* emulator)
worker_running_(false),
decoder_running_(false) {
std::memset(clients_, 0, sizeof(clients_));
for (size_t i = 0; i < maximum_client_count_; ++i) {
for (size_t i = 0; i < kMaximumClientCount; ++i) {
unused_clients_.push(i);
}
for (size_t i = 0; i < xe::countof(client_wait_handles_); ++i) {
client_wait_handles_[i] = CreateEvent(NULL, TRUE, FALSE, NULL);
for (size_t i = 0; i < kMaximumClientCount; ++i) {
client_semaphores_[i] = CreateSemaphore(NULL, 0, kMaximumQueuedFrames, NULL);
wait_handles_[i] = client_semaphores_[i];
}
shutdown_event_ = CreateEvent(NULL, TRUE, FALSE, NULL);
wait_handles_[kMaximumClientCount] = shutdown_event_;
}
AudioSystem::~AudioSystem() {
for (size_t i = 0; i < xe::countof(client_wait_handles_); ++i) {
CloseHandle(client_wait_handles_[i]);
for (size_t i = 0; i < kMaximumClientCount; ++i) {
CloseHandle(client_semaphores_[i]);
}
CloseHandle(shutdown_event_);
}
void av_log_callback(void *avcl, int level, const char *fmt, va_list va) {
@ -149,16 +153,16 @@ void AudioSystem::WorkerThreadMain() {
// Main run loop.
while (worker_running_) {
auto result =
WaitForMultipleObjectsEx(DWORD(xe::countof(client_wait_handles_)),
client_wait_handles_, FALSE, INFINITE, FALSE);
WaitForMultipleObjectsEx(DWORD(xe::countof(wait_handles_)),
wait_handles_, FALSE, INFINITE, FALSE);
if (result == WAIT_FAILED ||
result == WAIT_OBJECT_0 + maximum_client_count_) {
result == WAIT_OBJECT_0 + kMaximumClientCount) {
continue;
}
size_t pumped = 0;
if (result >= WAIT_OBJECT_0 &&
result <= WAIT_OBJECT_0 + (maximum_client_count_ - 1)) {
result <= WAIT_OBJECT_0 + (kMaximumClientCount - 1)) {
size_t index = result - WAIT_OBJECT_0;
do {
lock_.lock();
@ -174,8 +178,8 @@ void AudioSystem::WorkerThreadMain() {
}
pumped++;
index++;
} while (index < maximum_client_count_ &&
WaitForSingleObject(client_wait_handles_[index], 0) ==
} while (index < kMaximumClientCount &&
WaitForSingleObject(client_semaphores_[index], 0) ==
WAIT_OBJECT_0);
}
@ -225,7 +229,7 @@ void AudioSystem::Initialize() {}
void AudioSystem::Shutdown() {
worker_running_ = false;
SetEvent(client_wait_handles_[maximum_client_count_]);
SetEvent(shutdown_event_);
worker_thread_->Wait(0, 0, 0, nullptr);
worker_thread_.reset();
@ -297,10 +301,10 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg,
auto index = unused_clients_.front();
auto wait_handle = client_wait_handles_[index];
ResetEvent(wait_handle);
auto client_semaphore = client_semaphores_[index];
assert_true(ReleaseSemaphore(client_semaphore, kMaximumQueuedFrames, NULL) == TRUE);
AudioDriver* driver;
auto result = CreateDriver(index, wait_handle, &driver);
auto result = CreateDriver(index, client_semaphore, &driver);
if (XFAILED(result)) {
return result;
}
@ -324,7 +328,7 @@ void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
SCOPE_profile_cpu_f("apu");
std::lock_guard<xe::mutex> lock(lock_);
assert_true(index < maximum_client_count_);
assert_true(index < kMaximumClientCount);
assert_true(clients_[index].driver != NULL);
(clients_[index].driver)->SubmitFrame(samples_ptr);
}
@ -333,18 +337,25 @@ void AudioSystem::UnregisterClient(size_t index) {
SCOPE_profile_cpu_f("apu");
std::lock_guard<xe::mutex> lock(lock_);
assert_true(index < maximum_client_count_);
assert_true(index < kMaximumClientCount);
DestroyDriver(clients_[index].driver);
clients_[index] = {0};
unused_clients_.push(index);
ResetEvent(client_wait_handles_[index]);
// drain the semaphore of its count
auto client_semaphore = client_semaphores_[index];
DWORD wait_result;
do {
wait_result = WaitForSingleObject(client_semaphore, 0);
} while (wait_result == WAIT_OBJECT_0);
assert_true(wait_result == WAIT_TIMEOUT);
}
void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) {
SCOPE_profile_cpu_f("apu");
// Translate this for future use.
uint8_t* out = memory()->TranslatePhysical(data.output_buffer_ptr);
uint8_t* output_buffer = memory()->TranslatePhysical(data.output_buffer_ptr);
// What I see:
// XMA outputs 2 bytes per sample
@ -371,13 +382,15 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) {
// Output buffers are in raw PCM samples, 256 bytes per block.
// Output buffer is a ring buffer. We need to write from the write offset
// to the read offset.
uint32_t output_size_bytes = data.output_buffer_block_count * 256;
uint32_t output_write_offset_bytes = data.output_buffer_write_offset * 256;
uint32_t output_read_offset_bytes = data.output_buffer_read_offset * 256;
uint32_t output_capacity = data.output_buffer_block_count * 256;
uint32_t output_read_offset = data.output_buffer_read_offset * 256;
uint32_t output_write_offset = data.output_buffer_write_offset * 256;
RingBuffer output_buffer(out, output_size_bytes, output_read_offset_bytes, output_write_offset_bytes);
size_t output_remaining_bytes = output_buffer.write_size();
RingBuffer output_rb(output_buffer, output_capacity);
output_rb.set_read_offset(output_read_offset);
output_rb.set_write_offset(output_write_offset);
size_t output_remaining_bytes = output_rb.write_count();
if (!output_remaining_bytes) {
// Can't write any more data. Break.
// The game will kick us again with a new output buffer later.
@ -395,7 +408,9 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) {
read_bytes = context.decoder->DecodePacket(tmp_buff, 0,
output_remaining_bytes);
if (read_bytes >= 0) {
output_buffer.Write(tmp_buff, read_bytes);
assert_true((read_bytes % 256) == 0);
auto written_bytes = output_rb.Write(tmp_buff, read_bytes);
assert_true(read_bytes == written_bytes);
// Ok.
break;
@ -421,11 +436,7 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) {
}
}
data.output_buffer_write_offset += uint32_t(read_bytes) / 256;
if (data.output_buffer_write_offset > data.output_buffer_block_count) {
// Wraparound!
data.output_buffer_write_offset -= data.output_buffer_block_count;
}
data.output_buffer_write_offset = output_rb.write_offset() / 256;
// If we need more data and the input buffers have it, grab it.
if (read_bytes) {

View File

@ -142,13 +142,16 @@ class AudioSystem {
void UnregisterClient(size_t index);
void SubmitFrame(size_t index, uint32_t samples_ptr);
virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle,
virtual X_STATUS CreateDriver(size_t index, HANDLE semaphore,
AudioDriver** out_driver) = 0;
virtual void DestroyDriver(AudioDriver* driver) = 0;
virtual uint64_t ReadRegister(uint32_t addr);
virtual void WriteRegister(uint32_t addr, uint64_t value);
// TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64, XAUDIO2_MAX_QUEUED_BUFFERS))
static const size_t kMaximumQueuedFrames = 64;
protected:
virtual void Initialize();
@ -204,6 +207,7 @@ class AudioSystem {
} registers_;
uint32_t register_file_[0xFFFF / 4];
};
struct XMAContext {
uint32_t guest_ptr;
xe::mutex lock;
@ -218,16 +222,18 @@ class AudioSystem {
std::vector<uint32_t> xma_context_free_list_;
std::vector<uint32_t> xma_context_used_list_; // XMA contexts in use
static const size_t maximum_client_count_ = 8;
static const size_t kMaximumClientCount = 8;
struct {
AudioDriver* driver;
uint32_t callback;
uint32_t callback_arg;
uint32_t wrapped_callback_arg;
} clients_[maximum_client_count_];
// Last handle is always there in case we have no clients.
HANDLE client_wait_handles_[maximum_client_count_ + 1];
} clients_[kMaximumClientCount];
HANDLE client_semaphores_[kMaximumClientCount];
HANDLE shutdown_event_; // Event is always there in case we have no clients.
HANDLE wait_handles_[kMaximumClientCount + 1];
std::queue<size_t> unused_clients_;
};

View File

@ -20,26 +20,26 @@ namespace xaudio2 {
class XAudio2AudioDriver::VoiceCallback : public IXAudio2VoiceCallback {
public:
VoiceCallback(HANDLE wait_handle) : wait_handle_(wait_handle) {}
VoiceCallback(HANDLE semaphore) : semaphore_(semaphore) {}
~VoiceCallback() {}
void OnStreamEnd() {}
void OnVoiceProcessingPassEnd() {}
void OnVoiceProcessingPassStart(uint32_t samples_required) {}
void OnBufferEnd(void* context) { SetEvent(wait_handle_); }
void OnBufferEnd(void* context) { assert_true(ReleaseSemaphore(semaphore_, 1, NULL) == TRUE); }
void OnBufferStart(void* context) {}
void OnLoopEnd(void* context) {}
void OnVoiceError(void* context, HRESULT result) {}
private:
HANDLE wait_handle_;
HANDLE semaphore_;
};
XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE wait)
XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore)
: audio_(nullptr),
mastering_voice_(nullptr),
pcm_voice_(nullptr),
wait_handle_(wait),
semaphore_(semaphore),
voice_callback_(nullptr),
current_frame_(0),
AudioDriver(emulator) {}
@ -61,7 +61,7 @@ const DWORD ChannelMasks[] = {
void XAudio2AudioDriver::Initialize() {
HRESULT hr;
voice_callback_ = new VoiceCallback(wait_handle_);
voice_callback_ = new VoiceCallback(semaphore_);
hr = XAudio2Create(&audio_, 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr)) {
@ -123,8 +123,6 @@ void XAudio2AudioDriver::Initialize() {
if (FLAGS_mute) {
pcm_voice_->SetVolume(0.0f);
}
SetEvent(wait_handle_);
}
void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
@ -169,13 +167,6 @@ void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) {
// Update playback ratio to our time scalar.
// This will keep audio in sync with the game clock.
pcm_voice_->SetFrequencyRatio(float(xe::Clock::guest_time_scalar()));
XAUDIO2_VOICE_STATE state2;
pcm_voice_->GetState(&state2, XAUDIO2_VOICE_NOSAMPLESPLAYED);
if (state2.BuffersQueued >= frame_count_) {
ResetEvent(wait_handle_);
}
}
void XAudio2AudioDriver::Shutdown() {

View File

@ -21,7 +21,7 @@ namespace xaudio2 {
class XAudio2AudioDriver : public AudioDriver {
public:
XAudio2AudioDriver(Emulator* emulator, HANDLE wait);
XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore);
virtual ~XAudio2AudioDriver();
virtual void Initialize();
@ -32,7 +32,7 @@ class XAudio2AudioDriver : public AudioDriver {
IXAudio2* audio_;
IXAudio2MasteringVoice* mastering_voice_;
IXAudio2SourceVoice* pcm_voice_;
HANDLE wait_handle_;
HANDLE semaphore_;
class VoiceCallback;
VoiceCallback* voice_callback_;

View File

@ -25,10 +25,10 @@ XAudio2AudioSystem::~XAudio2AudioSystem() {}
void XAudio2AudioSystem::Initialize() { AudioSystem::Initialize(); }
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE wait,
X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE semaphore,
AudioDriver** out_driver) {
assert_not_null(out_driver);
auto driver = new XAudio2AudioDriver(emulator_, wait);
auto driver = new XAudio2AudioDriver(emulator_, semaphore);
driver->Initialize();
*out_driver = driver;
return X_STATUS_SUCCESS;

View File

@ -24,7 +24,7 @@ class XAudio2AudioSystem : public AudioSystem {
XAudio2AudioSystem(Emulator* emulator);
virtual ~XAudio2AudioSystem();
virtual X_RESULT CreateDriver(size_t index, HANDLE wait,
virtual X_RESULT CreateDriver(size_t index, HANDLE semaphore,
AudioDriver** out_driver);
virtual void DestroyDriver(AudioDriver* driver);

View File

@ -14,60 +14,50 @@
namespace xe {
RingBuffer::RingBuffer(uint8_t* raw_buffer, size_t size, size_t read_offset, size_t write_offset)
: raw_buffer_(raw_buffer)
, size_(size)
, read_offset_(read_offset)
, write_offset_(write_offset) {}
RingBuffer::RingBuffer(uint8_t* buffer, size_t capacity)
: buffer_(buffer)
, capacity_(capacity)
, read_offset_(0)
, write_offset_(0) {}
size_t RingBuffer::Skip(size_t num_bytes) {
num_bytes = std::min(read_size(), num_bytes);
if (read_offset_ + num_bytes < size_) {
read_offset_ += num_bytes;
} else {
read_offset_ = num_bytes - (size_ - read_offset_);
}
return num_bytes;
}
size_t RingBuffer::Read(uint8_t* buffer, size_t num_bytes) {
num_bytes = std::min(read_size(), num_bytes);
if (!num_bytes) {
size_t RingBuffer::Read(uint8_t* buffer, size_t count) {
count = std::min(count, capacity_);
if (!count) {
return 0;
}
if (read_offset_ + num_bytes < size_) {
std::memcpy(buffer, raw_buffer_ + read_offset_, num_bytes);
read_offset_ += num_bytes;
if (read_offset_ + count < capacity_) {
std::memcpy(buffer, buffer_ + read_offset_, count);
read_offset_ += count;
} else {
size_t left_half = size_ - read_offset_;
size_t right_half = size_ - left_half;
std::memcpy(buffer, raw_buffer_ + read_offset_, left_half);
std::memcpy(buffer + left_half, raw_buffer_, right_half);
size_t left_half = capacity_ - read_offset_;
size_t right_half = count - left_half;
std::memcpy(buffer, buffer_ + read_offset_, left_half);
std::memcpy(buffer + left_half, buffer_, right_half);
read_offset_ = right_half;
}
return num_bytes;
return count;
}
size_t RingBuffer::Write(uint8_t* buffer, size_t num_bytes) {
num_bytes = std::min(num_bytes, write_size());
if (!num_bytes) {
size_t RingBuffer::Write(uint8_t* buffer, size_t count) {
count = std::min(count, capacity_);
if (!count) {
return 0;
}
if (write_offset_ + num_bytes < size_) {
std::memcpy(raw_buffer_ + write_offset_, buffer, num_bytes);
write_offset_ += num_bytes;
if (write_offset_ + count < capacity_) {
std::memcpy(buffer_ + write_offset_, buffer, count);
write_offset_ += count;
} else {
size_t left_half = size_ - write_offset_;
size_t right_half = size_ - left_half;
std::memcpy(raw_buffer_ + write_offset_, buffer, left_half);
std::memcpy(raw_buffer_, buffer + left_half, right_half);
size_t left_half = capacity_ - write_offset_;
size_t right_half = count - left_half;
std::memcpy(buffer_ + write_offset_, buffer, left_half);
std::memcpy(buffer_, buffer + left_half, right_half);
write_offset_ = right_half;
}
return num_bytes;
return count;
}
} // namespace xe

View File

@ -18,38 +18,47 @@ namespace xe {
class RingBuffer {
public:
RingBuffer(uint8_t* raw_buffer, size_t size, size_t read_offset, size_t write_offset);
RingBuffer(uint8_t* buffer, size_t capacity);
size_t Read(uint8_t* buffer, size_t num_bytes);
size_t Skip(size_t num_bytes);
size_t Write(uint8_t* buffer, size_t num_bytes);
size_t Read(uint8_t* buffer, size_t count);
size_t Write(uint8_t* buffer, size_t count);
uint8_t* buffer() { return buffer_; }
size_t capacity() { return capacity_; }
size_t read_offset() { return read_offset_; }
size_t write_offset() { return write_offset_; }
size_t read_size() {
size_t read_count() {
if (read_offset_ == write_offset_) {
return 0;
} else if (read_offset_ < write_offset_) {
return write_offset_ - read_offset_;
} else {
return (capacity_ - read_offset_) + write_offset_;
}
if (read_offset_ < write_offset_) {
return write_offset_ - read_offset_;
}
return (size_ - read_offset_) + write_offset_;
}
size_t write_size() {
if (write_offset_ == read_offset_) {
return size_;
}
if (write_offset_ < read_offset_) {
size_t write_offset() { return write_offset_; }
size_t write_count() {
if (read_offset_ == write_offset_) {
return capacity_;
} else if (write_offset_ < read_offset_) {
return read_offset_ - write_offset_;
} else {
return (capacity_ - write_offset_) + read_offset_;
}
return (size_ - write_offset_) + read_offset_;
}
void set_read_offset(size_t offset) {
read_offset_ = offset % capacity_;
}
void set_write_offset(size_t offset) {
write_offset_ = offset % capacity_;
}
private:
uint8_t* raw_buffer_;
size_t size_;
uint8_t* buffer_;
size_t capacity_;
size_t read_offset_;
size_t write_offset_;
};