summaryrefslogtreecommitdiff
path: root/src/audio/audio_converter.cpp
diff options
context:
space:
mode:
authorailurux <ailuruxx@gmail.com>2024-04-02 11:13:50 +1100
committerailurux <ailuruxx@gmail.com>2024-04-02 11:13:50 +1100
commite20ebe7574db5aedc73f07b7bb3a0a01eae93c84 (patch)
tree34c93ec8a80e282f3ce3e47dd60c41e46de0f8b3 /src/audio/audio_converter.cpp
parenta750af35aa6afda40aadca8f7cf8db75f41a43b2 (diff)
parent0d0c4b2307cac8436fea7276956f293262b265ed (diff)
downloadtangara-fw-e20ebe7574db5aedc73f07b7bb3a0a01eae93c84.tar.gz
Merge branch 'main' into lua-volume
Diffstat (limited to 'src/audio/audio_converter.cpp')
-rw-r--r--src/audio/audio_converter.cpp232
1 files changed, 148 insertions, 84 deletions
diff --git a/src/audio/audio_converter.cpp b/src/audio/audio_converter.cpp
index dc2fef95..eb1cde80 100644
--- a/src/audio/audio_converter.cpp
+++ b/src/audio/audio_converter.cpp
@@ -5,18 +5,20 @@
*/
#include "audio_converter.hpp"
+#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
+#include "audio_events.hpp"
#include "audio_sink.hpp"
#include "esp_heap_caps.h"
#include "esp_log.h"
+#include "event_queue.hpp"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
#include "i2s_dac.hpp"
-#include "idf_additions.h"
#include "resample.hpp"
#include "sample.hpp"
@@ -25,7 +27,7 @@
[[maybe_unused]] static constexpr char kTag[] = "mixer";
static constexpr std::size_t kSampleBufferLength =
- drivers::kI2SBufferLengthFrames * sizeof(sample::Sample);
+ drivers::kI2SBufferLengthFrames * sizeof(sample::Sample) * 2;
static constexpr std::size_t kSourceBufferLength = kSampleBufferLength * 2;
namespace audio {
@@ -35,7 +37,9 @@ SampleConverter::SampleConverter()
resampler_(nullptr),
source_(xStreamBufferCreateWithCaps(kSourceBufferLength,
sizeof(sample::Sample) * 2,
- MALLOC_CAP_DMA)) {
+ MALLOC_CAP_DMA)),
+ leftover_bytes_(0),
+ samples_sunk_(0) {
input_buffer_ = {
reinterpret_cast<sample::Sample*>(heap_caps_calloc(
kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_DMA)),
@@ -63,24 +67,32 @@ auto SampleConverter::SetOutput(std::shared_ptr<IAudioOutput> output) -> void {
sink_ = output;
}
-auto SampleConverter::ConvertSamples(cpp::span<sample::Sample> input,
- const IAudioOutput::Format& format,
- bool is_eos) -> void {
+auto SampleConverter::beginStream(std::shared_ptr<TrackInfo> track) -> void {
Args args{
- .format = format,
+ .track = new std::shared_ptr<TrackInfo>(track),
+ .samples_available = 0,
+ .is_end_of_stream = false,
+ };
+ xQueueSend(commands_, &args, portMAX_DELAY);
+}
+
+auto SampleConverter::continueStream(cpp::span<sample::Sample> input) -> void {
+ Args args{
+ .track = nullptr,
.samples_available = input.size(),
- .is_end_of_stream = is_eos,
+ .is_end_of_stream = false,
};
xQueueSend(commands_, &args, portMAX_DELAY);
+ xStreamBufferSend(source_, input.data(), input.size_bytes(), portMAX_DELAY);
+}
- cpp::span<std::byte> input_as_bytes = {
- reinterpret_cast<std::byte*>(input.data()), input.size_bytes()};
- size_t bytes_sent = 0;
- while (bytes_sent < input_as_bytes.size()) {
- bytes_sent +=
- xStreamBufferSend(source_, input_as_bytes.subspan(bytes_sent).data(),
- input_as_bytes.size() - bytes_sent, portMAX_DELAY);
- }
+auto SampleConverter::endStream() -> void {
+ Args args{
+ .track = nullptr,
+ .samples_available = 0,
+ .is_end_of_stream = true,
+ };
+ xQueueSend(commands_, &args, portMAX_DELAY);
}
auto SampleConverter::Main() -> void {
@@ -88,75 +100,94 @@ auto SampleConverter::Main() -> void {
Args args;
while (!xQueueReceive(commands_, &args, portMAX_DELAY)) {
}
- if (args.format != source_format_) {
- resampler_.reset();
- source_format_ = args.format;
- leftover_bytes_ = 0;
-
- auto new_target = sink_->PrepareFormat(args.format);
- if (new_target != target_format_) {
- // The new format is different to the old one. Wait for the sink to
- // drain before continuing.
- while (!xStreamBufferIsEmpty(sink_->stream())) {
- ESP_LOGI(kTag, "waiting for sink stream to drain...");
- // TODO(jacqueline): Get the sink drain ISR to notify us of this
- // via semaphore instead of busy-ish waiting.
- vTaskDelay(pdMS_TO_TICKS(10));
- }
-
- sink_->Configure(new_target);
- }
- target_format_ = new_target;
+
+ if (args.track) {
+ handleBeginStream(*args.track);
+ delete args.track;
+ }
+ if (args.samples_available) {
+ handleContinueStream(args.samples_available);
+ }
+ if (args.is_end_of_stream) {
+ handleEndStream();
}
+ }
+}
- // Loop until we finish reading all the bytes indicated. There might be
- // leftovers from each iteration, and from this process as a whole,
- // depending on the resampling stage.
- size_t bytes_read = 0;
- size_t bytes_to_read = args.samples_available * sizeof(sample::Sample);
- while (bytes_read < bytes_to_read) {
- // First top up the input buffer, taking care not to overwrite anything
- // remaining from a previous iteration.
- size_t bytes_read_this_it = xStreamBufferReceive(
- source_, input_buffer_as_bytes_.subspan(leftover_bytes_).data(),
- std::min(input_buffer_as_bytes_.size() - leftover_bytes_,
- bytes_to_read - bytes_read),
- portMAX_DELAY);
- bytes_read += bytes_read_this_it;
-
- // Calculate the number of whole samples that are now in the input buffer.
- size_t bytes_in_buffer = bytes_read_this_it + leftover_bytes_;
- size_t samples_in_buffer = bytes_in_buffer / sizeof(sample::Sample);
-
- size_t samples_used =
- HandleSamples(input_buffer_.first(samples_in_buffer),
- args.is_end_of_stream && bytes_read == bytes_to_read);
-
- // Maybe the resampler didn't consume everything. Maybe the last few
- // bytes we read were half a frame. Either way, we need to calculate the
- // size of the remainder in bytes, then move it to the front of our
- // buffer.
- size_t bytes_used = samples_used * sizeof(sample::Sample);
- assert(bytes_used <= bytes_in_buffer);
-
- leftover_bytes_ = bytes_in_buffer - bytes_used;
- if (leftover_bytes_ > 0) {
- std::memmove(input_buffer_as_bytes_.data(),
- input_buffer_as_bytes_.data() + bytes_used,
- leftover_bytes_);
+auto SampleConverter::handleBeginStream(std::shared_ptr<TrackInfo> track)
+ -> void {
+ if (track->format != source_format_) {
+ resampler_.reset();
+ source_format_ = track->format;
+ leftover_bytes_ = 0;
+
+ auto new_target = sink_->PrepareFormat(track->format);
+ if (new_target != target_format_) {
+ // The new format is different to the old one. Wait for the sink to
+ // drain before continuing.
+ while (!xStreamBufferIsEmpty(sink_->stream())) {
+ ESP_LOGI(kTag, "waiting for sink stream to drain...");
+ // TODO(jacqueline): Get the sink drain ISR to notify us of this
+ // via semaphore instead of busy-ish waiting.
+ vTaskDelay(pdMS_TO_TICKS(10));
}
+
+ sink_->Configure(new_target);
}
+ target_format_ = new_target;
}
+
+ samples_sunk_ = 0;
+ events::Audio().Dispatch(internal::StreamStarted{
+ .track = track,
+ .src_format = source_format_,
+ .dst_format = target_format_,
+ });
}
-auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input,
- bool is_eos) -> size_t {
+auto SampleConverter::handleContinueStream(size_t samples_available) -> void {
+ // Loop until we finish reading all the bytes indicated. There might be
+ // leftovers from each iteration, and from this process as a whole,
+ // depending on the resampling stage.
+ size_t bytes_read = 0;
+ size_t bytes_to_read = samples_available * sizeof(sample::Sample);
+ while (bytes_read < bytes_to_read) {
+ // First top up the input buffer, taking care not to overwrite anything
+ // remaining from a previous iteration.
+ size_t bytes_read_this_it = xStreamBufferReceive(
+ source_, input_buffer_as_bytes_.subspan(leftover_bytes_).data(),
+ std::min(input_buffer_as_bytes_.size() - leftover_bytes_,
+ bytes_to_read - bytes_read),
+ portMAX_DELAY);
+ bytes_read += bytes_read_this_it;
+
+ // Calculate the number of whole samples that are now in the input buffer.
+ size_t bytes_in_buffer = bytes_read_this_it + leftover_bytes_;
+ size_t samples_in_buffer = bytes_in_buffer / sizeof(sample::Sample);
+
+ size_t samples_used = handleSamples(input_buffer_.first(samples_in_buffer));
+
+ // Maybe the resampler didn't consume everything. Maybe the last few
+ // bytes we read were half a frame. Either way, we need to calculate the
+ // size of the remainder in bytes, then move it to the front of our
+ // buffer.
+ size_t bytes_used = samples_used * sizeof(sample::Sample);
+ assert(bytes_used <= bytes_in_buffer);
+
+ leftover_bytes_ = bytes_in_buffer - bytes_used;
+ if (leftover_bytes_ > 0) {
+ std::memmove(input_buffer_as_bytes_.data(),
+ input_buffer_as_bytes_.data() + bytes_used, leftover_bytes_);
+ }
+ }
+}
+
+auto SampleConverter::handleSamples(cpp::span<sample::Sample> input) -> size_t {
if (source_format_ == target_format_) {
// The happiest possible case: the input format matches the output
// format already.
- std::size_t bytes_sent = xStreamBufferSend(
- sink_->stream(), input.data(), input.size_bytes(), portMAX_DELAY);
- return bytes_sent / sizeof(sample::Sample);
+ sendToSink(input);
+ return input.size();
}
size_t samples_used = 0;
@@ -173,7 +204,7 @@ auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input,
size_t read, written;
std::tie(read, written) = resampler_->Process(input.subspan(samples_used),
- resampled_buffer_, is_eos);
+ resampled_buffer_, false);
samples_used += read;
if (read == 0 && written == 0) {
@@ -186,16 +217,49 @@ auto SampleConverter::HandleSamples(cpp::span<sample::Sample> input,
samples_used = input.size();
}
- size_t bytes_sent = 0;
- size_t bytes_to_send = output_source.size_bytes();
- while (bytes_sent < bytes_to_send) {
- bytes_sent += xStreamBufferSend(
- sink_->stream(),
- reinterpret_cast<std::byte*>(output_source.data()) + bytes_sent,
- bytes_to_send - bytes_sent, portMAX_DELAY);
- }
+ sendToSink(output_source);
}
+
return samples_used;
}
+auto SampleConverter::handleEndStream() -> void {
+ if (resampler_) {
+ size_t read, written;
+ std::tie(read, written) = resampler_->Process({}, resampled_buffer_, true);
+
+ if (written > 0) {
+ sendToSink(resampled_buffer_.first(written));
+ }
+ }
+
+ // Send a final update to finish off this stream's samples.
+ if (samples_sunk_ > 0) {
+ events::Audio().Dispatch(internal::StreamUpdate{
+ .samples_sunk = samples_sunk_,
+ });
+ samples_sunk_ = 0;
+ }
+ leftover_bytes_ = 0;
+
+ events::Audio().Dispatch(internal::StreamEnded{});
+}
+
+auto SampleConverter::sendToSink(cpp::span<sample::Sample> samples) -> void {
+ // Update the number of samples sunk so far *before* actually sinking them,
+ // since writing to the stream buffer will block when the buffer gets full.
+ samples_sunk_ += samples.size();
+ if (samples_sunk_ >=
+ target_format_.sample_rate * target_format_.num_channels) {
+ events::Audio().Dispatch(internal::StreamUpdate{
+ .samples_sunk = samples_sunk_,
+ });
+ samples_sunk_ = 0;
+ }
+
+ xStreamBufferSend(sink_->stream(),
+ reinterpret_cast<std::byte*>(samples.data()),
+ samples.size_bytes(), portMAX_DELAY);
+}
+
} // namespace audio