BackgroundAudio 1.4.4
Loading...
Searching...
No Matches
BackgroundAudioMP3.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 "libmad/config.h"
28#include "libmad/mad.h"
29
35template<class DataBuffer>
37public:
39 _playing = false;
40 _out = nullptr;
41 _paused = false;
42 }
43
49 BackgroundAudioMP3Class(AudioOutputBase &d) {
50 _playing = false;
51 _paused = false;
52 setDevice(&d);
53 }
54
56
64 bool setDevice(AudioOutputBase *d) {
65 if (!_playing) {
66 _out = d;
67 return true;
68 }
69 return false;
70 }
71
77 void setGain(float scale) {
78 _gain = (int32_t)(scale * (1 << 16));
79 }
80
81
82
83
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 // MP3 processing init
103 mad_stream_init(&_stream);
104 mad_frame_init(&_frame);
105 mad_synth_init(&_synth);
106 mad_stream_options(&_stream, 0);
107
108 // We will use natural frame size to minimize mismatch
109 _out->setBuffers(5, framelen); // Framelen is in samples, but buffers takes words, which means 2 samples per word so we're good!
110 _out->onTransmit(&_cb, (void *)this); // The pump we will use to generate our audio
111 _out->setBitsPerSample(16);
112 _out->setStereo(true);
113 _out->setFrequency(44100);
114 _out->begin();
115
116 // Stuff with silence to start
117 uint16_t zeros[32] __attribute__((aligned(4))) = {};
118 while (_out->availableForWrite() > 32) {
119 _out->write((uint8_t *)zeros, sizeof(zeros));
120 }
121
122 _playing = true;
123
124 return true;
125 }
126
130 void end() {
131 if (_playing) {
132#ifdef ARDUINO_ARCH_RP2040
133 irq_set_enabled(_workIRQ, false);
134 user_irq_unclaim(_workIRQ);
135#endif
136 _out->end();
137 }
138 _playing = false;
139 }
140
146 bool playing() {
147 return _playing;
148 }
149
168 size_t write(const void *data, size_t len) {
169 return _ib.write((const uint8_t *)data, len);
170 }
171
184 return _ib.availableForWrite();
185 }
186
192 size_t available() {
193 return _ib.available() - _accumShift;
194 }
195
201 bool done() {
202 return available() <= MAD_BUFFER_GUARD * 2; // At EOF we could bounce between n and 2n guard bytes when app checks
203 }
204
210 uint32_t frames() {
211 return _frames;
212 }
213
219 uint32_t shifts() {
220 return _shifts;
221 }
222
228 uint32_t underflows() {
229 return _underflows;
230 }
231
237 uint32_t errors() {
238 return _errors;
239 }
240
246 uint32_t dumps() {
247 return _dumps;
248 }
249
253 void pause() {
254 _paused = true;
255 }
256
262 bool paused() {
263 return _paused;
264 }
265
266
270 void unpause() {
271 _paused = false;
272 }
273
281 void flush() {
282 noInterrupts();
283 _ib.flush();
284 _accumShift = 0;
285 interrupts();
286 }
287
288private:
289#ifdef ARDUINO_ARCH_RP2040
290 static void _irqStub() {
292 }
293
294 static void _cb(void *ptr) {
295 // Don't actually do work in the DMA interrupt, do it in the work IRQ context (low prio)
297 }
298#else
299 static void _cb(void *ptr) {
300 ((BackgroundAudioMP3Class*)ptr)->pump();
301 }
302#endif
303
304 void generateOneFrame() {
305 // Every frame requires shifting all remaining data (6K?) before processing.
306 // We're not decoding MP3s, we're shifting data! Instead, scroll down and only
307 // shift when we're > 1/2 of the total buffer size. We'll still shift to
308 // allow new data to be written, but we'll do it much less frequently.
309
310 // Shift out the used data
311 if (_stream.buffer) {
312 if (_stream.next_frame && _stream.next_frame != _stream.this_frame) {
313 _accumShift += _stream.next_frame - _stream.this_frame;
314 } else {
315 // We have only a partial frame in the buffer. We need data *now* do dump it and send zeros. Underflow case
316 _accumShift += maxFrameSize;
317 }
318 }
319
320 // Try and find a frame header to send in. Should be a single check in most cases since we should be aligned after the 1st frame
321 int ptr = 0;
322 int pend = _ib.available() - 1 - _accumShift;
323 const uint8_t *b = _ib.buffer() + _accumShift;
324 bool found = false;
325 for (ptr = 0; ptr < pend; ptr++) {
326 if ((b[ptr] == 0xff) && ((b[ptr + 1] & 0xe0) == 0xe0)) {
327 _accumShift += ptr;
328 found = true;
329 break;
330 }
331 }
332 if (!found) {
333 // Couldn't find a header at all, the entire buffer is junk so dump it
334 _accumShift = 0;
335 _ib.shiftUp(_ib.available());
336 _dumps++;
337 }
338
339 // If we accumulate too large of a shift, actually do the shift so more space for writer
340 if (_accumShift > _ib.size() / 2) {
341 _ib.shiftUp(_accumShift);
342 _accumShift = 0;
343 _shifts++;
344 }
345
346 // Ensure everything gets pumped out
347 if (_ib.available() - _accumShift < MAD_BUFFER_GUARD) {
348 _ib.write0(MAD_BUFFER_GUARD);
349 _underflows++;
350 }
351
352 // Pass the new buffer information to libmad.
353 mad_stream_buffer(&_stream, _ib.buffer() + _accumShift, _ib.available() - _accumShift);
354
355 // Decode and send next frame
356 if (mad_frame_decode(&_frame, &_stream)) {
357 mad_frame_mute(&_frame);
358 _errors++;
359 }
360 mad_synth_frame(&_synth, &_frame);
361 _frames++;
362
363 // Only stereo for sanity
364 if (_synth.pcm.channels == 1) {
365 for (size_t i = 0; i < framelen; i++) {
366 _synth.pcm.samplesX[i][1] = _synth.pcm.samplesX[i][0];
367 }
368 }
369
370 ApplyGain((int16_t*)_synth.pcm.samplesX, framelen * 2, _gain);
371 }
372
373#ifdef ARDUINO_ARCH_RP2040
374public:
375#endif
376 void pump() {
377 while (_out->availableForWrite() >= (int)(framelen * 4)) {
378 if (_paused) {
379 bzero(_synth.pcm.samplesX, _synth.pcm.length * 4);
380 } else {
381 generateOneFrame();
382 if (_synth.pcm.samplerate) {
383 _out->setFrequency(_synth.pcm.samplerate);
384 }
385 }
386 assert(_out->write((uint8_t *)_synth.pcm.samplesX, _synth.pcm.length * 4) == _synth.pcm.length * 4);
387 }
388#ifdef ARDUINO_ARCH_RP2040
389 irq_clear(_workIRQ);
390#endif
391 }
392
393#ifdef ARDUINO_ARCH_RP2040
394 static uint8_t _workIRQ;
396#endif
397
398private:
399 AudioOutputBase *_out;
400 bool _playing = false;
401 bool _paused = false;
402
403 static const size_t framelen = 1152;
404 static const size_t maxFrameSize = 2881;
405 DataBuffer _ib;
406 struct mad_stream _stream;
407 struct mad_frame _frame;
408 struct mad_synth _synth;
409 int32_t _gain = 1 << 16;
410 uint32_t _accumShift = 0;
411
412 // MP3 quality stats, cumulative
413 uint32_t _frames = 0;
414 uint32_t _shifts = 0;
415 uint32_t _underflows = 0;
416 uint32_t _errors = 0;
417 uint32_t _dumps = 0;
418};
419
420#ifdef ARDUINO_ARCH_RP2040
421template<class DataBuffer> uint8_t BackgroundAudioMP3Class<DataBuffer>::_workIRQ;
423#endif
424
429
Interrupt-driven MP3 decoder. Generates a full frame of samples each cycle and uses the RawBuffer to ...
Definition BackgroundAudioMP3.h:36
size_t availableForWrite()
Gets number of bytes available to write to raw buffer.
Definition BackgroundAudioMP3.h:183
uint32_t underflows()
Get the number of times the MP3 decoder has underflowed waiting on raw data since begin
Definition BackgroundAudioMP3.h:228
uint32_t dumps()
Get the number of full buffer dumps (catastrophic data error) since begin
Definition BackgroundAudioMP3.h:246
void end()
Stops the MP3 decoder process and the calls the output device's end to shut it down,...
Definition BackgroundAudioMP3.h:130
BackgroundAudioMP3Class(AudioOutputBase &d)
Construct an MP3 output device using the specified physical audio output.
Definition BackgroundAudioMP3.h:49
void flush()
Flushes any existing raw data, resets the processor to start a new MP3.
Definition BackgroundAudioMP3.h:281
uint32_t shifts()
Get the number of input data shifts processed by decoder since begin
Definition BackgroundAudioMP3.h:219
void setGain(float scale)
Set the gain multiplier (volume) for the stream. Takes effect immediately.
Definition BackgroundAudioMP3.h:77
bool done()
Determine if no more MP3 file is present in the buffer.
Definition BackgroundAudioMP3.h:201
void unpause()
Unpause previously paused playback. Will start processing input data again.
Definition BackgroundAudioMP3.h:270
bool setDevice(AudioOutputBase *d)
Set an output device before begin
Definition BackgroundAudioMP3.h:64
bool begin()
Starts the background MP3 decoder/player. Will initialize the output device and start sending silence...
Definition BackgroundAudioMP3.h:89
size_t available()
Gets number of bytes already in the raw buffer.
Definition BackgroundAudioMP3.h:192
uint32_t errors()
Get the number of decoder errors since begin
Definition BackgroundAudioMP3.h:237
size_t write(const void *data, size_t len)
Writes a block of raw data to the decoder's buffer.
Definition BackgroundAudioMP3.h:168
void pause()
Pause the decoder. Won't process raw input data and will transmit silence.
Definition BackgroundAudioMP3.h:253
bool playing()
Determines if the MP3 decoder has been started.
Definition BackgroundAudioMP3.h:146
uint32_t frames()
Get number of "frames" (1152 stereo samples) processed by decoder.
Definition BackgroundAudioMP3.h:210
bool paused()
Determine if the playback is paused.
Definition BackgroundAudioMP3.h:262