WIP audio play and pause

custom
jacqueline 2 years ago
parent 37041b810f
commit 530fd15e66
  1. 2
      src/drivers/CMakeLists.txt
  2. 295
      src/drivers/audio_playback.cpp
  3. 92
      src/drivers/i2s_audio_output.cpp
  4. 15
      src/drivers/include/a2dp_audio_output.hpp
  5. 27
      src/drivers/include/audio_output.hpp
  6. 99
      src/drivers/include/audio_playback.hpp
  7. 30
      src/drivers/include/i2s_audio_output.hpp
  8. 67
      src/drivers/include/playback.hpp
  9. 246
      src/drivers/playback.cpp
  10. 31
      src/main/main.cpp

@ -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})

@ -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

@ -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);
}
}

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -8,6 +8,7 @@
#include "freertos/portmacro.h"
#include "gpio-expander.hpp"
#include "i2c.hpp"
#include "i2s_audio_output.hpp"
#include "misc/lv_color.h"
#include "misc/lv_timer.h"
#include "playback.hpp"
@ -106,34 +107,34 @@ extern "C" void app_main(void) {
}
std::unique_ptr<drivers::SdStorage> storage = std::move(storage_res.value());
ESP_LOGI(TAG, "Init DAC");
auto dac_res = drivers::AudioDac::create(expander);
if (storage_res.has_error()) {
ESP_LOGE(TAG, "Failed: %d", dac_res.error());
LvglArgs* lvglArgs = (LvglArgs*)calloc(1, sizeof(LvglArgs));
lvglArgs->gpio_expander = expander;
xTaskCreateStaticPinnedToCore(&lvgl_main, "LVGL", kLvglStackSize,
(void*)lvglArgs, 1, sLvglStack,
&sLvglTaskBuffer, 1);
ESP_LOGI(TAG, "Init Audio Output (I2S)");
auto sink_res = drivers::I2SAudioOutput::create(expander);
if (sink_res.has_error()) {
ESP_LOGE(TAG, "Failed: %d", sink_res.error());
return;
}
std::unique_ptr<drivers::AudioDac> dac = std::move(dac_res.value());
std::unique_ptr<drivers::AudioOutput> sink = std::move(sink_res.value());
ESP_LOGI(TAG, "Init Audio Pipeline");
auto playback_res = drivers::DacAudioPlayback::create(dac.get());
auto playback_res = drivers::AudioPlayback::create(std::move(sink));
if (playback_res.has_error()) {
ESP_LOGE(TAG, "Failed: %d", playback_res.error());
return;
}
std::unique_ptr<drivers::DacAudioPlayback> playback =
std::unique_ptr<drivers::AudioPlayback> playback =
std::move(playback_res.value());
ESP_LOGI(TAG, "Everything looks good! Waiting a mo for debugger.");
vTaskDelay(pdMS_TO_TICKS(1500));
LvglArgs* lvglArgs = (LvglArgs*)calloc(1, sizeof(LvglArgs));
lvglArgs->gpio_expander = expander;
xTaskCreateStaticPinnedToCore(&lvgl_main, "LVGL", kLvglStackSize,
(void*)lvglArgs, 1, sLvglStack,
&sLvglTaskBuffer, 1);
while (1) {
// TODO: Find owners for everything so we can quit this task safely.
vTaskDelay(pdMS_TO_TICKS(1000));
playback->ProcessEvents(5);
vTaskDelay(pdMS_TO_TICKS(100));
}
}

Loading…
Cancel
Save