summaryrefslogtreecommitdiff
path: root/src/audio/audio_playback.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2022-12-07 15:36:47 +1100
committerjacqueline <me@jacqueline.id.au>2022-12-07 15:36:47 +1100
commit01be69eca1fa89c047fc29f5cb0ea8ba0898dad1 (patch)
treed40f749b3ebf6327f13d51d585f7c315a6d864c3 /src/audio/audio_playback.cpp
parentf35bb64c2b8dbb72fd15f1880e4d01d263660910 (diff)
downloadtangara-fw-01be69eca1fa89c047fc29f5cb0ea8ba0898dad1.tar.gz
better handling of chunk buffer
Diffstat (limited to 'src/audio/audio_playback.cpp')
-rw-r--r--src/audio/audio_playback.cpp358
1 files changed, 62 insertions, 296 deletions
diff --git a/src/audio/audio_playback.cpp b/src/audio/audio_playback.cpp
index 300bf176..7b8418d7 100644
--- a/src/audio/audio_playback.cpp
+++ b/src/audio/audio_playback.cpp
@@ -4,322 +4,88 @@
#include <cstdint>
#include <memory>
#include <string_view>
-
-#include "aac_decoder.h"
-#include "amr_decoder.h"
-#include "audio_element.h"
-#include "audio_event_iface.h"
-#include "audio_pipeline.h"
-#include "esp_err.h"
-#include "flac_decoder.h"
-#include "mp3_decoder.h"
-#include "ogg_decoder.h"
-#include "opus_decoder.h"
-#include "wav_decoder.h"
-
-#include "audio_output.hpp"
-
-static const char* kTag = "PLAYBACK";
-static const char* kSource = "src";
-static const char* kDecoder = "dec";
-static const char* kSink = "sink";
-
-static audio_element_status_t toStatus(void* status) {
- uintptr_t as_pointer_int = reinterpret_cast<uintptr_t>(status);
- return static_cast<audio_element_status_t>(as_pointer_int);
-}
-
-static bool endsWith(std::string_view str, std::string_view suffix) {
- return str.size() >= suffix.size() &&
- 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
-}
-
-static void toLower(std::string& str) {
- std::transform(str.begin(), str.end(), str.begin(),
- [](unsigned char c) { return std::tolower(c); });
-}
-
-namespace drivers {
-
-auto AudioPlayback::create(std::unique_ptr<IAudioOutput> output)
+#include "audio_decoder.hpp"
+#include "audio_task.hpp"
+#include "chunk.hpp"
+#include "fatfs_audio_input.hpp"
+#include "freertos/portmacro.h"
+#include "gpio_expander.hpp"
+#include "i2s_audio_output.hpp"
+#include "storage.hpp"
+#include "stream_buffer.hpp"
+#include "stream_info.hpp"
+#include "stream_message.hpp"
+
+namespace audio {
+
+// TODO: idk
+static const std::size_t kMinElementBufferSize = 1024;
+
+auto AudioPlayback::create(drivers::GpioExpander* expander,
+ std::shared_ptr<drivers::SdStorage> storage)
-> cpp::result<std::unique_ptr<AudioPlayback>, Error> {
- audio_pipeline_handle_t pipeline;
- audio_element_handle_t fatfs_stream_reader;
- audio_event_iface_handle_t event_interface;
-
- audio_pipeline_cfg_t pipeline_config =
- audio_pipeline_cfg_t(DEFAULT_AUDIO_PIPELINE_CONFIG());
- pipeline = audio_pipeline_init(&pipeline_config);
- if (pipeline == NULL) {
- return cpp::fail(Error::PIPELINE_INIT);
- }
+ // Create everything
+ auto source = std::make_shared<FatfsAudioInput>(storage);
+ auto codec = std::make_shared<AudioDecoder>();
- fatfs_stream_cfg_t fatfs_stream_config =
- fatfs_stream_cfg_t(FATFS_STREAM_CFG_DEFAULT());
- fatfs_stream_config.type = AUDIO_STREAM_READER;
- fatfs_stream_reader = fatfs_stream_init(&fatfs_stream_config);
- if (fatfs_stream_reader == NULL) {
- return cpp::fail(Error::FATFS_INIT);
+ auto sink_res = I2SAudioOutput::create(expander);
+ if (sink_res.has_error()) {
+ return cpp::fail(ERR_INIT_ELEMENT);
}
+ auto sink = sink_res.value();
- audio_event_iface_cfg_t event_config = AUDIO_EVENT_IFACE_DEFAULT_CFG();
- event_interface = audio_event_iface_init(&event_config);
+ auto playback = std::make_unique<AudioPlayback>();
- audio_pipeline_set_listener(pipeline, event_interface);
- audio_element_msg_set_listener(fatfs_stream_reader, event_interface);
- audio_element_msg_set_listener(output->GetAudioElement(), event_interface);
+ // Configure the pipeline
+ source->InputBuffer(&playback->stream_start_);
+ sink->OutputBuffer(&playback->stream_end_);
+ playback->ConnectElements(source.get(), codec.get());
+ playback->ConnectElements(codec.get(), sink.get());
- audio_pipeline_register(pipeline, fatfs_stream_reader, kSource);
- audio_pipeline_register(pipeline, output->GetAudioElement(), kSink);
+ // Launch!
+ StartAudioTask("src", source);
+ StartAudioTask("dec", codec);
+ StartAudioTask("sink", sink);
- return std::make_unique<AudioPlayback>(output, pipeline, fatfs_stream_reader,
- event_interface);
+ return playback;
}
-AudioPlayback::AudioPlayback(std::unique_ptr<IAudioOutput>& output,
- audio_pipeline_handle_t pipeline,
- audio_element_handle_t source_element,
- audio_event_iface_handle_t event_interface)
- : output_(std::move(output)),
- pipeline_(pipeline),
- source_element_(source_element),
- event_interface_(event_interface) {}
+// TODO(jacqueline): think about sizes
+AudioPlayback::AudioPlayback()
+ : stream_start_(128, 128), stream_end_(128, 128) {}
AudioPlayback::~AudioPlayback() {
- audio_pipeline_remove_listener(pipeline_);
- audio_element_msg_remove_listener(source_element_, event_interface_);
- audio_element_msg_remove_listener(output_->GetAudioElement(),
- event_interface_);
-
- audio_pipeline_stop(pipeline_);
- audio_pipeline_wait_for_stop(pipeline_);
- audio_pipeline_terminate(pipeline_);
-
- ReconfigurePipeline(NONE);
-
- audio_pipeline_unregister(pipeline_, source_element_);
- audio_pipeline_unregister(pipeline_, output_->GetAudioElement());
-
- audio_event_iface_destroy(event_interface_);
-
- audio_pipeline_deinit(pipeline_);
- audio_element_deinit(source_element_);
-}
-
-void AudioPlayback::Play(const std::string& filename) {
- output_->SetSoftMute(true);
-
- if (playback_state_ != STOPPED) {
- audio_pipeline_stop(pipeline_);
- audio_pipeline_wait_for_stop(pipeline_);
- audio_pipeline_terminate(pipeline_);
- }
-
- playback_state_ = PLAYING;
- Decoder decoder = GetDecoderForFilename(filename);
- ReconfigurePipeline(decoder);
- audio_element_set_uri(source_element_, filename.c_str());
- audio_pipeline_reset_ringbuffer(pipeline_);
- audio_pipeline_reset_elements(pipeline_);
- audio_pipeline_run(pipeline_);
-
- output_->SetSoftMute(false);
-}
-
-void AudioPlayback::Toggle() {
- if (playback_state_ == PLAYING) {
- Pause();
- } else if (playback_state_ == PAUSED) {
- Resume();
- }
+ // TODO(jacqueline): signal the end of all things, and maybe wait for it?
}
-void AudioPlayback::Resume() {
- if (playback_state_ == PAUSED) {
- ESP_LOGI(kTag, "resuming");
- playback_state_ = PLAYING;
- audio_pipeline_resume(pipeline_);
- output_->SetSoftMute(false);
- }
-}
-void AudioPlayback::Pause() {
- if (GetPlaybackState() == PLAYING) {
- ESP_LOGI(kTag, "pausing");
- output_->SetSoftMute(true);
- playback_state_ = PAUSED;
- audio_pipeline_pause(pipeline_);
- }
-}
+auto AudioPlayback::Play(const std::string& filename) -> void {
+ StreamInfo info;
+ info.Path(filename);
-auto AudioPlayback::GetPlaybackState() const -> PlaybackState {
- return playback_state_;
-}
+ std::array<std::byte, 128> dest;
+ auto len = WriteMessage(
+ TYPE_STREAM_INFO, [&](auto enc) { return info.Encode(enc); }, dest);
-void AudioPlayback::ProcessEvents(uint16_t max_time_ms) {
- if (playback_state_ == STOPPED) {
+ if (len.has_error()) {
+ // TODO.
return;
}
- while (1) {
- audio_event_iface_msg_t event;
- esp_err_t err = audio_event_iface_listen(event_interface_, &event,
- pdMS_TO_TICKS(max_time_ms));
- if (err != ESP_OK) {
- // Error should only be timeouts, so use a 'failure' as an indication that
- // we're out of events to process.
- break;
- }
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)decoder_ &&
- event.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
- audio_element_info_t music_info;
- audio_element_getinfo(decoder_, &music_info);
- ESP_LOGI(kTag, "sample_rate=%d, bits=%d, ch=%d", music_info.sample_rates,
- music_info.bits, music_info.channels);
- }
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)source_element_ &&
- event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
- audio_element_status_t status = toStatus(event.data);
- if (status == AEL_STATUS_STATE_FINISHED) {
- // TODO: Could we change the uri here? hmm.
- ESP_LOGI(kTag, "finished reading input.");
- }
- }
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)output_->GetAudioElement() &&
- event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
- audio_element_status_t status = toStatus(event.data);
- if (status == AEL_STATUS_STATE_FINISHED) {
- if (next_filename_ != "") {
- ESP_LOGI(kTag, "finished writing output. enqueing next.");
- Decoder decoder = GetDecoderForFilename(next_filename_);
- if (decoder == decoder_type_) {
- output_->SetSoftMute(true);
- audio_element_set_uri(source_element_, next_filename_.c_str());
- audio_pipeline_reset_ringbuffer(pipeline_);
- audio_pipeline_reset_elements(pipeline_);
- audio_pipeline_change_state(pipeline_, AEL_STATE_INIT);
- audio_pipeline_run(pipeline_);
- output_->SetSoftMute(true);
- } else {
- Play(next_filename_);
- }
- next_filename_ = "";
- } else {
- ESP_LOGI(kTag, "finished writing output. stopping.");
- audio_pipeline_wait_for_stop(pipeline_);
- audio_pipeline_terminate(pipeline_);
- playback_state_ = STOPPED;
- }
- return;
- }
- }
-
- if (event.need_free_data) {
- // AFAICT this never happens in practice, but it doesn't hurt to follow
- // the api here anyway.
- free(event.data);
- }
- }
-}
-
-void AudioPlayback::SetNextFile(const std::string& filename) {
- next_filename_ = filename;
-}
-
-void AudioPlayback::SetVolume(uint8_t volume) {
- output_->SetVolume(volume);
+ // TODO: short delay, return error on fail
+ xMessageBufferSend(*stream_start_.Handle(), dest.data(), len.value(),
+ portMAX_DELAY);
}
-auto AudioPlayback::GetVolume() const -> uint8_t {
- return output_->GetVolume();
-}
+auto AudioPlayback::ConnectElements(IAudioElement* src, IAudioElement* sink)
+ -> void {
+ std::size_t chunk_size =
+ std::max(src->InputMinChunkSize(), sink->InputMinChunkSize());
+ std::size_t buffer_size = std::max(kMinElementBufferSize, chunk_size * 2);
-auto AudioPlayback::GetDecoderForFilename(std::string filename) const
- -> Decoder {
- toLower(filename);
- if (endsWith(filename, "mp3")) {
- return MP3;
- }
- if (endsWith(filename, "amr") || endsWith(filename, "wamr")) {
- return AMR;
- }
- if (endsWith(filename, "opus")) {
- return OPUS;
- }
- if (endsWith(filename, "ogg")) {
- return OGG;
- }
- if (endsWith(filename, "flac")) {
- return FLAC;
- }
- if (endsWith(filename, "wav")) {
- return WAV;
- }
- if (endsWith(filename, "aac") || endsWith(filename, "m4a") ||
- endsWith(filename, "ts") || endsWith(filename, "mp4")) {
- return AAC;
- }
- return NONE;
-}
-
-auto AudioPlayback::CreateDecoder(Decoder decoder) const
- -> audio_element_handle_t {
- if (decoder == MP3) {
- mp3_decoder_cfg_t config = DEFAULT_MP3_DECODER_CONFIG();
- return mp3_decoder_init(&config);
- }
- if (decoder == AMR) {
- amr_decoder_cfg_t config = DEFAULT_AMR_DECODER_CONFIG();
- return amr_decoder_init(&config);
- }
- if (decoder == OPUS) {
- opus_decoder_cfg_t config = DEFAULT_OPUS_DECODER_CONFIG();
- return decoder_opus_init(&config);
- }
- if (decoder == OGG) {
- ogg_decoder_cfg_t config = DEFAULT_OGG_DECODER_CONFIG();
- return ogg_decoder_init(&config);
- }
- if (decoder == FLAC) {
- flac_decoder_cfg_t config = DEFAULT_FLAC_DECODER_CONFIG();
- return flac_decoder_init(&config);
- }
- if (decoder == WAV) {
- wav_decoder_cfg_t config = DEFAULT_WAV_DECODER_CONFIG();
- return wav_decoder_init(&config);
- }
- if (decoder == AAC) {
- aac_decoder_cfg_t config = DEFAULT_AAC_DECODER_CONFIG();
- return aac_decoder_init(&config);
- }
- return nullptr;
-}
-
-void AudioPlayback::ReconfigurePipeline(Decoder decoder) {
- if (decoder_type_ == decoder) {
- return;
- }
-
- if (decoder_type_ != NONE) {
- audio_pipeline_unlink(pipeline_);
- audio_element_msg_remove_listener(decoder_, event_interface_);
- audio_pipeline_unregister(pipeline_, decoder_);
- audio_element_deinit(decoder_);
- }
-
- if (decoder != NONE) {
- decoder_ = CreateDecoder(decoder);
- decoder_type_ = decoder;
- audio_pipeline_register(pipeline_, decoder_, kDecoder);
- audio_element_msg_set_listener(decoder_, event_interface_);
- static const char* link_tag[3] = {kSource, kDecoder, kSink};
- audio_pipeline_link(pipeline_, &link_tag[0], 3);
- }
+ auto buffer = std::make_unique<StreamBuffer>(chunk_size, buffer_size);
+ src->OutputBuffer(buffer.get());
+ sink->OutputBuffer(buffer.get());
+ element_buffers_.push_back(std::move(buffer));
}
-} // namespace drivers
+} // namespace audio