ESPHome 2026.5.0b1
Loading...
Searching...
No Matches
spdif_encoder.cpp
Go to the documentation of this file.
1#include "spdif_encoder.h"
2
3#if defined(USE_ESP32) && defined(USE_I2S_AUDIO_SPDIF_MODE)
4
5#include "esphome/core/log.h"
6
7namespace esphome::i2s_audio {
8
9static const char *const TAG = "i2s_audio.spdif_encoder";
10
11// S/PDIF preamble patterns (8 BMC bits each)
12// These are the BMC-encoded sync patterns that violate normal BMC rules for easy detection.
13// All preambles end at phase HIGH (last bit = 1), enabling consistent data encoding.
14// Preamble is placed at bits 24-31 of word[0] for MSB-first transmission.
15static constexpr uint8_t PREAMBLE_B = 0x17; // Block start (left channel, frame 0)
16static constexpr uint8_t PREAMBLE_M = 0x1d; // Left channel (not block start)
17static constexpr uint8_t PREAMBLE_W = 0x1b; // Right channel
18
19// BMC encoding of 4 zero bits starting at phase HIGH: 00_11_00_11 = 0x33
20// Since both aux nibbles (bits 4-7, 8-11) are zero for 16-bit audio and phase is preserved, both are 0x33.
21static constexpr uint32_t BMC_ZERO_NIBBLE = 0x33;
22
23// Constexpr BMC encoder for compile-time LUT generation.
24// Encodes with start phase=true (HIGH). The complement property allows phase=false
25// via XOR: bmc_encode(v, N, false) == bmc_encode(v, N, true) ^ mask
26static constexpr uint16_t bmc_lut_encode(uint32_t data, uint8_t num_bits) {
27 uint16_t bmc = 0;
28 bool phase = true;
29 for (uint8_t i = 0; i < num_bits; i++) {
30 bool bit = (data >> i) & 1;
31 uint8_t bmc_pair = phase ? (bit ? 0b01 : 0b00) : (bit ? 0b10 : 0b11);
32 bmc |= static_cast<uint16_t>(bmc_pair) << ((num_bits - 1 - i) * 2);
33 if (!bit)
34 phase = !phase;
35 }
36 return bmc;
37}
38
39// 4-bit BMC lookup table: 16 entries (16 bytes in flash)
40// Index: 4-bit data value (0-15), always phase=true start
41static constexpr auto BMC_LUT_4 = [] {
42 std::array<uint8_t, 16> t{};
43 for (uint32_t i = 0; i < 16; i++)
44 t[i] = static_cast<uint8_t>(bmc_lut_encode(i, 4));
45 return t;
46}();
47
48// 8-bit BMC lookup table: 256 entries (512 bytes in flash)
49// Index: 8-bit data value (0-255), always phase=true start
50static constexpr auto BMC_LUT_8 = [] {
51 std::array<uint16_t, 256> t{};
52 for (uint32_t i = 0; i < 256; i++)
53 t[i] = bmc_lut_encode(i, 8);
54 return t;
55}();
56
57// Initialize S/PDIF buffer
59 this->spdif_block_buf_ = std::make_unique<uint32_t[]>(SPDIF_BLOCK_SIZE_U32);
60 if (!this->spdif_block_buf_) {
61 ESP_LOGE(TAG, "Buffer allocation failed (%zu bytes)", SPDIF_BLOCK_SIZE_BYTES);
62 return false;
63 }
64 ESP_LOGV(TAG, "Buffer allocated (%zu bytes)", SPDIF_BLOCK_SIZE_BYTES);
65
66 // Build initial channel status block with default sample rate
68
69 this->reset();
70 return true;
71}
72
74 this->spdif_block_ptr_ = this->spdif_block_buf_.get();
75 this->frame_in_block_ = 0;
76 this->is_left_channel_ = true;
77}
78
80 if (this->sample_rate_ != sample_rate) {
81 this->sample_rate_ = sample_rate;
83 ESP_LOGD(TAG, "Sample rate set to %lu Hz", (unsigned long) sample_rate);
84 }
85}
86
88 // IEC 60958-3 Consumer Channel Status Block (192 bits = 24 bytes)
89 // Transmitted LSB-first within each byte, one bit per frame via C bit
90 //
91 // Byte 0: Control bits
92 // Bit 0: 0 = Consumer format (not professional AES3)
93 // Bit 1: 0 = PCM audio (not non-audio data like AC3)
94 // Bit 2: 0 = No copyright assertion
95 // Bits 3-5: 000 = No pre-emphasis
96 // Bits 6-7: 00 = Mode 0 (basic consumer format)
97 //
98 // Byte 1: Category code (0x00 = general, 0x01 = CD, etc.)
99 //
100 // Byte 2: Source/channel numbers
101 // Bits 0-3: Source number (0 = unspecified)
102 // Bits 4-7: Channel number (0 = unspecified)
103 //
104 // Byte 3: Sample frequency and clock accuracy
105 // Bits 0-3: Sample frequency code
106 // Bits 4-5: Clock accuracy (00 = Level II, ±1000 ppm, appropriate for ESP32)
107 // Bits 6-7: Reserved (0)
108 //
109 // Bytes 4-23: Reserved (zeros for basic compliance)
110
111 // Clear all bytes first
112 this->channel_status_.fill(0);
113
114 // Byte 0: Consumer, PCM audio, no copyright, no pre-emphasis, Mode 0
115 // All bits are 0, which is already set
116
117 // Byte 1: Category code = 0x00 (general)
118 // Already 0
119
120 // Byte 2: Source/channel unspecified
121 // Already 0
122
123 // Byte 3: Sample frequency code (bits 0-3) + clock accuracy (bits 4-5)
124 // Clock accuracy = 00 (Level II, ±1000 ppm) - appropriate for ESP32
125 uint8_t freq_code;
126 switch (this->sample_rate_) {
127 case 44100:
128 freq_code = 0x0; // 0000
129 break;
130 case 48000:
131 freq_code = 0x2; // 0010
132 break;
133 default:
134 // Other values are possible but they're not supported by ESPHome
135 freq_code = 0x1; // 0001 = not indicated
136 ESP_LOGW(TAG, "Unsupported sample rate %lu Hz, channel status will indicate 'not specified'",
137 (unsigned long) this->sample_rate_);
138 break;
139 }
140 // Byte 3: freq_code in bits 0-3, clock accuracy (00) in bits 4-5
141 this->channel_status_[3] = freq_code; // Clock accuracy bits 4-5 are already 0
142
143 // Bytes 4-23 remain zero (word length not specified, no original sample freq, etc.)
144}
145
146HOT void SPDIFEncoder::encode_sample_(const uint8_t *pcm_sample) {
147 // ============================================================================
148 // Build raw 32-bit subframe (IEC 60958 format)
149 // ============================================================================
150 // Bit layout:
151 // Bits 0-3: Preamble (handled separately, not in raw_subframe)
152 // Bits 4-7: Auxiliary audio data (zeros for 16-bit audio)
153 // Bits 8-11: Audio LSB extension (zeros for 16-bit audio)
154 // Bits 12-27: 16-bit audio sample (MSB-aligned in 20-bit audio field)
155 // Bit 28: V (Validity) - 0 = valid audio
156 // Bit 29: U (User data) - 0
157 // Bit 30: C (Channel status) - from channel status block
158 // Bit 31: P (Parity) - even parity over bits 4-31
159 // ============================================================================
160
161 // Place 16-bit audio sample at bits 12-27 (little-endian input: [0]=LSB, [1]=MSB)
162 uint32_t raw_subframe = (static_cast<uint32_t>(pcm_sample[1]) << 20) | (static_cast<uint32_t>(pcm_sample[0]) << 12);
163
164 // V = 0 (valid audio), U = 0 (no user data)
165 // C = channel status bit for current frame (same bit used for both L and R subframes)
166 bool c_bit = this->get_channel_status_bit_(this->frame_in_block_);
167 if (c_bit) {
168 raw_subframe |= (1U << 30);
169 }
170
171 // Calculate even parity over bits 4-30
172 // This ensures consistent BMC ending phase regardless of audio content
173 uint32_t bits_4_30 = (raw_subframe >> 4) & 0x07FFFFFF; // 27 bits (4-30)
174 uint32_t ones_count = __builtin_popcount(bits_4_30);
175 uint32_t parity = ones_count & 1; // 1 if odd count, 0 if even
176 raw_subframe |= parity << 31; // Set P bit to make total even
177
178 // ============================================================================
179 // Select preamble based on position in block and channel
180 // ============================================================================
181 // B = block start (left channel, frame 0 of 192-frame block)
182 // M = left channel (frames 1-191)
183 // W = right channel (all frames)
184 uint8_t preamble;
185 if (this->is_left_channel_) {
186 preamble = (this->frame_in_block_ == 0) ? PREAMBLE_B : PREAMBLE_M;
187 } else {
188 preamble = PREAMBLE_W;
189 }
190
191 // ============================================================================
192 // BMC encode the data portion (bits 4-31) using lookup tables
193 // ============================================================================
194 // The I2S uses 16-bit halfword swap: bits 16-31 transmit before bits 0-15.
195 // This applies to BOTH word[0] and word[1].
196 //
197 // word[0] transmission order: [16-23] → [24-31] → [0-7] → [8-15]
198 // For correct S/PDIF subframe order (preamble → aux → audio):
199 // - bits 16-23: preamble (8 BMC bits)
200 // - bits 24-31: BMC(subframe bits 4-7) - first aux nibble
201 // - bits 0-7: BMC(subframe bits 8-11) - second aux nibble
202 // - bits 8-15: BMC(subframe bits 12-15) - audio low nibble
203 //
204 // word[1] transmission order: [16-31] → [0-15]
205 // For correct S/PDIF subframe order:
206 // - bits 16-31: BMC(subframe bits 16-23) - audio mid byte
207 // - bits 0-15: BMC(subframe bits 24-31) - audio high nibble + VUCP
208 // ============================================================================
209
210 // All preambles end at phase HIGH. Bits 4-11 are always zero for 16-bit audio;
211 // two zero nibbles flip phase 8 times total → back to HIGH.
212 // So bits 12-15 always start encoding at phase=true.
213
214 // Bits 12-15: 4-bit LUT lookup (always phase=true start)
215 uint32_t nibble = (raw_subframe >> 12) & 0xF;
216 uint32_t bmc_12_15 = BMC_LUT_4[nibble];
217
218 // Phase tracking via branchless XOR mask:
219 // - 0x0000 means phase=true (use LUT value directly)
220 // - 0xFFFF means phase=false (complement LUT value)
221 // End phase = start XOR (popcount & 1) since zero-bits flip phase,
222 // and for even bit widths: #zeros parity == popcount parity.
223 uint32_t phase_mask = -(__builtin_popcount(nibble) & 1u) & 0xFFFF;
224
225 // Bits 16-23: 8-bit LUT lookup with phase correction
226 uint32_t byte_mid = (raw_subframe >> 16) & 0xFF;
227 uint32_t bmc_16_23 = BMC_LUT_8[byte_mid] ^ phase_mask;
228 phase_mask ^= -(__builtin_popcount(byte_mid) & 1u) & 0xFFFF;
229
230 // Bits 24-31: 8-bit LUT lookup with phase correction
231 uint32_t byte_hi = (raw_subframe >> 24) & 0xFF;
232 uint32_t bmc_24_31 = BMC_LUT_8[byte_hi] ^ phase_mask;
233
234 // ============================================================================
235 // Combine with correct positioning for I2S transmission
236 // ============================================================================
237 // I2S with halfword swap: transmits bits 16-31, then bits 0-15.
238 // Within each halfword, MSB (highest bit) is transmitted first.
239 //
240 // For upper halfword (bits 16-31): bit 31 → bit 16
241 // For lower halfword (bits 0-15): bit 15 → bit 0
242 //
243 // Desired S/PDIF order: preamble → bmc_4_7 → bmc_8_11 → bmc_12_15
244 //
245 // word[0] layout for correct transmission:
246 // bits 24-31: preamble (transmitted 1st, as MSB of upper halfword)
247 // bits 16-23: BMC_ZERO_NIBBLE (transmitted 2nd, aux bits 4-7)
248 // bits 8-15: BMC_ZERO_NIBBLE (transmitted 3rd, aux bits 8-11)
249 // bits 0-7: bmc_12_15 (transmitted 4th, audio low nibble)
250 //
251 // word[1] layout:
252 // bits 16-31: bmc_16_23 (transmitted 5th)
253 // bits 0-15: bmc_24_31 (transmitted 6th)
254 this->spdif_block_ptr_[0] =
255 bmc_12_15 | (BMC_ZERO_NIBBLE << 8) | (BMC_ZERO_NIBBLE << 16) | (static_cast<uint32_t>(preamble) << 24);
256 this->spdif_block_ptr_[1] = bmc_24_31 | (bmc_16_23 << 16);
257 this->spdif_block_ptr_ += 2;
258
259 // ============================================================================
260 // Update position tracking
261 // ============================================================================
262 if (!this->is_left_channel_) {
263 // Completed a stereo frame, advance frame counter
264 if (++this->frame_in_block_ >= SPDIF_BLOCK_SAMPLES) {
265 this->frame_in_block_ = 0;
266 }
267 }
268 this->is_left_channel_ = !this->is_left_channel_;
269}
270
271esp_err_t SPDIFEncoder::send_block_(TickType_t ticks_to_wait) {
272 // Use the appropriate callback and context based on preload mode
273 SPDIFBlockCallback callback;
274 void *ctx;
275
276 if (this->preload_mode_) {
277 callback = this->preload_callback_;
278 ctx = this->preload_callback_ctx_;
279 } else {
280 callback = this->write_callback_;
281 ctx = this->write_callback_ctx_;
282 }
283
284 if (callback == nullptr) {
285 return ESP_ERR_INVALID_STATE;
286 }
287
288 esp_err_t err = callback(ctx, this->spdif_block_buf_.get(), SPDIF_BLOCK_SIZE_BYTES, ticks_to_wait);
289
290 if (err == ESP_OK) {
291 // Reset pointer for next block; position tracking continues from where it left off
292 this->spdif_block_ptr_ = this->spdif_block_buf_.get();
293 }
294
295 return err;
296}
297
299 if (this->spdif_block_ptr_ == nullptr || this->spdif_block_buf_ == nullptr) {
300 return 0;
301 }
302 // Each PCM sample (2 bytes) produces 2 uint32_t values in the SPDIF buffer
303 // So pending uint32s / 2 = pending samples, and each sample is 2 bytes
304 size_t pending_uint32s = this->spdif_block_ptr_ - this->spdif_block_buf_.get();
305 size_t pending_samples = pending_uint32s / 2;
306 return pending_samples * 2; // 2 bytes per sample
307}
308
309HOT esp_err_t SPDIFEncoder::write(const uint8_t *src, size_t size, TickType_t ticks_to_wait, uint32_t *blocks_sent,
310 size_t *bytes_consumed) {
311 const uint8_t *pcm_data = src;
312 const uint8_t *pcm_end = src + size;
313 uint32_t block_count = 0;
314
315 while (pcm_data < pcm_end) {
316 // Check if there's a pending complete block from a previous failed send
317 if (this->spdif_block_ptr_ >= &this->spdif_block_buf_[SPDIF_BLOCK_SIZE_U32]) {
318 esp_err_t err = this->send_block_(ticks_to_wait);
319 if (err != ESP_OK) {
320 if (blocks_sent != nullptr) {
321 *blocks_sent = block_count;
322 }
323 if (bytes_consumed != nullptr) {
324 *bytes_consumed = pcm_data - src;
325 }
326 return err;
327 }
328 ++block_count;
329 }
330
331 // Encode one 16-bit sample
332 this->encode_sample_(pcm_data);
333 pcm_data += 2;
334 }
335
336 // Send any complete block that was just finished
337 if (this->spdif_block_ptr_ >= &this->spdif_block_buf_[SPDIF_BLOCK_SIZE_U32]) {
338 esp_err_t err = this->send_block_(ticks_to_wait);
339 if (err != ESP_OK) {
340 if (blocks_sent != nullptr) {
341 *blocks_sent = block_count;
342 }
343 if (bytes_consumed != nullptr) {
344 *bytes_consumed = pcm_data - src;
345 }
346 return err;
347 }
348 ++block_count;
349 }
350
351 if (blocks_sent != nullptr) {
352 *blocks_sent = block_count;
353 }
354 if (bytes_consumed != nullptr) {
355 *bytes_consumed = size;
356 }
357 return ESP_OK;
358}
359
360esp_err_t SPDIFEncoder::flush_with_silence(TickType_t ticks_to_wait) {
361 // If a complete block is already pending (from a previous failed send), emit just that block.
362 // Otherwise pad the partial block with silence (or generate a full silence block if empty)
363 // and send. Always emits exactly one block on success.
364 if (this->spdif_block_ptr_ < &this->spdif_block_buf_[SPDIF_BLOCK_SIZE_U32]) {
365 static const uint8_t SILENCE[2] = {0, 0};
366 while (this->spdif_block_ptr_ < &this->spdif_block_buf_[SPDIF_BLOCK_SIZE_U32]) {
367 this->encode_sample_(SILENCE);
368 }
369 }
370 return this->send_block_(ticks_to_wait);
371}
372
373} // namespace esphome::i2s_audio
374
375#endif // USE_I2S_AUDIO_SPDIF_MODE
std::unique_ptr< uint32_t[]> spdif_block_buf_
esp_err_t send_block_(TickType_t ticks_to_wait)
Send the completed block via the appropriate callback.
size_t get_pending_pcm_bytes() const
Get the number of PCM bytes currently pending in the partial block buffer.
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.
void set_sample_rate(uint32_t sample_rate)
Set the sample rate for Channel Status Block encoding.
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.
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.
ESPHOME_ALWAYS_INLINE bool get_channel_status_bit_(uint8_t frame) const
Get the channel status bit for a specific frame.
HOT void encode_sample_(const uint8_t *pcm_sample)
Encode a single 16-bit PCM sample into the current block position.
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)
static float float float t
const std::vector< uint8_t > & data
uint16_t size
Definition helpers.cpp:25
static void uint32_t