arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
SPDIFOutput.h
1
18#pragma once
19
20#include "AudioConfig.h"
21#include "AudioTools/CoreAudio/AudioLogger.h"
22#include "AudioTools/CoreAudio/AudioStreams.h"
23#include "AudioTools/CoreAudio/AudioI2S/I2SConfig.h"
24#include "AudioTools/CoreAudio/AudioI2S/I2SStream.h"
25
26// Default Data Pin
27#ifndef SPDIF_DATA_PIN
28#define SPDIF_DATA_PIN 23
29#endif
30
31#define I2S_NUM ((i2s_port_t)0)
32#define I2S_BITS_PER_SAMPLE (32)
33#define I2S_CHANNELS 2
34#define BMC_BITS_PER_SAMPLE 64
35#define BMC_BITS_FACTOR (BMC_BITS_PER_SAMPLE / I2S_BITS_PER_SAMPLE)
36#define SPDIF_BLOCK_SAMPLES 192
37#define SPDIF_BUF_DIV 2 // double buffering
38#define DMA_BUF_COUNT 2
39#define DMA_BUF_LEN \
40 (SPDIF_BLOCK_SAMPLES * BMC_BITS_PER_SAMPLE / I2S_BITS_PER_SAMPLE / \
41 SPDIF_BUF_DIV)
42#define I2S_BUG_MAGIC (26 * 1000 * 1000) // magic number for avoiding I2S bug
43#define SPDIF_BLOCK_SIZE \
44 (SPDIF_BLOCK_SAMPLES * (BMC_BITS_PER_SAMPLE / 8) * I2S_CHANNELS)
45#define SPDIF_BUF_SIZE (SPDIF_BLOCK_SIZE / SPDIF_BUF_DIV)
46#define SPDIF_BUF_ARRAY_SIZE (SPDIF_BUF_SIZE / sizeof(uint32_t))
47
48// BMC preamble
49#define BMC_B 0x33173333 // block start
50#define BMC_M 0x331d3333 // left ch
51#define BMC_W 0x331b3333 // right ch
52#define BMC_MW_DIF (BMC_M ^ BMC_W)
53#define SYNC_OFFSET 2 // byte offset of SYNC
54#define SYNC_FLIP ((BMC_B ^ BMC_M) >> (SYNC_OFFSET * 8))
55
56
57namespace audio_tools {
58
59/*
60 * 8bit PCM to 16bit BMC conversion table, LSb first, 1 end
61 */
62static const uint16_t bmc_tab_uint[256] = {
63 0x3333, 0xb333, 0xd333, 0x5333, 0xcb33, 0x4b33, 0x2b33, 0xab33, 0xcd33,
64 0x4d33, 0x2d33, 0xad33, 0x3533, 0xb533, 0xd533, 0x5533, 0xccb3, 0x4cb3,
65 0x2cb3, 0xacb3, 0x34b3, 0xb4b3, 0xd4b3, 0x54b3, 0x32b3, 0xb2b3, 0xd2b3,
66 0x52b3, 0xcab3, 0x4ab3, 0x2ab3, 0xaab3, 0xccd3, 0x4cd3, 0x2cd3, 0xacd3,
67 0x34d3, 0xb4d3, 0xd4d3, 0x54d3, 0x32d3, 0xb2d3, 0xd2d3, 0x52d3, 0xcad3,
68 0x4ad3, 0x2ad3, 0xaad3, 0x3353, 0xb353, 0xd353, 0x5353, 0xcb53, 0x4b53,
69 0x2b53, 0xab53, 0xcd53, 0x4d53, 0x2d53, 0xad53, 0x3553, 0xb553, 0xd553,
70 0x5553, 0xcccb, 0x4ccb, 0x2ccb, 0xaccb, 0x34cb, 0xb4cb, 0xd4cb, 0x54cb,
71 0x32cb, 0xb2cb, 0xd2cb, 0x52cb, 0xcacb, 0x4acb, 0x2acb, 0xaacb, 0x334b,
72 0xb34b, 0xd34b, 0x534b, 0xcb4b, 0x4b4b, 0x2b4b, 0xab4b, 0xcd4b, 0x4d4b,
73 0x2d4b, 0xad4b, 0x354b, 0xb54b, 0xd54b, 0x554b, 0x332b, 0xb32b, 0xd32b,
74 0x532b, 0xcb2b, 0x4b2b, 0x2b2b, 0xab2b, 0xcd2b, 0x4d2b, 0x2d2b, 0xad2b,
75 0x352b, 0xb52b, 0xd52b, 0x552b, 0xccab, 0x4cab, 0x2cab, 0xacab, 0x34ab,
76 0xb4ab, 0xd4ab, 0x54ab, 0x32ab, 0xb2ab, 0xd2ab, 0x52ab, 0xcaab, 0x4aab,
77 0x2aab, 0xaaab, 0xcccd, 0x4ccd, 0x2ccd, 0xaccd, 0x34cd, 0xb4cd, 0xd4cd,
78 0x54cd, 0x32cd, 0xb2cd, 0xd2cd, 0x52cd, 0xcacd, 0x4acd, 0x2acd, 0xaacd,
79 0x334d, 0xb34d, 0xd34d, 0x534d, 0xcb4d, 0x4b4d, 0x2b4d, 0xab4d, 0xcd4d,
80 0x4d4d, 0x2d4d, 0xad4d, 0x354d, 0xb54d, 0xd54d, 0x554d, 0x332d, 0xb32d,
81 0xd32d, 0x532d, 0xcb2d, 0x4b2d, 0x2b2d, 0xab2d, 0xcd2d, 0x4d2d, 0x2d2d,
82 0xad2d, 0x352d, 0xb52d, 0xd52d, 0x552d, 0xccad, 0x4cad, 0x2cad, 0xacad,
83 0x34ad, 0xb4ad, 0xd4ad, 0x54ad, 0x32ad, 0xb2ad, 0xd2ad, 0x52ad, 0xcaad,
84 0x4aad, 0x2aad, 0xaaad, 0x3335, 0xb335, 0xd335, 0x5335, 0xcb35, 0x4b35,
85 0x2b35, 0xab35, 0xcd35, 0x4d35, 0x2d35, 0xad35, 0x3535, 0xb535, 0xd535,
86 0x5535, 0xccb5, 0x4cb5, 0x2cb5, 0xacb5, 0x34b5, 0xb4b5, 0xd4b5, 0x54b5,
87 0x32b5, 0xb2b5, 0xd2b5, 0x52b5, 0xcab5, 0x4ab5, 0x2ab5, 0xaab5, 0xccd5,
88 0x4cd5, 0x2cd5, 0xacd5, 0x34d5, 0xb4d5, 0xd4d5, 0x54d5, 0x32d5, 0xb2d5,
89 0xd2d5, 0x52d5, 0xcad5, 0x4ad5, 0x2ad5, 0xaad5, 0x3355, 0xb355, 0xd355,
90 0x5355, 0xcb55, 0x4b55, 0x2b55, 0xab55, 0xcd55, 0x4d55, 0x2d55, 0xad55,
91 0x3555, 0xb555, 0xd555, 0x5555,
92};
93static const int16_t *bmc_tab = (int16_t *)bmc_tab_uint;
94
95static uint32_t spdif_buf[SPDIF_BUF_ARRAY_SIZE];
96static uint32_t *spdif_ptr = nullptr;
97
103struct SPDIFConfig : public AudioInfo {
104 SPDIFConfig() {
105 bits_per_sample = 16;
106 channels = 2;
107 sample_rate = 44100;
108 }
109 int port_no = 0; // processor dependent port
110 int pin_data = SPDIF_DATA_PIN;
111 int buffer_count = 30;
112 int buffer_size = 384;
113#if defined(RP2040_HOWER)
114 int pin_bck = -1;
115 uint32_t sys_clock = 192000;
116#endif
117};
118
127class SPDIFOutput : public AudioStream {
128 public:
130 SPDIFOutput() = default;
131
133 virtual ~SPDIFOutput() { end(); }
134
136 bool begin() { return begin(cfg); }
137
139 bool begin(SPDIFConfig config) {
140 TRACED();
141 cfg = config;
142 // Some validations to make sure that the config is valid
143 if (!(cfg.channels == 1 || cfg.channels == 2)) {
144 LOGE("Unsupported number of channels: %d", cfg.channels);
145 return false;
146 }
147 if (cfg.bits_per_sample != 16) {
148 LOGE("Unsupported bits per sample: %d - must be 16!",
149 cfg.bits_per_sample);
150 return false;
151 }
152
153 if (i2sOn) {
154 i2s.end();
155 }
156
157 // initialize S/PDIF buffer
158 spdif_buf_init();
159 spdif_ptr = spdif_buf;
160
161 // Setup I2S
162 int sample_rate = cfg.sample_rate * BMC_BITS_FACTOR;
163 if (sample_rate==0){
164 TRACEE();
165 return false;
166 }
167 int bclk = sample_rate * I2S_BITS_PER_SAMPLE * I2S_CHANNELS;
168 int mclk = (I2S_BUG_MAGIC / bclk) * bclk; // use mclk for avoiding I2S bug
169
171 i2s_cfg.sample_rate = sample_rate;
172 i2s_cfg.channels = cfg.channels;
173#if defined(RP2040_HOWER)
174 if (cfg.sys_clock > 0) set_sys_clock_khz(cfg.sys_clock, false);
175 // RP2040 does not support -1 for no pin
176 if (cfg.pin_bck >= 0) {
177 i2s_cfg.pin_bck = cfg.pin_bck;
178 i2s_cfg.pin_ws = cfg.pin_bck + 1;
179 } else {
180 LOGE("pin_bck not defined");
181 }
182#elif defined(STM32)
183 // pins are fixed
184#else
185 // default logic for ESP32
186 i2s_cfg.pin_ws = -1;
187 i2s_cfg.pin_bck = -1;
188 i2s_cfg.pin_data = cfg.pin_data;
189#endif
190 i2s_cfg.buffer_count = cfg.buffer_count;
191 i2s_cfg.buffer_size = cfg.buffer_size;
192 i2s_cfg.bits_per_sample = I2S_BITS_PER_SAMPLE;
193 i2s_cfg.i2s_format = I2S_STD_FORMAT;
194 i2s_cfg.rx_tx_mode = TX_MODE;
195#ifdef ESP32
196 i2s_cfg.use_apll = true;
197# if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0 , 0)
198 i2s_cfg.fixed_mclk = mclk;
199# endif
200#endif
201 i2sOn = i2s.begin(i2s_cfg);
202 return i2sOn;
203 }
204
205 void end() {
206 TRACED();
207 i2s.end();
208 i2sOn = false;
209 }
210
213 TRACED();
214 if (info.bits_per_sample != 16) {
215 LOGE("Unsupported bits per sample: %d - must be 16!",
216 info.bits_per_sample);
217 }
218 if (cfg.bits_per_sample != info.bits_per_sample
219 || cfg.channels != info.channels
220 || cfg.sample_rate != info.sample_rate
221 || !i2sOn) {
223 cfg.channels = info.channels;
224 cfg.sample_rate = info.sample_rate;
225 begin(cfg);
226 }
227 }
228
232 return c;
233 }
234
236 size_t write(const uint8_t *data, size_t len) {
237 if (!i2sOn) return 0;
238 const uint8_t *p = data;
239 size_t result = 0;
240
241 while (p < (uint8_t *)data + len) {
242 // convert PCM 16bit data to BMC 32bit pulse pattern
243 if (cfg.channels == 2) {
244 *(spdif_ptr + 1) =
245 (uint32_t)(((bmc_tab[*p] << 16) ^ bmc_tab[*(p + 1)]) << 1) >> 1;
246 p += 2;
247 } else {
248 // must be one channels -> use the same value for both
249 *(spdif_ptr + 1) =
250 (uint32_t)(((bmc_tab[*p] << 16) ^ bmc_tab[*(p)]) << 1) >> 1;
251 p++;
252 }
253 result += 2;
254 spdif_ptr += 2; // advance to next audio data
255
256 if (spdif_ptr >= &spdif_buf[SPDIF_BUF_ARRAY_SIZE]) {
257 // set block start preamble
258 ((uint8_t *)spdif_buf)[SYNC_OFFSET] ^= SYNC_FLIP;
259
260 i2s.write((uint8_t *)spdif_buf, sizeof(spdif_buf));
261 spdif_ptr = spdif_buf;
262 }
263 }
264
265 return result;
266 }
267
268 protected:
269 bool i2sOn = false;
270 SPDIFConfig cfg;
271 I2SStream i2s;
272
273 // initialize S/PDIF buffer
274 void spdif_buf_init(void) {
275 TRACED();
276 size_t i;
277 uint32_t bmc_mw = BMC_W;
278
279 for (i = 0; i < (size_t)SPDIF_BUF_ARRAY_SIZE; i += 2) {
280 spdif_buf[i] = bmc_mw ^= BMC_MW_DIF;
281 }
282 }
283};
284
285using SPDIFStream = SPDIFOutput;
286
287} // namespace audio_tools
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:115
Configuration for ESP32 legacy i2s.
Definition I2SConfigESP32.h:23
We support the Stream interface for the I2S access. In addition we allow a separate mute pin which mi...
Definition I2SStream.h:33
virtual size_t write(const uint8_t *data, size_t len)
Writes the audio data to I2S.
Definition I2SStream.h:115
void end()
Stops the I2S interface.
Definition I2SStream.h:84
Output as 16 bit stereo SPDIF on the I2S data output pin. For the time beeing only the ESP32 is offic...
Definition SPDIFOutput.h:127
SPDIFConfig defaultConfig()
Provides the default configuration.
Definition SPDIFOutput.h:230
virtual ~SPDIFOutput()
destructor
Definition SPDIFOutput.h:133
void setAudioInfo(AudioInfo info)
Change the audio parameters.
Definition SPDIFOutput.h:212
bool begin()
Starting with last or default settings.
Definition SPDIFOutput.h:136
SPDIFOutput()=default
default constructor
bool begin(SPDIFConfig config)
Start with the provided parameters.
Definition SPDIFOutput.h:139
size_t write(const uint8_t *data, size_t len)
Writes the audio data as SPDIF to the defined output pin.
Definition SPDIFOutput.h:236
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59
SPDIF configuration.
Definition SPDIFOutput.h:103