ESPHome 2026.5.0
Loading...
Searching...
No Matches
spdif_encoder.h
Go to the documentation of this file.
1#pragma once
2
4
5#if defined(USE_ESP32) && defined(USE_I2S_AUDIO_SPDIF_MODE)
6
7#include <array>
8#include <cstdint>
9#include <memory>
10#include <freertos/FreeRTOS.h>
11#include "esp_err.h"
13
14namespace esphome::i2s_audio {
15
16// A SPDIF sample is 64-bits
17static constexpr uint8_t SPDIF_BITS_PER_SAMPLE = 64;
18// Number of samples in a SPDIF block
19static constexpr uint16_t SPDIF_BLOCK_SAMPLES = 192;
20// To emulate bi-phase mark code (BMC) (aka differential Manchester encoding) we send twice
21// as many bits per sample so that we can generate the transitions this encoding requires.
22static constexpr uint8_t EMULATED_BMC_BITS_PER_SAMPLE = SPDIF_BITS_PER_SAMPLE * 2;
23static constexpr uint16_t SPDIF_BLOCK_SIZE_BYTES = SPDIF_BLOCK_SAMPLES * (EMULATED_BMC_BITS_PER_SAMPLE / 8);
24static constexpr uint32_t SPDIF_BLOCK_SIZE_U32 = SPDIF_BLOCK_SIZE_BYTES / sizeof(uint32_t); // 3072 bytes / 4 = 768
25// I2S frame count for one SPDIF block (for new driver where frame = 8 bytes for 32-bit stereo)
26static constexpr uint32_t SPDIF_BLOCK_I2S_FRAMES = SPDIF_BLOCK_SIZE_BYTES / 8; // 3072 / 8 = 384 frames
27
34using SPDIFBlockCallback = esp_err_t (*)(void *user_ctx, uint32_t *data, size_t size, TickType_t ticks_to_wait);
35
37 public:
40 bool setup();
41
45 void set_write_callback(SPDIFBlockCallback callback, void *user_ctx) {
46 this->write_callback_ = callback;
47 this->write_callback_ctx_ = user_ctx;
48 }
49
53 void set_preload_callback(SPDIFBlockCallback callback, void *user_ctx) {
54 this->preload_callback_ = callback;
55 this->preload_callback_ctx_ = user_ctx;
56 }
57
60 void set_preload_mode(bool preload) { this->preload_mode_ = preload; }
61
63 bool is_preload_mode() const { return this->preload_mode_; }
64
68 void set_bytes_per_sample(uint8_t bytes_per_sample);
69
71 uint8_t get_bytes_per_sample() const { return this->bytes_per_sample_; }
72
80 esp_err_t write(const uint8_t *src, size_t size, TickType_t ticks_to_wait, uint32_t *blocks_sent = nullptr,
81 size_t *bytes_consumed = nullptr);
82
87 esp_err_t flush_with_silence(TickType_t ticks_to_wait);
88
90 void reset();
91
95 void set_sample_rate(uint32_t sample_rate);
96
98 uint32_t get_sample_rate() const { return this->sample_rate_; }
99
100 protected:
104 template<uint8_t Bps> void encode_silence_frame_();
105
107 template<uint8_t Bps>
108 HOT esp_err_t write_typed_(const uint8_t *src, size_t size, TickType_t ticks_to_wait, uint32_t *blocks_sent,
109 size_t *bytes_consumed);
110
113 template<uint8_t Bps> esp_err_t flush_with_silence_typed_(TickType_t ticks_to_wait);
114
116 esp_err_t send_block_(TickType_t ticks_to_wait);
117
120
121 // Member ordering optimized to minimize padding (largest alignment first)
122
123 // 4-byte aligned members (pointers and uint32_t)
126 void *write_callback_ctx_{nullptr};
127 void *preload_callback_ctx_{nullptr};
128 std::unique_ptr<uint32_t[]> spdif_block_buf_; // Working buffer for SPDIF block (heap allocated)
129 uint32_t *spdif_block_ptr_{nullptr}; // Current position in block buffer
130 uint32_t sample_rate_{48000}; // Sample rate for Channel Status Block encoding
131
132 // 1-byte aligned members (grouped together to avoid internal padding)
133 uint8_t bytes_per_sample_{2}; // Input PCM width: 2/3/4 (16/24/32-bit). 32-bit truncates to 24-bit on the wire.
134 uint8_t frame_in_block_{0}; // 0-191, tracks stereo frame position within block
135 bool preload_mode_{false}; // Whether to use preload callback vs write callback
136 // True when spdif_block_buf_ currently holds a complete full-silence block valid for the active
137 // channel status. A full silence block is deterministic for a given sample rate and word length,
138 // so when this is set flush_with_silence() can re-send the buffer verbatim instead of re-encoding.
140
141 // Channel Status Block (192 bits = 24 bytes, transmitted over 192 frames)
142 // Placed last since std::array<uint8_t> has 1-byte alignment
143 std::array<uint8_t, 24> channel_status_{};
144};
145
146} // namespace esphome::i2s_audio
147
148#endif // USE_I2S_AUDIO_SPDIF_MODE
std::unique_ptr< uint32_t[]> spdif_block_buf_
bool is_preload_mode() const
Check if currently in preload mode.
esp_err_t send_block_(TickType_t ticks_to_wait)
Send the completed block via the appropriate callback.
uint32_t get_sample_rate() const
Get the currently configured sample rate.
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).
std::array< uint8_t, 24 > channel_status_
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.
uint8_t get_bytes_per_sample() const
Get the configured input PCM width in bytes per sample.
void set_sample_rate(uint32_t sample_rate)
Set the sample rate for Channel Status Block encoding.
void encode_silence_frame_()
Encode a single stereo silence frame at the current block position.
esp_err_t flush_with_silence_typed_(TickType_t ticks_to_wait)
Templated flush-with-silence.
SPDIFBlockCallback preload_callback_
void reset()
Reset the SPDIF block buffer and position tracking, discarding any partial block.
void build_channel_status_()
Build the channel status block from current configuration.
HOT esp_err_t write_typed_(const uint8_t *src, size_t size, TickType_t ticks_to_wait, uint32_t *blocks_sent, size_t *bytes_consumed)
Templated write loop. Called from the public write() via runtime dispatch on bytes_per_sample_.
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...
esp_err_t(*)(void *user_ctx, uint32_t *data, size_t size, TickType_t ticks_to_wait) SPDIFBlockCallback
Callback signature for block completion (raw function pointer for minimal overhead)
uint16_t size
Definition helpers.cpp:25
static void uint32_t