3#if defined(USE_ESP32) && defined(USE_I2S_AUDIO_SPDIF_MODE)
5#include <driver/i2s_std.h>
17static const char *
const TAG =
"i2s_audio.spdif";
24static constexpr size_t SPDIF_DMA_BUFFERS_COUNT = 15;
28static constexpr uint32_t SPDIF_DMA_EVENTS_PER_CALLBACK = 4;
31static constexpr uint32_t SPDIF_PLAY_RETRY_WAIT_MS = 5;
33static constexpr size_t SPDIF_I2S_EVENT_QUEUE_COUNT = 2 * SPDIF_DMA_BUFFERS_COUNT;
36static esp_err_t spdif_preload_cb(
void *user_ctx,
uint32_t *data,
size_t size, TickType_t ticks_to_wait) {
37 auto *speaker =
static_cast<I2SAudioSpeakerSPDIF *
>(user_ctx);
38 size_t bytes_written = 0;
39 esp_err_t err = i2s_channel_preload_data(speaker->get_tx_handle(), data,
size, &bytes_written);
40 if (err != ESP_OK || bytes_written !=
size) {
41 ESP_LOGV(TAG,
"Preload failed: %s (wrote %zu/%zu bytes)", esp_err_to_name(err), bytes_written,
size);
42 return (err != ESP_OK) ? err : ESP_ERR_NO_MEM;
47static esp_err_t spdif_write_cb(
void *user_ctx,
uint32_t *data,
size_t size, TickType_t ticks_to_wait) {
48 auto *speaker =
static_cast<I2SAudioSpeakerSPDIF *
>(user_ctx);
49 size_t bytes_written = 0;
50 esp_err_t err = i2s_channel_write(speaker->get_tx_handle(), data,
size, &bytes_written, ticks_to_wait);
52 ESP_LOGV(TAG,
"I2S write failed: %s (wrote %zu/%zu bytes)", esp_err_to_name(err), bytes_written,
size);
65 ESP_LOGE(TAG,
"Encoder setup failed");
82 " Sample Rate: %" PRIu32
" Hz",
90 ESP_LOGE(TAG,
"Setup failed; cannot play audio");
104 vTaskDelay(ticks_to_wait);
108 size_t bytes_written = 0;
110 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->
audio_ring_buffer_.lock();
111 if (temp_ring_buffer !=
nullptr) {
113 TickType_t effective_ticks_to_wait = ticks_to_wait;
114 if (effective_ticks_to_wait == 0) {
115 effective_ticks_to_wait = pdMS_TO_TICKS(1);
117 bytes_written = temp_ring_buffer->write_without_replacement((
void *) data,
length, effective_ticks_to_wait);
118 if (bytes_written == 0 &&
length > 0) {
121 temp_ring_buffer->write_without_replacement((
void *) data,
length, pdMS_TO_TICKS(SPDIF_PLAY_RETRY_WAIT_MS));
126 return bytes_written;
141 const uint32_t dma_buffers_duration_ms = DMA_BUFFER_DURATION_MS * SPDIF_DMA_BUFFERS_COUNT;
149 const size_t ring_buffer_size =
153 const uint32_t frames_to_fill_single_dma_buffer = SPDIF_BLOCK_SAMPLES;
154 const size_t bytes_to_fill_single_dma_buffer =
157 bool successful_setup =
false;
158 std::unique_ptr<audio::RingBufferAudioSource> audio_source;
163 static_cast<uint8_t
>(bytes_per_frame));
164 if (audio_source !=
nullptr) {
166 successful_setup =
true;
170 if (!successful_setup) {
179 for (
size_t i = 0; i < SPDIF_DMA_BUFFERS_COUNT; i++) {
181 if (preload_err != ESP_OK) {
192 const i2s_event_callbacks_t callbacks = {.on_sent =
i2s_on_sent_cb};
193 i2s_channel_register_event_callback(this->
tx_handle_, &callbacks,
this);
203 const TickType_t write_timeout_ticks =
204 pdMS_TO_TICKS(((block_duration_us * (SPDIF_DMA_BUFFERS_COUNT + 1)) + 999) / 1000);
206 const TickType_t read_timeout_ticks = pdMS_TO_TICKS(((block_duration_us / 2) + 999) / 1000);
210 int64_t spdif_pending_timestamp = 0;
222 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::COMMAND_STOP);
227 ESP_LOGV(TAG,
"Exiting: ISR dropped event, restarting to recover lockstep");
233 ESP_LOGV(TAG,
"COMMAND_STOP received, continuing in silence mode");
238 xEventGroupClearBits(this->
event_group_, SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY);
242 ESP_LOGV(TAG,
"Exiting: stream info changed");
247 int64_t write_timestamp;
248 bool lockstep_broken =
false;
255 ESP_LOGV(TAG,
"Event without matching write record");
257 lockstep_broken =
true;
264 if (real_frames < SPDIF_BLOCK_SAMPLES) {
265 const uint32_t frames_zeroed = SPDIF_BLOCK_SAMPLES - real_frames;
269 spdif_dma_event_count++;
272 if (frames_sent > 0) {
273 spdif_pending_timestamp = write_timestamp;
274 spdif_pending_frames += frames_sent;
277 bool decimation_reached = (spdif_dma_event_count >= SPDIF_DMA_EVENTS_PER_CALLBACK);
281 bool partial_flush = (real_frames > 0 && real_frames < SPDIF_BLOCK_SAMPLES);
283 if (decimation_reached || partial_flush) {
284 if (spdif_pending_frames > 0) {
286 spdif_pending_frames = 0;
288 spdif_dma_event_count = 0;
291 if (lockstep_broken) {
292 ESP_LOGV(TAG,
"Exiting: lockstep desync, restarting task");
299 bool block_committed =
false;
300 bool partial_write_failure =
false;
303 while (real_frames_in_block < SPDIF_BLOCK_SAMPLES) {
304 if (audio_source->available() == 0) {
305 size_t bytes_read = audio_source->fill(read_timeout_ticks,
false);
306 if (bytes_read == 0) {
309 uint8_t *
new_data = audio_source->mutable_data();
314 const uint32_t frames_still_needed = SPDIF_BLOCK_SAMPLES - real_frames_in_block;
316 const size_t bytes_to_feed = std::min(audio_source->available(), bytes_still_needed);
319 size_t pcm_consumed = 0;
320 esp_err_t err = this->
spdif_encoder_->
write(audio_source->data(), bytes_to_feed, write_timeout_ticks,
321 &blocks_sent, &pcm_consumed);
327 partial_write_failure =
true;
331 if (pcm_consumed > 0) {
332 audio_source->consume(pcm_consumed);
335 if (blocks_sent > 0) {
336 block_committed =
true;
342 if (partial_write_failure) {
346 if (!block_committed) {
364 if (real_frames_in_block == 0) {
371 if (silence_duration >= this->
timeout_.value()) {
372 ESP_LOGV(TAG,
"Silence timeout reached (%" PRIu32
"ms) - stopping speaker", silence_duration);
378 if (silence_duration > 100) {
379 ESP_LOGV(TAG,
"Exiting silence mode after %" PRIu32
"ms, have audio data", silence_duration);
394 audio_source.reset();
400 vTaskDelay(pdMS_TO_TICKS(10));
409 ESP_LOGE(TAG,
"Only supports a single sample rate (configured: %" PRIu32
" Hz, stream: %" PRIu32
" Hz)",
411 return ESP_ERR_NOT_SUPPORTED;
414 ESP_LOGE(TAG,
"Only supports 16 bits per sample");
415 return ESP_ERR_NOT_SUPPORTED;
418 ESP_LOGE(TAG,
"Only supports stereo (2 channels)");
419 return ESP_ERR_NOT_SUPPORTED;
424 ESP_LOGE(TAG,
"Stream bits per sample must be less than or equal to the speaker's configuration");
425 return ESP_ERR_NOT_SUPPORTED;
428 if (!this->
parent_->try_lock()) {
429 ESP_LOGE(TAG,
"Parent bus is busy");
430 return ESP_ERR_INVALID_STATE;
433 i2s_clock_src_t clk_src = I2S_CLK_SRC_DEFAULT;
435#if SOC_CLK_APLL_SUPPORTED
437 clk_src = i2s_clock_src_t::I2S_CLK_SRC_APLL;
443 uint32_t dma_buffer_length = SPDIF_BLOCK_I2S_FRAMES;
446 ESP_LOGV(TAG,
"I2S DMA config: %zu buffers x %lu frames = %lu bytes total", (
size_t) SPDIF_DMA_BUFFERS_COUNT,
447 (
unsigned long) dma_buffer_length,
448 (
unsigned long) (SPDIF_DMA_BUFFERS_COUNT * dma_buffer_length * 8));
450 i2s_chan_config_t chan_cfg = {
451 .id = this->
parent_->get_port(),
453 .dma_desc_num = SPDIF_DMA_BUFFERS_COUNT,
454 .dma_frame_num = dma_buffer_length,
460 i2s_std_clk_config_t clk_cfg = {
466 i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO);
468 i2s_std_gpio_config_t gpio_cfg = {
482 i2s_std_config_t std_cfg = {
484 .slot_cfg = slot_cfg,
485 .gpio_cfg = gpio_cfg,
488 esp_err_t err = this->
init_i2s_channel_(chan_cfg, std_cfg, SPDIF_I2S_EVENT_QUEUE_COUNT);
void mark_failed()
Mark this component as failed.
I2SAudioComponent * parent_
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
uint8_t get_bits_per_sample() const
uint32_t frames_to_microseconds(uint32_t frames) const
Computes the duration, in microseconds, the given amount of frames represents.
uint32_t bytes_to_frames(size_t bytes) const
Convert bytes to frames.
uint8_t get_channels() const
uint32_t get_sample_rate() const
static std::unique_ptr< RingBufferAudioSource > create(std::shared_ptr< ring_buffer::RingBuffer > ring_buffer, size_t max_fill_bytes, uint8_t alignment_bytes=1)
Creates a new ring-buffer-backed audio source after validating its parameters.
i2s_slot_bit_width_t slot_bit_width_
i2s_mclk_multiple_t mclk_multiple_
uint32_t buffer_duration_ms_
static bool i2s_on_sent_cb(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx)
Callback function used to send playback timestamps to the speaker task.
i2s_chan_handle_t tx_handle_
void apply_software_volume_(uint8_t *data, size_t bytes_read)
Apply software volume control using Q15 fixed-point scaling.
std::weak_ptr< ring_buffer::RingBuffer > audio_ring_buffer_
TaskHandle_t speaker_task_handle_
void swap_esp32_mono_samples_(uint8_t *data, size_t bytes_read)
Swap adjacent 16-bit mono samples for ESP32 (non-variant) hardware quirk.
audio::AudioStreamInfo current_stream_info_
void dump_config() override
optional< uint32_t > timeout_
QueueHandle_t write_records_queue_
QueueHandle_t i2s_event_queue_
EventGroupHandle_t event_group_
esp_err_t init_i2s_channel_(const i2s_chan_config_t &chan_cfg, const i2s_std_config_t &std_cfg, size_t event_queue_size)
Shared I2S channel allocation, initialization, and event queue setup.
SPDIFEncoder * spdif_encoder_
size_t play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) override
esp_err_t start_i2s_driver(audio::AudioStreamInfo &audio_stream_info) override
void dump_config() override
void run_speaker_task() override
void on_task_stopped() override
uint32_t spdif_silence_start_
esp_err_t write(const uint8_t *src, size_t size, TickType_t ticks_to_wait, uint32_t *blocks_sent=nullptr, size_t *bytes_consumed=nullptr)
Convert PCM audio data to SPDIF BMC encoded data.
void set_sample_rate(uint32_t sample_rate)
Set the sample rate for Channel Status Block encoding.
void reset()
Reset the SPDIF block buffer and position tracking, discarding any partial block.
esp_err_t flush_with_silence(TickType_t ticks_to_wait)
Emit one complete SPDIF block: pad any pending partial block with silence and send,...
bool setup()
Initialize the SPDIF working buffer.
void set_write_callback(SPDIFBlockCallback callback, void *user_ctx)
Set callback for normal writes (used when channel is running)
void set_preload_callback(SPDIFBlockCallback callback, void *user_ctx)
Set callback for preload writes (used when preloading to DMA before enabling channel)
void set_preload_mode(bool preload)
Enable or disable preload mode When in preload mode, completed blocks use the preload callback instea...
static std::unique_ptr< RingBuffer > create(size_t len, MemoryPreference preference=MemoryPreference::EXTERNAL_FIRST)
CallbackManager< void(uint32_t, int64_t)> audio_output_callback_
audio::AudioStreamInfo audio_stream_info_
@ COMMAND_STOP_GRACEFULLY
uint32_t IRAM_ATTR HOT millis()