arduino-audio-tools
CodecWAV.h
1 #pragma once
2 
3 #include "AudioCodecs/AudioEncoded.h"
5 
6 
7 #define TAG(a, b, c, d) \
8  ((static_cast<uint32_t>(a) << 24) | (static_cast<uint32_t>(b) << 16) | \
9  (static_cast<uint32_t>(c) << 8) | (d))
10 #define READ_BUFFER_SIZE 512
11 
12 namespace audio_tools {
13 
21  WAVAudioInfo() = default;
22  WAVAudioInfo(const AudioInfo &from) {
23  sample_rate = from.sample_rate;
24  channels = from.channels;
25  bits_per_sample = from.bits_per_sample;
26  }
27 
28  AudioFormat format = AudioFormat::PCM;
29  int byte_rate = 0;
30  int block_align = 0;
31  bool is_streamed = true;
32  bool is_valid = false;
33  uint32_t data_length = 0;
34  uint32_t file_size = 0;
35  int offset = 0;
36 };
37 
38 static const char *wav_mime = "audio/wav";
39 
47 class WAVHeader {
48  public:
49  WAVHeader() = default;
50 
52  int write(uint8_t *data, size_t data_len) {
53  int write_len = min(data_len, 44 - len);
54  memmove(buffer, data + len, write_len);
55  len += write_len;
56  LOGI("WAVHeader::write: %u -> %d -> %d", (unsigned)data_len, write_len,
57  (int)len);
58  return write_len;
59  }
60 
62  void parse() {
63  LOGI("WAVHeader::begin: %u", (unsigned)len);
64  this->data_pos = 0l;
65  memset((void *)&headerInfo, 0, sizeof(WAVAudioInfo));
66  while (!eof()) {
67  uint32_t tag, tag2, length;
68  tag = read_tag();
69  if (eof()) break;
70  length = read_int32();
71  if (!length || length >= 0x7fff0000) {
72  headerInfo.is_streamed = true;
73  length = ~0;
74  }
75  if (tag != TAG('R', 'I', 'F', 'F') || length < 4) {
76  seek(length, SEEK_CUR);
77  continue;
78  }
79  tag2 = read_tag();
80  length -= 4;
81  if (tag2 != TAG('W', 'A', 'V', 'E')) {
82  seek(length, SEEK_CUR);
83  continue;
84  }
85  // RIFF chunk found, iterate through it
86  while (length >= 8) {
87  uint32_t subtag, sublength;
88  subtag = read_tag();
89  if (eof()) break;
90  sublength = read_int32();
91  length -= 8;
92  if (length < sublength) break;
93  if (subtag == TAG('f', 'm', 't', ' ')) {
94  if (sublength < 16) {
95  // Insufficient data for 'fmt '
96  break;
97  }
98  headerInfo.format = (AudioFormat)read_int16();
99  headerInfo.channels = read_int16();
100  headerInfo.sample_rate = read_int32();
101  headerInfo.byte_rate = read_int32();
102  headerInfo.block_align = read_int16();
103  headerInfo.bits_per_sample = read_int16();
104  if (headerInfo.format == (AudioFormat) 0xfffe) {
105  if (sublength < 28) {
106  // Insufficient data for waveformatex
107  break;
108  }
109  skip(8);
110  headerInfo.format = (AudioFormat)read_int32();
111  skip(sublength - 28);
112  } else {
113  skip(sublength - 16);
114  }
115  headerInfo.is_valid = true;
116  } else if (subtag == TAG('d', 'a', 't', 'a')) {
117  sound_pos = tell();
118  headerInfo.data_length = sublength;
119  if (!headerInfo.data_length || headerInfo.is_streamed) {
120  headerInfo.is_streamed = true;
121  logInfo();
122  return;
123  }
124  seek(sublength, SEEK_CUR);
125  } else {
126  skip(sublength);
127  }
128  length -= sublength;
129  }
130  if (length > 0) {
131  // Bad chunk?
132  seek(length, SEEK_CUR);
133  }
134  }
135  logInfo();
136  len = 0;
137  }
138 
140  bool isDataComplete() { return len == 44; }
141 
143  WAVAudioInfo &audioInfo() { return headerInfo; }
144 
147  headerInfo = info;
148  }
149 
151  void writeHeader(Print *out) {
152  SingleBuffer<uint8_t> buffer(50);
153  writeRiffHeader(buffer);
154  writeFMT(buffer);
155  writeDataHeader(buffer);
156  len = buffer.available();
157  out->write(buffer.data(), buffer.available());
158  }
159 
160  protected:
161  struct WAVAudioInfo headerInfo;
162  uint8_t buffer[44];
163  size_t len = 0;
164  size_t data_pos = 0;
165  size_t sound_pos = 0;
166 
167  uint32_t read_tag() {
168  uint32_t tag = 0;
169  tag = (tag << 8) | getChar();
170  tag = (tag << 8) | getChar();
171  tag = (tag << 8) | getChar();
172  tag = (tag << 8) | getChar();
173  return tag;
174  }
175 
176  uint32_t getChar32() { return getChar(); }
177 
178  uint32_t read_int32() {
179  uint32_t value = 0;
180  value |= getChar32() << 0;
181  value |= getChar32() << 8;
182  value |= getChar32() << 16;
183  value |= getChar32() << 24;
184  return value;
185  }
186 
187  uint16_t read_int16() {
188  uint16_t value = 0;
189  value |= getChar() << 0;
190  value |= getChar() << 8;
191  return value;
192  }
193 
194  void skip(int n) {
195  int i;
196  for (i = 0; i < n; i++) getChar();
197  }
198 
199  int getChar() {
200  if (data_pos < len)
201  return buffer[data_pos++];
202  else
203  return -1;
204  }
205 
206  void seek(long int offset, int origin) {
207  if (origin == SEEK_SET) {
208  data_pos = offset;
209  } else if (origin == SEEK_CUR) {
210  data_pos += offset;
211  }
212  }
213 
214  size_t tell() { return data_pos; }
215 
216  bool eof() { return data_pos >= len - 1; }
217 
218  void logInfo() {
219  LOGI("WAVHeader sound_pos: %lu", (unsigned long)sound_pos);
220  LOGI("WAVHeader channels: %d ", headerInfo.channels);
221  LOGI("WAVHeader bits_per_sample: %d", headerInfo.bits_per_sample);
222  LOGI("WAVHeader sample_rate: %d ", headerInfo.sample_rate);
223  LOGI("WAVHeader format: %d", (int)headerInfo.format);
224  }
225 
226  void writeRiffHeader(BaseBuffer<uint8_t> &buffer) {
227  buffer.writeArray((uint8_t *)"RIFF", 4);
228  write32(buffer, headerInfo.file_size - 8);
229  buffer.writeArray((uint8_t *)"WAVE", 4);
230  }
231 
232  void writeFMT(BaseBuffer<uint8_t> &buffer) {
233  uint16_t fmt_len = 16;
234  buffer.writeArray((uint8_t *)"fmt ", 4);
235  write32(buffer, fmt_len);
236  write16(buffer, (uint16_t)headerInfo.format); // PCM
237  write16(buffer, headerInfo.channels);
238  write32(buffer, headerInfo.sample_rate);
239  write32(buffer, headerInfo.byte_rate);
240  write16(buffer, headerInfo.block_align); // frame size
241  write16(buffer, headerInfo.bits_per_sample);
242  }
243 
244  void write32(BaseBuffer<uint8_t> &buffer, uint64_t value) {
245  buffer.writeArray((uint8_t *)&value, 4);
246  }
247 
248  void write16(BaseBuffer<uint8_t> &buffer, uint16_t value) {
249  buffer.writeArray((uint8_t *)&value, 2);
250  }
251 
252  void writeDataHeader(BaseBuffer<uint8_t> &buffer) {
253  buffer.writeArray((uint8_t *)"data", 4);
254  write32(buffer, headerInfo.file_size);
255  int offset = headerInfo.offset;
256  if (offset > 0) {
257  uint8_t empty[offset];
258  memset(empty, 0, offset);
259  buffer.writeArray(empty, offset); // resolve issue with wrong aligment
260  }
261  }
262 
263 };
264 
275 class WAVDecoder : public AudioDecoder {
276  public:
280  WAVDecoder() = default;
281 
287  setDecoder(dec, fmt);
288  }
289 
292  TRACED();
293  decoder_format = fmt;
294  p_decoder = &dec;
295  }
296 
298  void setOutput(Print &out_stream) { this->p_print = &out_stream; }
299 
302  this->audioBaseInfoSupport = &bi;
303  }
304 
305  void begin() {
306  TRACED();
307  setupEncodedAudio();
308  isFirst = true;
309  active = true;
310  }
311 
312  void end() {
313  TRACED();
314  active = false;
315  }
316 
317  const char *mime() { return wav_mime; }
318 
319  WAVAudioInfo &audioInfoEx() { return header.audioInfo(); }
320 
321  AudioInfo audioInfo() { return header.audioInfo(); }
322 
323  virtual size_t write(const void *in_ptr, size_t in_size) {
324  TRACED();
325  size_t result = 0;
326  if (active) {
327  if (isFirst) {
328  result = decodeHeader((uint8_t*) in_ptr, in_size);
329  } else if (isValid) {
330  result = out().write((uint8_t *)in_ptr, in_size);
331  }
332  }
333  return result;
334  }
335 
336  virtual operator bool() { return active; }
337 
338  protected:
339  WAVHeader header;
340  AudioInfoSupport *audioBaseInfoSupport=nullptr;
341  bool isFirst = true;
342  bool isValid = true;
343  bool active = false;
344  AudioFormat decoder_format = AudioFormat::PCM;
345  AudioDecoderExt *p_decoder = nullptr;
346  EncodedAudioOutput dec_out;
347 
348  Print& out() {
349  return p_decoder==nullptr ? *p_print : dec_out;
350  }
351 
352  int decodeHeader(uint8_t *in_ptr, size_t in_size) {
353  int result = 0;
354  // we expect at least the full header
355  int written = header.write(in_ptr, in_size);
356  if (!header.isDataComplete()) {
357  return written;
358  }
359  // parse header
360  header.parse();
361 
362  size_t len = in_size - written;
363  uint8_t *sound_ptr = (uint8_t *)in_ptr + written;
364  isFirst = false;
365  isValid = header.audioInfo().is_valid;
366 
367  LOGI("WAV sample_rate: %d", header.audioInfo().sample_rate);
368  LOGI("WAV data_length: %u", (unsigned)header.audioInfo().data_length);
369  LOGI("WAV is_streamed: %d", header.audioInfo().is_streamed);
370  LOGI("WAV is_valid: %s",
371  header.audioInfo().is_valid ? "true" : "false");
372 
373  // check format
374  AudioFormat format = header.audioInfo().format;
375  isValid = format == decoder_format;
376  if (isValid) {
377  // update blocksize
378  if(p_decoder!=nullptr){
379  int block_size = header.audioInfo().block_align;
380  p_decoder->setBlockSize(block_size);
381  }
382 
383  // update sampling rate if the target supports it
384  AudioInfo bi;
385  bi.sample_rate = header.audioInfo().sample_rate;
386  bi.channels = header.audioInfo().channels;
387  bi.bits_per_sample = header.audioInfo().bits_per_sample;
388  // we provide some functionality so that we could check if the
389  // destination supports the requested format
390  if (audioBaseInfoSupport != nullptr) {
391  isValid = audioBaseInfoSupport->validate(bi);
392  if (isValid) {
393  LOGI("isValid: %s", isValid ? "true" : "false");
394  audioBaseInfoSupport->setAudioInfo(bi);
395  // write prm data from first record
396  LOGI("WAVDecoder writing first sound data");
397  result = out().write(sound_ptr, len);
398  } else {
399  LOGE("isValid: %s", isValid ? "true" : "false");
400  }
401  }
402  } else {
403  LOGE("WAV format not supported: %d", (int)format);
404  }
405  return result;
406  }
407 
408  void setupEncodedAudio() {
409  if (p_decoder!=nullptr){
410  assert(p_print!=nullptr);
411  dec_out.setOutput(p_print);
412  dec_out.setDecoder(p_decoder);
413  dec_out.begin();
414  }
415  }
416 };
417 
427 class WAVEncoder : public AudioEncoder {
428  public:
432  WAVEncoder() = default;
433 
438  setEncoder(enc, fmt);
439  };
440 
441  void setEncoder(AudioEncoderExt &enc, AudioFormat fmt) {
442  TRACED();
443  audioInfo.format = fmt;
444  p_encoder = &enc;
445  }
446 
448  void setOutput(Print &out) override {
449  TRACED();
450  p_print = &out;
451  }
452 
454  const char *mime() override { return wav_mime; }
455 
456  // Provides the default configuration
457  WAVAudioInfo defaultConfig() {
458  WAVAudioInfo info;
459  info.format = AudioFormat::PCM;
460  info.sample_rate = DEFAULT_SAMPLE_RATE;
461  info.bits_per_sample = DEFAULT_BITS_PER_SAMPLE;
462  info.channels = DEFAULT_CHANNELS;
463  info.is_streamed = true;
464  info.is_valid = true;
465  info.data_length = 0x7fff0000;
466  info.file_size = info.data_length + 36;
467  return info;
468  }
469 
471  virtual void setAudioInfo(AudioInfo from) override {
472  audioInfo.sample_rate = from.sample_rate;
473  audioInfo.channels = from.channels;
474  audioInfo.bits_per_sample = from.bits_per_sample;
475  // recalculate byte rate, block align...
476  setAudioInfo(audioInfo);
477  }
478 
480  virtual void setAudioInfo(WAVAudioInfo ai) {
481  audioInfo = ai;
482  LOGI("sample_rate: %d", audioInfo.sample_rate);
483  LOGI("channels: %d", audioInfo.channels);
484  // bytes per second
485  audioInfo.byte_rate = audioInfo.sample_rate * audioInfo.channels * audioInfo.bits_per_sample / 8;
486  if (audioInfo.format == AudioFormat::PCM){
487  audioInfo.block_align = audioInfo.bits_per_sample / 8 * audioInfo.channels;
488  }
489  if (audioInfo.is_streamed || audioInfo.data_length == 0 ||
490  audioInfo.data_length >= 0x7fff0000) {
491  LOGI("is_streamed! because length is %u",
492  (unsigned)audioInfo.data_length);
493  audioInfo.is_streamed = true;
494  audioInfo.data_length = ~0;
495  } else {
496  size_limit = audioInfo.data_length;
497  LOGI("size_limit is %d", (int)size_limit);
498  }
499  }
500 
502  void begin(WAVAudioInfo ai) {
503  setAudioInfo(ai);
504  begin();
505  }
506 
508  virtual void begin() override {
509  TRACED();
510  setupEncodedAudio();
511  header_written = false;
512  is_open = true;
513  }
514 
516  void end() override { is_open = false; }
517 
519  virtual size_t write(const void *in_ptr, size_t in_size) override {
520  if (!is_open) {
521  LOGE("The WAVEncoder is not open - please call begin()");
522  return 0;
523  }
524 
525  if (p_print == nullptr) {
526  LOGE("No output stream was provided");
527  return 0;
528  }
529 
530  if (!header_written) {
531  LOGI("Writing Header");
532  header.setAudioInfo(audioInfo);
533  header.writeHeader(p_print);
534  audioInfo.file_size -= 44;
535  header_written = true;
536  }
537 
538  int32_t result = 0;
539  Print *p_out = p_encoder==nullptr ? p_print : &enc_out;;
540  if (audioInfo.is_streamed) {
541  result = p_out->write((uint8_t *)in_ptr, in_size);
542  } else if (size_limit > 0) {
543  size_t write_size = min((size_t)in_size, (size_t)size_limit);
544  result = p_out->write((uint8_t *)in_ptr, write_size);
545  size_limit -= result;
546 
547  if (size_limit <= 0) {
548  LOGI("The defined size was written - so we close the WAVEncoder now");
549  is_open = false;
550  }
551  }
552  return result;
553  }
554 
555  operator bool() override { return is_open; }
556 
557  bool isOpen() { return is_open; }
558 
560  void setDataOffset(uint16_t offset) { audioInfo.offset = offset; }
561 
562  protected:
563  WAVHeader header;
564  Print *p_print = nullptr; // final output CopyEncoder copy; // used for PCM
565  AudioEncoderExt *p_encoder = nullptr;
566  EncodedAudioOutput enc_out;
567  WAVAudioInfo audioInfo = defaultConfig();
568  int64_t size_limit = 0;
569  bool header_written = false;
570  volatile bool is_open = false;
571 
572  void setupEncodedAudio() {
573  if (p_encoder!=nullptr){
574  assert(p_print!=nullptr);
575  enc_out.setOutput(p_print);
576  enc_out.setEncoder(p_encoder);
577  enc_out.setAudioInfo(audioInfo);
578  enc_out.begin();
579  // block size only available after begin(): update block size
580  audioInfo.block_align = p_encoder->blockSize();
581  }
582  }
583 };
584 
585 } // namespace audio_tools
WAV Audio Formats used by Microsoft e.g. in AVI video files.
Definition: AudioEncoded.h:86
Docoding of encoded audio into PCM data.
Definition: AudioEncoded.h:17
Definition: AudioEncoded.h:91
Encoding of PCM data.
Definition: AudioEncoded.h:74
Supports changes to the sampling rate, bits and channels.
Definition: AudioTypes.h:109
A more natural Print class to process encoded data (aac, wav, mp3...). Just define the output and the...
Definition: AudioEncoded.h:193
bool begin() override
Starts the processing - sets the status to active.
Definition: AudioEncoded.h:328
void setOutput(Print *outputStream)
Defines the output.
Definition: AudioEncoded.h:292
Definition: NoArduino.h:51
T * data()
Provides address of actual data.
Definition: Buffers.h:240
int available() override
provides the number of entries that are available to read
Definition: Buffers.h:207
A simple WAVDecoder: We parse the header data on the first record to determine the format....
Definition: CodecWAV.h:275
void setDecoder(AudioDecoderExt &dec, AudioFormat fmt)
Defines an optional decoder if the format is not PCM.
Definition: CodecWAV.h:291
void setNotifyAudioChange(AudioInfoSupport &bi)
Defines the object that needs to be notified about audio format changes.
Definition: CodecWAV.h:301
void setOutput(Print &out_stream)
Defines the output Stream.
Definition: CodecWAV.h:298
WAVDecoder()=default
Construct a new WAVDecoder object for PCM data.
WAVDecoder(AudioDecoderExt &dec, AudioFormat fmt)
Construct a new WAVDecoder object for ADPCM data.
Definition: CodecWAV.h:286
A simple WAV file encoder. If no AudioEncoderExt is specified the WAV file contains PCM data,...
Definition: CodecWAV.h:427
void setOutput(Print &out) override
Defines the otuput stream.
Definition: CodecWAV.h:448
virtual size_t write(const void *in_ptr, size_t in_size) override
Writes PCM data to be encoded as WAV.
Definition: CodecWAV.h:519
const char * mime() override
Provides "audio/wav".
Definition: CodecWAV.h:454
void end() override
stops the processing
Definition: CodecWAV.h:516
virtual void setAudioInfo(WAVAudioInfo ai)
Defines the WAVAudioInfo.
Definition: CodecWAV.h:480
WAVEncoder()=default
Construct a new WAVEncoder object for PCM data.
void begin(WAVAudioInfo ai)
starts the processing
Definition: CodecWAV.h:502
void setDataOffset(uint16_t offset)
Adds n empty bytes at the beginning of the data.
Definition: CodecWAV.h:560
virtual void begin() override
starts the processing using the actual WAVAudioInfo
Definition: CodecWAV.h:508
WAVEncoder(AudioEncoderExt &enc, AudioFormat fmt)
Construct a new WAVEncoder object for ADPCM data.
Definition: CodecWAV.h:437
virtual void setAudioInfo(AudioInfo from) override
Update actual WAVAudioInfo.
Definition: CodecWAV.h:471
Parser for Wav header data for details see https://de.wikipedia.org/wiki/RIFF_WAVE.
Definition: CodecWAV.h:47
bool isDataComplete()
Returns true if the header is complete (with 44 bytes)
Definition: CodecWAV.h:140
void setAudioInfo(WAVAudioInfo info)
Sets the info in the header.
Definition: CodecWAV.h:146
void writeHeader(Print *out)
Just write a wav header to the indicated output.
Definition: CodecWAV.h:151
int write(uint8_t *data, size_t data_len)
Adds data to the 44 byte wav header data buffer and make it available for parsing.
Definition: CodecWAV.h:52
void parse()
Call begin when header data is complete to parse the data.
Definition: CodecWAV.h:62
WAVAudioInfo & audioInfo()
provides the info from the header
Definition: CodecWAV.h:143
AudioFormat
Audio format codes used by Microsoft e.g. in avi or wav files.
Definition: AudioFormat.h:19
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:9
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:43
Sound information which is available in the WAV header.
Definition: CodecWAV.h:20