arduino-audio-tools
CodecBase64.h
1 
2 #pragma once
3 
4 #include "AudioBasic/Str.h"
5 #include "AudioCodecs/AudioEncoded.h"
6 
7 namespace audio_tools {
8 
9 
10 enum Base46Logic { NoCR, CRforFrame, CRforWrite };
11 static char encoding_table[] = {
12  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
13  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
14  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
15  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
16  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
17 static int mod_table[] = {0, 2, 1};
18 
19 static const int B64index[256] = {
20  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
21  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
22  0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57,
23  58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6,
24  7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25  25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
26  37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51};
27 
38 class DecoderBase64 : public AudioDecoder {
39  public:
44  DecoderBase64() { TRACED(); }
45 
52  TRACED();
53  setOutput(out);
54  }
55 
57  void setOutput(Print &out) override { p_print = &out; }
58 
60  void setNewLine(Base46Logic logic) { newline_logic = logic; }
61 
62  bool begin() override {
63  TRACED();
64  is_valid = newline_logic == NoCR;
65  active = true;
66  return true;
67  }
68 
69  void end() override {
70  TRACED();
71  // deconde ramaining bytes
72  int len = buffer.available();
73  uint8_t tmp[len];
74  buffer.readArray(tmp, len);
75  decodeLine(tmp, len);
76 
77  active = false;
78  buffer.resize(0);
79  }
80 
81  size_t write(const void *data, size_t byteCount) override {
82  if (p_print == nullptr) return 0;
83  TRACED();
84  addToBuffer((uint8_t *)data, byteCount);
85  int decode_size = 4; // maybe we should increase this ?
86  while (buffer.available() >= decode_size) {
87  uint8_t tmp[decode_size];
88  buffer.readArray(tmp, decode_size);
89  decodeLine(tmp, decode_size);
90  }
91 
92  return byteCount;
93  }
94 
95  operator bool() override { return active; }
96 
97  protected:
98  bool active = false;
99  bool is_valid = false;
100  Base46Logic newline_logic = CRforFrame;
101  Vector<uint8_t> result;
102  RingBuffer<uint8_t> buffer{1500};
103  AudioInfo info;
104 
105  void decodeLine(uint8_t *data, size_t byteCount) {
106  LOGD("decode: %d", (int)byteCount);
107  int len = byteCount;
108 
109  unsigned char *p = (unsigned char *)data;
110  int pad = len > 0 && (len % 4 || p[len - 1] == '=');
111  const size_t L = ((len + 3) / 4 - pad) * 4;
112  result.resize(L / 4 * 3 + pad);
113  memset(result.data(), 0, result.size());
114 
115  for (size_t i = 0, j = 0; i < L; i += 4) {
116  int32_t n = static_cast<int32_t>(B64index[p[i]]) << 18 | B64index[p[i + 1]] << 12 |
117  B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
118  result[j++] = n >> 16;
119  result[j++] = n >> 8 & 0xFF;
120  result[j++] = n & 0xFF;
121  }
122  if (pad) {
123  int32_t n = static_cast<int32_t>(B64index[p[L]]) << 18 | B64index[p[L + 1]] << 12;
124  result[result.size() - 1] = n >> 16;
125 
126  if (len > L + 2 && p[L + 2] != '=') {
127  n |= B64index[p[L + 2]] << 6;
128  result.push_back(n >> 8 & 0xFF);
129  }
130  }
131  writeBlocking(p_print, result.data(), result.size());
132  }
133 
134  void addToBuffer(uint8_t *data, size_t len) {
135  TRACED();
136  if (buffer.size() < len) {
137  buffer.resize(len);
138  }
139  // syncronize to find a valid start position
140  int start = 0;
141  if (!is_valid) {
142  for (int j = 0; j < len; j++) {
143  if (data[j] == '\n') {
144  start = j;
145  is_valid = true;
146  break;
147  }
148  }
149  }
150 
151  if (is_valid) {
152  // remove white space
153  for (int j = start; j < len; j++) {
154  if (!isspace(data[j])) {
155  buffer.write(data[j]);
156  } else if (data[j] == '\n') {
157  int offset = buffer.available() % 4;
158  if (offset > 0) {
159  LOGW("Resync %d (-%d)...", buffer.available(), offset);
160  uint8_t tmp[4];
161  buffer.readArray(tmp, offset);
162  }
163  }
164  }
165  }
166  LOGD("buffer: %d, is_valid: %s", buffer.available(),
167  is_valid ? "true" : "false");
168  }
169 };
170 
171 
182 class EncoderBase64 : public AudioEncoder {
183  public:
184  // Empty Conbuffeructor - the output buffeream must be provided with begin()
185  EncoderBase64() {}
186 
187  // Conbuffeructor providing the output buffeream
188  EncoderBase64(Print &out) { p_print = &out; }
189 
191  void setOutput(Print &out_buffeream) override {
192  p_print = &out_buffeream;
193  }
194 
196  const char *mime() override { return "text/base64"; }
197 
199  void setNewLine(Base46Logic flag) { newline_logic = flag; }
200 
202  virtual bool begin() override {
203  is_open = true;
204  frame_size = info.bits_per_sample * info.channels / 8;
205  if (newline_logic != NoCR) {
206  if (frame_size==0){
207  LOGW("AudioInfo not defined");
208  // assume frame size
209  frame_size = 4;
210  }
211  p_print->write('\n');
212  flush();
213  }
214  return true;
215  }
216 
218  void end() override { is_open = false; }
219 
221  virtual size_t write(const void *binary, size_t len) override {
222  LOGD("EncoderBase64::write: %d", (int)len);
223  uint8_t *data = (uint8_t *)binary;
224 
225  switch (newline_logic) {
226  case NoCR:
227  case CRforWrite:
228  encodeLine((uint8_t *)binary, len);
229  break;
230  case CRforFrame: {
231  int frames = len / frame_size;
232  int open = len;
233  int offset = 0;
234  while (open > 0) {
235  int write_size = min(frame_size, open);
236  encodeLine(data + offset, write_size);
237  open -= write_size;
238  offset += write_size;
239  }
240  break;
241  }
242  }
243 
244  return len;
245  }
246 
247  operator bool() override { return is_open; }
248 
249  bool isOpen() { return is_open; }
250 
251  protected:
252  Print *p_print = nullptr;
253  bool is_open;
254  Base46Logic newline_logic = CRforFrame;
255  Vector<uint8_t> ret;
256  AudioInfo info;
257  int frame_size;
258 
259  void flush() {
260 #if defined(ESP32)
261 # if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(3, 3, 5)
262  p_print->flush();
263 # endif
264 #else
265  p_print->flush();
266 #endif
267  }
268 
269  void encodeLine(uint8_t *data, size_t input_length) {
270  LOGD("EncoderBase64::encodeLine: %d", (int)input_length);
271  int output_length = 4 * ((input_length + 2) / 3);
272  if (ret.size() < output_length + 1) {
273  ret.resize(output_length + 1);
274  }
275 
276  for (int i = 0, j = 0; i < input_length;) {
277  uint32_t octet_a = i < input_length ? (unsigned char)data[i++] : 0;
278  uint32_t octet_b = i < input_length ? (unsigned char)data[i++] : 0;
279  uint32_t octet_c = i < input_length ? (unsigned char)data[i++] : 0;
280 
281  uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
282 
283  ret[j++] = encoding_table[(triple >> 3 * 6) & 0x3F];
284  ret[j++] = encoding_table[(triple >> 2 * 6) & 0x3F];
285  ret[j++] = encoding_table[(triple >> 1 * 6) & 0x3F];
286  ret[j++] = encoding_table[(triple >> 0 * 6) & 0x3F];
287  }
288 
289  for (int i = 0; i < mod_table[input_length % 3]; i++)
290  ret[output_length - 1 - i] = '=';
291 
292  // add a new line to the end
293  if (newline_logic != NoCR) {
294  ret[output_length] = '\n';
295  output_length++;
296  }
297 
298  writeBlocking(p_print, ret.data(), output_length);
299  flush();
300  }
301 };
302 
303 } // namespace audio_tools
Docoding of encoded audio into PCM data.
Definition: AudioEncoded.h:18
Encoding of PCM data.
Definition: AudioEncoded.h:88
virtual int readArray(T data[], int len)
reads multiple values
Definition: Buffers.h:41
DecoderBase64 - Converts a Base64 encoded Stream into the original data stream. Decoding only gives a...
Definition: CodecBase64.h:38
void setOutput(Print &out) override
Defines the output Stream.
Definition: CodecBase64.h:57
DecoderBase64()
Constructor for a new DecoderBase64 object.
Definition: CodecBase64.h:44
DecoderBase64(Print &out)
Constructor for a new DecoderBase64 object.
Definition: CodecBase64.h:51
void setNewLine(Base46Logic logic)
We expect new lines to delimit the individual lines.
Definition: CodecBase64.h:60
EncoderBase64s - Encodes the input data into a Base64 string. By default each audio frame is followed...
Definition: CodecBase64.h:182
virtual size_t write(const void *binary, size_t len) override
Writes PCM data to be encoded as RAW.
Definition: CodecBase64.h:221
void setOutput(Print &out_buffeream) override
Defines the output Stream.
Definition: CodecBase64.h:191
virtual bool begin() override
starts the processing using the actual RAWAudioInfo
Definition: CodecBase64.h:202
const char * mime() override
Provides "text/base64".
Definition: CodecBase64.h:196
void end() override
stops the processing
Definition: CodecBase64.h:218
void setNewLine(Base46Logic flag)
We add a new line after each write.
Definition: CodecBase64.h:199
Definition: NoArduino.h:58
virtual int available()
provides the number of entries that are available to read
Definition: Buffers.h:362
virtual bool write(T data)
write add an entry to the buffer
Definition: Buffers.h:343
virtual size_t size()
Returns the maximum capacity of the buffer.
Definition: Buffers.h:379
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10