Modified audio handling to use SDL_QueueAudio to simplify code and changed remaining bytes calculation to reduce audio latency
This commit is contained in:
parent
7babd24bd1
commit
38f6304d44
|
@ -5,6 +5,7 @@
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
static SDL_AudioDeviceID audio_device = 0;
|
static SDL_AudioDeviceID audio_device = 0;
|
||||||
|
static uint32_t sample_rate = 48000;
|
||||||
|
|
||||||
void Multilibultra::init_audio() {
|
void Multilibultra::init_audio() {
|
||||||
// Initialize SDL audio.
|
// Initialize SDL audio.
|
||||||
|
@ -13,8 +14,6 @@ void Multilibultra::init_audio() {
|
||||||
set_audio_frequency(48000);
|
set_audio_frequency(48000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLCALL feed_audio(void* userdata, Uint8* stream, int len);
|
|
||||||
|
|
||||||
void Multilibultra::set_audio_frequency(uint32_t freq) {
|
void Multilibultra::set_audio_frequency(uint32_t freq) {
|
||||||
if (audio_device != 0) {
|
if (audio_device != 0) {
|
||||||
SDL_CloseAudioDevice(audio_device);
|
SDL_CloseAudioDevice(audio_device);
|
||||||
|
@ -27,7 +26,7 @@ void Multilibultra::set_audio_frequency(uint32_t freq) {
|
||||||
.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
|
.samples = 0x100, // Fairly small sample count to reduce the latency of internal buffering
|
||||||
.padding = 0, // unused
|
.padding = 0, // unused
|
||||||
.size = 0, // calculated
|
.size = 0, // calculated
|
||||||
.callback = feed_audio, // Use a callback as QueueAudio causes popping
|
.callback = nullptr,//feed_audio, // Use a callback as QueueAudio causes popping
|
||||||
.userdata = nullptr
|
.userdata = nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -38,105 +37,52 @@ void Multilibultra::set_audio_frequency(uint32_t freq) {
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
SDL_PauseAudioDevice(audio_device, 0);
|
SDL_PauseAudioDevice(audio_device, 0);
|
||||||
}
|
sample_rate = freq;
|
||||||
|
|
||||||
// Struct representing a queued audio buffer.
|
|
||||||
struct AudioBuffer {
|
|
||||||
// All samples in the buffer, including those that have already been sent.
|
|
||||||
std::vector<int16_t> samples;
|
|
||||||
// The count of samples that have already been sent to the audio device.
|
|
||||||
size_t used_samples = 0;
|
|
||||||
|
|
||||||
// Helper methods.
|
|
||||||
size_t remaining_samples() const { return samples.size() - used_samples; };
|
|
||||||
size_t remaining_bytes() const { return remaining_samples() * sizeof(samples[0]); };
|
|
||||||
int16_t* first_unused_sample() { return &samples[used_samples]; }
|
|
||||||
bool empty() { return used_samples == samples.size(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mutex for locking the queued audio buffer list.
|
|
||||||
std::mutex audio_buffers_mutex;
|
|
||||||
// The queued audio buffer list, holds a list of buffers that have been queued by the game.
|
|
||||||
std::vector<AudioBuffer> audio_buffers;
|
|
||||||
|
|
||||||
void SDLCALL feed_audio(void* userdata, Uint8* stream, int byte_count) {
|
|
||||||
// Ensure that the byte count is an integer multiple of samples.
|
|
||||||
assert((byte_count & 1) == 0);
|
|
||||||
|
|
||||||
// Calculate the sample count from the byte count.
|
|
||||||
size_t remaining_samples = byte_count / sizeof(int16_t);
|
|
||||||
|
|
||||||
// Lock the queued audio buffer list.
|
|
||||||
std::lock_guard lock{ audio_buffers_mutex };
|
|
||||||
|
|
||||||
// Empty the audio buffers until we've sent all the required samples
|
|
||||||
// or until there are no samples left in the audio buffers.
|
|
||||||
while (!audio_buffers.empty() && remaining_samples > 0) {
|
|
||||||
auto& cur_buffer = audio_buffers.front();
|
|
||||||
// Prevent overrunning either the input or output buffer.
|
|
||||||
size_t to_copy = std::min(remaining_samples, cur_buffer.remaining_samples());
|
|
||||||
// Copy samples from the input buffer to the output one.
|
|
||||||
memcpy(stream, cur_buffer.first_unused_sample(), to_copy * sizeof(int16_t));
|
|
||||||
// Advance the output buffer by the copied byte count.
|
|
||||||
stream += to_copy * sizeof(int16_t);
|
|
||||||
// Advance the input buffer by the copied sample count.
|
|
||||||
cur_buffer.used_samples += to_copy;
|
|
||||||
// Updated the remaining sample count.
|
|
||||||
remaining_samples -= to_copy;
|
|
||||||
|
|
||||||
// If the input buffer was emptied, remove it from the list of queued buffers.
|
|
||||||
if (cur_buffer.empty()) {
|
|
||||||
audio_buffers.erase(audio_buffers.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero out any remaining audio data to lessen audio issues during lag
|
|
||||||
memset(stream, 0, remaining_samples * sizeof(int16_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) {
|
void Multilibultra::queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data_, uint32_t byte_count) {
|
||||||
|
// Buffer for holding the output of swapping the audio channels. This is reused across
|
||||||
|
// calls to reduce runtime allocations.
|
||||||
|
static std::vector<uint16_t> swap_buffer;
|
||||||
|
|
||||||
// Ensure that the byte count is an integer multiple of samples.
|
// Ensure that the byte count is an integer multiple of samples.
|
||||||
assert((byte_count & 1) == 0);
|
assert((byte_count & 1) == 0);
|
||||||
|
|
||||||
s16* audio_data = TO_PTR(s16, audio_data_);
|
|
||||||
// Calculate the number of samples from the number of bytes.
|
// Calculate the number of samples from the number of bytes.
|
||||||
uint32_t sample_count = byte_count / sizeof(s16);
|
uint32_t sample_count = byte_count / sizeof(s16);
|
||||||
|
|
||||||
// Lock the queued audio buffer list.
|
// Make sure the swap buffer is large enough to hold all the incoming audio data.
|
||||||
std::lock_guard lock{ audio_buffers_mutex };
|
if (sample_count > swap_buffer.size()) {
|
||||||
|
swap_buffer.resize(sample_count);
|
||||||
// Set up a new queued audio buffer.
|
|
||||||
AudioBuffer& new_buf = audio_buffers.emplace_back();
|
|
||||||
new_buf.samples.resize(sample_count);
|
|
||||||
new_buf.used_samples = 0;
|
|
||||||
|
|
||||||
// Copy the data into the new buffer.
|
|
||||||
// Swap the audio channels to correct for the address xor caused by endianness handling.
|
|
||||||
for (size_t i = 0; i < sample_count; i += 2) {
|
|
||||||
new_buf.samples[i + 0] = audio_data[i + 1];
|
|
||||||
new_buf.samples[i + 1] = audio_data[i + 0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Swap the audio channels into the swap buffer to correct for the address xor caused by endianness handling.
|
||||||
|
s16* audio_data = TO_PTR(s16, audio_data_);
|
||||||
|
for (size_t i = 0; i < sample_count; i += 2) {
|
||||||
|
swap_buffer[i + 0] = audio_data[i + 1];
|
||||||
|
swap_buffer[i + 1] = audio_data[i + 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the swapped audio data.
|
||||||
|
SDL_QueueAudio(audio_device, swap_buffer.data(), byte_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's ever any audio popping, check here first. Some games are very sensitive to
|
// If there's ever any audio popping, check here first. Some games are very sensitive to
|
||||||
// the remaining sample count and reporting a number that's too high here can lead to issues.
|
// the remaining sample count and reporting a number that's too high here can lead to issues.
|
||||||
// Reporting a number that's too low can lead to audio lag in some games.
|
// Reporting a number that's too low can lead to audio lag in some games.
|
||||||
uint32_t Multilibultra::get_remaining_audio_bytes() {
|
uint32_t Multilibultra::get_remaining_audio_bytes() {
|
||||||
// Calculate the number of samples still in the queued audio buffers
|
// Get the number of remaining buffered audio bytes.
|
||||||
size_t buffered_byte_count = 0;
|
uint32_t buffered_byte_count = SDL_GetQueuedAudioSize(audio_device);
|
||||||
{
|
|
||||||
// Lock the queued audio buffer list.
|
|
||||||
std::lock_guard lock{ audio_buffers_mutex };
|
|
||||||
|
|
||||||
// Gather the remaining byte count of the next buffer, if any exists.
|
// Adjust the reported count to be four refreshes in the future, which helps ensure that
|
||||||
if (!audio_buffers.empty()) {
|
// there are enough samples even if the game experiences a small amount of lag. This prevents
|
||||||
buffered_byte_count = audio_buffers.front().remaining_bytes();
|
// audio popping on games that use the buffered audio byte count to determine how many samples
|
||||||
}
|
// to generate.
|
||||||
|
uint32_t samples_per_vi = (sample_rate / 60);
|
||||||
|
if (buffered_byte_count > (4u * samples_per_vi)) {
|
||||||
|
buffered_byte_count -= (4u * samples_per_vi);
|
||||||
|
} else {
|
||||||
|
buffered_byte_count = 0;
|
||||||
}
|
}
|
||||||
// Add the number of remaining bytes in the audio data that's been sent to the device.
|
return buffered_byte_count;
|
||||||
buffered_byte_count += SDL_GetQueuedAudioSize(audio_device);
|
|
||||||
|
|
||||||
// Add a slight positive scaling bias, which helps audio respond quicker. Remove the bias
|
|
||||||
// if games have popping issues.
|
|
||||||
return buffered_byte_count + (buffered_byte_count / 10);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue