diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 6163588bf..0dda3e826 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -13,13 +13,11 @@ namespace AudioCore { struct CubebSink::Impl { unsigned int sample_rate = 0; - std::vector device_list; cubeb* ctx = nullptr; cubeb_stream* stream = nullptr; - std::mutex queue_mutex; - std::vector queue; + std::function cb; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); @@ -95,45 +93,19 @@ unsigned int CubebSink::GetNativeSampleRate() const { return impl->sample_rate; } -void CubebSink::EnqueueSamples(const s16* samples, std::size_t sample_count) { - if (!impl->ctx) - return; - - std::lock_guard lock{impl->queue_mutex}; - - impl->queue.reserve(impl->queue.size() + sample_count * 2); - std::copy(samples, samples + sample_count * 2, std::back_inserter(impl->queue)); -} - -size_t CubebSink::SamplesInQueue() const { - if (!impl->ctx) - return 0; - - std::lock_guard lock{impl->queue_mutex}; - return impl->queue.size() / 2; +void CubebSink::SetCallback(std::function cb) { + impl->cb = cb; } long CubebSink::Impl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { Impl* impl = static_cast(user_data); - u8* buffer = reinterpret_cast(output_buffer); + s16* buffer = reinterpret_cast(output_buffer); - if (!impl) + if (!impl || !impl->cb) return 0; - std::lock_guard lock{impl->queue_mutex}; - - std::size_t frames_to_write = - std::min(impl->queue.size() / 2, static_cast(num_frames)); - - memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * 2); - impl->queue.erase(impl->queue.begin(), impl->queue.begin() + frames_to_write * 2); - - if (frames_to_write < num_frames) { - // Fill the rest of the frames with silence - memset(buffer + frames_to_write * sizeof(s16) * 2, 0, - (num_frames - frames_to_write) * sizeof(s16) * 2); - } + impl->cb(buffer, num_frames); return num_frames; } diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h index 59e633562..bee777010 100644 --- a/src/audio_core/cubeb_sink.h +++ b/src/audio_core/cubeb_sink.h @@ -17,9 +17,7 @@ public: unsigned int GetNativeSampleRate() const override; - void EnqueueSamples(const s16* samples, std::size_t sample_count) override; - - std::size_t SamplesInQueue() const override; + void SetCallback(std::function cb) override; private: struct Impl; diff --git a/src/audio_core/dsp_interface.cpp b/src/audio_core/dsp_interface.cpp index d52440967..70f2c4340 100644 --- a/src/audio_core/dsp_interface.cpp +++ b/src/audio_core/dsp_interface.cpp @@ -12,16 +12,13 @@ namespace AudioCore { DspInterface::DspInterface() = default; - -DspInterface::~DspInterface() { - if (perform_time_stretching) { - FlushResidualStretcherAudio(); - } -} +DspInterface::~DspInterface() = default; void DspInterface::SetSink(const std::string& sink_id, const std::string& audio_device) { const SinkDetails& sink_details = GetSinkDetails(sink_id); sink = sink_details.factory(audio_device); + sink->SetCallback( + [this](s16* buffer, std::size_t num_frames) { OutputCallback(buffer, num_frames); }); time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); } @@ -51,32 +48,21 @@ void DspInterface::OutputFrame(StereoFrame16& frame) { frame[i][1] = static_cast(frame[i][1] * volume_scale_factor); } - if (perform_time_stretching) { - time_stretcher.AddSamples(&frame[0][0], frame.size()); - std::vector stretched_samples = time_stretcher.Process(sink->SamplesInQueue()); - sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2); - } else { - constexpr std::size_t maximum_sample_latency = 2048; // about 64 miliseconds - if (sink->SamplesInQueue() > maximum_sample_latency) { - // This can occur if we're running too fast and samples are starting to back up. - // Just drop the samples. - return; - } - - sink->EnqueueSamples(&frame[0][0], frame.size()); - } + fifo.Push(frame.data(), frame.size()); } -void DspInterface::FlushResidualStretcherAudio() { - if (!sink) - return; +void DspInterface::FlushResidualStretcherAudio() {} - time_stretcher.Flush(); - while (true) { - std::vector residual_audio = time_stretcher.Process(sink->SamplesInQueue()); - if (residual_audio.empty()) - break; - sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); +void DspInterface::OutputCallback(s16* buffer, size_t num_frames) { + const size_t frames_written = fifo.Pop(buffer, num_frames); + + if (frames_written > 0) { + std::memcpy(&last_frame[0], buffer + 2 * (frames_written - 1), 2 * sizeof(s16)); + } + + // Hold last emitted frame; this prevents popping. + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(buffer + 2 * i, &last_frame[0], 2 * sizeof(s16)); } } diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h index f3004d657..f10bf9f7e 100644 --- a/src/audio_core/dsp_interface.h +++ b/src/audio_core/dsp_interface.h @@ -9,6 +9,7 @@ #include "audio_core/audio_types.h" #include "audio_core/time_stretch.h" #include "common/common_types.h" +#include "common/ring_buffer.h" #include "core/memory.h" namespace Service { @@ -81,9 +82,12 @@ protected: private: void FlushResidualStretcherAudio(); + void OutputCallback(s16* buffer, std::size_t num_frames); std::unique_ptr sink; bool perform_time_stretching = false; + Common::RingBuffer fifo; + std::array last_frame{}; TimeStretcher time_stretcher; }; diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index bc60b0784..8218b24ff 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -19,11 +19,7 @@ public: return native_sample_rate; } - void EnqueueSamples(const s16*, std::size_t) override {} - - std::size_t SamplesInQueue() const override { - return 0; - } + void SetCallback(std::function) override {} }; } // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp index e3b90b176..3fb90ac04 100644 --- a/src/audio_core/sdl2_sink.cpp +++ b/src/audio_core/sdl2_sink.cpp @@ -2,8 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include -#include +#include +#include #include #include "audio_core/audio_types.h" #include "audio_core/sdl2_sink.h" @@ -17,7 +17,7 @@ struct SDL2Sink::Impl { SDL_AudioDeviceID audio_device_id = 0; - std::list> queue; + std::function cb; static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes); }; @@ -74,58 +74,18 @@ unsigned int SDL2Sink::GetNativeSampleRate() const { return impl->sample_rate; } -void SDL2Sink::EnqueueSamples(const s16* samples, std::size_t sample_count) { - if (impl->audio_device_id <= 0) - return; - - SDL_LockAudioDevice(impl->audio_device_id); - impl->queue.emplace_back(samples, samples + sample_count * 2); - SDL_UnlockAudioDevice(impl->audio_device_id); -} - -size_t SDL2Sink::SamplesInQueue() const { - if (impl->audio_device_id <= 0) - return 0; - - SDL_LockAudioDevice(impl->audio_device_id); - - std::size_t total_size = - std::accumulate(impl->queue.begin(), impl->queue.end(), static_cast(0), - [](std::size_t sum, const auto& buffer) { - // Division by two because each stereo sample is made of - // two s16. - return sum + buffer.size() / 2; - }); - - SDL_UnlockAudioDevice(impl->audio_device_id); - - return total_size; +void SDL2Sink::SetCallback(std::function cb) { + impl->cb = cb; } void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) { Impl* impl = reinterpret_cast(impl_); + if (!impl || !impl->cb) + return; - std::size_t remaining_size = static_cast(buffer_size_in_bytes) / - sizeof(s16); // Keep track of size in 16-bit increments. + const size_t num_frames = buffer_size_in_bytes / (2 * sizeof(s16)); - while (remaining_size > 0 && !impl->queue.empty()) { - if (impl->queue.front().size() <= remaining_size) { - memcpy(buffer, impl->queue.front().data(), impl->queue.front().size() * sizeof(s16)); - buffer += impl->queue.front().size() * sizeof(s16); - remaining_size -= impl->queue.front().size(); - impl->queue.pop_front(); - } else { - memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16)); - buffer += remaining_size * sizeof(s16); - impl->queue.front().erase(impl->queue.front().begin(), - impl->queue.front().begin() + remaining_size); - remaining_size = 0; - } - } - - if (remaining_size > 0) { - memset(buffer, 0, remaining_size * sizeof(s16)); - } + impl->cb(reinterpret_cast(buffer), num_frames); } std::vector ListSDL2SinkDevices() { diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h index 46b1c28b6..6e262a0b6 100644 --- a/src/audio_core/sdl2_sink.h +++ b/src/audio_core/sdl2_sink.h @@ -17,9 +17,7 @@ public: unsigned int GetNativeSampleRate() const override; - void EnqueueSamples(const s16* samples, std::size_t sample_count) override; - - std::size_t SamplesInQueue() const override; + void SetCallback(std::function cb) override; private: struct Impl; diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h index 5d97f424b..65b5a820c 100644 --- a/src/audio_core/sink.h +++ b/src/audio_core/sink.h @@ -4,7 +4,7 @@ #pragma once -#include +#include #include "common/common_types.h" namespace AudioCore { @@ -20,19 +20,16 @@ class Sink { public: virtual ~Sink() = default; - /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: - /// samples/sec) + /// The native rate of this sink. The sink expects to be fed samples that respect this. + /// (Units: samples/sec) virtual unsigned int GetNativeSampleRate() const = 0; /** - * Feed stereo samples to sink. + * Set callback for samples * @param samples Samples in interleaved stereo PCM16 format. * @param sample_count Number of samples. */ - virtual void EnqueueSamples(const s16* samples, std::size_t sample_count) = 0; - - /// Samples enqueued that have not been played yet. - virtual std::size_t SamplesInQueue() const = 0; + virtual void SetCallback(std::function cb) = 0; }; } // namespace AudioCore