arduino-audio-tools
Loading...
Searching...
No Matches
CodecWAV.h
Go to the documentation of this file.
1#pragma once
2
7
8#define READ_BUFFER_SIZE 512
9#define MAX_WAV_HEADER_LEN 200
14#define MAX_WAV_HEADER_LEN_LIMIT 2048
20#define WAV_ENCODER_COMPRESSED_CHUNK_SIZE 1024
21
22namespace audio_tools {
23
31 WAVAudioInfo() = default;
32 WAVAudioInfo(const AudioInfo &from) {
34 channels = from.channels;
36 }
37
39 int byte_rate = 0;
40 int block_align = 0;
41 bool is_streamed = true;
42 bool is_valid = false;
43 uint32_t data_length = 0;
44 uint32_t file_size = 0;
45 int offset = 0;
47 bool ext_adpcm_header = false;
48};
49
50static const char *wav_mime = "audio/wav";
51
58static bool isADPCMFormat(AudioFormat format) {
59 return format == AudioFormat::ADPCM || format == AudioFormat::DVI_ADPCM ||
61}
62
89class WAVHeader {
90 public:
91 WAVHeader() = default;
92
97 int write(uint8_t *data, size_t data_len) {
98 size_t needed = (size_t)buffer.available() + data_len;
99 if (needed > buffer.size() && buffer.size() < MAX_WAV_HEADER_LEN_LIMIT) {
100 size_t new_size = needed < (size_t)MAX_WAV_HEADER_LEN_LIMIT
101 ? needed
102 : (size_t)MAX_WAV_HEADER_LEN_LIMIT;
103 buffer.resize(new_size);
104 }
105 return buffer.writeArray(data, data_len);
106 }
107
109 bool parse() {
110 LOGI("WAVHeader::begin: %u", (unsigned)buffer.available());
111 this->data_pos = 0l;
112 memset((void *)&headerInfo, 0, sizeof(WAVAudioInfo));
113
114 if (!setPos("RIFF")) return false;
115 // RIFF stores chunk_size (= file_size - 8): normalize to full file size
117 if (!setPos("WAVE")) return false;
118 if (!setPos("fmt ")) return false;
119 int fmt_length = read_int32();
126 if (!setPos("data")) return false;
128 if (headerInfo.data_length == 0 || headerInfo.data_length >= 0x7fff0000) {
129 headerInfo.is_streamed = true;
131 }
132
133 logInfo();
134 buffer.clear();
135 return true;
136 }
137
140 int pos = getDataPos();
141 return pos > 0 && buffer.available() >= pos;
142 }
143
147 bool isOverflow() {
149 }
150
152 size_t available() { return buffer.available(); }
153
156 int pos =
158 .indexOf("data");
159 return pos > 0 ? pos + 8 : 0;
160 }
161
164
166 void setAudioInfo(WAVAudioInfo info) { headerInfo = info; }
167
169 bool writeHeader(Print *out) {
170 return writeHeader(out, headerInfo);
171 }
172
174 bool writeHeader(Print *out, const WAVAudioInfo &info) {
175 // reset first: buffer otherwise keeps accumulating bytes from earlier calls
176 buffer.reset();
177 writeRiffHeader(buffer, info);
178 writeFMT(buffer, info);
179 if (isADPCMFormat(info.format) && info.ext_adpcm_header) {
180 writeFactChunk(buffer, info);
181 }
182 writeDataHeader(buffer, info);
183 int len = buffer.available();
184 int written = out->write(buffer.data(), len);
185 if (written != len) {
186 LOGE("Failed to write WAV header to output: written %d of %d bytes", written, len);
187 }
188 return written == len;
189 }
190
195 static int extraHeaderBytes(const WAVAudioInfo &info) {
196 if (!info.ext_adpcm_header) return 0;
197 switch (info.format) {
198 case AudioFormat::ADPCM: // MS ADPCM: 34 byte fmt extension + 12 byte fact chunk
199 return 34 + 12;
200 case AudioFormat::DVI_ADPCM: // IMA/DVI ADPCM: 4 byte fmt extension + 12 byte fact chunk
201 return 4 + 12;
202 default:
203 return 0;
204 }
205 }
206
208 void clear() {
209 data_pos = 0;
210 WAVAudioInfo empty;
211 empty.sample_rate = 0;
212 empty.channels = 0;
213 empty.bits_per_sample = 0;
214 headerInfo = empty;
216 buffer.reset();
217 }
218
220 void dumpHeader() {
221 char msg[buffer.available() + 1];
222 memset(msg, 0, buffer.available() + 1);
223 for (int j = 0; j < buffer.available(); j++) {
224 char c = (char)buffer.data()[j];
225 if (!isalpha(c)) {
226 c = '.';
227 }
228 msg[j] = c;
229 }
230 LOGI("Header: %s", msg);
231 }
232
233 protected:
236 size_t data_pos = 0;
237
238 bool setPos(const char *id) {
239 int id_len = strlen(id);
240 int pos = indexOf(id);
241 if (pos < 0) return false;
242 data_pos = pos + id_len;
243 return true;
244 }
245
246 int indexOf(const char *str) {
247 return StrView((char *)buffer.data(), MAX_WAV_HEADER_LEN,
249 .indexOf(str);
250 }
251
252 uint32_t read_tag() {
253 uint32_t tag = 0;
254 tag = (tag << 8) | getChar();
255 tag = (tag << 8) | getChar();
256 tag = (tag << 8) | getChar();
257 tag = (tag << 8) | getChar();
258 return tag;
259 }
260
261 uint32_t getChar32() { return getChar(); }
262
263 uint32_t read_int32() {
264 uint32_t value = 0;
265 value |= getChar32() << 0;
266 value |= getChar32() << 8;
267 value |= getChar32() << 16;
268 value |= getChar32() << 24;
269 return value;
270 }
271
272 uint16_t read_int16() {
273 uint16_t value = 0;
274 value |= getChar() << 0;
275 value |= getChar() << 8;
276 return value;
277 }
278
279 void skip(int n) {
280 int i;
281 for (i = 0; i < n; i++) getChar();
282 }
283
284 int getChar() {
285 if (data_pos < buffer.size())
286 return buffer.data()[data_pos++];
287 else
288 return -1;
289 }
290
291 void seek(long int offset, int origin) {
292 if (origin == SEEK_SET) {
293 data_pos = offset;
294 } else if (origin == SEEK_CUR) {
295 data_pos += offset;
296 }
297 }
298
299 size_t tell() { return data_pos; }
300
301 bool eof() { return data_pos >= buffer.size() - 1; }
302
303 void logInfo() {
304 LOGI("WAVHeader sound_pos: %d", getDataPos());
305 LOGI("WAVHeader channels: %d ", headerInfo.channels);
306 LOGI("WAVHeader bits_per_sample: %d", headerInfo.bits_per_sample);
307 LOGI("WAVHeader sample_rate: %d ", (int)headerInfo.sample_rate);
308 LOGI("WAVHeader format: %d", (int)headerInfo.format);
309 }
310
312 const WAVAudioInfo &info) {
313 buffer.writeArray((uint8_t *)"RIFF", 4);
314 // chunk_size = file_size - 8 (RIFF header size)
315 uint32_t chunk_size = info.file_size > 8 ? info.file_size - 8 : 0;
316 LOGI("writeRiffHeader: file_size=%u riff_size=%u", info.file_size, chunk_size);
317 write32(buffer, chunk_size);
318 buffer.writeArray((uint8_t *)"WAVE", 4);
319 }
320
322 bool is_ms_adpcm = info.ext_adpcm_header && info.format == AudioFormat::ADPCM;
323 bool is_ima_adpcm = info.ext_adpcm_header && info.format == AudioFormat::DVI_ADPCM;
324 uint16_t fmt_len = 16;
325 if (is_ima_adpcm) fmt_len = 20;
326 else if (is_ms_adpcm) fmt_len = 50;
327
328 uint16_t spb = samplesPerBlock(info);
329 uint32_t byte_rate = info.byte_rate;
330 // use the real average byte rate for ADPCM formats whenever the block
331 // layout is known, regardless of whether the extended 'fmt '/'fact'
332 // chunk is written: byte_rate is informational (e.g. used by some
333 // players for scrubbing/duration estimates) and the linear-PCM-based
334 // default in info.byte_rate is misleading for compressed formats
335 if (isADPCMFormat(info.format) && spb > 0 && info.block_align > 0) {
336 // average bytes/sec = (sample_rate * block_align) / samples_per_block
337 byte_rate = ((uint64_t)info.sample_rate * info.block_align) / spb;
338 }
339
340 // wBitsPerSample for ADPCM formats is the number of bits per *coded*
341 // sample (always 4 for MS/IMA WAV ADPCM), not the decoded PCM depth
342 // tracked in info.bits_per_sample (typically 16) - writing 16 here
343 // makes strict decoders (e.g. ffmpeg) reject the stream since it is
344 // outside the codec's valid range
345 uint16_t bits_per_coded_sample =
346 isADPCMFormat(info.format) ? 4 : info.bits_per_sample;
347
348 buffer.writeArray((uint8_t *)"fmt ", 4);
349 write32(buffer, fmt_len);
350 write16(buffer, (uint16_t)info.format);
351 write16(buffer, info.channels);
353 write32(buffer, byte_rate);
354 write16(buffer, info.block_align); // frame size
355 write16(buffer, bits_per_coded_sample);
356
357 if (is_ima_adpcm) {
358 write16(buffer, 2); // cbSize: size of extra format bytes
359 write16(buffer, spb); // wSamplesPerBlock
360 } else if (is_ms_adpcm) {
361 // standard MS ADPCM coefficient table (7 predictor pairs)
362 static const int16_t ms_adpcm_coef[7][2] = {
363 {256, 0}, {512, -256}, {0, 0}, {192, 64},
364 {240, 0}, {460, -208}, {392, -232}};
365 write16(buffer, 32); // cbSize: size of extra format bytes
366 write16(buffer, spb); // wSamplesPerBlock
367 write16(buffer, 7); // wNumCoef
368 for (auto &c : ms_adpcm_coef) {
369 write16(buffer, (uint16_t)c[0]);
370 write16(buffer, (uint16_t)c[1]);
371 }
372 }
373 }
374
378 buffer.writeArray((uint8_t *)"fact", 4);
379 write32(buffer, 4); // chunk size
380 uint32_t sample_length = 0;
381 uint16_t spb = samplesPerBlock(info);
382 if (!info.is_streamed && spb > 0 && info.block_align > 0) {
383 sample_length = (info.data_length / info.block_align) * spb;
384 }
385 write32(buffer, sample_length);
386 }
387
389 buffer.writeArray((uint8_t *)"data", 4);
390 uint32_t data_length = info.data_length;
391 uint32_t header_bytes = 36 + extraHeaderBytes(info);
392 if (headerInfo.is_streamed && data_length == 0) {
393 data_length = ~0; // use max value for streamed data if not set
394 }
395 if (!headerInfo.is_streamed && info.file_size >= header_bytes && (data_length == 0 || data_length == ~0)) {
396 data_length = info.file_size - header_bytes; // data length = file size - header size
397 }
398 LOGI("writeDataHeader: data_length=%u", data_length);
399 write32(buffer, data_length);
400 int offset = info.offset;
401 if (offset > 0) {
402 uint8_t empty[offset];
403 memset(empty, 0, offset);
404 buffer.writeArray(empty, offset); // resolve issue with wrong aligment
405 }
406 }
407
410 static uint16_t samplesPerBlock(const WAVAudioInfo &info) {
411 if (info.channels <= 0 || info.block_align <= 0) return 0;
412 switch (info.format) {
413 case AudioFormat::ADPCM: // MS ADPCM
414 return ((info.block_align / info.channels) - 7) * 2 + 2;
415 case AudioFormat::DVI_ADPCM: // IMA/DVI ADPCM
416 return ((info.block_align / info.channels) - 4) * 2 + 1;
417 default:
418 return 0;
419 }
420 }
421
422 void write32(BaseBuffer<uint8_t> &buffer, uint64_t value) {
423 buffer.writeArray((uint8_t *)&value, 4);
424 }
425
426 void write16(BaseBuffer<uint8_t> &buffer, uint16_t value) {
427 buffer.writeArray((uint8_t *)&value, 2);
428 }
429
430};
431
466class WAVDecoder : public AudioDecoder {
467
468 public:
472 WAVDecoder() = default;
473
481
494 TRACED();
495 decoders.clear();
496 addDecoder(dec, fmt);
497 }
498
508 TRACED();
509 if (fmt == AudioFormat::UNKNOWN) fmt = dec.wavFormat();
510 if (fmt == AudioFormat::UNKNOWN) {
511 LOGE(
512 "addDecoder: could not determine the WAV format tag for this "
513 "decoder - please provide it explicitly");
514 return;
515 }
516 for (int i = 0; i < decoders.size(); i++) {
517 if (decoders[i].format == fmt) {
518 decoders[i].decoder = &dec;
519 return;
520 }
521 }
522 decoders.push_back({fmt, &dec});
523 }
524
526 void setOutput(Print &out_stream) override { this->p_print = &out_stream; }
527
529 bool begin() override {
530 TRACED();
531 header.clear();
532 // close the decoder used for a previous stream (if any) before
533 // selecting a new one for this stream: the match is only known once
534 // the header has been parsed, so setupEncodedAudio() is triggered from
535 // decodeHeader() instead of here
538 buffer24.reset();
540 isFirst = true;
541 active = true;
542 return true;
543 }
544
546 void end() override {
547 TRACED();
550 buffer24.reset();
552 active = false;
553 }
554
556 const char *mime() { return wav_mime; }
557
560
562 AudioInfo audioInfo() override {
564 if (convert8to16 && info.format == AudioFormat::PCM &&
565 info.bits_per_sample == 8) {
567 }
568 // 32 bits gives better result
569 if (convert24 && info.format == AudioFormat::PCM &&
570 info.bits_per_sample == 24) {
572 }
573 // ALAW/MULAW are expanded from 8-bit logarithmic to 16-bit linear PCM
574 if (convertALawMuLaw && (info.format == AudioFormat::ALAW ||
575 info.format == AudioFormat::MULAW)) {
577 }
578 // IEEE_FLOAT can optionally be converted to 16-bit linear PCM
580 info.bits_per_sample == 32) {
582 }
583 // any other non-PCM/non-IEEE_FLOAT format (e.g. ADPCM) is decoded by
584 // p_decoder to 16-bit PCM; IEEE_FLOAT is passed through as-is
585 if (info.format != AudioFormat::PCM &&
586 info.format != AudioFormat::IEEE_FLOAT &&
587 info.format != AudioFormat::ALAW &&
588 info.format != AudioFormat::MULAW) {
590 }
591 return info;
592 }
593
595 virtual size_t write(const uint8_t *data, size_t len) override {
596 TRACED();
597 size_t result = 0;
598 if (active) {
599 if (isFirst) {
600 int data_start = decodeHeader((uint8_t *)data, len);
601 // we do not have the complete header yet: need more data
602 if (data_start == 0) return len;
603 // process the outstanding data - only if the format is supported;
604 // otherwise report 0 bytes written, consistent with subsequent
605 // write() calls once the format has been found to be invalid
606 if (isValid) {
607 result = data_start +
608 write_out((uint8_t *)data + data_start, len - data_start);
609 }
610
611 } else if (isValid) {
612 result = write_out((uint8_t *)data, len);
613 }
614 }
615 return result;
616 }
617
619 virtual operator bool() override { return active; }
620
622 void setConvert8Bit(bool enable) {
623 convert8to16 = enable;
624 }
625
627 void setConvert24Bit(bool enable) {
628 convert24 = enable;
629 }
630
634 void setConvertALawMuLaw(bool enable) {
635 convertALawMuLaw = enable;
636 }
637
642 void setConvertFloatToInt16(bool enable) {
643 convertFloatToInt16 = enable;
644 }
645
648
649 protected:
658
660 bool isFirst = true;
661 bool isValid = true;
662 bool active = false;
672 bool convert8to16 = true; // Optional conversion flag
673 bool convert24 = true; // Optional conversion flag
674 bool convertALawMuLaw = true; // Optional conversion flag
675 bool convertFloatToInt16 = true; // Optional conversion flag
676 const size_t batch_size = 256;
677
678 Print &out() { return p_decoder == nullptr ? *p_print : dec_out; }
679
680 virtual size_t write_out(const uint8_t *in_ptr, size_t in_size) {
681 // check if we need to convert int24 data from 3 bytes to 4 bytes
682 size_t result = 0;
684 if (convert24 && format == AudioFormat::PCM &&
685 header.audioInfo().bits_per_sample == 24 && sizeof(int24_t) == 4) {
686 write_out_24(in_ptr, in_size);
687 result = in_size;
688 } else if (convert8to16 && format == AudioFormat::PCM &&
690 result = write_out_8to16(in_ptr, in_size);
691 } else if (convertALawMuLaw && format == AudioFormat::ALAW) {
692 result = write_out_alaw(in_ptr, in_size);
693 } else if (convertALawMuLaw && format == AudioFormat::MULAW) {
694 result = write_out_mulaw(in_ptr, in_size);
695 } else if (convertFloatToInt16 && format == AudioFormat::IEEE_FLOAT &&
697 result = write_out_float_to_int16(in_ptr, in_size);
698 } else {
699 result = out().write(in_ptr, in_size);
700 }
701 return result;
702 }
703
705 size_t write_out_8to16(const uint8_t *in_ptr, size_t in_size) {
706 size_t samples_remaining = in_size;
707 size_t offset = 0;
708 int16_t out_buf[batch_size];
709 while (samples_remaining > 0) {
710 size_t current_batch =
711 samples_remaining > batch_size ? batch_size : samples_remaining;
712 for (size_t i = 0; i < current_batch; ++i) {
713 out_buf[i] = ((int16_t)in_ptr[offset + i] - 128) << 8;
714 }
715 writeDataT<int16_t>(&out(), out_buf, current_batch);
716 offset += current_batch;
717 samples_remaining -= current_batch;
718 }
719 return in_size;
720 }
721
723 size_t write_out_24(const uint8_t *in_ptr, size_t in_size) {
724 // store 1 sample
727
728 for (size_t i = 0; i < in_size; i++) {
729 // Add byte to buffer
730 byte_buffer.write(in_ptr[i]);
731
732 // Process complete sample when buffer is full
733 if (byte_buffer.isFull()) {
734 int24_3bytes_t sample24{byte_buffer.data()};
735 int32_t converted_sample = sample24.scale32();
736 buffer24.write(converted_sample);
737 if (buffer24.isFull()) {
738 writeDataT<int32_t>(&out(), buffer24.data(), buffer24.available());
739 buffer24.reset();
740 }
742 }
743 }
744
745 return in_size;
746 }
747
750 size_t write_out_float_to_int16(const uint8_t *in_ptr, size_t in_size) {
753
754 for (size_t i = 0; i < in_size; i++) {
755 byte_buffer.write(in_ptr[i]);
756
757 if (byte_buffer.isFull()) {
758 float sample_f;
759 memcpy(&sample_f, byte_buffer.data(), sizeof(sample_f));
760 if (sample_f > 1.0f) sample_f = 1.0f;
761 if (sample_f < -1.0f) sample_f = -1.0f;
762 buffer_float16.write((int16_t)(sample_f * 32767.0f));
763 if (buffer_float16.isFull()) {
764 writeDataT<int16_t>(&out(), buffer_float16.data(),
767 }
769 }
770 }
771
772 return in_size;
773 }
774
776 size_t write_out_alaw(const uint8_t *in_ptr, size_t in_size) {
777 size_t remaining = in_size;
778 size_t offset = 0;
779 int16_t out_buf[batch_size];
780 while (remaining > 0) {
781 size_t current_batch =
782 remaining > batch_size ? batch_size : remaining;
783 for (size_t i = 0; i < current_batch; ++i) {
784 out_buf[i] = alaw2linear(in_ptr[offset + i]);
785 }
786 writeDataT<int16_t>(&out(), out_buf, current_batch);
787 offset += current_batch;
788 remaining -= current_batch;
789 }
790 return in_size;
791 }
792
794 size_t write_out_mulaw(const uint8_t *in_ptr, size_t in_size) {
795 size_t remaining = in_size;
796 size_t offset = 0;
797 int16_t out_buf[batch_size];
798 while (remaining > 0) {
799 size_t current_batch =
800 remaining > batch_size ? batch_size : remaining;
801 for (size_t i = 0; i < current_batch; ++i) {
802 out_buf[i] = ulaw2linear(in_ptr[offset + i]);
803 }
804 writeDataT<int16_t>(&out(), out_buf, current_batch);
805 offset += current_batch;
806 remaining -= current_batch;
807 }
808 return in_size;
809 }
810
812 static int16_t alaw2linear(uint8_t a_val) {
813 a_val ^= 0x55;
814 int t = (a_val & 0x0f) << 4;
815 int seg = ((unsigned)a_val & 0x70) >> 4;
816 switch (seg) {
817 case 0:
818 t += 8;
819 break;
820 case 1:
821 t += 0x108;
822 break;
823 default:
824 t += 0x108;
825 t <<= (seg - 1);
826 break;
827 }
828 return (int16_t)((a_val & 0x80) ? t : -t);
829 }
830
832 static int16_t ulaw2linear(uint8_t u_val) {
833 u_val = ~u_val;
834 int t = ((u_val & 0x0f) << 3) + 0x84;
835 t <<= ((unsigned)u_val & 0x70) >> 4;
836 return (int16_t)((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
837 }
838
840 int decodeHeader(uint8_t *in_ptr, size_t in_size) {
841 // we expect at least the full header
842 header.write(in_ptr, in_size);
843 if (!header.isDataComplete()) {
844 if (header.isOverflow()) {
845 // the header (up to and including the 'data' chunk tag) exceeds
846 // MAX_WAV_HEADER_LEN_LIMIT without ever completing - give up
847 // instead of waiting forever for data that will never arrive
848 LOGE(
849 "WAV header exceeds the maximum supported size of %d bytes "
850 "without a 'data' chunk - aborting",
852 isFirst = false;
853 isValid = false;
854 return 0;
855 }
856 LOGW("WAV header misses 'data' section in len: %d",
857 (int)header.available());
859 return 0;
860 }
861 // parse header
862 if (!header.parse()) {
863 LOGE("WAV header parsing failed");
864 return 0;
865 }
866
867 isFirst = false;
868
869 LOGI("WAV sample_rate: %d", (int)header.audioInfo().sample_rate);
870 LOGI("WAV data_length: %u", (unsigned)header.audioInfo().data_length);
871 LOGI("WAV is_streamed: %d", header.audioInfo().is_streamed);
872
873 // select the decoder matching the format found in the header (if any
874 // was registered via setDecoder()/addDecoder()); otherwise fall back to
875 // the natively supported formats (PCM, IEEE_FLOAT, ALAW, MULAW)
877 p_decoder = findDecoder(format);
878 isValid = p_decoder != nullptr ? true : isNativelySupported(format);
879 if (isValid) {
880 if (p_decoder != nullptr) {
881 // update blocksize before begin() is called in setupEncodedAudio()
882 int block_size = header.audioInfo().block_align;
883 p_decoder->setBlockSize(block_size);
885 }
886
887 // update sampling rate if the target supports it
888 AudioInfo bi = audioInfo();
890 } else {
891 LOGE("WAV format not supported: 0x%04X", (unsigned)format);
893 }
894 return header.getDataPos();
895 }
896
901 LOGE(
902 "Natively supported: PCM (0x%04X), IEEE_FLOAT (0x%04X), ALAW "
903 "(0x%04X), MULAW (0x%04X)",
904 (unsigned)AudioFormat::PCM, (unsigned)AudioFormat::IEEE_FLOAT,
905 (unsigned)AudioFormat::ALAW, (unsigned)AudioFormat::MULAW);
906 if (decoders.empty()) {
907 LOGE(
908 "No additional decoders registered - use "
909 "setDecoder()/addDecoder() to support e.g. ADPCM formats");
910 } else {
911 for (int i = 0; i < decoders.size(); i++) {
912 LOGE("Registered decoder for format: 0x%04X",
913 (unsigned)decoders[i].format);
914 }
915 }
916 }
917
920 for (int i = 0; i < decoders.size(); i++) {
921 if (decoders[i].format == format) return decoders[i].decoder;
922 }
923 return nullptr;
924 }
925
927 if (p_decoder != nullptr) {
928 assert(p_print != nullptr);
932 }
933 }
934
939 if (p_decoder != nullptr) {
940 p_decoder->end();
941 p_decoder = nullptr;
942 }
943 }
944
946 static bool isNativelySupported(AudioFormat format) {
947 return format == AudioFormat::PCM || format == AudioFormat::IEEE_FLOAT ||
948 format == AudioFormat::ALAW || format == AudioFormat::MULAW;
949 }
950};
951
962class CountingPrint : public Print {
963 public:
964 void setOutput(Print *out) { p_out = out; }
965 size_t write(uint8_t c) override { return write(&c, 1); }
966 size_t write(const uint8_t *data, size_t len) override {
967 size_t written = p_out != nullptr ? p_out->write(data, len) : 0;
968 count += written;
969 return written;
970 }
971 size_t count = 0;
972
973 protected:
974 Print *p_out = nullptr;
975};
976
987class WAVEncoder : public AudioEncoder {
988 public:
992 WAVEncoder() = default;
993
998
1004 TRACED();
1005 wav_info.format = fmt;
1006 p_encoder = &enc;
1007 }
1008
1010 void setOutput(Print &out) override {
1011 TRACED();
1012 p_print = &out;
1013 }
1014
1016 const char *mime() override { return wav_mime; }
1017
1021 info.format = AudioFormat::PCM;
1025 info.is_streamed = true;
1026 info.is_valid = true;
1027 info.data_length = 0x7fff0000;
1028 info.file_size = info.data_length + 36;
1029 return info;
1030 }
1031
1033 virtual void setAudioInfo(AudioInfo from) override {
1035 wav_info.channels = from.channels;
1037 // recalculate byte rate, block align...
1039 }
1040
1046 virtual void setAudioInfo(WAVAudioInfo ai) {
1049 wav_info = ai;
1050 LOGI("sample_rate: %d", (int)wav_info.sample_rate);
1051 LOGI("channels: %d", wav_info.channels);
1052 LOGI("bits_per_sample: %d", wav_info.bits_per_sample);
1053 // bytes per second
1056 // uncompressed formats with a fixed size per sample: block_align is
1057 // simply the frame size. Compressed formats (e.g. ADPCM) are handled
1058 // via p_encoder instead.
1065 }
1067 }
1068
1071 setAudioInfo(ai);
1072 return begin();
1073 }
1074
1076 virtual bool begin() override {
1077 TRACED();
1078 header.clear();
1079 if (!setupEncodedAudio()) {
1080 is_open = false;
1081 return false;
1082 }
1083
1084 // normalize streaming mode and payload limits at start time
1086 wav_info.data_length >= 0x7fff0000) {
1087 LOGI("is_streamed! because length is %u",
1088 (unsigned)wav_info.data_length);
1089 wav_info.is_streamed = true;
1090 wav_info.data_length = ~0;
1091 size_limit = 0;
1092 } else {
1093 wav_info.is_streamed = false;
1095 LOGI("size_limit is %d", (int)size_limit);
1096 }
1097
1098 header_written = false;
1099 is_open = true;
1100 return true;
1101 }
1102
1104 void end() override { is_open = false; }
1105
1107 virtual size_t write(const uint8_t *data, size_t len) override {
1108 if (!is_open) {
1109 LOGE("The WAVEncoder is not open - please call begin()");
1110 return 0;
1111 }
1112
1113 if (p_print == nullptr) {
1114 LOGE("No output stream was provided");
1115 return 0;
1116 }
1117
1118 if (!header_written) {
1119 LOGI("Writing Header");
1121 LOGE("Failed to write WAV header");
1122 is_open = false;
1123 return 0;
1124 }
1125 header_written = true;
1126 }
1127
1128 size_t result = 0;
1129 Print *p_out = p_encoder == nullptr ? p_print : &enc_out;
1130
1131 if (wav_info.is_streamed) {
1132 result = p_out->write((uint8_t *)data, len);
1133 } else if (size_limit > 0) {
1134 if (p_encoder == nullptr) {
1135 // uncompressed: input bytes map 1:1 to output bytes, so we can
1136 // truncate exactly at the declared data_length boundary
1137 size_t write_size = min((size_t)len, (size_t)size_limit);
1138 result = p_out->write((uint8_t *)data, write_size);
1139 size_limit -= result;
1140 } else {
1141 // compressed: the number of encoded bytes an external encoder
1142 // produces for a given amount of PCM input is not known in advance
1143 // (and may only be emitted once a full internal block is ready), so
1144 // AudioEncoder::write()'s return value (raw PCM input bytes
1145 // accepted) cannot be used to track progress towards data_length.
1146 // Track the *actual* encoded bytes written via counting_print, and
1147 // feed the encoder in frame-aligned chunks so we can stop as soon
1148 // as data_length is reached - result then correctly reflects only
1149 // the bytes of `data` that were actually consumed, so the caller
1150 // knows what still needs to be resent (e.g. to a new file).
1151 int frame_size = wav_info.bits_per_sample / 8 * wav_info.channels;
1152 if (frame_size <= 0) frame_size = 1;
1153 size_t chunk = (WAV_ENCODER_COMPRESSED_CHUNK_SIZE / frame_size) * frame_size;
1154 if (chunk == 0) chunk = frame_size;
1155
1156 while (result < len && size_limit > 0) {
1157 size_t n = min(chunk, len - result);
1159 size_t accepted = p_out->write((uint8_t *)data + result, n);
1161 result += accepted;
1162 if (accepted < n) break; // encoder didn't accept the full chunk
1163 }
1164 }
1165
1166 if (size_limit <= 0) {
1167 LOGI("The defined size was written - so we close the WAVEncoder now");
1168 is_open = false;
1169 }
1170 }
1171 return result;
1172 }
1173
1175 operator bool() override { return is_open; }
1176
1178 bool isOpen() { return is_open; }
1179
1183 void setDataOffset(uint16_t offset) {
1184 has_pending_offset = true;
1185 pending_offset = offset;
1186 wav_info.offset = offset;
1187 }
1188
1193 void setExtADPCMHeader(bool enable) {
1195 pending_ext_adpcm_header = enable;
1196 wav_info.ext_adpcm_header = enable;
1197 }
1198
1203 void setDataLength(uint32_t data_length) {
1205 pending_data_length = data_length;
1208 }
1209
1212
1215
1216 protected:
1218 Print *p_print = nullptr; // final output CopyEncoder copy; // used for PCM
1224 int64_t size_limit = 0;
1225 bool header_written = false;
1226 volatile bool is_open = false;
1227
1228 // fields set via setDataLength()/setDataOffset()/setExtADPCMHeader() that
1229 // must survive a subsequent wholesale setAudioInfo(WAVAudioInfo)/
1230 // begin(WAVAudioInfo) call, regardless of call order - see
1231 // applyPendingOverrides()
1235 uint16_t pending_offset = 0;
1238
1245 // must run before pending_data_length below: its file_size calculation
1246 // depends on wav_info.ext_adpcm_header
1249 } else if (p_encoder != nullptr && isADPCMFormat(wav_info.format)) {
1250 // default to the extended header for ADPCM formats unless the user
1251 // explicitly overrode it via setExtADPCMHeader()
1253 }
1254 if (has_pending_offset) {
1256 }
1260 (pending_data_length == 0 || pending_data_length >= 0x7fff0000);
1261 if (!wav_info.is_streamed) {
1262 // full file size = RIFF chunk (36) + format specific extra header
1263 // bytes (e.g. ADPCM 'fmt ' extension + 'fact' chunk) + data chunk
1264 // payload
1267 }
1268 }
1269 }
1270
1275 if (p_encoder != nullptr) {
1276 if (p_print == nullptr) {
1277 LOGE(
1278 "setupEncodedAudio: no output stream was provided - call "
1279 "setOutput() before begin()");
1280 return false;
1281 }
1287 enc_out.begin();
1288 // block size only available after begin(): update block size
1290 }
1291 return true;
1292 }
1293};
1294
1295} // namespace audio_tools
WAV Audio Formats used by Microsoft e.g. in AVI video files.
#define LOGW(...)
Definition AudioLoggerIDF.h:29
#define TRACED()
Definition AudioLoggerIDF.h:31
#define LOGI(...)
Definition AudioLoggerIDF.h:28
#define LOGE(...)
Definition AudioLoggerIDF.h:30
#define DEFAULT_BITS_PER_SAMPLE
Definition AudioToolsConfig.h:99
#define DEFAULT_CHANNELS
Definition AudioToolsConfig.h:95
#define DEFAULT_SAMPLE_RATE
Definition AudioToolsConfig.h:91
#define MAX_WAV_HEADER_LEN
Definition CodecWAV.h:9
#define MAX_WAV_HEADER_LEN_LIMIT
Definition CodecWAV.h:14
#define WAV_ENCODER_COMPRESSED_CHUNK_SIZE
Definition CodecWAV.h:20
#define assert(T)
Definition avr.h:10
Definition Arduino.h:56
virtual size_t write(const uint8_t *data, size_t len)
Definition Arduino.h:120
Extended AudioDecoder interface to support block size configuration.
Definition AudioCodecsBase.h:133
virtual void setBlockSize(int blockSize)=0
virtual AudioFormat wavFormat()
Definition AudioCodecsBase.h:142
Decoding of encoded audio into PCM data.
Definition AudioCodecsBase.h:19
AudioInfo info
Definition AudioCodecsBase.h:80
void end() override
Definition AudioCodecsBase.h:63
Print * p_print
Definition AudioCodecsBase.h:79
Extended AudioEncoder interface to support block size configuration.
Definition AudioCodecsBase.h:146
Encoding of PCM data.
Definition AudioCodecsBase.h:101
AudioInfo info
Definition AudioCodecsBase.h:129
void setAudioInfo(AudioInfo from) override
Defines the sample rate, number of channels and bits per sample.
Definition AudioCodecsBase.h:110
void notifyAudioChange(AudioInfo info)
Definition AudioTypes.h:180
Shared functionality of all buffers.
Definition Buffers.h:23
void clear()
same as reset
Definition Buffers.h:96
Minimal Print wrapper that counts the bytes actually written to the wrapped output....
Definition CodecWAV.h:962
size_t write(uint8_t c) override
Definition CodecWAV.h:965
void setOutput(Print *out)
Definition CodecWAV.h:964
size_t write(const uint8_t *data, size_t len) override
Definition CodecWAV.h:966
size_t count
Definition CodecWAV.h:971
Print * p_out
Definition CodecWAV.h:974
A more natural Print class to process encoded data (aac, wav, mp3...). Just define the output and the...
Definition AudioEncoded.h:21
void setEncoder(AudioEncoder *encoder)
Definition AudioEncoded.h:131
bool begin() override
Starts the processing - sets the status to active.
Definition AudioEncoded.h:161
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition AudioEncoded.h:87
void setDecoder(AudioDecoder *decoder)
Definition AudioEncoded.h:144
void setOutput(Print *outputStream)
Defines the output.
Definition AudioEncoded.h:107
A simple Buffer implementation which just uses a (dynamically sized) array.
Definition Buffers.h:194
size_t size() override
Definition Buffers.h:325
bool write(T sample) override
write add an entry to the buffer
Definition Buffers.h:228
void setClearWithZero(bool flag)
Sets the buffer to 0 on clear.
Definition Buffers.h:336
int available() override
provides the number of entries that are available to read
Definition Buffers.h:255
bool isFull() override
checks if the buffer is full
Definition Buffers.h:262
int writeArray(const T data[], int len) override
Fills the buffer data.
Definition Buffers.h:223
T * data()
Provides address of actual data.
Definition Buffers.h:306
bool resize(size_t size)
Resizes the buffer if supported: returns false if not supported.
Definition Buffers.h:327
void reset() override
clears the buffer
Definition Buffers.h:308
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:29
virtual int indexOf(const char c, int start=0)
Definition StrView.h:336
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
A simple WAVDecoder: We parse the header data on the first record to determine the format....
Definition CodecWAV.h:466
virtual size_t write(const uint8_t *data, size_t len) override
Write incoming WAV data (header + PCM) into output.
Definition CodecWAV.h:595
bool active
Definition CodecWAV.h:662
void setOutput(Print &out_stream) override
Defines the output Stream.
Definition CodecWAV.h:526
bool isFirst
Definition CodecWAV.h:660
bool convertFloatToInt16
Definition CodecWAV.h:675
size_t write_out_24(const uint8_t *in_ptr, size_t in_size)
convert 3 byte int24 to 4 byte int32
Definition CodecWAV.h:723
SingleBuffer< int16_t > buffer_float16
Definition CodecWAV.h:671
void setConvertFloatToInt16(bool enable)
Definition CodecWAV.h:642
static bool isNativelySupported(AudioFormat format)
Formats that are handled internally without an external decoder.
Definition CodecWAV.h:946
Print & out()
Definition CodecWAV.h:678
void setDecoder(AudioDecoderExt &dec, AudioFormat fmt=AudioFormat::UNKNOWN)
Definition CodecWAV.h:493
Vector< DecoderEntry > decoders
decoders registered via setDecoder()/addDecoder(), keyed by format tag
Definition CodecWAV.h:667
int decodeHeader(uint8_t *in_ptr, size_t in_size)
Decodes the header data: Returns the start pos of the data.
Definition CodecWAV.h:840
size_t write_out_float_to_int16(const uint8_t *in_ptr, size_t in_size)
Definition CodecWAV.h:750
AudioDecoderExt * p_decoder
Definition CodecWAV.h:665
void setConvert8Bit(bool enable)
Convert 8 bit to 16 bit PCM data (default: enabled)
Definition CodecWAV.h:622
void setupEncodedAudio()
Definition CodecWAV.h:926
void end() override
Finish decoding and release temporary buffers.
Definition CodecWAV.h:546
void endCurrentDecoder()
Definition CodecWAV.h:938
WAVHeader & getHeader()
Access to the internal header parser and info.
Definition CodecWAV.h:647
const char * mime()
Provides MIME type "audio/wav".
Definition CodecWAV.h:556
size_t write_out_alaw(const uint8_t *in_ptr, size_t in_size)
Expand G.711 A-law 8-bit samples to 16-bit linear PCM and write out.
Definition CodecWAV.h:776
bool isValid
Definition CodecWAV.h:661
bool convertALawMuLaw
Definition CodecWAV.h:674
void logSupportedFormats()
Definition CodecWAV.h:900
AudioDecoderExt * findDecoder(AudioFormat format)
Finds the decoder registered for the given format tag, if any.
Definition CodecWAV.h:919
WAVDecoder()=default
Construct a new WAVDecoder object for PCM data.
WAVDecoder(AudioDecoderExt &dec, AudioFormat fmt=AudioFormat::UNKNOWN)
Construct a new WAVDecoder object for ADPCM data. If fmt is not provided, it is derived from dec....
Definition CodecWAV.h:478
static int16_t alaw2linear(uint8_t a_val)
Converts a G.711 A-law encoded byte to a 16-bit linear PCM sample.
Definition CodecWAV.h:812
const size_t batch_size
Definition CodecWAV.h:676
SingleBuffer< uint8_t > byte_buffer
Definition CodecWAV.h:669
WAVAudioInfo & audioInfoEx()
Extended WAV specific info (original header values)
Definition CodecWAV.h:559
size_t write_out_mulaw(const uint8_t *in_ptr, size_t in_size)
Expand G.711 mu-law 8-bit samples to 16-bit linear PCM and write out.
Definition CodecWAV.h:794
WAVHeader header
Definition CodecWAV.h:659
void addDecoder(AudioDecoderExt &dec, AudioFormat fmt=AudioFormat::UNKNOWN)
Definition CodecWAV.h:507
static int16_t ulaw2linear(uint8_t u_val)
Converts a G.711 mu-law encoded byte to a 16-bit linear PCM sample.
Definition CodecWAV.h:832
void setConvert24Bit(bool enable)
Convert 24 bit (3 byte) to 32 bit (4 byte) PCM data (default: enabled)
Definition CodecWAV.h:627
bool begin() override
Prepare decoder for a new WAV stream.
Definition CodecWAV.h:529
size_t write_out_8to16(const uint8_t *in_ptr, size_t in_size)
Convert 8-bit PCM to 16-bit PCM and write out.
Definition CodecWAV.h:705
EncodedAudioOutput dec_out
Definition CodecWAV.h:668
bool convert24
Definition CodecWAV.h:673
virtual size_t write_out(const uint8_t *in_ptr, size_t in_size)
Definition CodecWAV.h:680
AudioInfo audioInfo() override
Exposed AudioInfo (may reflect conversion flags)
Definition CodecWAV.h:562
bool convert8to16
Definition CodecWAV.h:672
SingleBuffer< int32_t > buffer24
Definition CodecWAV.h:670
void setConvertALawMuLaw(bool enable)
Definition CodecWAV.h:634
A simple WAV file encoder. If no AudioEncoderExt is specified the WAV file contains PCM data,...
Definition CodecWAV.h:987
virtual size_t write(const uint8_t *data, size_t len) override
Writes PCM data to be encoded as WAV.
Definition CodecWAV.h:1107
void setOutput(Print &out) override
Defines the otuput stream.
Definition CodecWAV.h:1010
CountingPrint counting_print
tracks the actual encoded bytes p_encoder emits to p_print (see write())
Definition CodecWAV.h:1222
volatile bool is_open
Definition CodecWAV.h:1226
uint32_t pending_data_length
Definition CodecWAV.h:1233
bool isOpen()
Check if encoder is open.
Definition CodecWAV.h:1178
bool has_pending_offset
Definition CodecWAV.h:1234
virtual bool begin() override
starts the processing using the actual WAVAudioInfo
Definition CodecWAV.h:1076
bool header_written
Definition CodecWAV.h:1225
AudioEncoderExt * p_encoder
Definition CodecWAV.h:1219
bool begin(WAVAudioInfo ai)
starts the processing
Definition CodecWAV.h:1070
WAVAudioInfo wav_info
Definition CodecWAV.h:1223
void end() override
stops the processing
Definition CodecWAV.h:1104
virtual void setAudioInfo(WAVAudioInfo ai)
Definition CodecWAV.h:1046
int64_t size_limit
Definition CodecWAV.h:1224
WAVEncoder()=default
Construct a new WAVEncoder object for PCM data.
void setDataOffset(uint16_t offset)
Definition CodecWAV.h:1183
WAVHeader & getHeader()
Access to the internal header parser and info.
Definition CodecWAV.h:1214
EncodedAudioOutput enc_out
Definition CodecWAV.h:1220
void setExtADPCMHeader(bool enable)
Definition CodecWAV.h:1193
bool setupEncodedAudio()
Definition CodecWAV.h:1274
bool has_pending_data_length
Definition CodecWAV.h:1232
WAVAudioInfo & audioInfoEx()
Extended WAV specific info.
Definition CodecWAV.h:1211
bool pending_ext_adpcm_header
Definition CodecWAV.h:1237
WAVEncoder(AudioEncoderExt &enc, AudioFormat fmt)
Construct a new WAVEncoder object for ADPCM data.
Definition CodecWAV.h:997
WAVAudioInfo defaultConfig()
Provides the default configuration.
Definition CodecWAV.h:1019
WAVHeader header
Definition CodecWAV.h:1217
const char * mime() override
Provides "audio/wav".
Definition CodecWAV.h:1016
virtual void setAudioInfo(AudioInfo from) override
Update actual WAVAudioInfo.
Definition CodecWAV.h:1033
Print * p_print
Definition CodecWAV.h:1218
void applyPendingOverrides()
Definition CodecWAV.h:1244
void setEncoder(AudioEncoderExt &enc, AudioFormat fmt)
Definition CodecWAV.h:1003
uint16_t pending_offset
Definition CodecWAV.h:1235
bool has_pending_ext_adpcm_header
Definition CodecWAV.h:1236
void setDataLength(uint32_t data_length)
Definition CodecWAV.h:1203
Parser for Wav header data for details see https://de.wikipedia.org/wiki/RIFF_WAVE.
Definition CodecWAV.h:89
void writeDataHeader(BaseBuffer< uint8_t > &buffer, const WAVAudioInfo &info)
Definition CodecWAV.h:388
void logInfo()
Definition CodecWAV.h:303
void write16(BaseBuffer< uint8_t > &buffer, uint16_t value)
Definition CodecWAV.h:426
bool parse()
Call when header data write is complete to parse the data.
Definition CodecWAV.h:109
bool isDataComplete()
Returns true if the header is complete (containd data tag)
Definition CodecWAV.h:139
void skip(int n)
Definition CodecWAV.h:279
void seek(long int offset, int origin)
Definition CodecWAV.h:291
void setAudioInfo(WAVAudioInfo info)
Sets the info in the header.
Definition CodecWAV.h:166
bool eof()
Definition CodecWAV.h:301
void writeFMT(BaseBuffer< uint8_t > &buffer, const WAVAudioInfo &info)
Definition CodecWAV.h:321
bool writeHeader(Print *out, const WAVAudioInfo &info)
Just write a wav header with explicit info to the indicated output.
Definition CodecWAV.h:174
size_t available()
number of bytes available in the header buffer
Definition CodecWAV.h:152
bool writeHeader(Print *out)
Just write a wav header to the indicated outputbu.
Definition CodecWAV.h:169
int getChar()
Definition CodecWAV.h:284
int getDataPos()
Determines the data start position using the data tag.
Definition CodecWAV.h:155
SingleBuffer< uint8_t > buffer
Definition CodecWAV.h:235
WAVAudioInfo & audioInfo()
provides the info from the header
Definition CodecWAV.h:163
static uint16_t samplesPerBlock(const WAVAudioInfo &info)
Definition CodecWAV.h:410
int write(uint8_t *data, size_t data_len)
Definition CodecWAV.h:97
void clear()
Reset internal stored header information and buffer.
Definition CodecWAV.h:208
uint32_t read_tag()
Definition CodecWAV.h:252
void writeRiffHeader(BaseBuffer< uint8_t > &buffer, const WAVAudioInfo &info)
Definition CodecWAV.h:311
WAVAudioInfo headerInfo
Definition CodecWAV.h:234
uint16_t read_int16()
Definition CodecWAV.h:272
bool isOverflow()
Definition CodecWAV.h:147
bool setPos(const char *id)
Definition CodecWAV.h:238
uint32_t read_int32()
Definition CodecWAV.h:263
int indexOf(const char *str)
Definition CodecWAV.h:246
void writeFactChunk(BaseBuffer< uint8_t > &buffer, const WAVAudioInfo &info)
Definition CodecWAV.h:377
size_t data_pos
Definition CodecWAV.h:236
static int extraHeaderBytes(const WAVAudioInfo &info)
Definition CodecWAV.h:195
void write32(BaseBuffer< uint8_t > &buffer, uint64_t value)
Definition CodecWAV.h:422
void dumpHeader()
Debug helper: dumps header bytes as printable characters.
Definition CodecWAV.h:220
size_t tell()
Definition CodecWAV.h:299
uint32_t getChar32()
Definition CodecWAV.h:261
24bit integer which is used for I2S sound processing. The values are really using 3 bytes....
Definition int24_3bytes_t.h:21
24bit integer which is used for I2S sound processing. The values are represented as int32_t,...
Definition int24_4bytes_t.h:22
AudioFormat
Audio format codes used by Microsoft e.g. in avi or wav files.
Definition AudioFormat.h:21
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition LMSEchoCancellationStream.h:6
static const char * wav_mime
Definition CodecWAV.h:50
static bool isADPCMFormat(AudioFormat format)
Definition CodecWAV.h:58
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:56
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:58
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:60
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:62
Sound information which is available in the WAV header.
Definition CodecWAV.h:30
AudioFormat format
Definition CodecWAV.h:38
bool is_streamed
Definition CodecWAV.h:41
bool is_valid
Definition CodecWAV.h:42
int block_align
Definition CodecWAV.h:40
WAVAudioInfo(const AudioInfo &from)
Definition CodecWAV.h:32
uint32_t file_size
Definition CodecWAV.h:44
int offset
Definition CodecWAV.h:45
int byte_rate
Definition CodecWAV.h:39
uint32_t data_length
Definition CodecWAV.h:43
bool ext_adpcm_header
write the extended 'fmt ' chunk + 'fact' chunk for ADPCM formats
Definition CodecWAV.h:47
Associates a WAV format tag with the decoder responsible for it.
Definition CodecWAV.h:651
AudioFormat format
Definition CodecWAV.h:652
DecoderEntry()=default
AudioDecoderExt * decoder
Definition CodecWAV.h:653
DecoderEntry(AudioFormat fmt, AudioDecoderExt *dec)
Definition CodecWAV.h:655