new pipeline working(?), but the dac eludes me

custom
jacqueline 2 years ago
parent 7c6fd654f5
commit 3836768bb8
  1. 10
      src/audio/audio_decoder.cpp
  2. 5
      src/audio/audio_playback.cpp
  3. 20
      src/audio/audio_task.cpp
  4. 3
      src/audio/fatfs_audio_input.cpp
  5. 6
      src/audio/i2s_audio_output.cpp
  6. 1
      src/audio/include/audio_sink.hpp
  7. 1
      src/audio/include/i2s_audio_output.hpp
  8. 13
      src/audio/include/stream_info.hpp
  9. 5
      src/audio/pipeline.cpp
  10. 44
      src/drivers/dac.cpp
  11. 5
      src/drivers/include/dac.hpp
  12. 2
      src/main/main.cpp
  13. 13
      src/memory/include/himem.hpp

@ -6,6 +6,7 @@
#include <cstddef>
#include <cstdint>
#include <memory>
#include <variant>
#include "cbor/tinycbor/src/cborinternal_p.h"
#include "freertos/FreeRTOS.h"
@ -37,6 +38,7 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
if (!std::holds_alternative<StreamInfo::Encoded>(info.format)) {
return false;
}
ESP_LOGI(kTag, "got new stream");
const auto& encoded = std::get<StreamInfo::Encoded>(info.format);
// Reuse the existing codec if we can. This will help with gapless playback,
@ -45,12 +47,14 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
if (current_codec_ != nullptr &&
current_codec_->CanHandleType(encoded.type)) {
current_codec_->ResetForNewStream();
ESP_LOGI(kTag, "reusing existing decoder");
return true;
}
// TODO: use audio type from stream
auto result = codecs::CreateCodecForType(encoded.type);
if (result.has_value()) {
ESP_LOGI(kTag, "creating new decoder");
current_codec_ = std::move(result.value());
} else {
ESP_LOGE(kTag, "no codec for this file");
@ -73,6 +77,9 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
}
const StreamInfo& info = input->info();
if (std::holds_alternative<std::monostate>(info.format)) {
return;
}
if (!current_input_format_ || *current_input_format_ != info.format) {
// The input stream has changed! Immediately throw everything away and
// start from scratch.
@ -100,6 +107,9 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
}
auto write_res = current_codec_->WriteOutputSamples(output->data());
if (write_res.first > 0) {
ESP_LOGI(kTag, "wrote %u bytes of samples", write_res.first);
}
output->add(write_res.first);
has_samples_to_send_ = !write_res.second;

@ -32,7 +32,8 @@ auto AudioPlayback::create(drivers::GpioExpander* expander)
}
AudioPlayback::AudioPlayback(std::unique_ptr<I2SAudioOutput> output)
: file_source_(), i2s_output_(std::move(output)) {
: file_source_(std::make_unique<FatfsAudioInput>()),
i2s_output_(std::move(output)) {
AudioDecoder* codec = new AudioDecoder();
elements_.emplace_back(codec);
@ -51,7 +52,7 @@ auto AudioPlayback::Play(const std::string& filename) -> void {
}
auto AudioPlayback::LogStatus() -> void {
// TODO.
i2s_output_->Log();
}
} // namespace audio

@ -7,6 +7,7 @@
#include <cstdint>
#include <deque>
#include <memory>
#include <variant>
#include "audio_sink.hpp"
#include "cbor.h"
@ -114,16 +115,29 @@ void AudioTaskMain(void* args) {
OutputStream out_stream(&raw_out_stream);
elements.at(i)->OutputElement()->Process(in_streams, &out_stream);
std::for_each(in_regions.begin(), in_regions.end(),
[](auto&& r) { r.Unmap(); });
out_region.Unmap();
}
RawStream raw_sink_stream = elements.back()->OutStream(&out_region);
RawStream raw_sink_stream = elements.front()->OutStream(&out_region);
InputStream sink_stream(&raw_sink_stream);
if (!output_format || output_format != sink_stream.info().format) {
if (sink_stream.data().size_bytes() == 0) {
out_region.Unmap();
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if ((!output_format || output_format != sink_stream.info().format) &&
!std::holds_alternative<std::monostate>(
sink_stream.info().format)) {
// The format of the stream within the sink stream has changed. We
// need to reconfigure the sink, but shouldn't do so until we've fully
// drained the current buffer.
if (xStreamBufferIsEmpty(sink->buffer())) {
ESP_LOGI(kTag, "reconfiguring dac");
output_format = sink_stream.info().format;
sink->Configure(*output_format);
}
@ -140,6 +154,8 @@ void AudioTaskMain(void* args) {
sink_stream.data().size_bytes(), pdMS_TO_TICKS(10));
sink_stream.consume(sent);
}
out_region.Unmap();
}
}
}

