summaryrefslogtreecommitdiff
path: root/src/drivers
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2022-11-15 13:25:58 +1100
committerjacqueline <me@jacqueline.id.au>2022-11-15 13:25:58 +1100
commit530fd15e66a2c89c0dcd6edd1b2a318958c349a4 (patch)
tree2b4b75a644587440a6a5cf6620f1b6e36f81c2d6 /src/drivers
parent37041b810fbd10aab0834a33ae1dbd9edbb8bcb9 (diff)
downloadtangara-fw-530fd15e66a2c89c0dcd6edd1b2a318958c349a4.tar.gz
WIP audio play and pause
Diffstat (limited to 'src/drivers')
-rw-r--r--src/drivers/CMakeLists.txt2
-rw-r--r--src/drivers/audio_playback.cpp295
-rw-r--r--src/drivers/i2s_audio_output.cpp92
-rw-r--r--src/drivers/include/a2dp_audio_output.hpp15
-rw-r--r--src/drivers/include/audio_output.hpp27
-rw-r--r--src/drivers/include/audio_playback.hpp99
-rw-r--r--src/drivers/include/i2s_audio_output.hpp30
-rw-r--r--src/drivers/include/playback.hpp67
-rw-r--r--src/drivers/playback.cpp246
9 files changed, 559 insertions, 314 deletions
diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt
index 3b3c4a65..899fcf79 100644
--- a/src/drivers/CMakeLists.txt
+++ b/src/drivers/CMakeLists.txt
@@ -1,6 +1,6 @@
idf_component_register(
SRCS "dac.cpp" "gpio-expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
- "playback.cpp" "display.cpp" "display-init.cpp" "spi.cpp"
+ "audio_playback.cpp" "i2s_audio_output.cpp" "display.cpp" "display-init.cpp" "spi.cpp"
INCLUDE_DIRS "include"
REQUIRES "esp_adc_cal" "fatfs" "audio_pipeline" "audio_stream" "result" "lvgl")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/drivers/audio_playback.cpp b/src/drivers/audio_playback.cpp
new file mode 100644
index 00000000..b38d0bd3
--- /dev/null
+++ b/src/drivers/audio_playback.cpp
@@ -0,0 +1,295 @@
+#include "audio_playback.hpp"
+
+#include "audio_output.hpp"
+#include "dac.hpp"
+
+#include <algorithm>
+#include <cstdint>
+#include <exception>
+#include <memory>
+#include <string_view>
+
+#include "audio_element.h"
+#include "audio_event_iface.h"
+#include "audio_pipeline.h"
+#include "driver/i2s.h"
+#include "esp_err.h"
+#include "freertos/portmacro.h"
+#include "mp3_decoder.h"
+
+static const char* kTag = "PLAYBACK";
+static const char* kSource = "src";
+static const char* kEncoder = "enc";
+static const char* kSink = "sink";
+
+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 {
+
+static audio_element_status_t status_from_the_void(void* status) {
+ uintptr_t as_pointer_int = reinterpret_cast<uintptr_t>(status);
+ return static_cast<audio_element_status_t>(as_pointer_int);
+}
+
+auto AudioPlayback::create(std::unique_ptr<IAudioOutput> output)
+ -> 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);
+ }
+
+ 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, outut->GetAudioElement(), kSink);
+
+
+ return std::make_unique<AudioPlayback>(output, pipeline, fatfs_stream_reader, event_interface
+}
+
+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,
+ audio_element_handle_t mp3_decoder)
+ : output_(std::move(outout)),
+ 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) {
+ if (GetPlaybackState() != STOPPED) {
+ audio_pipeline_stop(pipeline_);
+ audio_pipeline_wait_for_stop(pipeline_);
+ audio_pipeline_terminate(pipeline_);
+ }
+
+ current_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_);
+ dac_->WriteVolume(volume_);
+}
+
+void AudioPlayback::Resume() {
+ if (GetPlaybackState() == PAUSED) {
+ current_state_ = PLAYING;
+ audio_pipeline_resume(pipeline_);
+ }
+}
+void AudioPlayback::Pause() {
+ if (GetPlaybackState() == PLAYING) {
+ current_state_ = PAUSED;
+ audio_pipeline_pause(pipeline_);
+ }
+}
+
+auto AudioPlayback::GetPlaybackState() -> PlaybackState {
+ return current_state_;
+}
+
+void AudioPlayback::ProcessEvents(uint16_t max_time_ms) {
+ if (current_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) {
+ ESP_LOGE(kTag, "error listening for event:%x", err);
+ continue;
+ }
+
+ 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 = status_from_the_void(event.data);
+ if (status == AEL_STATUS_STATE_FINISHED) {
+ // TODO: Could we change the uri here? hmm.
+ }
+ }
+
+ 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 = status_from_the_void(event.data);
+ if (status == AEL_STATUS_STATE_FINISHED) {
+ if (next_filename_ != "") {
+ Decoder decoder = GetDecoderForFilename(next_filename_);
+ if (decoder == decoder_type_) {
+ audio_element_set_uri(source_element_, next_filename_);
+ audio_pipeline_reset_ringbuffer(pipeline_);
+ audio_pipeline_reset_elements(pipeline_);
+ audio_pipeline_change_state(pipeline_, AEL_STATE_INIT);
+ audio_pipeline_run(pipeline_);
+ } else {
+ Play(next_filename_);
+ }
+ next_filename_ = "";
+ } else {
+ audio_pipeline_stop(pipeline_);
+ audio_pipeline_wait_for_stop(pipeline_);
+ audio_pipeline_terminate(pipeline_);
+ current_state_ = STOPPED;
+ }
+ return;
+ }
+ }
+
+ if (event.need_free_data) {
+ ESP_LOGI(kTag, "freeing event data");
+ free(event.data);
+ }
+ }
+}
+
+void AudioPlayback::set_next_file(const std::string& filename) {
+ next_filename_ = filename;
+}
+
+void AudioPlayback::set_volume(uint8_t volume) {
+ volume_ = volume;
+ // TODO: don't write immediately if we're muted to change track or similar.
+ output_->SetVolume(volume);
+}
+
+auto AudioPlayback::volume() -> uint8_t {
+ return volume_;
+}
+
+auto AudioPlayback::GetDecoderForFilename(std::string filename) -> 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) -> audio_element_handle_t {
+ switch (decoder) {
+ case MP3:
+ mp3_decoder_cfg_t config = DEFAULT_MP3_DECODER_CONFIG();
+ return mp3_decoder_init(&config);
+ case AMR:
+ amr_decoder_cfg_t config = DEFAULT_AMR_DECODER_CONFIG();
+ return amr_decoder_init(&config);
+ case OPUS:
+ opus_decoder_cfg_t config = DEFAULT_OPUS_DECODER_CONFIG();
+ return decoder_opus_init(&config);
+ case OGG:
+ ogg_decoder_cfg_t config = DEFAULT_OGG_DECODER_CONFIG();
+ return ogg_decoder_init(&config);
+ case FLAC:
+ flac_decoder_cfg_t config = DEFAULT_FLAC_DECODER_CONFIG();
+ return flac_decoder_init(&config);
+ case WAV:
+ wav_decoder_cfg_t config = DEFAULT_WAV_DECODER_CONFIG();
+ return wav_decoder_init(&config);
+ case AAC:
+ aac_decoder_cfg_t aac_dec_cfg = DEFAULT_AAC_DECODER_CONFIG();
+ return aac_decoder_init(&aac_dec_cfg);
+ default:
+ 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
diff --git a/src/drivers/i2s_audio_output.cpp b/src/drivers/i2s_audio_output.cpp
new file mode 100644
index 00000000..6b231f0e
--- /dev/null
+++ b/src/drivers/i2s_audio_output.cpp
@@ -0,0 +1,92 @@
+#include "i2s_audio_output.hpp"
+#include <algorithm>
+#include "audio_output.hpp"
+#include "gpio-expander.hpp"
+
+static const i2s_port_t kI2SPort = I2S_NUM_0;
+
+namespace drivers {
+
+auto I2SAudioOutput::create(GpioExpander *expander)
+ -> cpp::result<std::unique_ptr<I2SAudioOutput>, Error> {
+
+ // First, we need to perform initial configuration of the DAC chip.
+ auto dac_result = drivers::AudioDac::create(expander);
+ if (dac_result.has_error()) {
+ ESP_LOGE(TAG, "failed to init dac: %d", dac_result.error());
+ return cpp::fail(DAC_CONFIG);
+ }
+ std::unique_ptr<AudioDac> dac = std::move(dac_result.value());
+
+ // Soft mute immediately, in order to minimise any clicks and pops caused by
+ // the initial output element and pipeline configuration.
+ dac->WriteVolume(255);
+
+ i2s_stream_cfg_t i2s_stream_config = i2s_stream_cfg_t{
+ .type = AUDIO_STREAM_WRITER,
+ .i2s_config =
+ {
+ // static_cast bc esp-adf uses enums incorrectly
+ .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX),
+ .sample_rate = 44100,
+ .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
+ .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
+ .communication_format = I2S_COMM_FORMAT_STAND_I2S,
+ .intr_alloc_flags = ESP_INTR_FLAG_LOWMED,
+ .dma_buf_count = 8,
+ .dma_buf_len = 64,
+ .use_apll = false,
+ .tx_desc_auto_clear = false,
+ .fixed_mclk = 0,
+ .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
+ .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
+ },
+ .i2s_port = kI2SPort,
+ .use_alc = false,
+ .volume = 0, // Does nothing; use AudioDac to change this.
+ .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE,
+ .task_stack = I2S_STREAM_TASK_STACK,
+ .task_core = I2S_STREAM_TASK_CORE,
+ .task_prio = I2S_STREAM_TASK_PRIO,
+ .stack_in_ext = false,
+ .multi_out_num = 0,
+ .uninstall_drv = true,
+ .need_expand = false,
+ .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT,
+ };
+ i2s_stream_writer = i2s_stream_init(&i2s_stream_config);
+ if (i2s_stream_writer == NULL) {
+ return cpp::fail(Error::STREAM_INIT);
+ }
+
+ // NOTE: i2s_stream_init does some additional setup that hardcodes MCK as
+ // GPIO0. This happens to work fine for us, but be careful if changing.
+ i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0,
+ .bck_io_num = GPIO_NUM_26,
+ .ws_io_num = GPIO_NUM_27,
+ .data_out_num = GPIO_NUM_5,
+ .data_in_num = I2S_PIN_NO_CHANGE};
+ if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) {
+ ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
+ return cpp::fail(Error::I2S_CONFIG);
+ }
+
+ return std::make_unique<I2SAudioOutput>(dac, i2s_stream_writer);
+}
+
+I2SAudioOutput(std::unique<AudioDac> dac, audio_element_handle_t element) : IAudioOutput(element), dac_(dac) {}
+~I2SAudioOutput() {
+ // TODO: power down the DAC.
+}
+
+auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
+ dac_->WriteVolume(255);
+}
+
+auto I2SAudioOutput::Configure(audio_element_info_t info) -> void {
+ audio_element_setinfo(element_, &music_info);
+ i2s_stream_set_clk(element_, music_info.sample_rates,
+ music_info.bits, music_info.channels);
+}
+
+}
diff --git a/src/drivers/include/a2dp_audio_output.hpp b/src/drivers/include/a2dp_audio_output.hpp
new file mode 100644
index 00000000..43b55956
--- /dev/null
+++ b/src/drivers/include/a2dp_audio_output.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include "audio_output.hpp"
+#include <cstdint>
+
+namespace drivers {
+
+class A2DPAudioOutput : IAudioOutput {
+ public:
+ virtual auto SetVolume(uint8_t volume) -> void;
+};
+
+} // namespace drivers
diff --git a/src/drivers/include/audio_output.hpp b/src/drivers/include/audio_output.hpp
new file mode 100644
index 00000000..63cba465
--- /dev/null
+++ b/src/drivers/include/audio_output.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include <cstdint>
+
+namespace drivers {
+
+class IAudioOutput {
+ public:
+ IAudioOutput(audio_element_handle_t element) : element_(element) {}
+ virtual ~IAudioOutput() {
+ audio_element_deinit(element_);
+ }
+
+ auto GetAudioElement() -> audio_element_handle_t {
+ return element_;
+ }
+
+ virtual auto SetVolume(uint8_t volume) -> void = 0;
+ virtual auto Configure(audio_element_info_t info) -> void = 0;
+
+ protected:
+ audio_element_handle_t element_;
+};
+
+} // namespace drivers
diff --git a/src/drivers/include/audio_playback.hpp b/src/drivers/include/audio_playback.hpp
new file mode 100644
index 00000000..dd0f7f7a
--- /dev/null
+++ b/src/drivers/include/audio_playback.hpp
@@ -0,0 +1,99 @@
+#pragma once
+
+#include "audio_output.hpp"
+#include "dac.hpp"
+#include "storage.hpp"
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include "audio_event_iface.h"
+#include "audio_pipeline.h"
+#include "esp_err.h"
+#include "fatfs_stream.h"
+#include "i2s_stream.h"
+#include "mp3_decoder.h"
+#include "result.hpp"
+
+namespace drivers {
+
+/*
+ * Sends an I2S audio stream to the DAC. Includes basic controls for pausing
+ * and resuming the stream, as well as support for gapless playback of the next
+ * queued song, but does not implement any kind of sophisticated queing or
+ * playback control; these should be handled at a higher level.
+ */
+class AudioPlayback {
+ public:
+ enum Error { FATFS_INIT, I2S_INIT, PIPELINE_INIT };
+ static auto create(std::unique_ptr<IAudioOutput> output)
+ -> cpp::result<std::unique_ptr<AudioPlayback>, Error>;
+
+ AudioPlayback(std::unqiue_ptr<IAudioOutput> output,
+ audio_pipeline_handle_t pipeline,
+ audio_element_handle_t source_element,
+ audio_event_iface_handle_t event_interface);
+ ~AudioPlayback();
+
+ /*
+ * Replaces any currently playing file with the one given, and begins
+ * playback.
+ *
+ * Any value set in `set_next_file` is cleared by this method.
+ */
+ void Play(const std::string& filename);
+ /* Toogle between resumed and paused. */
+ void Toggle();
+ void Resume();
+ void Pause();
+
+ enum PlaybackState { PLAYING, PAUSED, STOPPED };
+ auto GetPlaybackState() -> PlaybackState;
+
+ /*
+ * Handles any pending events from the underlying audio pipeline. This must
+ * be called regularly in order to handle configuring the I2S stream for
+ * different audio types (e.g. sample rate, bit depth), and for gapless
+ * playback.
+ */
+ void ProcessEvents(uint16_t max_time_ms);
+
+ /*
+ * Sets the file that should be played immediately after the current file
+ * finishes. This is used for gapless playback
+ */
+ void set_next_file(const std::string& filename);
+
+ void set_volume(uint8_t volume);
+ auto volume() -> uint8_t;
+
+ // Not copyable or movable.
+ AudioPlayback(const AudioPlayback&) = delete;
+ AudioPlayback& operator=(const AudioPlayback&) = delete;
+
+ private:
+ PlaybackState current_state_;
+
+ enum Decoder {NONE, MP3, AMR, OPUS, OGG, FLAC, WAV, AAC};
+ auto GetDecoderForFilename(std::string filename) -> Decoder;
+ auto CreateDecoder(Decoder decoder) -> audio_element_handle_t;
+ void ReconfigurePipeline();
+
+ std::unique_ptr<IAudioOutput> output_;
+ std::mutex playback_lock_;
+
+ std::string next_filename_ = "";
+ uint8_t volume_;
+
+ audio_pipeline_handle_t pipeline_;
+ audio_element_handle_t source_element_;
+ audio_event_iface_handle_t event_interface_;
+
+ audio_element_handle_t decoder_ = nullptr;
+ Decoder decoder_type_ = NONE;
+};
+
+} // namespace drivers
diff --git a/src/drivers/include/i2s_audio_output.hpp b/src/drivers/include/i2s_audio_output.hpp
new file mode 100644
index 00000000..531bddbc
--- /dev/null
+++ b/src/drivers/include/i2s_audio_output.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "audio_common.h"
+#include "audio_element.h"
+#include "audio_output.hpp"
+#include "gpio-expander.hpp"
+#include <cstdint>
+#include <memory>
+#include "result.hpp"
+#include "dac.hpp"
+
+namespace drivers {
+
+class I2SAudioOutput : public IAudioOutput {
+ public:
+ enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT };
+ static auto create(GpioExpander* expander)
+ -> cpp::result<std::unique_ptr<I2SAudioOutput>, Error>;
+
+ I2SAudioOutput(AudioDac* dac, audio_element_handle_t element);
+ ~I2SAudioOutput();
+
+ virtual auto SetVolume(uint8_t volume) -> void;
+ virtual auto Configure(audio_element_info_t info) -> void;
+
+ private:
+ std::unique_ptr<AudioDac> dac_;
+};
+
+} // namespace drivers
diff --git a/src/drivers/include/playback.hpp b/src/drivers/include/playback.hpp
deleted file mode 100644
index 5fa7ab38..00000000
--- a/src/drivers/include/playback.hpp
+++ /dev/null
@@ -1,67 +0,0 @@
-#pragma once
-
-#include "dac.hpp"
-#include "storage.hpp"
-
-#include <cstdint>
-#include <memory>
-#include <string>
-
-#include "audio_common.h"
-#include "audio_element.h"
-#include "audio_event_iface.h"
-#include "audio_pipeline.h"
-#include "esp_err.h"
-#include "fatfs_stream.h"
-#include "i2s_stream.h"
-#include "mp3_decoder.h"
-#include "result.hpp"
-
-namespace drivers {
-
-class DacAudioPlayback {
- public:
- enum Error { PIPELINE_INIT };
- static auto create(AudioDac* dac)
- -> cpp::result<std::unique_ptr<DacAudioPlayback>, Error>;
-
- DacAudioPlayback(AudioDac* dac,
- audio_pipeline_handle_t pipeline,
- audio_element_handle_t fatfs_stream_reader,
- audio_element_handle_t i2s_stream_writer,
- audio_event_iface_handle_t event_interface,
- audio_element_handle_t mp3_decoder);
- ~DacAudioPlayback();
-
- void Play(const std::string& filename);
- void Resume();
- void Pause();
-
- void ProcessEvents();
-
- /* for gapless */
- void set_next_file(const std::string& filename);
-
- void set_volume(uint8_t volume);
- auto volume() -> uint8_t;
-
- // Not copyable or movable.
- DacAudioPlayback(const DacAudioPlayback&) = delete;
- DacAudioPlayback& operator=(const DacAudioPlayback&) = delete;
-
- private:
- AudioDac* dac_;
- std::mutex playback_lock_;
-
- std::string next_filename_;
- uint8_t volume_;
-
- audio_pipeline_handle_t pipeline_;
- audio_element_handle_t fatfs_stream_reader_;
- audio_element_handle_t i2s_stream_writer_;
- audio_event_iface_handle_t event_interface_;
-
- audio_element_handle_t mp3_decoder_;
-};
-
-} // namespace drivers
diff --git a/src/drivers/playback.cpp b/src/drivers/playback.cpp
deleted file mode 100644
index a9290613..00000000
--- a/src/drivers/playback.cpp
+++ /dev/null
@@ -1,246 +0,0 @@
-#include "playback.hpp"
-
-#include "dac.hpp"
-
-#include <cstdint>
-
-#include "audio_element.h"
-#include "audio_event_iface.h"
-#include "audio_pipeline.h"
-#include "driver/i2s.h"
-#include "esp_err.h"
-#include "freertos/portmacro.h"
-#include "mp3_decoder.h"
-
-static const char* kTag = "PLAYBACK";
-static const i2s_port_t kI2SPort = I2S_NUM_0;
-
-namespace drivers {
-
-static audio_element_status_t status_from_the_void(void* status) {
- uintptr_t as_pointer_int = reinterpret_cast<uintptr_t>(status);
- return static_cast<audio_element_status_t>(as_pointer_int);
-}
-
-auto DacAudioPlayback::create(AudioDac* dac)
- -> cpp::result<std::unique_ptr<DacAudioPlayback>, Error> {
- // Ensure we're soft-muted before initialising, in order to reduce protential
- // clicks and pops.
- dac->WriteVolume(255);
-
- audio_pipeline_handle_t pipeline;
- audio_element_handle_t fatfs_stream_reader;
- audio_element_handle_t i2s_stream_writer;
- 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::PIPELINE_INIT);
- }
-
- i2s_stream_cfg_t i2s_stream_config = i2s_stream_cfg_t{
- .type = AUDIO_STREAM_WRITER,
- .i2s_config =
- {
- // static_cast bc esp-adf uses enums incorrectly
- .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX),
- .sample_rate = 44100,
- .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
- .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
- .communication_format = I2S_COMM_FORMAT_STAND_I2S,
- .intr_alloc_flags = ESP_INTR_FLAG_LOWMED,
- .dma_buf_count = 8,
- .dma_buf_len = 64,
- .use_apll = false,
- .tx_desc_auto_clear = false,
- .fixed_mclk = 0,
- .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
- .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
- },
- .i2s_port = kI2SPort,
- .use_alc = false,
- .volume = 0, // Does nothing; use AudioDac to change this.
- .out_rb_size = I2S_STREAM_RINGBUFFER_SIZE,
- .task_stack = I2S_STREAM_TASK_STACK,
- .task_core = I2S_STREAM_TASK_CORE,
- .task_prio = I2S_STREAM_TASK_PRIO,
- .stack_in_ext = false,
- .multi_out_num = 0,
- .uninstall_drv = true,
- .need_expand = false,
- .expand_src_bits = I2S_BITS_PER_SAMPLE_16BIT,
- };
- i2s_stream_writer = i2s_stream_init(&i2s_stream_config);
- if (i2s_stream_writer == NULL) {
- return cpp::fail(Error::PIPELINE_INIT);
- }
-
- // NOTE: i2s_stream_init does some additional setup that hardcodes MCK as
- // GPIO0. This happens to work fine for us, but be careful if changing.
- i2s_pin_config_t pin_config = {.mck_io_num = GPIO_NUM_0,
- .bck_io_num = GPIO_NUM_26,
- .ws_io_num = GPIO_NUM_27,
- .data_out_num = GPIO_NUM_5,
- .data_in_num = I2S_PIN_NO_CHANGE};
- if (esp_err_t err = i2s_set_pin(kI2SPort, &pin_config) != ESP_OK) {
- ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
- return cpp::fail(Error::PIPELINE_INIT);
- }
-
- // TODO: Create encoders dynamically when we need them.
- audio_element_handle_t mp3_decoder;
- mp3_decoder_cfg_t mp3_config =
- mp3_decoder_cfg_t(DEFAULT_MP3_DECODER_CONFIG());
- mp3_decoder = mp3_decoder_init(&mp3_config);
- assert(mp3_decoder != NULL);
-
- 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(mp3_decoder, event_interface);
- audio_element_msg_set_listener(i2s_stream_writer, event_interface);
-
- // TODO: most of this is likely post-init, since it involves a decoder.
- // All the elements of our pipeline have been initialised. Now switch them
- // together.
- audio_pipeline_register(pipeline, fatfs_stream_reader, "file");
- audio_pipeline_register(pipeline, mp3_decoder, "dec");
- audio_pipeline_register(pipeline, i2s_stream_writer, "i2s");
-
- const char* link_tag[3] = {"file", "dec", "i2s"};
- audio_pipeline_link(pipeline, &link_tag[0], 3);
-
- return std::make_unique<DacAudioPlayback>(dac, pipeline, fatfs_stream_reader,
- i2s_stream_writer, event_interface,
- mp3_decoder);
-}
-
-DacAudioPlayback::DacAudioPlayback(AudioDac* dac,
- audio_pipeline_handle_t pipeline,
- audio_element_handle_t fatfs_stream_reader,
- audio_element_handle_t i2s_stream_writer,
- audio_event_iface_handle_t event_interface,
- audio_element_handle_t mp3_decoder)
- : dac_(dac),
- pipeline_(pipeline),
- fatfs_stream_reader_(fatfs_stream_reader),
- i2s_stream_writer_(i2s_stream_writer),
- event_interface_(event_interface),
- mp3_decoder_(mp3_decoder) {}
-
-DacAudioPlayback::~DacAudioPlayback() {
- dac_->WriteVolume(255);
-
- audio_pipeline_remove_listener(pipeline_);
- audio_element_msg_remove_listener(fatfs_stream_reader_, event_interface_);
- audio_element_msg_remove_listener(mp3_decoder_, event_interface_);
- audio_element_msg_remove_listener(i2s_stream_writer_, event_interface_);
-
- audio_pipeline_stop(pipeline_);
- audio_pipeline_wait_for_stop(pipeline_);
- audio_pipeline_terminate(pipeline_);
-
- audio_pipeline_unregister(pipeline_, fatfs_stream_reader_);
- audio_pipeline_unregister(pipeline_, mp3_decoder_);
- audio_pipeline_unregister(pipeline_, i2s_stream_writer_);
-
- audio_event_iface_destroy(event_interface_);
-
- audio_pipeline_deinit(pipeline_);
- audio_element_deinit(fatfs_stream_reader_);
- audio_element_deinit(i2s_stream_writer_);
- audio_element_deinit(mp3_decoder_);
-}
-
-void DacAudioPlayback::Play(const std::string& filename) {
- dac_->WriteVolume(255);
- // TODO: handle reconfiguring the pipeline if needed.
- audio_element_set_uri(fatfs_stream_reader_, filename.c_str());
- audio_pipeline_run(pipeline_);
- dac_->WriteVolume(volume_);
-}
-
-void DacAudioPlayback::Resume() {
- // TODO.
-}
-void DacAudioPlayback::Pause() {
- // TODO.
-}
-
-void DacAudioPlayback::ProcessEvents() {
- while (1) {
- audio_event_iface_msg_t event;
- esp_err_t err =
- audio_event_iface_listen(event_interface_, &event, portMAX_DELAY);
- if (err != ESP_OK) {
- ESP_LOGI(kTag, "error listening for event:%x", err);
- continue;
- }
- ESP_LOGI(kTag, "received event, cmd %i", event.cmd);
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)mp3_decoder_ &&
- event.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
- audio_element_info_t music_info;
- audio_element_getinfo(mp3_decoder_, &music_info);
- ESP_LOGI(kTag, "sample_rate=%d, bits=%d, ch=%d", music_info.sample_rates,
- music_info.bits, music_info.channels);
- audio_element_setinfo(i2s_stream_writer_, &music_info);
- i2s_stream_set_clk(i2s_stream_writer_, music_info.sample_rates,
- music_info.bits, music_info.channels);
- }
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)fatfs_stream_reader_ &&
- event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
- audio_element_status_t status = status_from_the_void(event.data);
- if (status == AEL_STATUS_STATE_FINISHED) {
- // TODO: enqueue next track?
- }
- }
-
- if (event.source_type == AUDIO_ELEMENT_TYPE_ELEMENT &&
- event.source == (void*)i2s_stream_writer_ &&
- event.cmd == AEL_MSG_CMD_REPORT_STATUS) {
- audio_element_status_t status = status_from_the_void(event.data);
- if (status == AEL_STATUS_STATE_FINISHED) {
- // TODO.
- return;
- }
- }
-
- if (event.need_free_data) {
- ESP_LOGI(kTag, "freeing event data");
- free(event.data);
- }
- }
-}
-
-/* for gapless */
-void DacAudioPlayback::set_next_file(const std::string& filename) {
- next_filename_ = filename;
-}
-
-void DacAudioPlayback::set_volume(uint8_t volume) {
- volume_ = volume;
- // TODO: don't write immediately if we're muting to change track or similar.
- dac_->WriteVolume(volume);
-}
-
-auto DacAudioPlayback::volume() -> uint8_t {
- return volume_;
-}
-
-} // namespace drivers