|
| 1 | +#pragma once |
| 2 | + |
| 3 | +#include "AudioConfig.h" |
| 4 | +#include "AudioI2S/I2SConfig.h" |
| 5 | +#include "AudioI2S/I2SStream.h" |
| 6 | +#include "AudioTools/AudioStreams.h" |
| 7 | + |
| 8 | +#define LEFTCHANNEL 0 |
| 9 | +#define RIGHTCHANNEL 1 |
| 10 | + |
| 11 | +typedef int16_t frame[2]; |
| 12 | + |
| 13 | +// BMC (Biphase Mark Coded) values (bit order reversed, i.e. LSB first) |
| 14 | +static const uint16_t spdif_bmclookup[256] PROGMEM = { |
| 15 | + 0xcccc, 0x4ccc, 0x2ccc, 0xaccc, 0x34cc, 0xb4cc, 0xd4cc, 0x54cc, 0x32cc, |
| 16 | + 0xb2cc, 0xd2cc, 0x52cc, 0xcacc, 0x4acc, 0x2acc, 0xaacc, 0x334c, 0xb34c, |
| 17 | + 0xd34c, 0x534c, 0xcb4c, 0x4b4c, 0x2b4c, 0xab4c, 0xcd4c, 0x4d4c, 0x2d4c, |
| 18 | + 0xad4c, 0x354c, 0xb54c, 0xd54c, 0x554c, 0x332c, 0xb32c, 0xd32c, 0x532c, |
| 19 | + 0xcb2c, 0x4b2c, 0x2b2c, 0xab2c, 0xcd2c, 0x4d2c, 0x2d2c, 0xad2c, 0x352c, |
| 20 | + 0xb52c, 0xd52c, 0x552c, 0xccac, 0x4cac, 0x2cac, 0xacac, 0x34ac, 0xb4ac, |
| 21 | + 0xd4ac, 0x54ac, 0x32ac, 0xb2ac, 0xd2ac, 0x52ac, 0xcaac, 0x4aac, 0x2aac, |
| 22 | + 0xaaac, 0x3334, 0xb334, 0xd334, 0x5334, 0xcb34, 0x4b34, 0x2b34, 0xab34, |
| 23 | + 0xcd34, 0x4d34, 0x2d34, 0xad34, 0x3534, 0xb534, 0xd534, 0x5534, 0xccb4, |
| 24 | + 0x4cb4, 0x2cb4, 0xacb4, 0x34b4, 0xb4b4, 0xd4b4, 0x54b4, 0x32b4, 0xb2b4, |
| 25 | + 0xd2b4, 0x52b4, 0xcab4, 0x4ab4, 0x2ab4, 0xaab4, 0xccd4, 0x4cd4, 0x2cd4, |
| 26 | + 0xacd4, 0x34d4, 0xb4d4, 0xd4d4, 0x54d4, 0x32d4, 0xb2d4, 0xd2d4, 0x52d4, |
| 27 | + 0xcad4, 0x4ad4, 0x2ad4, 0xaad4, 0x3354, 0xb354, 0xd354, 0x5354, 0xcb54, |
| 28 | + 0x4b54, 0x2b54, 0xab54, 0xcd54, 0x4d54, 0x2d54, 0xad54, 0x3554, 0xb554, |
| 29 | + 0xd554, 0x5554, 0x3332, 0xb332, 0xd332, 0x5332, 0xcb32, 0x4b32, 0x2b32, |
| 30 | + 0xab32, 0xcd32, 0x4d32, 0x2d32, 0xad32, 0x3532, 0xb532, 0xd532, 0x5532, |
| 31 | + 0xccb2, 0x4cb2, 0x2cb2, 0xacb2, 0x34b2, 0xb4b2, 0xd4b2, 0x54b2, 0x32b2, |
| 32 | + 0xb2b2, 0xd2b2, 0x52b2, 0xcab2, 0x4ab2, 0x2ab2, 0xaab2, 0xccd2, 0x4cd2, |
| 33 | + 0x2cd2, 0xacd2, 0x34d2, 0xb4d2, 0xd4d2, 0x54d2, 0x32d2, 0xb2d2, 0xd2d2, |
| 34 | + 0x52d2, 0xcad2, 0x4ad2, 0x2ad2, 0xaad2, 0x3352, 0xb352, 0xd352, 0x5352, |
| 35 | + 0xcb52, 0x4b52, 0x2b52, 0xab52, 0xcd52, 0x4d52, 0x2d52, 0xad52, 0x3552, |
| 36 | + 0xb552, 0xd552, 0x5552, 0xccca, 0x4cca, 0x2cca, 0xacca, 0x34ca, 0xb4ca, |
| 37 | + 0xd4ca, 0x54ca, 0x32ca, 0xb2ca, 0xd2ca, 0x52ca, 0xcaca, 0x4aca, 0x2aca, |
| 38 | + 0xaaca, 0x334a, 0xb34a, 0xd34a, 0x534a, 0xcb4a, 0x4b4a, 0x2b4a, 0xab4a, |
| 39 | + 0xcd4a, 0x4d4a, 0x2d4a, 0xad4a, 0x354a, 0xb54a, 0xd54a, 0x554a, 0x332a, |
| 40 | + 0xb32a, 0xd32a, 0x532a, 0xcb2a, 0x4b2a, 0x2b2a, 0xab2a, 0xcd2a, 0x4d2a, |
| 41 | + 0x2d2a, 0xad2a, 0x352a, 0xb52a, 0xd52a, 0x552a, 0xccaa, 0x4caa, 0x2caa, |
| 42 | + 0xacaa, 0x34aa, 0xb4aa, 0xd4aa, 0x54aa, 0x32aa, 0xb2aa, 0xd2aa, 0x52aa, |
| 43 | + 0xcaaa, 0x4aaa, 0x2aaa, 0xaaaa}; |
| 44 | + |
| 45 | +const uint32_t VUCP_PREAMBLE_B = 0xCCE80000; // 11001100 11101000 |
| 46 | +const uint32_t VUCP_PREAMBLE_M = 0xCCE20000; // 11001100 11100010 |
| 47 | +const uint32_t VUCP_PREAMBLE_W = 0xCCE40000; // 11001100 11100100 |
| 48 | + |
| 49 | +/** |
| 50 | + * @brief SPDIF configuration |
| 51 | + * We only support 16 bits per sample |
| 52 | + */ |
| 53 | +struct SPDIFConfig { |
| 54 | + int port_no = 0; // processor dependent port |
| 55 | + int pin_data = PIN_I2S_DATA_OUT; |
| 56 | + int sample_rate = 44100; |
| 57 | + int channels = 2; |
| 58 | +}; |
| 59 | + |
| 60 | +/** |
| 61 | + * @brief Output as SPDIF on the data output pin |
| 62 | + * |
| 63 | + */ |
| 64 | +class SPDIFStream : public AudioStream { |
| 65 | + public: |
| 66 | + SPDIFStream() = default; |
| 67 | + |
| 68 | + bool begin(SPDIFConfig cfg) { |
| 69 | + this->cfg = cfg; |
| 70 | + i2s_cfg.sample_rate = cfg.sample_rate * 2; // 2 x sampling_rate |
| 71 | + i2s_cfg.bits_per_sample = 32; |
| 72 | + i2s_cfg.use_apll = true; |
| 73 | + i2s_cfg.channels = 2; |
| 74 | + i2s_cfg.pin_ws = -1; |
| 75 | + i2s_cfg.pin_bck = -1; |
| 76 | + i2s_cfg.pin_data = cfg.pin_data; |
| 77 | + |
| 78 | + i2s.begin(i2s_cfg); |
| 79 | + i2sOn = true; |
| 80 | + frame_num = 0; |
| 81 | + } |
| 82 | + |
| 83 | + bool end() { |
| 84 | + i2s.end(); |
| 85 | + frame_num = 0; |
| 86 | + i2sOn = false; |
| 87 | + return true; |
| 88 | + } |
| 89 | + |
| 90 | + size_t write(uint8_t *values, size_t len) { |
| 91 | + size_t result = 0; |
| 92 | + int16_t *v = (int16_t *)values; |
| 93 | + if (cfg.channels == 2) { |
| 94 | + for (int j = 0; j < len / 32; j++) { |
| 95 | + // provide the 2 channels from the data |
| 96 | + result += processFrame(v[j], v[j + 1]); |
| 97 | + } |
| 98 | + } else if (cfg.channels == 1) { |
| 99 | + for (int j = 0; j < len / 16; j++) { |
| 100 | + // generate 2 channels |
| 101 | + result += processFrame(v[j], v[j]); |
| 102 | + } |
| 103 | + } |
| 104 | + return result; |
| 105 | + } |
| 106 | + |
| 107 | + protected: |
| 108 | + SPDIFConfig cfg; |
| 109 | + I2SStream i2s; |
| 110 | + I2SConfig i2s_cfg; |
| 111 | + bool i2sOn; |
| 112 | + uint8_t frame_num = 0; |
| 113 | + |
| 114 | + // process a single frame |
| 115 | + int processFrame(int16_t left, int16_t right) { |
| 116 | + if (!i2sOn) return true; // Sink the data |
| 117 | + int16_t ms[2]; |
| 118 | + uint16_t hi, lo, aux; |
| 119 | + uint32_t buf[4]; |
| 120 | + |
| 121 | + ms[0] = left; |
| 122 | + ms[1] = right; |
| 123 | + |
| 124 | + // S/PDIF encoding: |
| 125 | + // http://www.hardwarebook.info/S/PDIF |
| 126 | + // Original sources: Teensy Audio Library |
| 127 | + // https://github.com/PaulStoffregen/Audio/blob/master/output_spdif2.cpp |
| 128 | + // |
| 129 | + // Order of bits, before BMC encoding, from the definition of SPDIF format |
| 130 | + // PPPP AAAA SSSS SSSS SSSS SSSS SSSS VUCP |
| 131 | + // are sent rearanged as |
| 132 | + // VUCP PPPP AAAA 0000 SSSS SSSS SSSS SSSS |
| 133 | + // This requires a bit less shifting as 16 sample bits align and can be |
| 134 | + // BMC encoded with two table lookups (and at the same time flipped to LSB |
| 135 | + // first). There is no separate word-clock, so hopefully the receiver won't |
| 136 | + // notice. |
| 137 | + |
| 138 | + uint16_t sample_left = ms[LEFTCHANNEL]; |
| 139 | + // BMC encode and flip left channel bits |
| 140 | + hi = spdif_bmclookup[(uint8_t)(sample_left >> 8)]; |
| 141 | + lo = spdif_bmclookup[(uint8_t)sample_left]; |
| 142 | + // Low word is inverted depending on first bit of high word |
| 143 | + lo ^= (~((int16_t)hi) >> 16); |
| 144 | + buf[0] = ((uint32_t)lo << 16) | hi; |
| 145 | + // Fixed 4 bits auxillary-audio-databits, the first used as parity |
| 146 | + // Depending on first bit of low word, invert the bits |
| 147 | + aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17); |
| 148 | + // Send 'B' preamble only for the first frame of data-block |
| 149 | + if (frame_num == 0) { |
| 150 | + buf[1] = VUCP_PREAMBLE_B | aux; |
| 151 | + } else { |
| 152 | + buf[1] = VUCP_PREAMBLE_M | aux; |
| 153 | + } |
| 154 | + |
| 155 | + uint16_t sample_right = ms[RIGHTCHANNEL]; |
| 156 | + // BMC encode right channel, similar as above |
| 157 | + hi = spdif_bmclookup[(uint8_t)(sample_right >> 8)]; |
| 158 | + lo = spdif_bmclookup[(uint8_t)sample_right]; |
| 159 | + lo ^= (~((int16_t)hi) >> 16); |
| 160 | + buf[2] = ((uint32_t)lo << 16) | hi; |
| 161 | + aux = 0xb333 ^ (((uint32_t)((int16_t)lo)) >> 17); |
| 162 | + buf[3] = VUCP_PREAMBLE_W | aux; |
| 163 | + |
| 164 | + // Assume DMA buffers are multiples of 16 bytes. Either we write all bytes |
| 165 | + // or none. |
| 166 | + int len = 8 * cfg.channels; |
| 167 | + size_t bytes_written = i2s.write((const uint8_t *)&buf, len); |
| 168 | + // If we didn't write all bytes, return false early and do not increment |
| 169 | + // frame_num |
| 170 | + if (bytes_written != len) return 0; |
| 171 | + // Increment and rotate frame number |
| 172 | + if (++frame_num > 191) frame_num = 0; |
| 173 | + return len; |
| 174 | + } |
| 175 | +}; |
0 commit comments