ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
i2s_audio_spdif.cpp
Go to the documentation of this file.
1#include "i2s_audio_spdif.h"
2
3#if defined(USE_ESP32) && defined(USE_I2S_AUDIO_SPDIF_MODE)
4
5#include <driver/i2s_std.h>
6
9
10#include "esphome/core/hal.h"
11#include "esphome/core/log.h"
12
13#include "esp_timer.h"
14
15namespace esphome::i2s_audio {
16
17ESPHOME_LOG_TAG(TAG, "i2s_audio.spdif");
18
19// SPDIF mode adds overhead as each sample is encapsulated in a subframe;
20// each DMA buffer can hold only 192 samples (~4ms each vs. ~15ms for standard I2S).
21// To match the standard I2S buffering duration, we use more buffers to minimize
22// the impact of the overhead, such as stuttering or audio/silence oscillation.
23// 15 buffers x 4ms = 60ms of DMA buffering (same as 4 x 15ms for standard)
24static constexpr size_t SPDIF_DMA_BUFFERS_COUNT = 15;
25
26// Number of DMA events between upstream callbacks (~16ms = 4 events x 4ms each).
27// Matches non-SPDIF timing to prevent overwhelming upstream sync algorithms.
28static constexpr uint32_t SPDIF_DMA_EVENTS_PER_CALLBACK = 4;
29
30// Brief retry wait used by play() to catch short free-space windows during rapid track transitions.
31static constexpr uint32_t SPDIF_PLAY_RETRY_WAIT_MS = 5;
32
33static constexpr size_t SPDIF_I2S_EVENT_QUEUE_COUNT = 2 * SPDIF_DMA_BUFFERS_COUNT;
34
35// Static callback functions for SPDIF encoder (avoids std::function overhead)
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;
43 }
44 return ESP_OK;
45}
46
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);
51 if (err != ESP_OK || bytes_written != size) {
52 ESP_LOGV(TAG, "I2S write failed: %s (wrote %zu/%zu bytes)", esp_err_to_name(err), bytes_written, size);
53 return (err != ESP_OK) ? err : ESP_FAIL;
54 }
55 return ESP_OK;
56}
57
60 if (this->is_failed()) {
61 return;
62 }
63
64 this->spdif_encoder_ = new SPDIFEncoder();
65 if (!this->spdif_encoder_->setup()) {
66 ESP_LOGE(TAG, "Encoder setup failed");
67 this->mark_failed();
68 return;
69 }
70
71 // Configure channel status block with the sample rate
73
74 // Separate callbacks for preload (during underflow recovery) and normal writes
75 this->spdif_encoder_->set_preload_callback(spdif_preload_cb, this);
76 this->spdif_encoder_->set_write_callback(spdif_write_cb, this);
77}
78
81 ESP_LOGCONFIG(TAG,
82 " SPDIF Mode: YES\n"
83 " Sample Rate: %" PRIu32 " Hz",
84 this->sample_rate_);
85}
86
88
89size_t I2SAudioSpeakerSPDIF::play(const uint8_t *data, size_t length, TickType_t ticks_to_wait) {
90 if (this->is_failed()) {
91 ESP_LOGE(TAG, "Setup failed; cannot play audio");
92 return 0;
93 }
94
95 // In SPDIF mode, keep accepting upstream audio while the speaker task is active.
96 // This avoids transient drops during stop/start transitions.
97 const bool task_active = (this->speaker_task_handle_ != nullptr);
98
100 this->start();
101 }
102
103 if (!task_active && this->state_ != speaker::STATE_RUNNING) {
104 // Unable to write data to a running speaker, so delay the max amount of time so it can get ready
105 vTaskDelay(ticks_to_wait);
106 ticks_to_wait = 0;
107 }
108
109 size_t bytes_written = 0;
110 if (this->state_ == speaker::STATE_RUNNING || task_active) {
111 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = this->audio_ring_buffer_.lock();
112 if (temp_ring_buffer != nullptr) {
113 // In SPDIF mode, a tiny wait helps avoid transient 0-byte writes during short backpressure windows.
114 TickType_t effective_ticks_to_wait = ticks_to_wait;
115 if (effective_ticks_to_wait == 0) {
116 effective_ticks_to_wait = pdMS_TO_TICKS(1);
117 }
118 bytes_written = temp_ring_buffer->write_without_replacement((void *) data, length, effective_ticks_to_wait);
119 if (bytes_written == 0 && length > 0) {
120 // Retry once to catch short free-space windows during rapid seek/track transitions.
121 bytes_written =
122 temp_ring_buffer->write_without_replacement((void *) data, length, pdMS_TO_TICKS(SPDIF_PLAY_RETRY_WAIT_MS));
123 }
124 }
125 }
126
127 return bytes_written;
128}
129
131 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::TASK_STARTING);
132
133 // Reset SPDIF encoder at task start to ensure clean state
134 // (previous task may have left stale data in encoder buffer)
135 if (this->spdif_encoder_ != nullptr) {
136 this->spdif_encoder_->reset();
137 }
138
139 // Reset lockstep records queue so it starts paired with the (also-reset) i2s_event_queue_.
140 xQueueReset(this->write_records_queue_);
141
142 // The DMA buffers may have more bits per sample, so calculate buffer sizes based on the input audio stream info
143 const size_t bytes_per_frame = this->current_stream_info_.frames_to_bytes(1);
144
145 // For SPDIF mode, one DMA buffer = one SPDIF block = 192 PCM frames (~4 ms at 48 kHz),
146 // not the ~15 ms a standard I2S DMA buffer holds. Derive the DMA floor from actual block size.
147 const uint32_t frames_to_fill_single_dma_buffer = SPDIF_BLOCK_SAMPLES;
148 const size_t bytes_to_fill_single_dma_buffer =
149 this->current_stream_info_.frames_to_bytes(frames_to_fill_single_dma_buffer);
150 const size_t dma_buffers_floor_bytes = bytes_to_fill_single_dma_buffer * SPDIF_DMA_BUFFERS_COUNT;
151
152 // Round the ring buffer size down to a multiple of bytes_per_frame so the wrap boundary stays frame-aligned and
153 // avoids unnecessary single-frame splices. Ensure it is at least large enough to cover all DMA buffers.
154 const size_t requested_ring_buffer_bytes =
155 (this->current_stream_info_.ms_to_bytes(this->buffer_duration_ms_) / bytes_per_frame) * bytes_per_frame;
156 const size_t ring_buffer_size = std::max(dma_buffers_floor_bytes, requested_ring_buffer_bytes);
157
158 bool successful_setup = false;
159 std::unique_ptr<audio::RingBufferAudioSource> audio_source;
160
161 {
162 std::shared_ptr<ring_buffer::RingBuffer> temp_ring_buffer = ring_buffer::RingBuffer::create(ring_buffer_size);
163 audio_source = audio::RingBufferAudioSource::create(temp_ring_buffer, bytes_to_fill_single_dma_buffer,
164 static_cast<uint8_t>(bytes_per_frame));
165 if (audio_source != nullptr) {
166 this->audio_ring_buffer_ = temp_ring_buffer;
167 successful_setup = true;
168 }
169 }
170
171 // Preload DMA buffers with SPDIF-encoded silence before enabling the channel.
172 // This ensures the first data transmitted is valid SPDIF (not raw zeros from
173 // auto_clear) and prevents phantom DMA events before real audio is available.
174 // Each preloaded block pushes a 0-real-frame record so that the corresponding
175 // on_sent events drain in lockstep without crediting any audio frames. Runs with
176 // the channel disabled: at startup and after a resync.
177 auto preload_silence = [&]() -> bool {
178 bool ok = true;
179 this->spdif_encoder_->set_preload_mode(true);
180 for (size_t i = 0; i < SPDIF_DMA_BUFFERS_COUNT; i++) {
181 // i2s_channel_preload_data is non-blocking (returns immediately when the preload buffer fills), so no wait.
182 const uint32_t silence_record = 0;
183 if ((this->spdif_encoder_->flush_with_silence(0) != ESP_OK) ||
184 (xQueueSendToBack(this->write_records_queue_, &silence_record, 0) != pdTRUE)) {
185 ok = false;
186 break;
187 }
188 }
189 this->spdif_encoder_->set_preload_mode(false);
190 this->spdif_encoder_->reset(); // Clean encoder state for the main loop
191 return ok;
192 };
193
194 if (successful_setup) {
195 successful_setup = preload_silence();
196 }
197
198 if (successful_setup) {
199 // Register the callback before enabling so the first transmitted block generates a queued event.
200 xQueueReset(this->i2s_event_queue_);
201 const i2s_event_callbacks_t callbacks = {.on_sent = i2s_on_sent_cb};
202 i2s_channel_register_event_callback(this->tx_handle_, &callbacks, this);
203 successful_setup = i2s_channel_enable(this->tx_handle_) == ESP_OK;
204 }
205
206 if (!successful_setup) {
207 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
208 } else {
209 // Always-fill model: each iteration produces exactly one SPDIF block (= one DMA buffer).
210 // We drain real PCM up to one block from the ring buffer and silence-pad any remainder.
211 // Blocking writes pace the loop at the DMA consumption rate. This mirrors the standard
212 // I2S speaker pattern (PR #16317): fill what you can, then silence-pad whatever is still
213 // missing to complete the DMA buffer.
214 const uint32_t block_duration_us = this->current_stream_info_.frames_to_microseconds(SPDIF_BLOCK_SAMPLES);
215 // Sized to absorb the worst case where every DMA buffer is full when we issue the write.
216 const TickType_t write_timeout_ticks =
217 pdMS_TO_TICKS(((block_duration_us * (SPDIF_DMA_BUFFERS_COUNT + 1)) + 999) / 1000);
218 // Brief read budget when the ring buffer is empty (~half a block).
219 const TickType_t read_timeout_ticks = pdMS_TO_TICKS(((block_duration_us / 2) + 999) / 1000);
220
221 // SPDIF Callback Decimation: fire every 4th DMA event (~16ms), matching non-SPDIF timing.
222 uint32_t spdif_pending_frames = 0;
223 int64_t spdif_pending_timestamp = 0;
224 uint32_t spdif_dma_event_count = 0;
225 bool resync_needed = false;
226 // Real frames consumed from the ring buffer that never reached a write record
227 uint32_t unrecorded_frames = 0;
228
229 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::TASK_RUNNING);
230
231 // SPDIF continuous mode: loop runs indefinitely, outputting silence when no audio data
232 // to keep the receiver synced. Exits only via break (stream info change, silence timeout,
233 // or a failed lockstep resync).
234 while (true) {
235 uint32_t event_group_bits = xEventGroupGetBits(this->event_group_);
236
237 if (event_group_bits & SpeakerEventGroupBits::COMMAND_STOP) {
238 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::COMMAND_STOP);
239 // User-initiated stop. In SPDIF continuous mode, transition to silence output rather
240 // than tearing the task down.
242 ESP_LOGV(TAG, "COMMAND_STOP received, continuing in silence mode");
243 }
244 if (event_group_bits & SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY) {
245 // SPDIF continuous mode never tears the channel down on graceful stop. Clear the flag and
246 // let the audio simply drain through the always-fill loop into the silence-timeout path.
247 xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::COMMAND_STOP_GRACEFULLY);
248 }
249
250 if (this->audio_stream_info_ != this->current_stream_info_) {
251 ESP_LOGV(TAG, "Exiting: stream info changed");
252 break;
253 }
254
255 if (event_group_bits & SpeakerEventGroupBits::ERR_DROPPED_EVENT) {
256 ESP_LOGE(TAG, "ISR event queue overflow, resyncing DMA lockstep");
257 resync_needed = true;
258 }
259 if (resync_needed) {
260 // Rebuild the lockstep in place. Frames held back by decimation are credited too, since their
261 // blocks are discarded with the rest of the DMA contents.
262 this->spdif_encoder_->reset();
263 const uint32_t credited_frames = unrecorded_frames + spdif_pending_frames;
264 const bool resynced = this->resync_lockstep_(credited_frames, preload_silence);
265 unrecorded_frames = 0;
266 spdif_pending_frames = 0;
267 spdif_dma_event_count = 0;
268 resync_needed = false;
269 if (credited_frames > 0) {
270 // Real audio was dropped, so the silence timer's start no longer reflects the stream
271 this->spdif_silence_start_ = 0;
272 }
273 if (!resynced) {
274 ESP_LOGE(TAG, "DMA lockstep resync failed, restarting speaker task");
275 break;
276 }
277 }
278
279 // Drain ISR completion events, popping a matching record for each.
280 int64_t write_timestamp;
281 bool lockstep_broken = false;
282 while (xQueueReceive(this->i2s_event_queue_, &write_timestamp, 0)) {
283 // Lockstep: pop the matching record (real audio frames packed into this DMA block).
284 // Records are pushed by the task right after each successful block commit, so the FIFO
285 // order matches DMA completion order. Empty records queue here means lockstep broke.
286 uint32_t real_frames = 0;
287 if (xQueueReceive(this->write_records_queue_, &real_frames, 0) != pdTRUE) {
288 ESP_LOGE(TAG, "Event without matching write record, resyncing DMA lockstep");
289 lockstep_broken = true;
290 break;
291 }
292
293 // Per-block timestamp adjustment: shift back by the silence-padding portion of the block
294 // so the reported timestamp reflects when the last real sample left the wire.
295 uint32_t frames_sent = real_frames;
296 if (real_frames < SPDIF_BLOCK_SAMPLES) {
297 const uint32_t frames_zeroed = SPDIF_BLOCK_SAMPLES - real_frames;
298 write_timestamp -= this->current_stream_info_.frames_to_microseconds(frames_zeroed);
299 }
300
301 spdif_dma_event_count++;
302 // Accumulate frames; keep the latest timestamp so the callback reports when the last
303 // sample left the wire, not the first.
304 if (frames_sent > 0) {
305 spdif_pending_timestamp = write_timestamp;
306 spdif_pending_frames += frames_sent;
307 }
308
309 bool decimation_reached = (spdif_dma_event_count >= SPDIF_DMA_EVENTS_PER_CALLBACK);
310 // Partial blocks mark an end-of-stream boundary (silence-padded tail). Fire immediately
311 // so the back-shifted timestamp isn't overwritten by a later full audio block landing
312 // in the same decimation window.
313 bool partial_flush = (real_frames > 0 && real_frames < SPDIF_BLOCK_SAMPLES);
314
315 if (decimation_reached || partial_flush) {
316 if (spdif_pending_frames > 0) {
317 this->audio_output_callback_(spdif_pending_frames, spdif_pending_timestamp);
318 spdif_pending_frames = 0;
319 }
320 spdif_dma_event_count = 0;
321 }
322 }
323 if (lockstep_broken) {
324 resync_needed = true;
325 continue;
326 }
327
328 // Always-fill: produce exactly one SPDIF block this iteration. The blocking encoder write
329 // paces the task at the DMA consumption rate.
330 uint32_t real_frames_in_block = 0;
331 bool block_committed = false;
332 bool partial_write_failure = false;
333
334 if (!this->pause_state_) {
335 while (real_frames_in_block < SPDIF_BLOCK_SAMPLES) {
336 if (audio_source->available() == 0) {
337 size_t bytes_read = audio_source->fill(read_timeout_ticks);
338 if (bytes_read == 0) {
339 break; // No upstream data within the read budget; silence-pad the remainder.
340 }
341 uint8_t *new_data = audio_source->mutable_data();
342 this->apply_software_volume_(new_data, bytes_read);
343 this->swap_esp32_mono_samples_(new_data, bytes_read);
344 }
345
346 const uint32_t frames_still_needed = SPDIF_BLOCK_SAMPLES - real_frames_in_block;
347 const size_t bytes_still_needed = this->current_stream_info_.frames_to_bytes(frames_still_needed);
348 const size_t bytes_to_feed = std::min(audio_source->available(), bytes_still_needed);
349
350 uint32_t blocks_sent = 0;
351 size_t pcm_consumed = 0;
352 esp_err_t err = this->spdif_encoder_->write(audio_source->data(), bytes_to_feed, write_timeout_ticks,
353 &blocks_sent, &pcm_consumed);
354 if (err != ESP_OK) {
355 // A failed (or timed-out) send leaves an unsent block in the encoder's stitch buffer;
356 // resuming would credit the next iteration's bytes against an old block.
357 ESP_LOGE(TAG, "SPDIF block send failed, resyncing DMA lockstep");
358 partial_write_failure = true;
359 break;
360 }
361
362 if (pcm_consumed > 0) {
363 audio_source->consume(pcm_consumed);
364 real_frames_in_block += this->current_stream_info_.bytes_to_frames(pcm_consumed);
365 }
366 if (blocks_sent > 0) {
367 block_committed = true;
368 break;
369 }
370 }
371 }
372
373 if (partial_write_failure) {
374 unrecorded_frames += real_frames_in_block;
375 resync_needed = true;
376 continue;
377 }
378
379 if (!block_committed) {
380 // Pad whatever real audio we managed to feed (if any) with silence to complete one block,
381 // or emit a full silence block if the encoder is empty.
382 esp_err_t err = this->spdif_encoder_->flush_with_silence(write_timeout_ticks);
383 if (err != ESP_OK) {
384 ESP_LOGE(TAG, "SPDIF block send failed, resyncing DMA lockstep");
385 unrecorded_frames += real_frames_in_block;
386 resync_needed = true;
387 continue;
388 }
389 }
390
391 // One block committed to DMA; push exactly one record carrying its real-audio frame count.
392 // Failure here means the records queue is full, which violates the lockstep invariant.
393 if (xQueueSendToBack(this->write_records_queue_, &real_frames_in_block, 0) != pdTRUE) {
394 ESP_LOGE(TAG, "Write records queue full, resyncing DMA lockstep");
395 unrecorded_frames += real_frames_in_block;
396 resync_needed = true;
397 continue;
398 }
399
400 // Silence-timeout tracking and graceful-stop reset.
401 if (real_frames_in_block == 0) {
402 if (this->spdif_silence_start_ == 0) {
404 }
405
406 if (this->timeout_.has_value()) {
407 const uint32_t silence_duration = millis() - this->spdif_silence_start_;
408 if (silence_duration >= this->timeout_.value()) {
409 ESP_LOGV(TAG, "Silence timeout reached (%" PRIu32 "ms) - stopping speaker", silence_duration);
410 break;
411 }
412 }
413 } else if (this->spdif_silence_start_ != 0) {
414 uint32_t silence_duration = millis() - this->spdif_silence_start_;
415 if (silence_duration > 100) {
416 ESP_LOGV(TAG, "Exiting silence mode after %" PRIu32 "ms, have audio data", silence_duration);
417 }
418 this->spdif_silence_start_ = 0;
419 }
420 }
421 }
422
423 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::TASK_STOPPING);
424
425 // Reset SPDIF encoder state to prevent stale state on next start
426 if (this->spdif_encoder_ != nullptr) {
427 this->spdif_encoder_->set_preload_mode(false);
428 this->spdif_encoder_->reset();
429 }
430
431 audio_source.reset();
432
433 xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::TASK_STOPPED);
434
435 while (true) {
436 // Continuously delay until the loop method deletes the task
437 vTaskDelay(pdMS_TO_TICKS(10));
438 }
439}
440
442 this->current_stream_info_ = audio_stream_info;
443 // SPDIF never narrows the bit depth; the encoder consumes the input format directly.
444 this->output_stream_info_ = audio_stream_info;
445
446 // SPDIF mode validation
447 if (this->sample_rate_ != audio_stream_info.get_sample_rate()) {
448 ESP_LOGE(TAG, "Only supports a single sample rate (configured: %" PRIu32 " Hz, stream: %" PRIu32 " Hz)",
449 this->sample_rate_, audio_stream_info.get_sample_rate());
450 return ESP_ERR_NOT_SUPPORTED;
451 }
452 const uint8_t bits_per_sample = audio_stream_info.get_bits_per_sample();
453 if (bits_per_sample != 16 && bits_per_sample != 24 && bits_per_sample != 32) {
454 ESP_LOGE(TAG, "Only supports 16, 24, or 32 bits per sample (got %u)", (unsigned) bits_per_sample);
455 return ESP_ERR_NOT_SUPPORTED;
456 }
457 if (audio_stream_info.get_channels() != 2) {
458 ESP_LOGE(TAG, "Only supports stereo (2 channels)");
459 return ESP_ERR_NOT_SUPPORTED;
460 }
461
462 // Tell the encoder what input width to expect. 32-bit input is truncated to 24-bit on the wire.
463 this->spdif_encoder_->set_bytes_per_sample(bits_per_sample / 8);
464
465 if (!this->parent_->try_lock()) {
466 ESP_LOGE(TAG, "Parent bus is busy");
467 return ESP_ERR_INVALID_STATE;
468 }
469
470 // SPDIF mode: fixed configuration for BMC encoding
471 // For new driver, dma_frame_num is in I2S frames (8 bytes each for 32-bit stereo)
472 uint32_t dma_buffer_length = SPDIF_BLOCK_I2S_FRAMES; // One SPDIF block = 384 I2S frames = 3072 bytes
473
474 // Log DMA configuration for debugging
475 ESP_LOGV(TAG, "I2S DMA config: %zu buffers x %lu frames = %lu bytes total", (size_t) SPDIF_DMA_BUFFERS_COUNT,
476 (unsigned long) dma_buffer_length,
477 (unsigned long) (SPDIF_DMA_BUFFERS_COUNT * dma_buffer_length * 8)); // 8 bytes per frame for 32-bit stereo
478
479 i2s_chan_config_t chan_cfg = {
480 .id = this->parent_->get_port(),
481 .role = this->i2s_role_,
482 .dma_desc_num = SPDIF_DMA_BUFFERS_COUNT,
483 .dma_frame_num = dma_buffer_length,
484 .auto_clear = true,
485 .intr_priority = 3,
486 };
487
488 // SPDIF: double sample rate for BMC, 32-bit stereo, only data pin needed
489 i2s_std_clk_config_t clk_cfg = {
490 .sample_rate_hz = this->sample_rate_ * 2,
491 .clk_src = this->get_clock_source_(),
492 .mclk_multiple = this->mclk_multiple_,
493 };
494
495 i2s_std_slot_config_t slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO);
496
497 i2s_std_gpio_config_t gpio_cfg = {
498 .mclk = GPIO_NUM_NC,
499 .bclk = GPIO_NUM_NC,
500 .ws = GPIO_NUM_NC,
501 .dout = this->dout_pin_,
502 .din = GPIO_NUM_NC,
503 .invert_flags =
504 {
505 .mclk_inv = false,
506 .bclk_inv = false,
507 .ws_inv = false,
508 },
509 };
510
511 i2s_std_config_t std_cfg = {
512 .clk_cfg = clk_cfg,
513 .slot_cfg = slot_cfg,
514 .gpio_cfg = gpio_cfg,
515 };
516
517 esp_err_t err = this->init_i2s_channel_(chan_cfg, std_cfg, SPDIF_I2S_EVENT_QUEUE_COUNT);
518 if (err != ESP_OK) {
519 return err;
520 }
521
522 // Channel is NOT enabled here. The speaker task will preload DMA buffers
523 // with SPDIF-encoded silence before enabling, ensuring the first data on
524 // the wire is valid SPDIF (not raw zeros from auto_clear) and preventing
525 // phantom DMA events before real audio data is available.
526
527 return ESP_OK;
528}
529
530} // namespace esphome::i2s_audio
531
532#endif // USE_ESP32 && USE_I2S_AUDIO_SPDIF_MODE
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
size_t ms_to_bytes(uint32_t ms) const
Converts duration to bytes.
Definition audio.h:73
size_t frames_to_bytes(uint32_t frames) const
Converts frames to bytes.
Definition audio.h:53
uint8_t get_bits_per_sample() const
Definition audio.h:28
uint32_t frames_to_microseconds(uint32_t frames) const
Computes the duration, in microseconds, the given amount of frames represents.
Definition audio.cpp:25
uint32_t bytes_to_frames(size_t bytes) const
Convert bytes to frames.
Definition audio.h:43
uint8_t get_channels() const
Definition audio.h:29
uint32_t get_sample_rate() const
Definition audio.h:30
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_clock_src_t get_clock_source_() const
Definition i2s_audio.h:26
i2s_mclk_multiple_t mclk_multiple_
Definition i2s_audio.h:41
std::weak_ptr< ring_buffer::RingBuffer > audio_ring_buffer_
void swap_esp32_mono_samples_(uint8_t *data, size_t bytes_read)
Plays the provided audio data.
SPDIF speaker implementation.
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 set_bytes_per_sample(uint8_t bytes_per_sample)
Set input PCM width: 2 = 16-bit, 3 = 24-bit, 4 = 32-bit (truncated to 24-bit on the wire).
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_
Definition speaker.h:138
audio::AudioStreamInfo audio_stream_info_
Definition speaker.h:131
ESPHOME_LOG_TAG(TAG, "i2s_audio")
uint16_t size
Definition helpers.cpp:34
auto * new_data
Definition helpers.cpp:38
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
static void uint32_t
uint16_t length
Definition tt21100.cpp:0