mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
AudioStream: Replace buffer queue with ring buffer
Should achieve a decent overall minimum latency reduction.
This commit is contained in:
parent
6acd8b27dd
commit
531c3ad5fa
|
@ -1,14 +1,16 @@
|
||||||
#include "audio_stream.h"
|
#include "audio_stream.h"
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
|
#include "log.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
Log_SetChannel(AudioStream);
|
||||||
|
|
||||||
AudioStream::AudioStream() = default;
|
AudioStream::AudioStream() = default;
|
||||||
|
|
||||||
AudioStream::~AudioStream() = default;
|
AudioStream::~AudioStream() = default;
|
||||||
|
|
||||||
bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate*/, u32 channels /*= 1*/,
|
bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate*/, u32 channels /*= 1*/,
|
||||||
u32 buffer_size /*= DefaultBufferSize*/, u32 buffer_count /*= DefaultBufferCount*/)
|
u32 buffer_size /*= DefaultBufferSize*/)
|
||||||
{
|
{
|
||||||
if (IsDeviceOpen())
|
if (IsDeviceOpen())
|
||||||
CloseDevice();
|
CloseDevice();
|
||||||
|
@ -16,13 +18,14 @@ bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate
|
||||||
m_output_sample_rate = output_sample_rate;
|
m_output_sample_rate = output_sample_rate;
|
||||||
m_channels = channels;
|
m_channels = channels;
|
||||||
m_buffer_size = buffer_size;
|
m_buffer_size = buffer_size;
|
||||||
AllocateBuffers(buffer_count);
|
|
||||||
m_output_paused = true;
|
m_output_paused = true;
|
||||||
|
|
||||||
|
if (!SetBufferSize(buffer_size))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (!OpenDevice())
|
if (!OpenDevice())
|
||||||
{
|
{
|
||||||
EmptyBuffers();
|
EmptyBuffers();
|
||||||
m_buffers.clear();
|
|
||||||
m_buffer_size = 0;
|
m_buffer_size = 0;
|
||||||
m_output_sample_rate = 0;
|
m_output_sample_rate = 0;
|
||||||
m_channels = 0;
|
m_channels = 0;
|
||||||
|
@ -32,7 +35,7 @@ bool AudioStream::Reconfigure(u32 output_sample_rate /*= DefaultOutputSampleRate
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::SetOutputVolume(s32 volume)
|
void AudioStream::SetOutputVolume(u32 volume)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||||
m_output_volume = volume;
|
m_output_volume = volume;
|
||||||
|
@ -58,7 +61,6 @@ void AudioStream::Shutdown()
|
||||||
|
|
||||||
CloseDevice();
|
CloseDevice();
|
||||||
EmptyBuffers();
|
EmptyBuffers();
|
||||||
m_buffers.clear();
|
|
||||||
m_buffer_size = 0;
|
m_buffer_size = 0;
|
||||||
m_output_sample_rate = 0;
|
m_output_sample_rate = 0;
|
||||||
m_channels = 0;
|
m_channels = 0;
|
||||||
|
@ -69,182 +71,149 @@ void AudioStream::BeginWrite(SampleType** buffer_ptr, u32* num_frames)
|
||||||
{
|
{
|
||||||
m_buffer_mutex.lock();
|
m_buffer_mutex.lock();
|
||||||
|
|
||||||
EnsureBuffer();
|
EnsureBuffer(*num_frames * m_channels);
|
||||||
|
|
||||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
*buffer_ptr = m_buffer.GetWritePointer();
|
||||||
*buffer_ptr = buffer.data.data() + (buffer.write_position * m_channels);
|
*num_frames = m_buffer.GetContiguousSpace() / m_channels;
|
||||||
*num_frames = m_buffer_size - buffer.write_position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
void AudioStream::WriteFrames(const SampleType* frames, u32 num_frames)
|
||||||
{
|
{
|
||||||
u32 remaining_frames = num_frames;
|
const u32 num_samples = num_frames * m_channels;
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||||
|
|
||||||
while (remaining_frames > 0)
|
EnsureBuffer(num_samples);
|
||||||
{
|
m_buffer.PushRange(frames, num_samples);
|
||||||
EnsureBuffer();
|
FramesAvailable();
|
||||||
|
|
||||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
|
||||||
const u32 to_this_buffer = std::min(m_buffer_size - buffer.write_position, remaining_frames);
|
|
||||||
|
|
||||||
const u32 copy_count = to_this_buffer * m_channels;
|
|
||||||
std::memcpy(&buffer.data[buffer.write_position * m_channels], frames, copy_count * sizeof(SampleType));
|
|
||||||
frames += copy_count;
|
|
||||||
|
|
||||||
remaining_frames -= to_this_buffer;
|
|
||||||
buffer.write_position += to_this_buffer;
|
|
||||||
|
|
||||||
// End of the buffer?
|
|
||||||
if (buffer.write_position == m_buffer_size)
|
|
||||||
{
|
|
||||||
// Reset it back to the start, and enqueue it.
|
|
||||||
buffer.write_position = 0;
|
|
||||||
m_num_free_buffers--;
|
|
||||||
m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size();
|
|
||||||
m_num_available_buffers++;
|
|
||||||
BufferAvailable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::EndWrite(u32 num_frames)
|
void AudioStream::EndWrite(u32 num_frames)
|
||||||
{
|
{
|
||||||
Buffer& buffer = m_buffers[m_first_free_buffer];
|
m_buffer.AdvanceTail(num_frames * m_channels);
|
||||||
DebugAssert((buffer.write_position + num_frames) <= m_buffer_size);
|
FramesAvailable();
|
||||||
buffer.write_position += num_frames;
|
|
||||||
|
|
||||||
// End of the buffer?
|
|
||||||
if (buffer.write_position == m_buffer_size)
|
|
||||||
{
|
|
||||||
// Reset it back to the start, and enqueue it.
|
|
||||||
// Log_DevPrintf("Enqueue buffer %u", m_first_free_buffer);
|
|
||||||
buffer.write_position = 0;
|
|
||||||
m_num_free_buffers--;
|
|
||||||
m_first_free_buffer = (m_first_free_buffer + 1) % m_buffers.size();
|
|
||||||
m_num_available_buffers++;
|
|
||||||
BufferAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_buffer_mutex.unlock();
|
m_buffer_mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
float AudioStream::GetMinLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count)
|
float AudioStream::GetMaxLatency(u32 sample_rate, u32 buffer_size)
|
||||||
{
|
{
|
||||||
return (static_cast<float>(buffer_size) / static_cast<float>(sample_rate));
|
return (static_cast<float>(buffer_size) / static_cast<float>(sample_rate));
|
||||||
}
|
}
|
||||||
|
|
||||||
float AudioStream::GetMaxLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count)
|
bool AudioStream::SetBufferSize(u32 buffer_size)
|
||||||
{
|
{
|
||||||
return (static_cast<float>(buffer_size * (buffer_count - 1)) / static_cast<float>(sample_rate));
|
const u32 buffer_size_in_samples = buffer_size * m_channels;
|
||||||
|
const u32 max_samples = buffer_size_in_samples * 2u;
|
||||||
|
if (max_samples > m_buffer.GetCapacity())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_buffer_size = buffer_size;
|
||||||
|
m_max_samples = max_samples;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 AudioStream::GetSamplesAvailable() const
|
u32 AudioStream::GetSamplesAvailable() const
|
||||||
{
|
{
|
||||||
// TODO: Use atomic loads
|
// TODO: Use atomic loads
|
||||||
u32 available_buffers;
|
u32 available_samples;
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||||
available_buffers = m_num_available_buffers;
|
available_samples = m_buffer.GetSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return available_buffers * m_buffer_size;
|
return available_samples / m_channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 AudioStream::ReadSamples(SampleType* samples, u32 num_samples)
|
u32 AudioStream::GetSamplesAvailableLocked() const
|
||||||
|
{
|
||||||
|
return m_buffer.GetSize() / m_channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioStream::ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume)
|
||||||
|
{
|
||||||
|
const u32 total_samples = num_frames * m_channels;
|
||||||
|
u32 samples_copied = 0;
|
||||||
{
|
{
|
||||||
u32 remaining_samples = num_samples;
|
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||||
|
samples_copied = std::min(m_buffer.GetSize(), total_samples);
|
||||||
|
if (samples_copied > 0)
|
||||||
|
m_buffer.PopRange(samples, samples_copied);
|
||||||
|
|
||||||
while (remaining_samples > 0 && m_num_available_buffers > 0)
|
m_buffer_draining_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples_copied < total_samples)
|
||||||
{
|
{
|
||||||
Buffer& buffer = m_buffers[m_first_available_buffer];
|
if (samples_copied > 0)
|
||||||
const u32 from_this_buffer = std::min(m_buffer_size - buffer.read_position, remaining_samples);
|
|
||||||
|
|
||||||
const u32 copy_count = from_this_buffer * m_channels;
|
|
||||||
const SampleType* read_pointer = &buffer.data[buffer.read_position * m_channels];
|
|
||||||
for (u32 i = 0; i < copy_count; i++)
|
|
||||||
*(samples++) = ApplyVolume(*(read_pointer++), m_output_volume);
|
|
||||||
|
|
||||||
remaining_samples -= from_this_buffer;
|
|
||||||
buffer.read_position += from_this_buffer;
|
|
||||||
|
|
||||||
if (buffer.read_position == m_buffer_size)
|
|
||||||
{
|
{
|
||||||
// Log_DevPrintf("Finish dequeing buffer %u", m_first_available_buffer);
|
m_resample_buffer.resize(samples_copied);
|
||||||
// End of this buffer.
|
std::memcpy(m_resample_buffer.data(), samples, sizeof(SampleType) * samples_copied);
|
||||||
buffer.read_position = 0;
|
|
||||||
m_num_available_buffers--;
|
// super basic resampler - spread the input samples evenly across the output samples. will sound like ass and have
|
||||||
m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size();
|
// aliasing, but better than popping by inserting silence.
|
||||||
m_num_free_buffers++;
|
const u32 increment =
|
||||||
m_buffer_available_cv.notify_one();
|
static_cast<u32>(65536.0f * (static_cast<float>(samples_copied / m_channels) / static_cast<float>(num_frames)));
|
||||||
|
|
||||||
|
SampleType* out_ptr = samples;
|
||||||
|
const SampleType* resample_ptr = m_resample_buffer.data();
|
||||||
|
const u32 copy_stride = sizeof(SampleType) * m_channels;
|
||||||
|
u32 resample_subpos = 0;
|
||||||
|
for (u32 i = 0; i < num_frames; i++)
|
||||||
|
{
|
||||||
|
std::memcpy(out_ptr, resample_ptr, copy_stride);
|
||||||
|
out_ptr += m_channels;
|
||||||
|
|
||||||
|
resample_subpos += increment;
|
||||||
|
resample_ptr += (resample_subpos >> 16) * m_channels;
|
||||||
|
resample_subpos %= 65536u;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_DevPrintf("Audio buffer underflow, resampled %u frames to %u", samples_copied / m_channels, num_frames);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// read nothing, so zero-fill
|
||||||
|
std::memset(samples, 0, sizeof(SampleType) * total_samples);
|
||||||
|
Log_DevPrintf("Audio buffer underflow with no samples, added %u frames silence", num_frames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return num_samples - remaining_samples;
|
if (apply_volume && m_output_volume != FullVolume)
|
||||||
|
{
|
||||||
|
SampleType* current_ptr = samples;
|
||||||
|
const SampleType* end_ptr = samples + (num_frames * m_channels);
|
||||||
|
while (current_ptr != end_ptr)
|
||||||
|
{
|
||||||
|
*current_ptr = ApplyVolume(*current_ptr, m_output_volume);
|
||||||
|
current_ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::AllocateBuffers(u32 buffer_count)
|
void AudioStream::EnsureBuffer(u32 size)
|
||||||
{
|
{
|
||||||
m_buffers.resize(buffer_count);
|
if (GetBufferSpace() >= size)
|
||||||
for (u32 i = 0; i < buffer_count; i++)
|
|
||||||
{
|
|
||||||
Buffer& buffer = m_buffers[i];
|
|
||||||
buffer.data.resize(m_buffer_size * m_channels);
|
|
||||||
buffer.read_position = 0;
|
|
||||||
buffer.write_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_first_available_buffer = 0;
|
|
||||||
m_num_available_buffers = 0;
|
|
||||||
m_first_free_buffer = 0;
|
|
||||||
m_num_free_buffers = buffer_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioStream::EnsureBuffer()
|
|
||||||
{
|
|
||||||
if (m_num_free_buffers > 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_sync)
|
if (m_sync)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex, std::adopt_lock);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex, std::adopt_lock);
|
||||||
m_buffer_available_cv.wait(lock, [this]() { return m_num_free_buffers > 0; });
|
m_buffer_draining_cv.wait(lock, [this, size]() { return GetBufferSpace() >= size; });
|
||||||
lock.release();
|
lock.release();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DropBuffer();
|
m_buffer.Remove(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::DropBuffer()
|
void AudioStream::DropFrames(u32 count)
|
||||||
{
|
{
|
||||||
DebugAssert(m_num_available_buffers > 0);
|
m_buffer.Remove(count);
|
||||||
// Log_DevPrintf("Dropping buffer %u", m_first_free_buffer);
|
|
||||||
|
|
||||||
// Out of space. We'll overwrite the oldest buffer with the new data.
|
|
||||||
// At the same time, we shift the available buffer forward one.
|
|
||||||
m_first_available_buffer = (m_first_available_buffer + 1) % m_buffers.size();
|
|
||||||
m_num_available_buffers--;
|
|
||||||
|
|
||||||
m_buffers[m_first_free_buffer].read_position = 0;
|
|
||||||
m_buffers[m_first_free_buffer].write_position = 0;
|
|
||||||
m_num_free_buffers++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioStream::EmptyBuffers()
|
void AudioStream::EmptyBuffers()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
std::unique_lock<std::mutex> lock(m_buffer_mutex);
|
||||||
|
m_buffer.Clear();
|
||||||
for (Buffer& buffer : m_buffers)
|
|
||||||
{
|
|
||||||
buffer.read_position = 0;
|
|
||||||
buffer.write_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_first_free_buffer = 0;
|
|
||||||
m_num_free_buffers = static_cast<u32>(m_buffers.size());
|
|
||||||
m_first_available_buffer = 0;
|
|
||||||
m_num_available_buffers = 0;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "fifo_queue.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -12,11 +13,12 @@ class AudioStream
|
||||||
public:
|
public:
|
||||||
using SampleType = s16;
|
using SampleType = s16;
|
||||||
|
|
||||||
enum
|
enum : u32
|
||||||
{
|
{
|
||||||
DefaultOutputSampleRate = 44100,
|
DefaultOutputSampleRate = 44100,
|
||||||
DefaultBufferSize = 2048,
|
DefaultBufferSize = 2048,
|
||||||
DefaultBufferCount = 3,
|
MaxSamples = 32768,
|
||||||
|
FullVolume = 100
|
||||||
};
|
};
|
||||||
|
|
||||||
AudioStream();
|
AudioStream();
|
||||||
|
@ -25,14 +27,14 @@ public:
|
||||||
u32 GetOutputSampleRate() const { return m_output_sample_rate; }
|
u32 GetOutputSampleRate() const { return m_output_sample_rate; }
|
||||||
u32 GetChannels() const { return m_channels; }
|
u32 GetChannels() const { return m_channels; }
|
||||||
u32 GetBufferSize() const { return m_buffer_size; }
|
u32 GetBufferSize() const { return m_buffer_size; }
|
||||||
u32 GetBufferCount() const { return static_cast<u32>(m_buffers.size()); }
|
|
||||||
s32 GetOutputVolume() const { return m_output_volume; }
|
s32 GetOutputVolume() const { return m_output_volume; }
|
||||||
bool IsSyncing() const { return m_sync; }
|
bool IsSyncing() const { return m_sync; }
|
||||||
|
|
||||||
bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1,
|
bool Reconfigure(u32 output_sample_rate = DefaultOutputSampleRate, u32 channels = 1,
|
||||||
u32 buffer_size = DefaultBufferSize, u32 buffer_count = DefaultBufferCount);
|
u32 buffer_size = DefaultBufferSize);
|
||||||
void SetSync(bool enable) { m_sync = enable; }
|
void SetSync(bool enable) { m_sync = enable; }
|
||||||
void SetOutputVolume(s32 volume);
|
|
||||||
|
virtual void SetOutputVolume(u32 volume);
|
||||||
|
|
||||||
void PauseOutput(bool paused);
|
void PauseOutput(bool paused);
|
||||||
void EmptyBuffers();
|
void EmptyBuffers();
|
||||||
|
@ -48,58 +50,43 @@ public:
|
||||||
static std::unique_ptr<AudioStream> CreateCubebAudioStream();
|
static std::unique_ptr<AudioStream> CreateCubebAudioStream();
|
||||||
|
|
||||||
// Latency computation - returns values in seconds
|
// Latency computation - returns values in seconds
|
||||||
static float GetMinLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count);
|
static float GetMaxLatency(u32 sample_rate, u32 buffer_size);
|
||||||
static float GetMaxLatency(u32 sample_rate, u32 buffer_size, u32 buffer_count);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool OpenDevice() = 0;
|
virtual bool OpenDevice() = 0;
|
||||||
virtual void PauseDevice(bool paused) = 0;
|
virtual void PauseDevice(bool paused) = 0;
|
||||||
virtual void CloseDevice() = 0;
|
virtual void CloseDevice() = 0;
|
||||||
virtual void BufferAvailable() = 0;
|
virtual void FramesAvailable() = 0;
|
||||||
|
|
||||||
ALWAYS_INLINE static SampleType ApplyVolume(SampleType sample, s32 volume)
|
ALWAYS_INLINE static SampleType ApplyVolume(SampleType sample, u32 volume)
|
||||||
{
|
{
|
||||||
return s16((s32(sample) * volume) / 100);
|
return s16((s32(sample) * s32(volume)) / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SetBufferSize(u32 buffer_size);
|
||||||
bool IsDeviceOpen() const { return (m_output_sample_rate > 0); }
|
bool IsDeviceOpen() const { return (m_output_sample_rate > 0); }
|
||||||
|
|
||||||
u32 GetSamplesAvailable() const;
|
u32 GetSamplesAvailable() const;
|
||||||
u32 ReadSamples(SampleType* samples, u32 num_samples);
|
u32 GetSamplesAvailableLocked() const;
|
||||||
|
void ReadFrames(SampleType* samples, u32 num_frames, bool apply_volume);
|
||||||
void DropBuffer();
|
void DropFrames(u32 count);
|
||||||
|
|
||||||
u32 m_output_sample_rate = 0;
|
u32 m_output_sample_rate = 0;
|
||||||
u32 m_channels = 0;
|
u32 m_channels = 0;
|
||||||
u32 m_buffer_size = 0;
|
u32 m_buffer_size = 0;
|
||||||
|
|
||||||
private:
|
|
||||||
struct Buffer
|
|
||||||
{
|
|
||||||
std::vector<SampleType> data;
|
|
||||||
u32 write_position;
|
|
||||||
u32 read_position;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AllocateBuffers(u32 buffer_count);
|
|
||||||
void EnsureBuffer();
|
|
||||||
|
|
||||||
std::vector<Buffer> m_buffers;
|
|
||||||
mutable std::mutex m_buffer_mutex;
|
|
||||||
|
|
||||||
// For input.
|
|
||||||
u32 m_first_free_buffer = 0;
|
|
||||||
u32 m_num_free_buffers = 0;
|
|
||||||
|
|
||||||
// For output.
|
|
||||||
u32 m_num_available_buffers = 0;
|
|
||||||
u32 m_first_available_buffer = 0;
|
|
||||||
|
|
||||||
// TODO: Switch to semaphore
|
|
||||||
std::condition_variable m_buffer_available_cv;
|
|
||||||
|
|
||||||
// volume, 0-100
|
// volume, 0-100
|
||||||
s32 m_output_volume = 100;
|
u32 m_output_volume = FullVolume;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ALWAYS_INLINE u32 GetBufferSpace() const { return (m_max_samples - m_buffer.GetSize()); }
|
||||||
|
void EnsureBuffer(u32 size);
|
||||||
|
|
||||||
|
HeapFIFOQueue<SampleType, MaxSamples> m_buffer;
|
||||||
|
mutable std::mutex m_buffer_mutex;
|
||||||
|
std::condition_variable m_buffer_draining_cv;
|
||||||
|
std::vector<SampleType> m_resample_buffer;
|
||||||
|
u32 m_max_samples = 0;
|
||||||
|
|
||||||
bool m_output_paused = true;
|
bool m_output_paused = true;
|
||||||
bool m_sync = true;
|
bool m_sync = true;
|
||||||
|
|
|
@ -56,9 +56,21 @@ bool CubebAudioStream::OpenDevice()
|
||||||
|
|
||||||
Log_InfoPrintf("Minimum latency in frames: %u", latency_frames);
|
Log_InfoPrintf("Minimum latency in frames: %u", latency_frames);
|
||||||
if (latency_frames > m_buffer_size)
|
if (latency_frames > m_buffer_size)
|
||||||
Log_WarningPrintf("Minimum latency is above buffer size: %u vs %u", latency_frames, m_buffer_size);
|
{
|
||||||
|
Log_WarningPrintf("Minimum latency is above buffer size: %u vs %u, adjusting to compensate.", latency_frames,
|
||||||
|
m_buffer_size);
|
||||||
|
|
||||||
|
if (!SetBufferSize(latency_frames))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to set new buffer size of %u frames", latency_frames);
|
||||||
|
DestroyContext();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
latency_frames = m_buffer_size;
|
latency_frames = m_buffer_size;
|
||||||
|
}
|
||||||
|
|
||||||
char stream_name[32];
|
char stream_name[32];
|
||||||
std::snprintf(stream_name, sizeof(stream_name), "AudioStream_%p", this);
|
std::snprintf(stream_name, sizeof(stream_name), "AudioStream_%p", this);
|
||||||
|
@ -72,6 +84,7 @@ bool CubebAudioStream::OpenDevice()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cubeb_stream_set_volume(m_cubeb_stream, static_cast<float>(m_output_volume) / 100.0f);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,15 +119,13 @@ long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const voi
|
||||||
{
|
{
|
||||||
CubebAudioStream* const this_ptr = static_cast<CubebAudioStream*>(user_ptr);
|
CubebAudioStream* const this_ptr = static_cast<CubebAudioStream*>(user_ptr);
|
||||||
|
|
||||||
const u32 read_frames =
|
if (this_ptr->m_output_volume_changed.load())
|
||||||
this_ptr->ReadSamples(reinterpret_cast<SampleType*>(output_buffer), static_cast<u32>(nframes));
|
|
||||||
const u32 silence_frames = static_cast<u32>(nframes) - read_frames;
|
|
||||||
if (silence_frames > 0)
|
|
||||||
{
|
{
|
||||||
std::memset(reinterpret_cast<SampleType*>(output_buffer) + (read_frames * this_ptr->m_channels), 0,
|
this_ptr->m_output_volume_changed.store(false);
|
||||||
silence_frames * this_ptr->m_channels * sizeof(SampleType));
|
cubeb_stream_set_volume(this_ptr->m_cubeb_stream, static_cast<float>(this_ptr->m_output_volume) / 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(output_buffer), static_cast<u32>(nframes), false);
|
||||||
return nframes;
|
return nframes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +136,7 @@ void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb
|
||||||
this_ptr->m_paused = (state != CUBEB_STATE_STARTED);
|
this_ptr->m_paused = (state != CUBEB_STATE_STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CubebAudioStream::BufferAvailable() {}
|
void CubebAudioStream::FramesAvailable() {}
|
||||||
|
|
||||||
void CubebAudioStream::DestroyContext()
|
void CubebAudioStream::DestroyContext()
|
||||||
{
|
{
|
||||||
|
@ -138,6 +149,12 @@ void CubebAudioStream::DestroyContext()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CubebAudioStream::SetOutputVolume(u32 volume)
|
||||||
|
{
|
||||||
|
AudioStream::SetOutputVolume(volume);
|
||||||
|
m_output_volume_changed.store(true);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream()
|
std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream()
|
||||||
{
|
{
|
||||||
return std::make_unique<CubebAudioStream>();
|
return std::make_unique<CubebAudioStream>();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "common/audio_stream.h"
|
#include "common/audio_stream.h"
|
||||||
#include "cubeb/cubeb.h"
|
#include "cubeb/cubeb.h"
|
||||||
|
#include <atomic>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
class CubebAudioStream final : public AudioStream
|
class CubebAudioStream final : public AudioStream
|
||||||
|
@ -9,13 +10,15 @@ public:
|
||||||
CubebAudioStream();
|
CubebAudioStream();
|
||||||
~CubebAudioStream();
|
~CubebAudioStream();
|
||||||
|
|
||||||
|
void SetOutputVolume(u32 volume) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool IsOpen() const { return m_cubeb_stream != nullptr; }
|
bool IsOpen() const { return m_cubeb_stream != nullptr; }
|
||||||
|
|
||||||
bool OpenDevice() override;
|
bool OpenDevice() override;
|
||||||
void PauseDevice(bool paused) override;
|
void PauseDevice(bool paused) override;
|
||||||
void CloseDevice() override;
|
void CloseDevice() override;
|
||||||
void BufferAvailable() override;
|
void FramesAvailable() override;
|
||||||
|
|
||||||
void DestroyContext();
|
void DestroyContext();
|
||||||
|
|
||||||
|
@ -26,6 +29,7 @@ protected:
|
||||||
cubeb* m_cubeb_context = nullptr;
|
cubeb* m_cubeb_context = nullptr;
|
||||||
cubeb_stream* m_cubeb_stream = nullptr;
|
cubeb_stream* m_cubeb_stream = nullptr;
|
||||||
bool m_paused = true;
|
bool m_paused = true;
|
||||||
|
std::atomic_bool m_output_volume_changed{ false };
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
bool m_com_initialized_by_us = false;
|
bool m_com_initialized_by_us = false;
|
||||||
|
|
|
@ -17,9 +17,10 @@ class FIFOQueue
|
||||||
public:
|
public:
|
||||||
const T* GetDataPointer() const { return m_ptr; }
|
const T* GetDataPointer() const { return m_ptr; }
|
||||||
T* GetDataPointer() { return m_ptr; }
|
T* GetDataPointer() { return m_ptr; }
|
||||||
const T* GetFrontPointer() const { return &m_ptr[m_head]; }
|
const T* GetReadPointer() const { return &m_ptr[m_head]; }
|
||||||
T* GetFrontPointer() { return &m_ptr[m_head]; }
|
T* GetReadPointer() { return &m_ptr[m_head]; }
|
||||||
constexpr u32 GetCapacity() const { return CAPACITY; }
|
constexpr u32 GetCapacity() const { return CAPACITY; }
|
||||||
|
T* GetWritePointer() { return &m_ptr[m_tail]; }
|
||||||
u32 GetSize() const { return m_size; }
|
u32 GetSize() const { return m_size; }
|
||||||
u32 GetSpace() const { return CAPACITY - m_size; }
|
u32 GetSpace() const { return CAPACITY - m_size; }
|
||||||
u32 GetContiguousSpace() const { return (m_tail >= m_head) ? (CAPACITY - m_tail) : (m_head - m_tail); }
|
u32 GetContiguousSpace() const { return (m_tail >= m_head) ? (CAPACITY - m_tail) : (m_head - m_tail); }
|
||||||
|
@ -148,6 +149,14 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AdvanceTail(u32 count)
|
||||||
|
{
|
||||||
|
DebugAssert((m_size + count) < CAPACITY);
|
||||||
|
DebugAssert((m_tail + count) <= CAPACITY);
|
||||||
|
m_tail = (m_tail + count) % CAPACITY;
|
||||||
|
m_size += count;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FIFOQueue() = default;
|
FIFOQueue() = default;
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ void NullAudioStream::PauseDevice(bool paused) {}
|
||||||
|
|
||||||
void NullAudioStream::CloseDevice() {}
|
void NullAudioStream::CloseDevice() {}
|
||||||
|
|
||||||
void NullAudioStream::BufferAvailable()
|
void NullAudioStream::FramesAvailable()
|
||||||
{
|
{
|
||||||
// drop any buffer as soon as they're available
|
// drop any buffer as soon as they're available
|
||||||
DropBuffer();
|
DropFrames(GetSamplesAvailableLocked());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AudioStream> AudioStream::CreateNullAudioStream()
|
std::unique_ptr<AudioStream> AudioStream::CreateNullAudioStream()
|
||||||
|
|
|
@ -11,5 +11,5 @@ protected:
|
||||||
bool OpenDevice() override;
|
bool OpenDevice() override;
|
||||||
void PauseDevice(bool paused) override;
|
void PauseDevice(bool paused) override;
|
||||||
void CloseDevice() override;
|
void CloseDevice() override;
|
||||||
void BufferAvailable() override;
|
void FramesAvailable() override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,20 +61,18 @@ void HostInterface::Shutdown() {}
|
||||||
|
|
||||||
void HostInterface::CreateAudioStream()
|
void HostInterface::CreateAudioStream()
|
||||||
{
|
{
|
||||||
Log_InfoPrintf("Creating '%s' audio stream, sample rate = %u, channels = %u, buffer size = %u, buffer count = %u",
|
Log_InfoPrintf("Creating '%s' audio stream, sample rate = %u, channels = %u, buffer size = %u",
|
||||||
Settings::GetAudioBackendName(m_settings.audio_backend), AUDIO_SAMPLE_RATE, AUDIO_CHANNELS,
|
Settings::GetAudioBackendName(m_settings.audio_backend), AUDIO_SAMPLE_RATE, AUDIO_CHANNELS,
|
||||||
m_settings.audio_buffer_size, m_settings.audio_buffer_count);
|
m_settings.audio_buffer_size);
|
||||||
|
|
||||||
m_audio_stream = CreateAudioStream(m_settings.audio_backend);
|
m_audio_stream = CreateAudioStream(m_settings.audio_backend);
|
||||||
|
|
||||||
if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size,
|
if (!m_audio_stream || !m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size))
|
||||||
m_settings.audio_buffer_count))
|
|
||||||
{
|
{
|
||||||
ReportFormattedError("Failed to create or configure audio stream, falling back to null output.");
|
ReportFormattedError("Failed to create or configure audio stream, falling back to null output.");
|
||||||
m_audio_stream.reset();
|
m_audio_stream.reset();
|
||||||
m_audio_stream = AudioStream::CreateNullAudioStream();
|
m_audio_stream = AudioStream::CreateNullAudioStream();
|
||||||
m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size,
|
m_audio_stream->Reconfigure(AUDIO_SAMPLE_RATE, AUDIO_CHANNELS, m_settings.audio_buffer_size);
|
||||||
m_settings.audio_buffer_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume);
|
m_audio_stream->SetOutputVolume(m_settings.audio_output_muted ? 0 : m_settings.audio_output_volume);
|
||||||
|
@ -1011,7 +1009,6 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si)
|
||||||
si.SetStringValue("Audio", "Backend", Settings::GetAudioBackendName(AudioBackend::Cubeb));
|
si.SetStringValue("Audio", "Backend", Settings::GetAudioBackendName(AudioBackend::Cubeb));
|
||||||
si.SetIntValue("Audio", "OutputVolume", 100);
|
si.SetIntValue("Audio", "OutputVolume", 100);
|
||||||
si.SetIntValue("Audio", "BufferSize", DEFAULT_AUDIO_BUFFER_SIZE);
|
si.SetIntValue("Audio", "BufferSize", DEFAULT_AUDIO_BUFFER_SIZE);
|
||||||
si.SetIntValue("Audio", "BufferCount", DEFAULT_AUDIO_BUFFER_COUNT);
|
|
||||||
si.SetIntValue("Audio", "OutputMuted", false);
|
si.SetIntValue("Audio", "OutputMuted", false);
|
||||||
si.SetBoolValue("Audio", "Sync", true);
|
si.SetBoolValue("Audio", "Sync", true);
|
||||||
si.SetBoolValue("Audio", "DumpOnBoot", false);
|
si.SetBoolValue("Audio", "DumpOnBoot", false);
|
||||||
|
@ -1072,8 +1069,7 @@ void HostInterface::UpdateSettings(SettingsInterface& si)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_settings.audio_backend != old_settings.audio_backend ||
|
if (m_settings.audio_backend != old_settings.audio_backend ||
|
||||||
m_settings.audio_buffer_size != old_settings.audio_buffer_size ||
|
m_settings.audio_buffer_size != old_settings.audio_buffer_size)
|
||||||
m_settings.audio_buffer_count != old_settings.audio_buffer_count)
|
|
||||||
{
|
{
|
||||||
if (m_settings.audio_backend != old_settings.audio_backend)
|
if (m_settings.audio_backend != old_settings.audio_backend)
|
||||||
ReportFormattedMessage("Switching to %s audio backend.",
|
ReportFormattedMessage("Switching to %s audio backend.",
|
||||||
|
|
|
@ -38,8 +38,7 @@ public:
|
||||||
{
|
{
|
||||||
AUDIO_SAMPLE_RATE = 44100,
|
AUDIO_SAMPLE_RATE = 44100,
|
||||||
AUDIO_CHANNELS = 2,
|
AUDIO_CHANNELS = 2,
|
||||||
DEFAULT_AUDIO_BUFFER_SIZE = 2048,
|
DEFAULT_AUDIO_BUFFER_SIZE = 2048
|
||||||
DEFAULT_AUDIO_BUFFER_COUNT = 4
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SaveStateInfo
|
struct SaveStateInfo
|
||||||
|
|
|
@ -61,7 +61,6 @@ void Settings::Load(SettingsInterface& si)
|
||||||
ParseAudioBackend(si.GetStringValue("Audio", "Backend", "Cubeb").c_str()).value_or(AudioBackend::Cubeb);
|
ParseAudioBackend(si.GetStringValue("Audio", "Backend", "Cubeb").c_str()).value_or(AudioBackend::Cubeb);
|
||||||
audio_output_volume = si.GetIntValue("Audio", "OutputVolume", 100);
|
audio_output_volume = si.GetIntValue("Audio", "OutputVolume", 100);
|
||||||
audio_buffer_size = si.GetIntValue("Audio", "BufferSize", HostInterface::DEFAULT_AUDIO_BUFFER_SIZE);
|
audio_buffer_size = si.GetIntValue("Audio", "BufferSize", HostInterface::DEFAULT_AUDIO_BUFFER_SIZE);
|
||||||
audio_buffer_count = si.GetIntValue("Audio", "BufferCount", HostInterface::DEFAULT_AUDIO_BUFFER_COUNT);
|
|
||||||
audio_output_muted = si.GetBoolValue("Audio", "OutputMuted", false);
|
audio_output_muted = si.GetBoolValue("Audio", "OutputMuted", false);
|
||||||
audio_sync_enabled = si.GetBoolValue("Audio", "Sync", true);
|
audio_sync_enabled = si.GetBoolValue("Audio", "Sync", true);
|
||||||
audio_dump_on_boot = si.GetBoolValue("Audio", "DumpOnBoot", false);
|
audio_dump_on_boot = si.GetBoolValue("Audio", "DumpOnBoot", false);
|
||||||
|
@ -151,7 +150,6 @@ void Settings::Save(SettingsInterface& si) const
|
||||||
si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend));
|
si.SetStringValue("Audio", "Backend", GetAudioBackendName(audio_backend));
|
||||||
si.SetIntValue("Audio", "OutputVolume", audio_output_volume);
|
si.SetIntValue("Audio", "OutputVolume", audio_output_volume);
|
||||||
si.SetIntValue("Audio", "BufferSize", audio_buffer_size);
|
si.SetIntValue("Audio", "BufferSize", audio_buffer_size);
|
||||||
si.SetIntValue("Audio", "BufferCount", audio_buffer_count);
|
|
||||||
si.SetBoolValue("Audio", "OutputMuted", audio_output_muted);
|
si.SetBoolValue("Audio", "OutputMuted", audio_output_muted);
|
||||||
si.SetBoolValue("Audio", "Sync", audio_sync_enabled);
|
si.SetBoolValue("Audio", "Sync", audio_sync_enabled);
|
||||||
si.SetBoolValue("Audio", "DumpOnBoot", audio_dump_on_boot);
|
si.SetBoolValue("Audio", "DumpOnBoot", audio_dump_on_boot);
|
||||||
|
|
|
@ -71,7 +71,6 @@ struct Settings
|
||||||
AudioBackend audio_backend = AudioBackend::Cubeb;
|
AudioBackend audio_backend = AudioBackend::Cubeb;
|
||||||
s32 audio_output_volume = 100;
|
s32 audio_output_volume = 100;
|
||||||
u32 audio_buffer_size = 2048;
|
u32 audio_buffer_size = 2048;
|
||||||
u32 audio_buffer_count = 4;
|
|
||||||
bool audio_output_muted = false;
|
bool audio_output_muted = false;
|
||||||
bool audio_sync_enabled = true;
|
bool audio_sync_enabled = true;
|
||||||
bool audio_dump_on_boot = true;
|
bool audio_dump_on_boot = true;
|
||||||
|
|
|
@ -674,7 +674,7 @@ void SPU::Execute(TickCount ticks)
|
||||||
{
|
{
|
||||||
AudioStream* const output_stream = m_system->GetHostInterface()->GetAudioStream();
|
AudioStream* const output_stream = m_system->GetHostInterface()->GetAudioStream();
|
||||||
s16* output_frame_start;
|
s16* output_frame_start;
|
||||||
u32 output_frame_space;
|
u32 output_frame_space = remaining_frames;
|
||||||
output_stream->BeginWrite(&output_frame_start, &output_frame_space);
|
output_stream->BeginWrite(&output_frame_start, &output_frame_space);
|
||||||
|
|
||||||
s16* output_frame = output_frame_start;
|
s16* output_frame = output_frame_start;
|
||||||
|
|
|
@ -14,13 +14,11 @@ AudioSettingsWidget::AudioSettingsWidget(QtHostInterface* host_interface, QWidge
|
||||||
&Settings::ParseAudioBackend, &Settings::GetAudioBackendName);
|
&Settings::ParseAudioBackend, &Settings::GetAudioBackendName);
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.syncToOutput, "Audio/Sync");
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.syncToOutput, "Audio/Sync");
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferSize, "Audio/BufferSize");
|
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferSize, "Audio/BufferSize");
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.bufferCount, "Audio/BufferCount");
|
|
||||||
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.volume, "Audio/OutputVolume");
|
SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.volume, "Audio/OutputVolume");
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.muted, "Audio/OutputMuted");
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.muted, "Audio/OutputMuted");
|
||||||
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startDumpingOnBoot, "Audio/DumpOnBoot");
|
SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.startDumpingOnBoot, "Audio/DumpOnBoot");
|
||||||
|
|
||||||
connect(m_ui.bufferSize, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel);
|
connect(m_ui.bufferSize, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel);
|
||||||
connect(m_ui.bufferCount, &QSlider::valueChanged, this, &AudioSettingsWidget::updateBufferingLabel);
|
|
||||||
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::updateVolumeLabel);
|
connect(m_ui.volume, &QSlider::valueChanged, this, &AudioSettingsWidget::updateVolumeLabel);
|
||||||
|
|
||||||
updateBufferingLabel();
|
updateBufferingLabel();
|
||||||
|
@ -32,14 +30,9 @@ AudioSettingsWidget::~AudioSettingsWidget() = default;
|
||||||
void AudioSettingsWidget::updateBufferingLabel()
|
void AudioSettingsWidget::updateBufferingLabel()
|
||||||
{
|
{
|
||||||
const u32 buffer_size = static_cast<u32>(m_ui.bufferSize->value());
|
const u32 buffer_size = static_cast<u32>(m_ui.bufferSize->value());
|
||||||
const u32 buffer_count = static_cast<u32>(m_ui.bufferCount->value());
|
const float max_latency = AudioStream::GetMaxLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size);
|
||||||
const float min_latency = AudioStream::GetMinLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size, buffer_count);
|
m_ui.bufferingLabel->setText(
|
||||||
const float max_latency = AudioStream::GetMaxLatency(HostInterface::AUDIO_SAMPLE_RATE, buffer_size, buffer_count);
|
tr("Maximum latency: %1 frames (%2ms)").arg(buffer_size).arg(max_latency * 1000.0f, 0, 'f', 2));
|
||||||
m_ui.bufferingLabel->setText(tr("%1 samples, %2 buffers (min %3ms, max %4ms)")
|
|
||||||
.arg(buffer_size)
|
|
||||||
.arg(buffer_count)
|
|
||||||
.arg(min_latency * 1000.0f, 0, 'f', 2)
|
|
||||||
.arg(max_latency * 1000.0f, 0, 'f', 2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioSettingsWidget::updateVolumeLabel()
|
void AudioSettingsWidget::updateVolumeLabel()
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="2" column="0">
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
|
@ -87,59 +87,30 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QLabel" name="bufferingLabel">
|
<widget class="QLabel" name="bufferingLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>2048 samples, 4 buffers (min 0.00ms, max 0.00ms))</string>
|
<string>Maximum latency: 0 frames (0.00ms)</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignCenter</set>
|
<set>Qt::AlignCenter</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="3" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="syncToOutput">
|
<widget class="QCheckBox" name="syncToOutput">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sync To Output</string>
|
<string>Sync To Output</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0" colspan="2">
|
<item row="4" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="startDumpingOnBoot">
|
<widget class="QCheckBox" name="startDumpingOnBoot">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Start Dumping On Boot</string>
|
<string>Start Dumping On Boot</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_4">
|
|
||||||
<property name="text">
|
|
||||||
<string>Buffer Count:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1">
|
|
||||||
<widget class="QSlider" name="bufferCount">
|
|
||||||
<property name="minimum">
|
|
||||||
<number>2</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>8</number>
|
|
||||||
</property>
|
|
||||||
<property name="pageStep">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickPosition">
|
|
||||||
<enum>QSlider::TicksBothSides</enum>
|
|
||||||
</property>
|
|
||||||
<property name="tickInterval">
|
|
||||||
<number>1</number>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#include "sdl_audio_stream.h"
|
#include "sdl_audio_stream.h"
|
||||||
#include "sdl_initializer.h"
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
|
#include "sdl_initializer.h"
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
Log_SetChannel(SDLAudioStream);
|
Log_SetChannel(SDLAudioStream);
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ SDLAudioStream::SDLAudioStream() = default;
|
||||||
|
|
||||||
SDLAudioStream::~SDLAudioStream()
|
SDLAudioStream::~SDLAudioStream()
|
||||||
{
|
{
|
||||||
if (m_is_open)
|
if (IsOpen())
|
||||||
SDLAudioStream::CloseDevice();
|
SDLAudioStream::CloseDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ std::unique_ptr<SDLAudioStream> SDLAudioStream::Create()
|
||||||
|
|
||||||
bool SDLAudioStream::OpenDevice()
|
bool SDLAudioStream::OpenDevice()
|
||||||
{
|
{
|
||||||
DebugAssert(!m_is_open);
|
DebugAssert(!IsOpen());
|
||||||
|
|
||||||
FrontendCommon::EnsureSDLInitialized();
|
FrontendCommon::EnsureSDLInitialized();
|
||||||
|
|
||||||
|
@ -38,41 +38,49 @@ bool SDLAudioStream::OpenDevice()
|
||||||
spec.callback = AudioCallback;
|
spec.callback = AudioCallback;
|
||||||
spec.userdata = static_cast<void*>(this);
|
spec.userdata = static_cast<void*>(this);
|
||||||
|
|
||||||
if (SDL_OpenAudio(&spec, nullptr) < 0)
|
SDL_AudioSpec obtained_spec = {};
|
||||||
|
m_device_id = SDL_OpenAudioDevice(nullptr, 0, &spec, &obtained_spec, SDL_AUDIO_ALLOW_SAMPLES_CHANGE);
|
||||||
|
if (m_device_id == 0)
|
||||||
{
|
{
|
||||||
Log_ErrorPrintf("SDL_OpenAudio failed");
|
Log_ErrorPrintf("SDL_OpenAudioDevice() failed: %s", SDL_GetError());
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_is_open = true;
|
if (obtained_spec.samples > spec.samples)
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("Requested buffer size %u, got buffer size %u. Adjusting to compensate.", spec.samples,
|
||||||
|
obtained_spec.samples);
|
||||||
|
|
||||||
|
if (!SetBufferSize(obtained_spec.samples))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to set new buffer size of %u", obtained_spec.samples);
|
||||||
|
CloseDevice();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLAudioStream::PauseDevice(bool paused)
|
void SDLAudioStream::PauseDevice(bool paused)
|
||||||
{
|
{
|
||||||
SDL_PauseAudio(paused ? 1 : 0);
|
SDL_PauseAudioDevice(m_device_id, paused ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLAudioStream::CloseDevice()
|
void SDLAudioStream::CloseDevice()
|
||||||
{
|
{
|
||||||
DebugAssert(m_is_open);
|
SDL_CloseAudioDevice(m_device_id);
|
||||||
SDL_CloseAudio();
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||||
m_is_open = false;
|
m_device_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
void SDLAudioStream::AudioCallback(void* userdata, uint8_t* stream, int len)
|
||||||
{
|
{
|
||||||
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
|
SDLAudioStream* const this_ptr = static_cast<SDLAudioStream*>(userdata);
|
||||||
const u32 num_samples = len / sizeof(SampleType) / this_ptr->m_channels;
|
const u32 num_frames = len / sizeof(SampleType) / this_ptr->m_channels;
|
||||||
const u32 read_samples = this_ptr->ReadSamples(reinterpret_cast<SampleType*>(stream), num_samples);
|
|
||||||
const u32 silence_samples = num_samples - read_samples;
|
this_ptr->ReadFrames(reinterpret_cast<SampleType*>(stream), num_frames, false);
|
||||||
if (silence_samples > 0)
|
|
||||||
{
|
|
||||||
std::memset(reinterpret_cast<SampleType*>(stream) + (read_samples * this_ptr->m_channels), 0,
|
|
||||||
silence_samples * this_ptr->m_channels * sizeof(SampleType));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLAudioStream::BufferAvailable() {}
|
void SDLAudioStream::FramesAvailable() {}
|
||||||
|
|
|
@ -11,12 +11,14 @@ public:
|
||||||
static std::unique_ptr<SDLAudioStream> Create();
|
static std::unique_ptr<SDLAudioStream> Create();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
ALWAYS_INLINE bool IsOpen() const { return (m_device_id != 0); }
|
||||||
|
|
||||||
bool OpenDevice() override;
|
bool OpenDevice() override;
|
||||||
void PauseDevice(bool paused) override;
|
void PauseDevice(bool paused) override;
|
||||||
void CloseDevice() override;
|
void CloseDevice() override;
|
||||||
void BufferAvailable() override;
|
void FramesAvailable() override;
|
||||||
|
|
||||||
static void AudioCallback(void* userdata, uint8_t* stream, int len);
|
static void AudioCallback(void* userdata, uint8_t* stream, int len);
|
||||||
|
|
||||||
bool m_is_open = false;
|
u32 m_device_id = 0;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue