From 01be69eca1fa89c047fc29f5cb0ea8ba0898dad1 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Wed, 7 Dec 2022 15:36:47 +1100 Subject: better handling of chunk buffer --- src/audio/audio_playback.cpp | 358 ++++++++----------------------------------- 1 file changed, 62 insertions(+), 296 deletions(-) (limited to 'src/audio/audio_playback.cpp') 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 #include #include - -#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(status); - return static_cast(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 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 storage) -> cpp::result, 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(storage); + auto codec = std::make_shared(); - 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(); - 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(output, pipeline, fatfs_stream_reader, - event_interface); + return playback; } -AudioPlayback::AudioPlayback(std::unique_ptr& 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 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(chunk_size, buffer_size); + src->OutputBuffer(buffer.get()); + sink->OutputBuffer(buffer.get()); + element_buffers_.push_back(std::move(buffer)); } -} // namespace drivers +} // namespace audio -- cgit v1.2.3