basic i2s output element

custom
jacqueline 2 years ago
parent e0b2562cc4
commit f35bb64c2b
  1. 2
      src/audio/CMakeLists.txt
  2. 4
      src/audio/audio_decoder.cpp
  3. 15
      src/audio/chunk.cpp
  4. 7
      src/audio/fatfs_audio_input.cpp
  5. 147
      src/audio/i2s_audio_output.cpp
  6. 13
      src/audio/include/audio_decoder.hpp
  7. 12
      src/audio/include/audio_element.hpp
  8. 14
      src/audio/include/fatfs_audio_input.hpp
  9. 38
      src/audio/include/i2s_audio_output.hpp
  10. 1
      src/codecs/include/mad.hpp
  11. 3
      src/codecs/test/test.mp3.hpp
  12. 26
      src/codecs/test/test_mad.cpp
  13. 2
      src/drivers/CMakeLists.txt
  14. 78
      src/drivers/dac.cpp
  15. 21
      src/drivers/include/dac.hpp
  16. 2
      src/main/CMakeLists.txt
  17. 5
      test/main/main.cpp

@ -1,6 +1,6 @@
idf_component_register(
SRCS "audio_decoder.cpp" "audio_task.cpp" "chunk.cpp" "fatfs_audio_input.cpp"
"stream_info.cpp" "stream_message.cpp"
"stream_info.cpp" "stream_message.cpp" "i2s_audio_output.cpp"
INCLUDE_DIRS "include"
REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span")

@ -40,7 +40,7 @@ auto AudioDecoder::SetOutputBuffer(MessageBufferHandle_t* buffer) -> void {
output_buffer_ = buffer;
}
auto AudioDecoder::ProcessStreamInfo(StreamInfo& info)
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
stream_info_ = info;
@ -62,7 +62,7 @@ auto AudioDecoder::ProcessStreamInfo(StreamInfo& info)
return {};
}
auto AudioDecoder::ProcessChunk(cpp::span<std::byte>& chunk)
auto AudioDecoder::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<size_t, AudioProcessingError> {
if (current_codec_ == nullptr) {
// Should never happen, but fail explicitly anyway.

@ -12,8 +12,15 @@
namespace audio {
// TODO: tune.
const std::size_t kMaxChunkSize = 512;
/*
* The maximum size of a single chunk of stream data. This should be comfortably
* larger than the largest size of a frame of audio we should expect to handle.
*
* 128 kbps MPEG-1 @ 44.1 kHz is approx. 418 bytes according to the internet.
*
* TODO(jacqueline): tune as more codecs are added.
*/
const std::size_t kMaxChunkSize = 2048;
// TODO: tune
static const std::size_t kWorkingBufferSize = kMaxChunkSize * 1.5;
@ -60,11 +67,11 @@ ChunkReader::ChunkReader(MessageBufferHandle_t* stream)
: stream_(stream),
raw_working_buffer_(static_cast<std::byte*>(
heap_caps_malloc(kWorkingBufferSize, MALLOC_CAP_SPIRAM))),
working_buffer_(raw_working_buffer_, kWorkingBufferSize){};
working_buffer_(raw_working_buffer_, kWorkingBufferSize) {}
ChunkReader::~ChunkReader() {
free(raw_working_buffer_);
};
}
auto ChunkReader::Reset() -> void {
leftover_bytes_ = 0;

@ -4,7 +4,6 @@
#include <memory>
#include <string>
#include "audio_element.hpp"
#include "esp_heap_caps.h"
#include "freertos/portmacro.h"
@ -50,7 +49,7 @@ FatfsAudioInput::~FatfsAudioInput() {
free(output_buffer_);
}
auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info)
auto FatfsAudioInput::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
if (is_file_open_) {
f_close(&current_file_);
@ -83,12 +82,12 @@ auto FatfsAudioInput::ProcessStreamInfo(StreamInfo& info)
return {};
}
auto FatfsAudioInput::ProcessChunk(cpp::span<std::byte>& chunk)
auto FatfsAudioInput::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<size_t, AudioProcessingError> {
return cpp::fail(UNSUPPORTED_STREAM);
}
auto FatfsAudioInput::GetRingBufferDistance() -> size_t {
auto FatfsAudioInput::GetRingBufferDistance() const -> size_t {
if (file_buffer_read_pos_ == file_buffer_write_pos_) {
return 0;
}

@ -2,18 +2,20 @@
#include <algorithm>
#include "audio_element.h"
#include "driver/i2s.h"
#include "esp_err.h"
#include "freertos/portmacro.h"
#include "i2s_stream.h"
static const i2s_port_t kI2SPort = I2S_NUM_0;
#include "audio_element.hpp"
#include "dac.hpp"
#include "gpio_expander.hpp"
#include "result.hpp"
static const TickType_t kIdleTimeBeforeMute = pdMS_TO_TICKS(1000);
static const char* kTag = "I2SOUT";
namespace drivers {
namespace audio {
auto I2SAudioOutput::create(GpioExpander* expander)
auto I2SAudioOutput::create(drivers::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);
@ -21,72 +23,82 @@ auto I2SAudioOutput::create(GpioExpander* expander)
ESP_LOGE(kTag, "failed to init dac: %d", dac_result.error());
return cpp::fail(DAC_CONFIG);
}
std::unique_ptr<AudioDac> dac = std::move(dac_result.value());
std::unique_ptr<drivers::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,
};
audio_element_handle_t i2s_stream_writer =
i2s_stream_init(&i2s_stream_config);
if (i2s_stream_writer == NULL) {
return cpp::fail(Error::STREAM_INIT);
return std::make_unique<I2SAudioOutput>(expander, std::move(dac));
}
I2SAudioOutput::I2SAudioOutput(drivers::GpioExpander* expander,
std::unique_ptr<drivers::AudioDac> dac)
: expander_(expander),
dac_(std::move(dac)),
volume_(255),
is_soft_muted_(false) {}
I2SAudioOutput::~I2SAudioOutput() {
// TODO: power down the DAC.
}
// 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);
auto I2SAudioOutput::ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> {
// TODO(jacqueline): probs do something with the channel hey
if (!info.BitsPerSample() && !info.SampleRate()) {
return cpp::fail(UNSUPPORTED_STREAM);
}
return std::make_unique<I2SAudioOutput>(dac, i2s_stream_writer);
drivers::AudioDac::BitsPerSample bps;
switch (*info.BitsPerSample()) {
case 16:
bps = drivers::AudioDac::BPS_16;
break;
case 24:
bps = drivers::AudioDac::BPS_24;
break;
case 32:
bps = drivers::AudioDac::BPS_32;
break;
default:
return cpp::fail(UNSUPPORTED_STREAM);
}
I2SAudioOutput::I2SAudioOutput(std::unique_ptr<AudioDac>& dac,
audio_element_handle_t element)
: IAudioOutput(element), dac_(std::move(dac)) {
volume_ = 255;
drivers::AudioDac::SampleRate sample_rate;
switch (*info.SampleRate()) {
case 44100:
sample_rate = drivers::AudioDac::SAMPLE_RATE_44_1;
break;
case 48000:
sample_rate = drivers::AudioDac::SAMPLE_RATE_48;
break;
default:
return cpp::fail(UNSUPPORTED_STREAM);
}
I2SAudioOutput::~I2SAudioOutput() {
// TODO: power down the DAC.
dac_->Reconfigure(bps, sample_rate);
return {};
}
auto I2SAudioOutput::ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> {
SetSoftMute(false);
// TODO(jacqueline): write smaller parts with a small delay so that we can
// be responsive to pause and seek commands.
return dac_->WriteData(chunk, portMAX_DELAY);
}
auto I2SAudioOutput::IdleTimeout() const -> TickType_t {
return kIdleTimeBeforeMute;
}
auto I2SAudioOutput::ProcessIdle() -> cpp::result<void, AudioProcessingError> {
// TODO(jacqueline): Consider powering down the dac completely maybe?
SetSoftMute(true);
return {};
}
auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
@ -97,18 +109,15 @@ auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
}
auto I2SAudioOutput::SetSoftMute(bool enabled) -> void {
if (enabled) {
is_soft_muted_ = true;
if (enabled == is_soft_muted_) {
return;
}
is_soft_muted_ = enabled;
if (is_soft_muted_) {
dac_->WriteVolume(255);
} else {
is_soft_muted_ = false;
dac_->WriteVolume(volume_);
}
}
auto I2SAudioOutput::Configure(audio_element_info_t& info) -> void {
audio_element_setinfo(element_, &info);
i2s_stream_set_clk(element_, info.sample_rates, info.bits, info.channels);
}
} // namespace drivers
} // namespace audio

@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
#include "ff.h"
#include "span.hpp"
@ -23,11 +24,13 @@ class AudioDecoder : public IAudioElement {
auto SetInputBuffer(MessageBufferHandle_t*) -> void;
auto SetOutputBuffer(MessageBufferHandle_t*) -> void;
auto ProcessStreamInfo(StreamInfo& info)
-> cpp::result<void, AudioProcessingError>;
auto ProcessChunk(cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError>;
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
auto StackSizeBytes() const -> std::size_t override { return 8196; };
auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
AudioDecoder(const AudioDecoder&) = delete;
AudioDecoder& operator=(const AudioDecoder&) = delete;

@ -48,26 +48,26 @@ class IAudioElement {
* be tuned according to the observed stack size of each element, as different
* elements have fairly different stack requirements.
*/
virtual auto StackSizeBytes() -> std::size_t { return 2048; };
virtual auto StackSizeBytes() const -> std::size_t { return 2048; };
/*
* How long to wait for new data on the input stream before triggering a call
* to ProcessIdle(). If this is portMAX_DELAY (the default), then ProcessIdle
* will never be called.
*/
virtual auto IdleTimeout() -> TickType_t { return portMAX_DELAY; }
virtual auto IdleTimeout() const -> TickType_t { return portMAX_DELAY; }
/* Returns this element's input buffer. */
auto InputBuffer() -> MessageBufferHandle_t* { return input_buffer_; }
auto InputBuffer() const -> MessageBufferHandle_t* { return input_buffer_; }
/* Returns this element's output buffer. */
auto OutputBuffer() -> MessageBufferHandle_t* { return output_buffer_; }
auto OutputBuffer() const -> MessageBufferHandle_t* { return output_buffer_; }
/*
* Called when a StreamInfo message is received. Used to configure this
* element in preperation for incoming chunks.
*/
virtual auto ProcessStreamInfo(StreamInfo& info)
virtual auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> = 0;
/*
@ -76,7 +76,7 @@ class IAudioElement {
* bytes in this chunk that were actually used; leftover bytes will be
* prepended to the next call.
*/
virtual auto ProcessChunk(cpp::span<std::byte>& chunk)
virtual auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> = 0;
/*

@ -17,19 +17,19 @@ namespace audio {
class FatfsAudioInput : public IAudioElement {
public:
FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
explicit FatfsAudioInput(std::shared_ptr<drivers::SdStorage> storage);
~FatfsAudioInput();
auto ProcessStreamInfo(StreamInfo& info)
-> cpp::result<void, AudioProcessingError>;
auto ProcessChunk(cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> = 0;
auto ProcessIdle() -> cpp::result<void, AudioProcessingError>;
auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
auto SendChunk(cpp::span<std::byte> dest) -> size_t;
private:
auto GetRingBufferDistance() -> size_t;
auto GetRingBufferDistance() const -> size_t;
std::shared_ptr<drivers::SdStorage> storage_;

@ -3,30 +3,46 @@
#include <cstdint>
#include <memory>
#include "audio_element.hpp"
#include "result.hpp"
#include "dac.hpp"
#include "gpio_expander.hpp"
#include "sys/_stdint.h"
namespace drivers {
namespace audio {
class I2SAudioOutput : public IAudioOutput {
class I2SAudioOutput : public IAudioElement {
public:
enum Error { DAC_CONFIG, I2S_CONFIG, STREAM_INIT };
static auto create(GpioExpander* expander)
static auto create(drivers::GpioExpander* expander)
-> cpp::result<std::unique_ptr<I2SAudioOutput>, Error>;
I2SAudioOutput(std::unique_ptr<AudioDac>& dac,
audio_element_handle_t element);
I2SAudioOutput(drivers::GpioExpander* expander,
std::unique_ptr<drivers::AudioDac> dac);
~I2SAudioOutput();
virtual auto SetVolume(uint8_t volume) -> void;
virtual auto Configure(audio_element_info_t& info) -> void;
virtual auto SetSoftMute(bool enabled) -> void;
auto SetInputBuffer(MessageBufferHandle_t* in) -> void { input_buffer_ = in; }
auto IdleTimeout() const -> TickType_t override;
auto ProcessStreamInfo(const StreamInfo& info)
-> cpp::result<void, AudioProcessingError> override;
auto ProcessChunk(const cpp::span<std::byte>& chunk)
-> cpp::result<std::size_t, AudioProcessingError> override;
auto ProcessIdle() -> cpp::result<void, AudioProcessingError> override;
I2SAudioOutput(const I2SAudioOutput&) = delete;
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;
private:
std::unique_ptr<AudioDac> dac_;
bool is_soft_muted_ = false;
auto SetVolume(uint8_t volume) -> void;
auto SetSoftMute(bool enabled) -> void;
drivers::GpioExpander* expander_;
std::unique_ptr<drivers::AudioDac> dac_;
uint8_t volume_;
bool is_soft_muted_;
};
} // namespace drivers
} // namespace audio

@ -3,6 +3,7 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include "mad.h"
#include "span.hpp"

@ -96,6 +96,5 @@ std::uint8_t test_mp3[] = {
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa
};
0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
std::size_t test_mp3_len = 1147;

@ -1,15 +1,16 @@
#include "mad.hpp"
#include <cstdint>
#include <algorithm>
#include <cstdint>
#include "span.hpp"
#include "catch2/catch.hpp"
#include "span.hpp"
#include "test.mp3.hpp"
void load_mp3(cpp::span<std::byte> dest) {
cpp::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3), test_mp3_len);
cpp::span<std::byte> src(reinterpret_cast<std::byte*>(test_mp3),
test_mp3_len);
std::copy(src.begin(), src.begin() + dest.size(), dest.begin());
}
@ -24,7 +25,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
}
SECTION("processes streams correctly") {
std::array<std::byte, 256> input;
std::array<std::byte, 2048> input;
input.fill(std::byte{0});
decoder.ResetForNewStream();
@ -38,7 +39,7 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
}
SECTION("invalid stream fails") {
input.fill(std::byte{0xFF});
input.fill(std::byte{0x69});
auto result = decoder.ProcessNextFrame();
@ -65,23 +66,26 @@ TEST_CASE("libmad mp3 decoder", "[unit]") {
// Just check that some kind of data was written. We don't care
// about what.
REQUIRE(std::to_integer<uint8_t>(output[0]) != 0);
REQUIRE(std::to_integer<uint8_t>(output[1]) != 0);
REQUIRE(std::to_integer<uint8_t>(output[2]) != 0);
REQUIRE(std::to_integer<uint8_t>(output[3]) != 0);
bool wrote_something = false;
for (int i = 0; i < output.size(); i++) {
if (std::to_integer<uint8_t>(output[0]) != 0) {
wrote_something = true;
break;
}
}
REQUIRE(wrote_something == true);
}
SECTION("output format correct") {
auto format = decoder.GetOutputFormat();
// Matches the test data.
REQUIRE(format.num_channels == 2);
REQUIRE(format.num_channels == 1);
REQUIRE(format.sample_rate_hz == 44100);
// Matches libmad output
REQUIRE(format.bits_per_sample == 24);
}
}
}
}

@ -2,5 +2,5 @@ idf_component_register(
SRCS "dac.cpp" "gpio_expander.cpp" "battery.cpp" "storage.cpp" "i2c.cpp"
"spi.cpp" "display.cpp" "display_init.cpp"
INCLUDE_DIRS "include"
REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl")
REQUIRES "esp_adc_cal" "fatfs" "result" "lvgl" "span")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -4,11 +4,13 @@
#include "assert.h"
#include "driver/i2c.h"
#include "driver/i2s.h"
#include "esp_err.h"
#include "esp_log.h"
#include "hal/i2c_types.h"
#include "gpio_expander.hpp"
#include "hal/i2s_types.h"
#include "i2c.hpp"
namespace drivers {
@ -16,11 +18,55 @@ namespace drivers {
static const char* kTag = "AUDIODAC";
static const uint8_t kPcm5122Address = 0x4C;
static const uint8_t kPcm5122Timeout = 100 / portTICK_RATE_MS;
static const i2s_port_t kI2SPort = I2S_NUM_0;
static const AudioDac::SampleRate kDefaultSampleRate =
AudioDac::SAMPLE_RATE_44_1;
static const AudioDac::BitsPerSample kDefaultBps = AudioDac::BPS_16;
auto AudioDac::create(GpioExpander* expander)
-> cpp::result<std::unique_ptr<AudioDac>, Error> {
// First, instantiate the instance so it can do all of its power on
// configuration.
std::unique_ptr<AudioDac> dac = std::make_unique<AudioDac>(expander);
// Whilst we wait for the initial boot, we can work on installing the I2S
// driver.
i2s_config_t 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,
// TODO(jacqueline): tune dma buffer size. this seems very smol.
.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,
};
if (esp_err_t err =
i2s_driver_install(kI2SPort, &i2s_config, 0, NULL) != ESP_OK) {
ESP_LOGE(kTag, "failed to configure i2s pins %x", err);
return cpp::fail(Error::FAILED_TO_INSTALL_I2S);
}
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::FAILED_TO_INSTALL_I2S);
}
// Now let's double check that the DAC itself came up whilst we we working.
bool is_booted = dac->WaitForPowerState(
[](bool booted, PowerState state) { return booted; });
if (!is_booted) {
@ -28,8 +74,9 @@ auto AudioDac::create(GpioExpander* expander)
return cpp::fail(Error::FAILED_TO_BOOT);
}
// Write the initial configuration.
dac->WriteRegister(Register::DE_EMPHASIS, 1 << 4);
dac->WriteVolume(100);
dac->WriteVolume(255);
bool is_configured =
dac->WaitForPowerState([](bool booted, PowerState state) {
@ -43,14 +90,16 @@ auto AudioDac::create(GpioExpander* expander)
return dac;
}
AudioDac::AudioDac(GpioExpander* gpio) {
this->gpio_ = gpio;
};
AudioDac::AudioDac(GpioExpander* gpio) : gpio_(gpio) {
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, true);
gpio_->Write();
}
AudioDac::~AudioDac() {
// TODO: reset stuff like de-emphasis? Reboot the whole dac? Need to think
// about this.
};
i2s_driver_uninstall(kI2SPort);
gpio_->set_pin(GpioExpander::AUDIO_POWER_ENABLE, false);
gpio_->Write();
}
void AudioDac::WriteVolume(uint8_t volume) {
WriteRegister(Register::DIGITAL_VOLUME_L, volume);
@ -93,6 +142,21 @@ bool AudioDac::WaitForPowerState(
return has_matched;
}
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> bool {
// TODO(jacqueline): investigate how reliable the auto-clocking of the dac
// is. We might need to explicit reconfigure the dac here as well if it's not
// good enough.
i2s_set_clk(kI2SPort, rate, bps, I2S_CHANNEL_STEREO);
return true;
}
auto AudioDac::WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
-> std::size_t {
std::size_t res = 0;
i2s_write(kI2SPort, data.data(), data.size(), &res, max_wait);
return res;
}
void AudioDac::WriteRegister(Register reg, uint8_t val) {
I2CTransaction transaction;
transaction.start()

@ -5,7 +5,10 @@
#include <functional>
#include "esp_err.h"
#include "freertos/portmacro.h"
#include "hal/i2s_types.h"
#include "result.hpp"
#include "span.hpp"
#include "gpio_expander.hpp"
@ -19,7 +22,9 @@ class AudioDac {
enum Error {
FAILED_TO_BOOT,
FAILED_TO_CONFIGURE,
FAILED_TO_INSTALL_I2S,
};
static auto create(GpioExpander* expander)
-> cpp::result<std::unique_ptr<AudioDac>, Error>;
@ -47,6 +52,22 @@ class AudioDac {
/* Returns the current boot-up status and internal state of the DAC */
std::pair<bool, PowerState> ReadPowerState();
enum BitsPerSample {
BPS_16 = I2S_BITS_PER_SAMPLE_16BIT,
BPS_24 = I2S_BITS_PER_SAMPLE_24BIT,
BPS_32 = I2S_BITS_PER_SAMPLE_32BIT
};
enum SampleRate {
SAMPLE_RATE_44_1 = 44100,
SAMPLE_RATE_48 = 48000,
};
// TODO(jacqueline): worth supporting channels here as well?
auto Reconfigure(BitsPerSample bps, SampleRate rate) -> bool;
auto WriteData(const cpp::span<std::byte>& data, TickType_t max_wait)
-> std::size_t;
// Not copyable or movable.
AudioDac(const AudioDac&) = delete;
AudioDac& operator=(const AudioDac&) = delete;

@ -1,5 +1,5 @@
idf_component_register(
SRCS "main.cpp" "app_console.cpp"
INCLUDE_DIRS "."
REQUIRES "drivers" "dev_console")
REQUIRES "audio" "drivers" "dev_console" "drivers")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})

@ -24,8 +24,9 @@ class TestConsole : public Console {
protected:
virtual auto RegisterExtraComponents() -> void { RegisterCatch2(); }
virtual auto GetStackSizeKiB() -> uint16_t {
// Catch2 requires a particularly large stack.
return 24;
// Catch2 requires a particularly large stack to begin with, and some of the
// tests (*cough*libmad*cough*) also use a lot of stack.
return 64;
}
};

Loading…
Cancel
Save