From a6efff8b02986daf6d3660c4f33c5f39cf3f3830 Mon Sep 17 00:00:00 2001 From: fearlessTobi Date: Thu, 23 Aug 2018 14:33:03 +0200 Subject: Add audio stretching support --- src/audio_core/cubeb_sink.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 5a1177d0c..0f77fd162 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -85,6 +85,13 @@ public: } } + size_t SamplesInQueue(u32 num_channels) const { + if (!ctx) + return 0; + + return queue.size() / num_channels; + } + u32 GetNumChannels() const { return num_channels; } -- cgit v1.2.3 From 6d9dd1dc6dacbba9907e7c4e92e2d9d111ef44f4 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sat, 8 Sep 2018 14:55:11 +0100 Subject: cubeb_sink: Use RingBuffer --- src/audio_core/cubeb_sink.cpp | 66 +++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 0f77fd162..552bcd051 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -4,18 +4,17 @@ #include #include -#include - #include "audio_core/cubeb_sink.h" #include "audio_core/stream.h" #include "common/logging/log.h" +#include "common/ring_buffer.h" namespace AudioCore { -class SinkStreamImpl final : public SinkStream { +class CubebSinkStream final : public SinkStream { public: - SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, - const std::string& name) + CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, + const std::string& name) : ctx{ctx}, num_channels{num_channels_} { if (num_channels == 6) { @@ -38,7 +37,7 @@ public: if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device, ¶ms, std::max(512u, minimum_latency), - &SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback, + &CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback, this) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream"); return; @@ -50,7 +49,7 @@ public: } } - ~SinkStreamImpl() { + ~CubebSinkStream() { if (!ctx) { return; } @@ -63,33 +62,27 @@ public: } void EnqueueSamples(u32 num_channels, const std::vector& samples) override { - if (!ctx) { - return; - } - - std::lock_guard lock{queue_mutex}; - - queue.reserve(queue.size() + samples.size() * GetNumChannels()); - if (is_6_channel) { // Downsample 6 channels to 2 const size_t sample_count_copy_size = samples.size() * 2; - queue.reserve(sample_count_copy_size); + std::vector buf; + buf.reserve(sample_count_copy_size); for (size_t i = 0; i < samples.size(); i += num_channels) { - queue.push_back(samples[i]); - queue.push_back(samples[i + 1]); + buf.push_back(samples[i]); + buf.push_back(samples[i + 1]); } - } else { - // Copy as-is - std::copy(samples.begin(), samples.end(), std::back_inserter(queue)); + queue.Push(buf); + return; } + + queue.Push(samples); } - size_t SamplesInQueue(u32 num_channels) const { + size_t SamplesInQueue(u32 num_channels) const override { if (!ctx) return 0; - return queue.size() / num_channels; + return queue.Size() / num_channels; } u32 GetNumChannels() const { @@ -104,8 +97,7 @@ private: u32 num_channels{}; bool is_6_channel{}; - std::mutex queue_mutex; - std::vector queue; + Common::RingBuffer queue; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); @@ -151,38 +143,32 @@ CubebSink::~CubebSink() { SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, const std::string& name) { sink_streams.push_back( - std::make_unique(ctx, sample_rate, num_channels, output_device, name)); + std::make_unique(ctx, sample_rate, num_channels, output_device, name)); return *sink_streams.back(); } -long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, +long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames) { - SinkStreamImpl* impl = static_cast(user_data); + CubebSinkStream* impl = static_cast(user_data); u8* buffer = reinterpret_cast(output_buffer); if (!impl) { return {}; } - std::lock_guard lock{impl->queue_mutex}; - - const size_t frames_to_write{ - std::min(impl->queue.size() / impl->GetNumChannels(), static_cast(num_frames))}; - - memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels()); - impl->queue.erase(impl->queue.begin(), - impl->queue.begin() + frames_to_write * impl->GetNumChannels()); + const size_t max_samples_to_write = impl->GetNumChannels() * num_frames; + const size_t samples_written = impl->queue.Pop(buffer, max_samples_to_write); - if (frames_to_write < num_frames) { + if (samples_written < max_samples_to_write) { // Fill the rest of the frames with silence - memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0, - (num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels()); + std::memset(buffer + samples_written * sizeof(s16), 0, + (max_samples_to_write - samples_written) * sizeof(s16)); } return num_frames; } -void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} +void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {} std::vector ListCubebSinkDevices() { std::vector device_list; -- cgit v1.2.3 From 7e697ab7ff729aee3d88eb18cd130132786444ac Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sat, 8 Sep 2018 16:15:40 +0100 Subject: cubeb_sink: Hold last available value instead of writing zeros This reduces clicking in output audio should we underrun. --- src/audio_core/cubeb_sink.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 552bcd051..3c129122f 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -89,6 +89,10 @@ public: return num_channels; } + u32 GetNumChannelsInQueue() const { + return num_channels == 1 ? 1 : 2; + } + private: std::vector device_list; @@ -98,6 +102,7 @@ private: bool is_6_channel{}; Common::RingBuffer queue; + std::array last_frame; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); @@ -156,13 +161,18 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const return {}; } - const size_t max_samples_to_write = impl->GetNumChannels() * num_frames; + const size_t num_channels = impl->GetNumChannelsInQueue(); + const size_t max_samples_to_write = num_channels * num_frames; const size_t samples_written = impl->queue.Pop(buffer, max_samples_to_write); - if (samples_written < max_samples_to_write) { - // Fill the rest of the frames with silence - std::memset(buffer + samples_written * sizeof(s16), 0, - (max_samples_to_write - samples_written) * sizeof(s16)); + if (samples_written >= num_channels) { + std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), + num_channels * sizeof(s16)); + } + + // Fill the rest of the frames with last_frame + for (size_t i = samples_written; i < max_samples_to_write; i += num_channels) { + std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); } return num_frames; -- cgit v1.2.3 From 1aa195a9c0416c986c8224d9dc66d9d5e45401a0 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sat, 8 Sep 2018 16:49:04 +0100 Subject: cubeb_sink: Perform audio stretching --- src/audio_core/cubeb_sink.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 3c129122f..7982306b3 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -6,8 +6,10 @@ #include #include "audio_core/cubeb_sink.h" #include "audio_core/stream.h" +#include "audio_core/time_stretch.h" #include "common/logging/log.h" #include "common/ring_buffer.h" +#include "core/settings.h" namespace AudioCore { @@ -15,14 +17,8 @@ class CubebSinkStream final : public SinkStream { public: CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, num_channels{num_channels_} { - - if (num_channels == 6) { - // 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2 - // channel for now - is_6_channel = true; - num_channels = 2; - } + : ctx{ctx}, is_6_channel{num_channels_ == 6}, num_channels{std::min(num_channels_, 2u)}, + time_stretch{sample_rate, num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; @@ -89,10 +85,6 @@ public: return num_channels; } - u32 GetNumChannelsInQueue() const { - return num_channels == 1 ? 1 : 2; - } - private: std::vector device_list; @@ -103,6 +95,7 @@ private: Common::RingBuffer queue; std::array last_frame; + TimeStretcher time_stretch; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, void* output_buffer, long num_frames); @@ -153,7 +146,7 @@ SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels, } long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, - void* output_buffer, long num_frames) { + void* output_buffer, long num_frames) { CubebSinkStream* impl = static_cast(user_data); u8* buffer = reinterpret_cast(output_buffer); @@ -161,9 +154,19 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const return {}; } - const size_t num_channels = impl->GetNumChannelsInQueue(); - const size_t max_samples_to_write = num_channels * num_frames; - const size_t samples_written = impl->queue.Pop(buffer, max_samples_to_write); + const size_t num_channels = impl->GetNumChannels(); + const size_t samples_to_write = num_channels * num_frames; + size_t samples_written; + + if (Settings::values.enable_audio_stretching) { + const std::vector in{impl->queue.Pop()}; + const size_t num_in{in.size() / num_channels}; + s16* const out{reinterpret_cast(buffer)}; + const size_t out_frames = impl->time_stretch.Process(in.data(), num_in, out, num_frames); + samples_written = out_frames * num_channels; + } else { + samples_written = impl->queue.Pop(buffer, samples_to_write); + } if (samples_written >= num_channels) { std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), @@ -171,7 +174,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const } // Fill the rest of the frames with last_frame - for (size_t i = samples_written; i < max_samples_to_write; i += num_channels) { + for (size_t i = samples_written; i < samples_to_write; i += num_channels) { std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16)); } -- cgit v1.2.3 From 55af5bda5574a34716680b23aab6482d340a00ed Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 9 Sep 2018 09:16:48 +0100 Subject: cubeb_sink: Downsample arbitrary number of channels --- src/audio_core/cubeb_sink.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 7982306b3..067dc98d2 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -17,8 +17,8 @@ class CubebSinkStream final : public SinkStream { public: CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device, const std::string& name) - : ctx{ctx}, is_6_channel{num_channels_ == 6}, num_channels{std::min(num_channels_, 2u)}, - time_stretch{sample_rate, num_channels} { + : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate, + num_channels} { cubeb_stream_params params{}; params.rate = sample_rate; @@ -57,15 +57,15 @@ public: cubeb_stream_destroy(stream_backend); } - void EnqueueSamples(u32 num_channels, const std::vector& samples) override { - if (is_6_channel) { + void EnqueueSamples(u32 source_num_channels, const std::vector& samples) override { + if (source_num_channels > num_channels) { // Downsample 6 channels to 2 - const size_t sample_count_copy_size = samples.size() * 2; std::vector buf; - buf.reserve(sample_count_copy_size); - for (size_t i = 0; i < samples.size(); i += num_channels) { - buf.push_back(samples[i]); - buf.push_back(samples[i + 1]); + buf.reserve(samples.size() * num_channels / source_num_channels); + for (size_t i = 0; i < samples.size(); i += source_num_channels) { + for (size_t ch = 0; ch < num_channels; ch++) { + buf.push_back(samples[i + ch]); + } } queue.Push(buf); return; @@ -91,7 +91,6 @@ private: cubeb* ctx{}; cubeb_stream* stream_backend{}; u32 num_channels{}; - bool is_6_channel{}; Common::RingBuffer queue; std::array last_frame; -- cgit v1.2.3 From 957ddab6796cb6f644c60993c3035d8bd9c0a398 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Wed, 12 Sep 2018 18:07:16 +0100 Subject: audio_core: Flush stream when not playing anything --- src/audio_core/cubeb_sink.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/audio_core/cubeb_sink.cpp') diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 067dc98d2..79155a7a0 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include "audio_core/cubeb_sink.h" #include "audio_core/stream.h" @@ -81,6 +82,10 @@ public: return queue.Size() / num_channels; } + void Flush() override { + should_flush = true; + } + u32 GetNumChannels() const { return num_channels; } @@ -94,6 +99,7 @@ private: Common::RingBuffer queue; std::array last_frame; + std::atomic should_flush{}; TimeStretcher time_stretch; static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer, @@ -163,6 +169,11 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const s16* const out{reinterpret_cast(buffer)}; const size_t out_frames = impl->time_stretch.Process(in.data(), num_in, out, num_frames); samples_written = out_frames * num_channels; + + if (impl->should_flush) { + impl->time_stretch.Flush(); + impl->should_flush = false; + } } else { samples_written = impl->queue.Pop(buffer, samples_to_write); } -- cgit v1.2.3