BackgroundAudio 1.3.3
Loading...
Searching...
No Matches
BackgroundAudioWAV.h
1/*
2 BackgroundAudio
3 Plays an audio file using IRQ driven decompression. Main loop() writes
4 data to the buffer but isn't blocked while playing
5
6 Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#pragma once
23#include <Arduino.h>
24#include "WrappedAudioOutputBase.h"
25#include "BackgroundAudioBuffers.h"
26#include "BackgroundAudioGain.h"
27
33template<class DataBuffer>
35public:
37 _playing = false;
38 _paused = false;
39 _out = nullptr;
40 }
41
47 BackgroundAudioWAVClass(AudioOutputBase &d) {
48 _playing = false;
49 _paused = false;
50 setDevice(&d);
51 }
52
54
60 bool setDevice(AudioOutputBase *d) {
61 if (!_playing) {
62 _out = d;
63 return true;
64 }
65 return false;
66 }
67
73 void setGain(float scale) {
74 _gain = (int32_t)(scale * (1 << 16));
75 }
76
82 bool begin() {
83 if (!_out) {
84 return false;
85 }
86 _seenRIFF = false;
87 _seenFMT = false;
88 _seenDATA = false;
89
90 // We will use natural frame size to minimize mismatch
91 _out->setBuffers(5, framelen); // Framelen is in samples, but buffers takes words, which means 2 samples per word so we're good!
92 _out->onTransmit(&_cb, (void *)this); // The pump we will use to generate our audio
93 _out->setBitsPerSample(16);
94 _out->setStereo(true);
95 _out->setFrequency(44100);
96 _out->begin();
97
98 // Stuff with silence to start
99 uint32_t zeros[32] = {};
100 while (_out->availableForWrite() > 32) {
101 _out->write((uint8_t *)zeros, sizeof(zeros));
102 }
103 _playing = true;
104
105 return true;
106 }
107
111 void end() {
112 _out->end();
113 }
114
120 bool playing() {
121 return _playing;
122 }
123
142 size_t write(const void *data, size_t len) {
143 return _ib.write((const uint8_t *)data, len);
144 }
145
158 return _ib.availableForWrite();
159 }
160
166 size_t available() {
167 return _ib.available() - _accumShift;
168 }
169
175 bool done() {
176 return !available();
177 }
178
184 uint32_t frames() {
185 return _frames;
186 }
187
193 uint32_t shifts() {
194 return _shifts;
195 }
196
202 uint32_t underflows() {
203 return _underflows;
204 }
205
211 uint32_t errors() {
212 return _errors;
213 }
214
220 uint32_t dumps() {
221 return _dumps;
222 }
223
231 void flush() {
232 noInterrupts();
233 _ib.flush();
234 _seenRIFF = false;
235 _seenFMT = false;
236 _seenDATA = false;
237 _dataSkipped = 0;
238 _dataRemaining = 0;
239 _accumShift = 0;
240 interrupts();
241 }
242
246 void pause() {
247 _paused = true;
248 }
249
255 bool paused() {
256 return _paused;
257 }
258
262 void unpause() {
263 _paused = false;
264 }
265
266private:
267 static void _cb(void *ptr) {
268 ((BackgroundAudioWAVClass*)ptr)->pump();
269 }
270
271 void generateOneFrame() {
272 int16_t *out = (int16_t *)_outSample;
273 int16_t *end = &_outSample[framelen * 2];
274 while (out < end) {
275 // If we accumulate too large of a shift, actually do the shift so more space for writer
276 if (_accumShift > _ib.size() / 2) {
277 _ib.shiftUp(_accumShift);
278 _accumShift = 0;
279 _shifts++;
280 }
281
282 const unsigned char *b = _ib.buffer() + _accumShift;
283 int avail = _ib.available() - _accumShift;
284 if (avail <= 0) {
285 goto underflow;
286 }
287 if (_dataSkipped) {
288 int toSkip = std::min(_dataSkipped, avail);
289 _accumShift += toSkip;
290 _dataSkipped -= toSkip;
291 continue;
292 }
293 if (_dataRemaining > 0) {
294 if ((_channels == 1) && (_bps == 8)) {
295 if (avail < 1) {
296 goto underflow;
297 }
298 int16_t l = b[0]; // 0-255 unsigned
299 l -= 128; // -128...127 signed
300 l <<= 8; // -32K..32K signed
301 *out++ = l;
302 *out++ = l;
303 _accumShift++;
304 } else if ((_channels == 2) && (_bps == 8)) {
305 if (avail < 2) {
306 goto underflow;
307 }
308 int16_t l = b[0]; // 0-255 unsigned
309 l -= 128; // -128...127 signed
310 l <<= 8; // -32K..32K signed
311 *out++ = l;
312 int16_t r = b[1]; // 0-255 unsigned
313 r -= 128; // -128...127 signed
314 r <<= 8; // -32K..32K signed
315 *out++ = r;
316 _accumShift += 2;
317 } else if ((_channels == 1) && (_bps == 16)) {
318 if (avail < 2) {
319 goto underflow;
320 }
321 int16_t l = b[0] | (b[1] << 8);
322 *out++ = l;
323 *out++ = l;
324 _accumShift += 2;
325 } else { // 16b, stereo
326 if (avail < 4) {
327 goto underflow;
328 }
329 int16_t l = b[0] | (b[1] << 8);
330 *out++ = l;
331 int16_t r = b[2] | (b[3] << 8);
332 *out++ = r;
333 _accumShift += 4;
334 }
335 _dataRemaining--;
336 if (!_dataRemaining) {
337 _seenDATA = false;
338 }
339 continue;
340 } else {
341 if (avail < 4) { // Need 4 bytes to get a header type
342 goto underflow;
343 }
344 if (!memcmp(b, "RIFF", 4)) {
345 if (avail < 12) {
346 goto underflow;
347 }
348 if (!memcmp(b + 8, "WAVE", 4)) {
349 // This is a RIFF header. We will ignore the total size
350 _seenRIFF = true;
351 _accumShift += 12; // Skip it
352 continue;
353 }
354 _accumShift++; // Error, shift along
355 continue;
356 } else if (_seenRIFF && !_seenFMT && !memcmp(b, "fmt ", 4)) {
357 if (avail < 24) {
358 goto underflow;
359 }
360 if ((b[4] != 16) || b[5] || b[6] || b[7] || (b[8] != 1) || b[9]) {
361 // Length for format not PCM, ignore this
362 _errors++;
363 _seenRIFF = false;
364 _accumShift++;
365 continue;
366 }
367 _channels = b[10] | (b[11] << 8);
368 _sampleRate = b[12] | (b[13] << 8) | (b[14] << 16) | (b[15] << 24);
369 _bps = b[22] | (b[23] << 8);
370 if ((_channels == 0) || (_channels > 2) || !((_bps == 8) || (_bps == 16)) || (_sampleRate < 4000) || (_sampleRate > 48000)) {
371 // Invalid config, we can't play
372 _errors++;
373 _sampleRate = 0;
374 _seenRIFF = false;
375 _accumShift++;
376 continue;
377 }
378 _accumShift += 24;
379 _seenFMT = true;
380 } else if (_seenRIFF && _seenFMT && !_seenDATA && !memcmp(b, "data", 4)) {
381 if (avail < 8) {
382 goto underflow;
383 }
384 _dataRemaining = b[4] | (b[5] << 8) | (b[6] << 16) | (b[7] << 24); // in bytes
385 _dataRemaining /= _bps / 8;
386 _dataRemaining /= _channels; // Now in samples
387 _seenDATA = true;
388 _seenRIFF = false;
389 _seenFMT = false;
390 _accumShift += 8; // Jump to the data
391 continue;
392 } else {
393 if (avail < 8) {
394 goto underflow;
395 }
396 _dataSkipped = b[4] | (b[5] << 8) | (b[6] << 16) | (b[7] << 24); // in bytes
397 _accumShift += 8;
398 // We'll just skip this chunk before processing
399 continue;
400 }
401 }
402underflow:
403 _underflows++;
404 if (_accumShift) {
405 _ib.shiftUp(_accumShift);
406 _accumShift = 0;
407 _shifts++;
408 }
409 // Because we're in IRQ and there's not enough data, we know no more can come it so just 0-fill remainder in tight loop
410 while (out < end) {
411 *out++ = 0;
412 *out++ = 0;
413 }
414 continue;
415 } // while(out < end-of-outsamples)
416
417 ApplyGain(_outSample, framelen * 2, _gain);
418 }
419
420
421
422 void pump() {
423 while (_out->availableForWrite() >= (int)framelen) {
424 if (_paused) {
425 bzero((uint8_t *)_outSample, framelen * 2 * sizeof(int16_t));
426 } else {
427 generateOneFrame();
428 if (_sampleRate) {
429 _out->setFrequency(_sampleRate);
430 }
431 }
432 _out->write((uint8_t *)_outSample, framelen * 2 * sizeof(int16_t));
433 }
434 }
435
436private:
437 AudioOutputBase *_out;
438 bool _playing = false;
439 bool _paused = false;
440 static const size_t framelen = 512;
441 DataBuffer _ib;
442 int16_t _outSample[framelen * 2] __attribute__((aligned(4)));
443 int32_t _gain = 1 << 16;
444 uint32_t _accumShift = 0;
445 bool _seenRIFF = false;
446 bool _seenFMT = false;
447 bool _seenDATA = false;
448 int _dataRemaining = 0;
449 int _dataSkipped = 0;
450
451 int _sampleRate = 44100;
452 int _channels = 2;
453 int _bps = 16;
454
455 // MP3 quality stats, cumulative
456 uint32_t _frames = 0;
457 uint32_t _shifts = 0;
458 uint32_t _underflows = 0;
459 uint32_t _errors = 0;
460 uint32_t _dumps = 0;
461};
462
Interrupt-driven WAV decoder. Generates a full frame of samples each cycle and uses the RawBuffer to ...
Definition BackgroundAudioWAV.h:34
BackgroundAudioWAVClass(AudioOutputBase &d)
Construct an AAC decoder with a given AudioOutputBase.
Definition BackgroundAudioWAV.h:47
uint32_t underflows()
Get the number of times the WAV decoder has underflowed waiting on raw data since begin
Definition BackgroundAudioWAV.h:202
size_t availableForWrite()
Gets number of bytes available to write to raw buffer.
Definition BackgroundAudioWAV.h:157
void flush()
Flushes any existing WAV data, resets the processor to start a new WAV.
Definition BackgroundAudioWAV.h:231
bool playing()
Determines if the WAV decoder has been started.
Definition BackgroundAudioWAV.h:120
bool done()
Determine if no more WAV file is present in the buffer.
Definition BackgroundAudioWAV.h:175
void pause()
Pause the decoder. Won't process raw input data and will transmit silence.
Definition BackgroundAudioWAV.h:246
uint32_t dumps()
Get the number of full buffer dumps (catastrophic data error) since begin
Definition BackgroundAudioWAV.h:220
void unpause()
Unpause previously paused WAV playback. Will start processing input data again.
Definition BackgroundAudioWAV.h:262
uint32_t errors()
Get the number of decoder errors since begin
Definition BackgroundAudioWAV.h:211
uint32_t shifts()
Get the number of input data shifts processed by decoder since begin
Definition BackgroundAudioWAV.h:193
size_t available()
Gets number of bytes already in the raw buffer.
Definition BackgroundAudioWAV.h:166
bool paused()
Determine if the WAV playback is paused.
Definition BackgroundAudioWAV.h:255
void end()
Stops the WAV decoder process and the calls the output device's end to shut it down,...
Definition BackgroundAudioWAV.h:111
void setGain(float scale)
Set the gain multiplier (volume) for the stream. Takes effect immediately.
Definition BackgroundAudioWAV.h:73
bool setDevice(AudioOutputBase *d)
Configure the output device before begin
Definition BackgroundAudioWAV.h:60
bool begin()
Starts the background WAV decoder/player. Will initialize the output device and start sending silence...
Definition BackgroundAudioWAV.h:82
uint32_t frames()
Get number of "frames" processed by decoder.
Definition BackgroundAudioWAV.h:184
size_t write(const void *data, size_t len)
Writes a block of raw data to the decoder's buffer.
Definition BackgroundAudioWAV.h:142