Switch to an MVP-ready 16bit three wire DAC setup
This commit is contained in:
@@ -37,15 +37,14 @@ namespace task {
|
|||||||
static const char* kTag = "task";
|
static const char* kTag = "task";
|
||||||
static const std::size_t kStackSize = 24 * 1024;
|
static const std::size_t kStackSize = 24 * 1024;
|
||||||
static const std::size_t kDrainStackSize = 1024;
|
static const std::size_t kDrainStackSize = 1024;
|
||||||
static const uint8_t kAudioCore = 0;
|
|
||||||
|
|
||||||
auto StartPipeline(Pipeline* pipeline, IAudioSink* sink) -> void {
|
auto StartPipeline(Pipeline* pipeline, IAudioSink* sink) -> void {
|
||||||
// Newly created task will free this.
|
// Newly created task will free this.
|
||||||
AudioTaskArgs* args = new AudioTaskArgs{.pipeline = pipeline, .sink = sink};
|
AudioTaskArgs* args = new AudioTaskArgs{.pipeline = pipeline, .sink = sink};
|
||||||
|
|
||||||
ESP_LOGI(kTag, "starting audio pipeline task");
|
ESP_LOGI(kTag, "starting audio pipeline task");
|
||||||
xTaskCreatePinnedToCore(&AudioTaskMain, "pipeline", kStackSize, args,
|
xTaskCreate(&AudioTaskMain, "pipeline", kStackSize, args,
|
||||||
kTaskPriorityAudioPipeline, NULL, kAudioCore);
|
kTaskPriorityAudioPipeline, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StartDrain(IAudioSink* sink) -> void {
|
auto StartDrain(IAudioSink* sink) -> void {
|
||||||
@@ -151,6 +150,13 @@ void AudioTaskMain(void* args) {
|
|||||||
std::size_t sent = xStreamBufferSend(
|
std::size_t sent = xStreamBufferSend(
|
||||||
sink->buffer(), sink_stream.data().data(),
|
sink->buffer(), sink_stream.data().data(),
|
||||||
sink_stream.data().size_bytes(), pdMS_TO_TICKS(10));
|
sink_stream.data().size_bytes(), pdMS_TO_TICKS(10));
|
||||||
|
if (sent > 0) {
|
||||||
|
ESP_LOGI(kTag, "sunk %u bytes out of %u (%d %%)", sent,
|
||||||
|
sink_stream.info().bytes_in_stream,
|
||||||
|
(int)(((float)sent /
|
||||||
|
(float)sink_stream.info().bytes_in_stream) *
|
||||||
|
100));
|
||||||
|
}
|
||||||
sink_stream.consume(sent);
|
sink_stream.consume(sent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace audio {
|
|||||||
class IAudioSink {
|
class IAudioSink {
|
||||||
private:
|
private:
|
||||||
// TODO: tune. at least about 12KiB seems right for mp3
|
// TODO: tune. at least about 12KiB seems right for mp3
|
||||||
static const std::size_t kDrainBufferSize = 24 * 1024;
|
static const std::size_t kDrainBufferSize = 48 * 1024;
|
||||||
uint8_t* buffer_;
|
uint8_t* buffer_;
|
||||||
StaticStreamBuffer_t* metadata_;
|
StaticStreamBuffer_t* metadata_;
|
||||||
StreamBufferHandle_t handle_;
|
StreamBufferHandle_t handle_;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
namespace audio {
|
namespace audio {
|
||||||
|
|
||||||
static const std::size_t kPipelineBufferSize = 32 * 1024;
|
static const std::size_t kPipelineBufferSize = 64 * 1024;
|
||||||
|
|
||||||
class Pipeline {
|
class Pipeline {
|
||||||
public:
|
public:
|
||||||
|
|||||||
+16
-4
@@ -1,4 +1,5 @@
|
|||||||
#include "mad.hpp"
|
#include "mad.hpp"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
@@ -9,9 +10,9 @@
|
|||||||
|
|
||||||
namespace codecs {
|
namespace codecs {
|
||||||
|
|
||||||
static int scaleTo16Bits(mad_fixed_t sample) {
|
static uint32_t scaleToBits(mad_fixed_t sample, uint8_t bits) {
|
||||||
// Round the bottom bits.
|
// Round the bottom bits.
|
||||||
sample += (1L << (MAD_F_FRACBITS - 16));
|
sample += (1L << (MAD_F_FRACBITS - bits));
|
||||||
|
|
||||||
// Clip the leftover bits to within range.
|
// Clip the leftover bits to within range.
|
||||||
if (sample >= MAD_F_ONE)
|
if (sample >= MAD_F_ONE)
|
||||||
@@ -20,7 +21,7 @@ static int scaleTo16Bits(mad_fixed_t sample) {
|
|||||||
sample = -MAD_F_ONE;
|
sample = -MAD_F_ONE;
|
||||||
|
|
||||||
// Quantize.
|
// Quantize.
|
||||||
return sample >> (MAD_F_FRACBITS + 1 - 16);
|
return sample >> (MAD_F_FRACBITS + 1 - bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
MadMp3Decoder::MadMp3Decoder() {
|
MadMp3Decoder::MadMp3Decoder() {
|
||||||
@@ -119,8 +120,19 @@ auto MadMp3Decoder::WriteOutputSamples(cpp::span<std::byte> output)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int channel = 0; channel < synth_.pcm.channels; channel++) {
|
for (int channel = 0; channel < synth_.pcm.channels; channel++) {
|
||||||
|
// TODO(jacqueline): output 24 bit samples when (if?) we have a downmix
|
||||||
|
// step in the pipeline.
|
||||||
|
/*
|
||||||
|
uint32_t sample_24 =
|
||||||
|
scaleToBits(synth_.pcm.samples[channel][current_sample_], 24);
|
||||||
|
output[output_byte++] = static_cast<std::byte>((sample_24 >> 16) & 0xFF);
|
||||||
|
output[output_byte++] = static_cast<std::byte>((sample_24 >> 8) & 0xFF);
|
||||||
|
output[output_byte++] = static_cast<std::byte>((sample_24)&0xFF);
|
||||||
|
// 24 bit samples must still be aligned to 32 bits. The LSB is ignored.
|
||||||
|
output[output_byte++] = static_cast<std::byte>(0);
|
||||||
|
*/
|
||||||
uint16_t sample_16 =
|
uint16_t sample_16 =
|
||||||
scaleTo16Bits(synth_.pcm.samples[channel][current_sample_]);
|
scaleToBits(synth_.pcm.samples[channel][current_sample_], 16);
|
||||||
output[output_byte++] = static_cast<std::byte>((sample_16 >> 8) & 0xFF);
|
output[output_byte++] = static_cast<std::byte>((sample_16 >> 8) & 0xFF);
|
||||||
output[output_byte++] = static_cast<std::byte>((sample_16)&0xFF);
|
output[output_byte++] = static_cast<std::byte>((sample_16)&0xFF);
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-18
@@ -38,7 +38,7 @@ auto AudioDac::create(GpioExpander* expander)
|
|||||||
channel_config.dma_frame_num = 1024;
|
channel_config.dma_frame_num = 1024;
|
||||||
// Triple buffering should be enough to keep samples flowing smoothly.
|
// Triple buffering should be enough to keep samples flowing smoothly.
|
||||||
// TODO(jacqueline): verify this with 192kHz 32bps.
|
// TODO(jacqueline): verify this with 192kHz 32bps.
|
||||||
channel_config.dma_desc_num = 8;
|
channel_config.dma_desc_num = 4;
|
||||||
// channel_config.auto_clear = true;
|
// channel_config.auto_clear = true;
|
||||||
|
|
||||||
ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL));
|
ESP_ERROR_CHECK(i2s_new_channel(&channel_config, &i2s_handle, NULL));
|
||||||
@@ -53,7 +53,7 @@ auto AudioDac::create(GpioExpander* expander)
|
|||||||
i2s_std_config_t i2s_config = {
|
i2s_std_config_t i2s_config = {
|
||||||
.clk_cfg = dac->clock_config_,
|
.clk_cfg = dac->clock_config_,
|
||||||
.slot_cfg = dac->slot_config_,
|
.slot_cfg = dac->slot_config_,
|
||||||
.gpio_cfg = {.mclk = I2S_GPIO_UNUSED,
|
.gpio_cfg = {.mclk = GPIO_NUM_0,
|
||||||
.bclk = GPIO_NUM_26,
|
.bclk = GPIO_NUM_26,
|
||||||
.ws = GPIO_NUM_27,
|
.ws = GPIO_NUM_27,
|
||||||
.dout = GPIO_NUM_5,
|
.dout = GPIO_NUM_5,
|
||||||
@@ -167,6 +167,8 @@ bool AudioDac::WaitForPowerState(
|
|||||||
|
|
||||||
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
||||||
if (i2s_active_) {
|
if (i2s_active_) {
|
||||||
|
WriteRegister(pcm512x::MUTE, 0b10001);
|
||||||
|
vTaskDelay(1);
|
||||||
WriteRegister(pcm512x::POWER, 1 << 4);
|
WriteRegister(pcm512x::POWER, 1 << 4);
|
||||||
i2s_channel_disable(i2s_handle_);
|
i2s_channel_disable(i2s_handle_);
|
||||||
}
|
}
|
||||||
@@ -198,10 +200,10 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
|
ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(i2s_handle_, &clock_config_));
|
||||||
|
|
||||||
// DAC reconfiguration.
|
// DAC reconfiguration.
|
||||||
//See here : https://e2e.ti.com/support/data_converters/audio_converters/f/64/t/428281
|
// Inspired heavily by https://github.com/tommag/PCM51xx_Arduino (MIT).
|
||||||
// for a config example
|
|
||||||
|
|
||||||
// Check that the bit clock (PLL input) is between 1MHz and 50MHz
|
// Check that the bit clock (PLL input) is between 1MHz and 50MHz. It always
|
||||||
|
// should be.
|
||||||
uint32_t bckFreq = rate * bps * 2;
|
uint32_t bckFreq = rate * bps * 2;
|
||||||
if (bckFreq < 1000000 || bckFreq > 50000000) {
|
if (bckFreq < 1000000 || bckFreq > 50000000) {
|
||||||
ESP_LOGE(kTag, "bck freq out of range");
|
ESP_LOGE(kTag, "bck freq out of range");
|
||||||
@@ -210,15 +212,19 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
|
|
||||||
// 24 bits is not supported for 44.1kHz and 48kHz.
|
// 24 bits is not supported for 44.1kHz and 48kHz.
|
||||||
if ((rate == SAMPLE_RATE_44_1 || rate == SAMPLE_RATE_48) && bps == BPS_24) {
|
if ((rate == SAMPLE_RATE_44_1 || rate == SAMPLE_RATE_48) && bps == BPS_24) {
|
||||||
// TODO(jacqueline): implement
|
// TODO(jacqueline): I think this *can* be implemented, but requires a bunch
|
||||||
|
// of maths.
|
||||||
ESP_LOGE(kTag, "sample rate and bps mismatch");
|
ESP_LOGE(kTag, "sample rate and bps mismatch");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize system clock from the I2S BCK input
|
// Initialize system clock from the I2S BCK input
|
||||||
WriteRegister(pcm512x::ERROR_DETECT, 0x1A); // Disable clock autoset and ignore SCK detection
|
// Disable clock autoset and ignore SCK detection
|
||||||
WriteRegister(pcm512x::PLL_REF, 0x10); // Set PLL clock source to BCK
|
WriteRegister(pcm512x::ERROR_DETECT, 0x1A);
|
||||||
WriteRegister(pcm512x::DAC_REF, 0x10); // Set DAC clock source to PLL output
|
// Set PLL clock source to BCK
|
||||||
|
WriteRegister(pcm512x::PLL_REF, 0x10);
|
||||||
|
// Set DAC clock source to PLL output
|
||||||
|
WriteRegister(pcm512x::DAC_REF, 0x10);
|
||||||
|
|
||||||
// PLL configuration
|
// PLL configuration
|
||||||
int p, j, d, r;
|
int p, j, d, r;
|
||||||
@@ -226,8 +232,8 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
// Clock dividers
|
// Clock dividers
|
||||||
int nmac, ndac, ncp, dosr, idac;
|
int nmac, ndac, ncp, dosr, idac;
|
||||||
|
|
||||||
if (rate == SAMPLE_RATE_11_025 || rate == SAMPLE_RATE_22_05 || rate == SAMPLE_RATE_44_1)
|
if (rate == SAMPLE_RATE_11_025 || rate == SAMPLE_RATE_22_05 ||
|
||||||
{
|
rate == SAMPLE_RATE_44_1) {
|
||||||
// 44.1kHz and derivatives.
|
// 44.1kHz and derivatives.
|
||||||
// P = 1, R = 2, D = 0 for all supported combinations.
|
// P = 1, R = 2, D = 0 for all supported combinations.
|
||||||
// Set J to have PLL clk = 90.3168 MHz
|
// Set J to have PLL clk = 90.3168 MHz
|
||||||
@@ -242,9 +248,7 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
ncp = 4;
|
ncp = 4;
|
||||||
dosr = 8;
|
dosr = 8;
|
||||||
idac = 1024; // DSP clock / sample rate
|
idac = 1024; // DSP clock / sample rate
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// 8kHz and multiples.
|
// 8kHz and multiples.
|
||||||
// PLL config for a 98.304 MHz PLL clk
|
// PLL config for a 98.304 MHz PLL clk
|
||||||
if (bps == BPS_24 && bckFreq > 1536000) {
|
if (bps == BPS_24 && bckFreq > 1536000) {
|
||||||
@@ -261,9 +265,15 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
|
|
||||||
// Derive clocks from the 98.304MHz PLL
|
// Derive clocks from the 98.304MHz PLL
|
||||||
switch (rate) {
|
switch (rate) {
|
||||||
case SAMPLE_RATE_16: nmac = 6; break;
|
case SAMPLE_RATE_16:
|
||||||
case SAMPLE_RATE_32: nmac = 3; break;
|
nmac = 6;
|
||||||
default: nmac = 2; break;
|
break;
|
||||||
|
case SAMPLE_RATE_32:
|
||||||
|
nmac = 3;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nmac = 2;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ndac = 16;
|
ndac = 16;
|
||||||
@@ -272,7 +282,6 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
idac = 98304000 / nmac / rate; // DSP clock / sample rate
|
idac = 98304000 / nmac / rate; // DSP clock / sample rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Configure PLL
|
// Configure PLL
|
||||||
WriteRegister(pcm512x::PLL_COEFF_0, p - 1);
|
WriteRegister(pcm512x::PLL_COEFF_0, p - 1);
|
||||||
WriteRegister(pcm512x::PLL_COEFF_1, j);
|
WriteRegister(pcm512x::PLL_COEFF_1, j);
|
||||||
@@ -311,6 +320,11 @@ auto AudioDac::Reconfigure(BitsPerSample bps, SampleRate rate) -> void {
|
|||||||
// shut itself down.
|
// shut itself down.
|
||||||
ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
|
ESP_ERROR_CHECK(i2s_channel_enable(i2s_handle_));
|
||||||
WriteRegister(pcm512x::POWER, 0);
|
WriteRegister(pcm512x::POWER, 0);
|
||||||
|
|
||||||
|
if (i2s_active_) {
|
||||||
|
vTaskDelay(1);
|
||||||
|
WriteRegister(pcm512x::MUTE, 0);
|
||||||
|
}
|
||||||
i2s_active_ = true;
|
i2s_active_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user