BackgroundAudio 1.4.4
Loading...
Searching...
No Matches
BackgroundAudioAAC.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 "BackgroundAudioGain.h"
26#include "BackgroundAudioBuffers.h"
27#include "libhelix-aac/aacdec.h"
28
34template<class DataBuffer>
36public:
38 _playing = false;
39 _paused = false;
40 _out = nullptr;
41 }
42
48 BackgroundAudioAACClass(AudioOutputBase &d) {
49 _playing = false;
50 _paused = false;
51 setDevice(&d);
52 }
53
55
63 bool setDevice(AudioOutputBase *d) {
64 if (!_playing) {
65 _out = d;
66 return true;
67 }
68 return false;
69 }
70
76 void setGain(float scale) {
77 _gain = (int32_t)(scale * (1 << 16));
78 }
79
89 bool begin() {
90 if (_playing || !_out) {
91 return false;
92 }
93
94#ifdef ARDUINO_ARCH_RP2040
95 _workIRQ = user_irq_claim_unused(true);
96 _workObj = this;
97 irq_set_exclusive_handler(_workIRQ, _irqStub);
98 irq_set_priority(_workIRQ, 0xc0); // Lowest prio
99 irq_set_enabled(_workIRQ, true);
100#endif
101
102 _hAACDecoder = AACInitDecoderPre(_private, sizeof(_private));
103 if (!_hAACDecoder) {
104 return false;
105 }
106
107 // We will use natural frame size to minimize mismatch
108 _out->setBuffers(5, framelen); // Framelen is in samples, but buffers takes words, which means 2 samples per word so we're good!
109 _out->onTransmit(&_cb, (void *)this); // The pump we will use to generate our audio
110 _out->setBitsPerSample(16);
111 _out->setStereo(true);
112 _out->setFrequency(44100);
113 _out->begin();
114
115 // Stuff with silence to start
116 uint16_t zeros[32] __attribute__((aligned(4))) = {};
117 while (_out->availableForWrite() > 32) {
118 _out->write((uint8_t *)zeros, sizeof(zeros));
119 }
120 _playing = true;
121
122 return true;
123 }
124
128 void end() {
129 if (_playing) {
130#ifdef ARDUINO_ARCH_RP2040
131 irq_set_enabled(_workIRQ, false);
132 user_irq_unclaim(_workIRQ);
133#endif
134 _out->end();
135 }
136 }
142 bool playing() {
143 return _playing;
144 }
145
164 size_t write(const void *data, size_t len) {
165 return _ib.write((const uint8_t *)data, len);
166 }
167
180 return _ib.availableForWrite();
181 }
182
188 size_t available() {
189 return _ib.available() - _accumShift;
190 }
191
197 bool done() {
198 return available() <= 16; // Some minimum framesize
199 }
200
206 uint32_t frames() {
207 return _frames;
208 }
209
215 uint32_t shifts() {
216 return _shifts;
217 }
218
224 uint32_t underflows() {
225 return _underflows;
226 }
227
233 uint32_t errors() {
234 return _errors;
235 }
236
242 uint32_t dumps() {
243 return _dumps;
244 }
245
253 void flush() {
254 noInterrupts();
255 _ib.flush();
256 _accumShift = 0;
257 interrupts();
258 }
259
263 void pause() {
264 _paused = true;
265 }
266
272 bool paused() {
273 return _paused;
274 }
275
279 void unpause() {
280 _paused = false;
281 }
282
283private:
284#ifdef ARDUINO_ARCH_RP2040
285 static void _irqStub() {
287 }
288
289 static void _cb(void *ptr) {
290 // Don't actually do work in the DMA interrupt, do it in the work IRQ context (low prio)
292 }
293#else
294 static void _cb(void *ptr) {
295 ((BackgroundAudioAACClass*)ptr)->pump();
296 }
297#endif
298
299 void generateOneFrame() {
300 // Every frame requires shifting all remaining data (6K?) before processing.
301 // We're not decoding AACs, we're shifting data! Instead, scroll down and only
302 // shift when we're > 1/2 of the total buffer size. We'll still shift to
303 // allow new data to be written, but we'll do it much less frequently.
304
305 int nextFrame = AACFindSyncWord((uint8_t *)_ib.buffer() + _accumShift, _ib.available() - _accumShift);
306 if (nextFrame == -1) {
307 // Could not find a sync word but we need to send a frame now do dump entire buffer and play silence
308 _ib.shiftUp(_ib.available());
309 _accumShift = 0;
310 bzero(_outSample, sizeof(_outSample));
311 _errors++;
312 _dumps++;
313 } else {
314 _accumShift += nextFrame;
315 const unsigned char *inBuff = _ib.buffer() + _accumShift;
316 int bytesLeft = _ib.available() - _accumShift;
317 int ret = AACDecode(_hAACDecoder, (unsigned char **)&inBuff, &bytesLeft, (int16_t *)_outSample);
318 if (ret) {
319 // Error in decode, play silence and skip
320 _accumShift++; // Just go one past the current bad sync and try again
321 _errors++;
322 bzero(_outSample, sizeof(_outSample));
323 } else {
324 AACFrameInfo fi;
325 AACGetLastFrameInfo(_hAACDecoder, &fi);
326 _sampleRate = fi.sampRateOut;
327 _outSamples = fi.outputSamps / 2;
328 _accumShift = inBuff - _ib.buffer();
329 _frames++;
330 if (fi.nChans == 1) {
331 for (int i = 0; i < _outSamples; i++) {
332 _outSample[i][1] = _outSample[1][0];
333 }
334 }
335 }
336 }
337
338 // If we accumulate too large of a shift, actually do the shift so more space for writer
339 if (_accumShift > _ib.size() / 2) {
340 _ib.shiftUp(_accumShift);
341 _accumShift = 0;
342 _shifts++;
343 }
344
345 ApplyGain((int16_t *)_outSample, _outSamples * 2, _gain);
346 }
347
348#ifdef ARDUINO_ARCH_RP2040
349public:
350#endif
351 void pump() {
352 while (_out->availableForWrite() >= (int)(framelen * 2 * sizeof(int16_t))) {
353 if (_paused) {
354 bzero((uint8_t *)_outSample, _outSamples * 2 * sizeof(int16_t));
355 } else {
356 generateOneFrame();
357 if (_sampleRate) {
358 _out->setFrequency(_sampleRate);
359 }
360 }
361 assert(_out->write((uint8_t *)_outSample, _outSamples * 2 * sizeof(int16_t)) == _outSamples * 2 * sizeof(int16_t));
362 }
363#ifdef ARDUINO_ARCH_RP2040
364 irq_clear(_workIRQ);
365#endif
366 }
367
368#ifdef ARDUINO_ARCH_RP2040
369 static uint8_t _workIRQ;
371#endif
372
373private:
374 AudioOutputBase *_out = nullptr;
375 HAACDecoder _hAACDecoder;
376 uint8_t _private[/*sizeof(AACDecInfo)*/ 96 + /*sizeof(PSInfoBase)*/ 28752 + /*sizeof(PSInfoSBR)*/ 50788 + 16];
377 bool _playing = false;
378 bool _paused = false;
379 static const size_t framelen = 2048;
380 int16_t _outSample[framelen][2] __attribute__((aligned(4)));
381 int _outSamples = 1024;
382 int _sampleRate = 44000;
383 DataBuffer _ib;
384 int32_t _gain = 1 << 16;
385 uint32_t _accumShift = 0;
386
387 // AAC quality stats, cumulative
388 uint32_t _frames = 0;
389 uint32_t _shifts = 0;
390 uint32_t _underflows = 0;
391 uint32_t _errors = 0;
392 uint32_t _dumps = 0;
393};
394
395#ifdef ARDUINO_ARCH_RP2040
396template<class DataBuffer> uint8_t BackgroundAudioAACClass<DataBuffer>::_workIRQ;
398#endif
399
404
using ROMBackgroundAudioAAC = BackgroundAudioAACClass<ROMDataBuffer>;
Interrupt-driven AAC decoder. Generates a full frame of samples each cycle and uses the RawBuffer to ...
Definition BackgroundAudioAAC.h:35
uint32_t underflows()
Get the number of times the AAC decoder has underflowed waiting on raw data since begin
Definition BackgroundAudioAAC.h:224
void pause()
Pause the decoder. Won't process raw input data and will transmit silence.
Definition BackgroundAudioAAC.h:263
bool begin()
Starts the background AAC decoder/player. Will initialize the output device and start sending silence...
Definition BackgroundAudioAAC.h:89
void unpause()
Unpause previously paused playback. Will start processing input data again.
Definition BackgroundAudioAAC.h:279
bool setDevice(AudioOutputBase *d)
Set an output device before begin
Definition BackgroundAudioAAC.h:63
size_t write(const void *data, size_t len)
Writes a block of raw data to the decoder's buffer.
Definition BackgroundAudioAAC.h:164
uint32_t dumps()
Get the number of full buffer dumps (catastrophic data error) since begin
Definition BackgroundAudioAAC.h:242
void flush()
Flushes any existing raw data, resets the processor to start a new AAC.
Definition BackgroundAudioAAC.h:253
uint32_t frames()
Get number of "frames" (1024 or 2048 stereo samples) processed by decoder.
Definition BackgroundAudioAAC.h:206
uint32_t errors()
Get the number of decoder errors since begin
Definition BackgroundAudioAAC.h:233
BackgroundAudioAACClass(AudioOutputBase &d)
Construct an AAC decoder with a given AudioOutputBase.
Definition BackgroundAudioAAC.h:48
bool playing()
Determines if the AAC decoder has been started.
Definition BackgroundAudioAAC.h:142
void end()
Stops the AAC decoder process and the calls the output device's end to shut it down,...
Definition BackgroundAudioAAC.h:128
size_t available()
Gets number of bytes already in the raw buffer.
Definition BackgroundAudioAAC.h:188
bool paused()
Determine if the playback is paused.
Definition BackgroundAudioAAC.h:272
bool done()
Determine if no more AAC file is present in the buffer.
Definition BackgroundAudioAAC.h:197
uint32_t shifts()
Get the number of input data shifts processed by decoder since begin
Definition BackgroundAudioAAC.h:215
size_t availableForWrite()
Gets number of bytes available to write to raw buffer.
Definition BackgroundAudioAAC.h:179
void setGain(float scale)
Set the gain multiplier (volume) for the stream. Takes effect immediately.
Definition BackgroundAudioAAC.h:76