summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-06-06 10:20:46 +1000
committerjacqueline <me@jacqueline.id.au>2023-06-06 10:20:46 +1000
commit8a2a2d226558d099243eea0aa9ae22b2791e0e0e (patch)
tree16c3defdb167ea7f452e5ce96118b165acf6602f /src/audio
parentee5f662f9bb150138545ca35ef5c4896eb74daea (diff)
downloadtangara-fw-8a2a2d226558d099243eea0aa9ae22b2791e0e0e.tar.gz
Get basic audio playback going again
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/audio_decoder.cpp11
-rw-r--r--src/audio/audio_fsm.cpp32
-rw-r--r--src/audio/audio_task.cpp102
-rw-r--r--src/audio/fatfs_audio_input.cpp4
-rw-r--r--src/audio/include/audio_decoder.hpp3
-rw-r--r--src/audio/include/audio_element.hpp3
-rw-r--r--src/audio/include/fatfs_audio_input.hpp2
-rw-r--r--src/audio/pipeline.cpp2
8 files changed, 117 insertions, 42 deletions
diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp
index 981cca6f..5a2c75c7 100644
--- a/src/audio/audio_decoder.cpp
+++ b/src/audio/audio_decoder.cpp
@@ -14,7 +14,6 @@
#include <memory>
#include <variant>
-#include "cbor/tinycbor/src/cborinternal_p.h"
#include "freertos/FreeRTOS.h"
#include "esp_heap_caps.h"
@@ -36,7 +35,8 @@ AudioDecoder::AudioDecoder()
current_codec_(),
current_input_format_(),
current_output_format_(),
- has_samples_to_send_(false) {}
+ has_samples_to_send_(false),
+ has_input_remaining_(false) {}
AudioDecoder::~AudioDecoder() {}
@@ -70,6 +70,10 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
return true;
}
+auto AudioDecoder::NeedsToProcess() const -> bool {
+ return has_samples_to_send_ || has_input_remaining_;
+}
+
auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void {
auto input = inputs.begin();
@@ -124,7 +128,8 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
return;
}
- if (res.value()) {
+ has_input_remaining_ = !res.value();
+ if (!has_input_remaining_) {
// We're out of useable data in this buffer. Finish immediately; there's
// nothing to send.
input->mark_incomplete();
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index a1bb956f..9c7af6fe 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -29,29 +29,29 @@ auto AudioState::Init(drivers::GpioExpander* gpio_expander,
sGpioExpander = gpio_expander;
sDac = dac;
sDatabase = database;
-}
-namespace states {
+ sFileSource.reset(new FatfsAudioInput());
+ sI2SOutput.reset(new I2SAudioOutput(sGpioExpander, sDac));
-void Uninitialised::react(const system_fsm::BootComplete&) {
- transit<Standby>([&]() {
- sFileSource.reset(new FatfsAudioInput());
- sI2SOutput.reset(new I2SAudioOutput(sGpioExpander, sDac));
+ // Perform initial pipeline configuration.
+ // TODO(jacqueline): Factor this out once we have any kind of dynamic
+ // reconfiguration.
+ AudioDecoder* codec = new AudioDecoder();
+ sPipeline.emplace_back(codec);
- // Perform initial pipeline configuration.
- // TODO(jacqueline): Factor this out once we have any kind of dynamic
- // reconfiguration.
- AudioDecoder* codec = new AudioDecoder();
- sPipeline.emplace_back(codec);
+ Pipeline* pipeline = new Pipeline(sPipeline.front().get());
+ pipeline->AddInput(sFileSource.get());
- Pipeline* pipeline = new Pipeline(sPipeline.front().get());
- pipeline->AddInput(sFileSource.get());
+ task::StartPipeline(pipeline, sI2SOutput.get());
+}
- task::StartPipeline(pipeline, sI2SOutput.get());
- });
+namespace states {
+
+void Uninitialised::react(const system_fsm::BootComplete&) {
+ transit<Standby>();
}
-void Standby::react(const PlayFile &ev) {
+void Standby::react(const PlayFile& ev) {
if (sFileSource->OpenFile(ev.filename)) {
transit<Playback>();
}
diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp
index 6cc2d927..343281a7 100644
--- a/src/audio/audio_task.cpp
+++ b/src/audio/audio_task.cpp
@@ -43,20 +43,64 @@ namespace task {
static const char* kTag = "task";
+// The default amount of time to wait between pipeline iterations for a single
+// song.
+static constexpr uint_fast16_t kDefaultDelayTicks = pdMS_TO_TICKS(5);
+static constexpr uint_fast16_t kMaxDelayTicks = pdMS_TO_TICKS(10);
+static constexpr uint_fast16_t kMinDelayTicks = pdMS_TO_TICKS(1);
+
void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
+ // The stream format for bytes currently in the sink buffer.
std::optional<StreamInfo::Format> output_format;
- uint_fast16_t delay_ticks = pdMS_TO_TICKS(5);
- std::vector<Pipeline*> elements = pipeline->GetIterationOrder();
+ // How long to wait between pipeline iterations. This is reset for each song,
+ // and readjusted on the fly to maintain a reasonable amount playback buffer.
+ // Buffering too much will mean we process samples inefficiently, wasting CPU
+ // time, whilst buffering too little will affect the quality of the output.
+ uint_fast16_t delay_ticks = kDefaultDelayTicks;
+
+ std::vector<Pipeline*> all_elements = pipeline->GetIterationOrder();
- events::EventQueue &event_queue = events::EventQueue::GetInstance();
+ events::EventQueue& event_queue = events::EventQueue::GetInstance();
while (1) {
- event_queue.ServiceAudio(delay_ticks);
+ // First, see if we actually have any pipeline work to do in this iteration.
+ bool has_work = false;
+ // We always have work to do if there's still bytes to be sunk.
+ has_work = all_elements.back()->OutStream().info->bytes_in_stream > 0;
+ if (!has_work) {
+ for (Pipeline* p : all_elements) {
+ has_work = p->OutputElement()->NeedsToProcess();
+ if (has_work) {
+ break;
+ }
+ }
+ }
+
+ // See if there's any new events.
+ event_queue.ServiceAudio(has_work ? delay_ticks : portMAX_DELAY);
+
+ if (!has_work) {
+ // See if we've been given work by this event.
+ for (Pipeline* p : all_elements) {
+ has_work = p->OutputElement()->NeedsToProcess();
+ if (has_work) {
+ delay_ticks = kDefaultDelayTicks;
+ break;
+ }
+ }
+ if (!has_work) {
+ continue;
+ }
+ }
+
+ // We have work to do! Allow each element in the pipeline to process one
+ // chunk. We iterate from input nodes first, so this should result in
+ // samples in the output buffer.
- for (int i = 0; i < elements.size(); i++) {
+ for (int i = 0; i < all_elements.size(); i++) {
std::vector<RawStream> raw_in_streams;
- elements.at(i)->InStreams(&raw_in_streams);
- RawStream raw_out_stream = elements.at(i)->OutStream();
+ all_elements.at(i)->InStreams(&raw_in_streams);
+ RawStream raw_out_stream = all_elements.at(i)->OutStream();
// Crop the input and output streams to the ranges that are safe to
// touch. For the input streams, this is the region that contains
@@ -67,14 +111,14 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
[&](RawStream& s) { in_streams.emplace_back(&s); });
OutputStream out_stream(&raw_out_stream);
- elements.at(i)->OutputElement()->Process(in_streams, &out_stream);
+ all_elements.at(i)->OutputElement()->Process(in_streams, &out_stream);
}
- RawStream raw_sink_stream = elements.front()->OutStream();
+ RawStream raw_sink_stream = all_elements.back()->OutStream();
InputStream sink_stream(&raw_sink_stream);
if (sink_stream.info().bytes_in_stream == 0) {
- vTaskDelay(pdMS_TO_TICKS(100));
+ // No new bytes to sink, so skip sinking completely.
continue;
}
@@ -86,24 +130,36 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
ESP_LOGI(kTag, "reconfiguring dac");
output_format = sink_stream.info().format;
sink->Configure(*output_format);
+ } else {
+ continue;
}
}
// We've reconfigured the sink, or it was already configured correctly.
// Send through some data.
- if (output_format == sink_stream.info().format &&
- !std::holds_alternative<std::monostate>(*output_format)) {
- std::size_t sent =
- xStreamBufferSend(sink->buffer(), sink_stream.data().data(),
- sink_stream.data().size_bytes(), 0);
- if (sent > 0) {
- ESP_LOGI(
- kTag, "sunk %u bytes out of %u (%d %%)", sent,
- sink_stream.info().bytes_in_stream,
- (int)(((float)sent / (float)sink_stream.info().bytes_in_stream) *
- 100));
- }
- sink_stream.consume(sent);
+ std::size_t bytes_sunk =
+ xStreamBufferSend(sink->buffer(), sink_stream.data().data(),
+ sink_stream.data().size_bytes(), 0);
+
+ // Adjust how long we wait for the next iteration if we're getting too far
+ // ahead or behind.
+ float sunk_percent = static_cast<float>(bytes_sunk) /
+ static_cast<float>(sink_stream.info().bytes_in_stream);
+
+ if (sunk_percent > 0.66f) {
+ // We're sinking a lot of the output buffer per iteration, so we need to
+ // be running faster.
+ delay_ticks--;
+ } else if (sunk_percent < 0.33f) {
+ // We're not sinking much of the output buffer per iteration, so we can
+ // slow down to save some cycles.
+ delay_ticks++;
+ }
+ delay_ticks = std::clamp(delay_ticks, kMinDelayTicks, kMaxDelayTicks);
+
+ // Finally, actually mark the bytes we sunk as consumed.
+ if (bytes_sunk > 0) {
+ sink_stream.consume(bytes_sunk);
}
}
}
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index ed5db315..8abc7d32 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -51,6 +51,10 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
return true;
}
+auto FatfsAudioInput::NeedsToProcess() const -> bool {
+ return is_file_open_;
+}
+
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void {
if (!is_file_open_) {
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
index 5927e150..3cda0305 100644
--- a/src/audio/include/audio_decoder.hpp
+++ b/src/audio/include/audio_decoder.hpp
@@ -30,6 +30,8 @@ class AudioDecoder : public IAudioElement {
AudioDecoder();
~AudioDecoder();
+ auto NeedsToProcess() const -> bool override;
+
auto Process(const std::vector<InputStream>& inputs, OutputStream* output)
-> void override;
@@ -41,6 +43,7 @@ class AudioDecoder : public IAudioElement {
std::optional<StreamInfo::Format> current_input_format_;
std::optional<StreamInfo::Format> current_output_format_;
bool has_samples_to_send_;
+ bool has_input_remaining_;
auto ProcessStreamInfo(const StreamInfo& info) -> bool;
};
diff --git a/src/audio/include/audio_element.hpp b/src/audio/include/audio_element.hpp
index 133a6ae7..7e3d9d7d 100644
--- a/src/audio/include/audio_element.hpp
+++ b/src/audio/include/audio_element.hpp
@@ -10,6 +10,7 @@
#include <cstdint>
#include <deque>
#include <memory>
+#include <vector>
#include "freertos/FreeRTOS.h"
@@ -46,6 +47,8 @@ class IAudioElement {
IAudioElement() {}
virtual ~IAudioElement() {}
+ virtual auto NeedsToProcess() const -> bool = 0;
+
virtual auto Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void = 0;
};
diff --git a/src/audio/include/fatfs_audio_input.hpp b/src/audio/include/fatfs_audio_input.hpp
index 98ad4d2b..bfc0064e 100644
--- a/src/audio/include/fatfs_audio_input.hpp
+++ b/src/audio/include/fatfs_audio_input.hpp
@@ -33,6 +33,8 @@ class FatfsAudioInput : public IAudioElement {
auto OpenFile(const std::string& path) -> bool;
+ auto NeedsToProcess() const -> bool override;
+
auto Process(const std::vector<InputStream>& inputs, OutputStream* output)
-> void override;
diff --git a/src/audio/pipeline.cpp b/src/audio/pipeline.cpp
index 60598151..f54f8d11 100644
--- a/src/audio/pipeline.cpp
+++ b/src/audio/pipeline.cpp
@@ -5,6 +5,7 @@
*/
#include "pipeline.hpp"
+#include <algorithm>
#include <memory>
#include "stream_info.hpp"
@@ -53,6 +54,7 @@ auto Pipeline::GetIterationOrder() -> std::vector<Pipeline*> {
}
}
+ std::reverse(found.begin(), found.end());
return found;
}