From 9176ef187227ffb56c249c5f321cd1bf50d4cfcc Mon Sep 17 00:00:00 2001 From: jacqueline Date: Tue, 22 Nov 2022 17:05:02 +1100 Subject: Add cbor wrapper, and chunk streaming util --- src/audio/audio_playback.cpp | 325 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 src/audio/audio_playback.cpp (limited to 'src/audio/audio_playback.cpp') diff --git a/src/audio/audio_playback.cpp b/src/audio/audio_playback.cpp new file mode 100644 index 00000000..300bf176 --- /dev/null +++ b/src/audio/audio_playback.cpp @@ -0,0 +1,325 @@ +#include "audio_playback.hpp" + +#include +#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) + -> 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); + } + + 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); + } + + audio_event_iface_cfg_t event_config = AUDIO_EVENT_IFACE_DEFAULT_CFG(); + event_interface = audio_event_iface_init(&event_config); + + 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); + + audio_pipeline_register(pipeline, fatfs_stream_reader, kSource); + audio_pipeline_register(pipeline, output->GetAudioElement(), kSink); + + return std::make_unique(output, pipeline, fatfs_stream_reader, + event_interface); +} + +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) {} + +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(); + } +} + +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::GetPlaybackState() const -> PlaybackState { + return playback_state_; +} + +void AudioPlayback::ProcessEvents(uint16_t max_time_ms) { + if (playback_state_ == STOPPED) { + 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); +} + +auto AudioPlayback::GetVolume() const -> uint8_t { + return output_->GetVolume(); +} + +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); + } +} + +} // namespace drivers -- cgit v1.2.3