@ -65,6 +65,9 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
return;
}
if (size > 0) {
ESP_LOGI(kTag, "read %u bytes", size);
}
output->add(size);
if (size < max_size || f_eof(&current_file_)) {

@ -44,12 +44,14 @@ I2SAudioOutput::~I2SAudioOutput() {}
auto I2SAudioOutput::Configure(const StreamInfo::Format& format) -> bool {
if (!std::holds_alternative<StreamInfo::Pcm>(format)) {
ESP_LOGI(kTag, "ignoring non-pcm stream (%d)", format.index());
return false;
}
StreamInfo::Pcm pcm = std::get<StreamInfo::Pcm>(format);
if (current_config_ && pcm == *current_config_) {
ESP_LOGI(kTag, "ignoring unchanged format");
return true;
}
@ -97,6 +99,10 @@ auto I2SAudioOutput::Send(const cpp::span<std::byte>& data) -> void {
dac_->WriteData(data);
}
auto I2SAudioOutput::Log() -> void {
dac_->LogStatus();
}
auto I2SAudioOutput::SetVolume(uint8_t volume) -> void {
dac_->WriteVolume(volume);
}

@ -15,6 +15,7 @@ class IAudioSink {
virtual auto Configure(const StreamInfo::Format& format) -> bool = 0;
virtual auto Send(const cpp::span<std::byte>& data) -> void = 0;
virtual auto Log() -> void {}
auto buffer() const -> StreamBufferHandle_t { return buffer_; }
};

@ -27,6 +27,7 @@ class I2SAudioOutput : public IAudioSink {
auto Configure(const StreamInfo::Format& format) -> bool override;
auto Send(const cpp::span<std::byte>& data) -> void override;
auto Log() -> void override;
I2SAudioOutput(const I2SAudioOutput&) = delete;
I2SAudioOutput& operator=(const I2SAudioOutput&) = delete;

@ -17,12 +17,12 @@ namespace audio {
struct StreamInfo {
// The number of bytes that are available for consumption within this
// stream's buffer.
std::size_t bytes_in_stream;
std::size_t bytes_in_stream{0};
// The total length of this stream, in case its source is finite (e.g. a
// file on disk). May be absent for endless streams (internet streams,
// generated audio, etc.)
std::optional<std::size_t> length_bytes;
std::optional<std::size_t> length_bytes{};
struct Encoded {
// The codec that this stream is associated with.
@ -42,8 +42,8 @@ struct StreamInfo {
bool operator==(const Pcm&) const = default;
};
typedef std::variant<Encoded, Pcm> Format;
Format format;
typedef std::variant<std::monostate, Encoded, Pcm> Format;
Format format{};
bool operator==(const StreamInfo&) const = default;
};
@ -91,8 +91,11 @@ class OutputStream {
void add(std::size_t bytes) const { raw_->info->bytes_in_stream += bytes; }
bool prepare(const StreamInfo::Format& new_format) {
if (new_format == raw_->info->format) {
if (std::holds_alternative<std::monostate>(raw_->info->format)) {
raw_->info->format = new_format;
raw_->info->bytes_in_stream = 0;
}
if (new_format == raw_->info->format) {
return true;
}
if (raw_->is_incomplete) {

@ -4,7 +4,10 @@
namespace audio {
Pipeline::Pipeline(IAudioElement* output) : root_(output), subtrees_() {}
Pipeline::Pipeline(IAudioElement* output) : root_(output), subtrees_() {
assert(output != nullptr);
}
Pipeline::~Pipeline() {}
auto Pipeline::AddInput(IAudioElement* input) -> Pipeline* {

@ -13,6 +13,7 @@
#include "esp_log.h"
#include "freertos/portmacro.h"
#include "freertos/projdefs.h"
#include "hal/gpio_types.h"
#include "hal/i2c_types.h"
#include "gpio_expander.hpp"
@ -50,9 +51,8 @@ auto AudioDac::create(GpioExpander* expander)
i2s_std_config_t i2s_config = {
.clk_cfg = dac->clock_config_,
.slot_cfg = dac->slot_config_,
.gpio_cfg =
{// TODO: investigate running in three wire mode for less noise
.mclk = GPIO_NUM_0,
.gpio_cfg = {.mclk = GPIO_NUM_0,
//.mclk = I2S_GPIO_UNUSED,
.bclk = GPIO_NUM_26,
.ws = GPIO_NUM_27,
.dout = GPIO_NUM_5,
@ -65,6 +65,9 @@ auto AudioDac::create(GpioExpander* expander)
}},
};
// gpio_set_direction(GPIO_NUM_0, GPIO_MODE_OUTPUT);
// gpio_set_level(GPIO_NUM_0, 0);
if (esp_err_t err =
i2s_channel_init_std_mode(i2s_handle, &i2s_config) != ESP_OK) {
ESP_LOGE(kTag, "failed to initialise i2s channel %x", err);
@ -81,20 +84,29 @@ auto AudioDac::create(GpioExpander* expander)
// The DAC should be booted but in power down mode, but it might not be if we
// didn't shut down cleanly. Reset it to ensure it is in a consistent state.
dac->WriteRegister(Register::POWER_MODE, 0b10001);
dac->WriteRegister(Register::POWER_MODE, 1 << 4);
dac->WriteRegister(Register::RESET, 0b10001);
// Use BCK for the internal PLL.
// dac->WriteRegister(Register::PLL_CLOCK_SOURCE, 1 << 4);
// dac->WriteRegister(Register::PLL_ENABLE, 0);
dac->WriteRegister(Register::INTERPOLATION, 1 << 4);
dac->Reconfigure(BPS_16, SAMPLE_RATE_44_1);
dac->WriteRegister(Register::POWER_MODE, 0);
// Now configure the DAC for standard auto-clock SCK mode.
dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5);
// dac->WriteRegister(Register::DAC_CLOCK_SOURCE, 0b11 << 5);
// Enable auto clocking, and do your best to carry on despite errors.
// dac->WriteRegister(Register::CLOCK_ERRORS, 0b1111101);
i2s_channel_enable(dac->i2s_handle_);
// i2s_channel_enable(dac->i2s_handle_);
dac->WaitForPowerState(
[](bool booted, PowerState state) { return state == STANDBY; });
dac->WaitForPowerState([](bool booted, PowerState state) {
return state == RUN || state == STANDBY;
});
return dac;
}
@ -102,6 +114,7 @@ auto AudioDac::create(GpioExpander* expander)
AudioDac::AudioDac(GpioExpander* gpio, i2s_chan_handle_t i2s_handle)
: gpio_(gpio),
i2s_handle_(i2s_handle),
i2s_active_(false),
clock_config_(I2S_STD_CLK_DEFAULT_CONFIG(44100)),
slot_config_(I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
I2S_SLOT_MODE_STEREO)) {
@ -163,9 +176,10 @@ bool AudioDac::WaitForPowerState(
}
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
// Disable the current output, if it isn't already stopped.
WriteRegister(Register::POWER_MODE, 1 << 4);
WriteRegister(Register::RESYNC_REQUEST, 1);
if (i2s_active_) {
i2s_channel_disable(i2s_handle_);
}
// I2S reconfiguration.
@ -181,15 +195,21 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
// DAC reconfiguration.
if (rate == SAMPLE_RATE_44_1) {
WriteRegister(Register::DE_EMPHASIS, 1 << 4);
} else {
WriteRegister(Register::DE_EMPHASIS, 0);
}
// TODO: base on BPS
WriteRegister(Register::I2S_FORMAT, 0);
WriteRegister(Register::I2S_FORMAT, 0b00);
// Configuration is all done, so we can now bring the DAC and I2S stream back
// up. I2S first, since otherwise the DAC will see that there's no clocks and
// shut itself down.
WriteRegister(Register::RESYNC_REQUEST, 0);
ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
WriteRegister(Register::POWER_MODE, 0);
i2s_active_ = true;
}
auto AudioDac::WriteData(const cpp::span<const std::byte>& data) -> void {

@ -83,6 +83,7 @@ class AudioDac {
private:
GpioExpander* gpio_;
i2s_chan_handle_t i2s_handle_;
bool i2s_active_;
i2s_std_clk_config_t clock_config_;
i2s_std_slot_config_t slot_config_;
@ -97,9 +98,13 @@ class AudioDac {
PAGE_SELECT = 0,
RESET = 1,
POWER_MODE = 2,
PLL_ENABLE = 4,
DE_EMPHASIS = 7,
PLL_CLOCK_SOURCE = 13,
DAC_CLOCK_SOURCE = 14,
RESYNC_REQUEST = 19,
CLOCK_ERRORS = 37,
INTERPOLATION = 34,
I2S_FORMAT = 40,
DIGITAL_VOLUME_L = 61,
DIGITAL_VOLUME_R = 62,

@ -41,7 +41,7 @@
static const char* TAG = "MAIN";
void IRAM_ATTR tick_hook(void) {
lv_tick_inc(1);
// lv_tick_inc(1);
}
static const size_t kLvglStackSize = 8 * 1024;

@ -57,21 +57,26 @@ class MappableRegion {
}
auto Get() -> cpp::span<std::byte> {
if (bytes_ != nullptr) {
if (bytes_ == nullptr) {
return {};
}
return {bytes_, size};
}
auto Map(const HimemAlloc<size>& alloc) -> cpp::span<std::byte> {
if (bytes_ != nullptr) {
ESP_ERROR_CHECK(esp_himem_unmap(range_handle, bytes_, size));
}
assert(bytes_ == nullptr);
ESP_ERROR_CHECK(esp_himem_map(alloc.handle, range_handle, 0, 0, size, 0,
reinterpret_cast<void**>(&bytes_)));
return Get();
}
auto Unmap() -> void {
if (bytes_ != nullptr) {
ESP_ERROR_CHECK(esp_himem_unmap(range_handle, bytes_, size));
bytes_ = nullptr;
}
}
// Not copyable or movable.
MappableRegion(const MappableRegion&) = delete;
MappableRegion& operator=(const MappableRegion&) = delete;

Loading…
Cancel
Save