diff --git a/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino b/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino index f85baa39..dbba37d0 100644 --- a/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino +++ b/examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino @@ -16,21 +16,22 @@ AudioFileSourceSPIFFS *file; AudioOutputI2SNoDAC *out; AudioFileSourceID3 *id3; -void ID3Callback(const char *type, bool unicode, int len, AudioFileSource *src) + +// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. +void MDCallback(void *cbData, const char *type, bool isUnicode, Stream *stream) { - Serial.printf("ID3 callback for: %s = [%d bytes] '", type, len); - if (unicode && len > 2) { - char ign[2]; - src->read(&ign, 2); - len -= 2; + (void)cbData; + Serial.printf("ID3 callback for: %s = '", type); + + if (isUnicode) { + // Skip byte order marker + stream->read(); + stream->read(); } - while (len) { - char a, b; - src->read(&a, 1); - len--; - if (unicode && len) { - src->read(&b, 1); - len--; + while (stream->available()) { + char a = stream->read(); + if (isUnicode) { + stream->read(); } Serial.printf("%c", a); } @@ -48,7 +49,7 @@ void setup() Serial.printf("Sample MP3 playback begins...\n"); file = new AudioFileSourceSPIFFS("/pno-cs.mp3"); id3 = new AudioFileSourceID3(file); - id3->setCallback(ID3Callback); + id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG"); out = new AudioOutputI2SNoDAC(); mp3 = new AudioGeneratorMP3(); mp3->begin(id3, out); diff --git a/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino b/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino index 68aa82d1..4f689db6 100644 --- a/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino +++ b/examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino @@ -1,4 +1,5 @@ #include + #include #include "AudioFileSourceICYStream.h" #include "AudioFileSourceBuffer.h" @@ -8,8 +9,8 @@ // To run, set your ESP8266 build to 160MHz, update the SSID info, and upload. // Enter your WiFi setup here: -const char *SSID = "....."; -const char *PASSWORD = "....."; +const char *SSID = "...."; +const char *PASSWORD = "...."; // Randomly picked URL const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; @@ -19,12 +20,24 @@ AudioFileSourceICYStream *file; AudioFileSourceBuffer *buff; AudioOutputI2SNoDAC *out; -void ICYCallback(const char *type, const char *value) +// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. +void MDCallback(void *cbData, const char *type, bool isUnicode, Stream *stream) { - Serial.printf("ICY MD: '%s' = '%s'\n", type, value); + const char *ptr = reinterpret_cast(cbData); + (void) isUnicode; // Punt this ball for now + Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, type, stream->readString().c_str()); Serial.flush(); } +// Called when there's a warning or error (like a buffer underflow or decode hiccup) +void StatusCallback(void *cbData, int code, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, string); + Serial.flush(); +} + + void setup() { Serial.begin(115200); @@ -47,15 +60,19 @@ void setup() Serial.println("...Connecting to WiFi"); delay(1000); } + Serial.println("Connected"); file = new AudioFileSourceICYStream(URL); - file->setCallback(ICYCallback); + file->RegisterMetadataCB(MDCallback, (void*)"ICY"); buff = new AudioFileSourceBuffer(file, 2048); + buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); out = new AudioOutputI2SNoDAC(); mp3 = new AudioGeneratorMP3(); + mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); mp3->begin(buff, out); } + void loop() { static int lastms = 0; diff --git a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino index 827740ad..1b84f21e 100644 --- a/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino +++ b/examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino @@ -1,6 +1,6 @@ #include #include -#include "AudioFileSourceHTTPStream.h" +#include "AudioFileSourceICYStream.h" #include "AudioFileSourceSPIRAMBuffer.h" #include "AudioGeneratorMP3.h" #include "AudioOutputI2SNoDAC.h" @@ -15,10 +15,27 @@ const char *PASSWORD = "....."; const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; AudioGeneratorMP3 *mp3; -AudioFileSourceHTTPStream *file; +AudioFileSourceICYStream *file; AudioFileSourceSPIRAMBuffer *buff; AudioOutputI2SNoDAC *out; +// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. +void MDCallback(void *cbData, const char *type, bool isUnicode, Stream *stream) +{ + const char *ptr = reinterpret_cast(cbData); + (void) isUnicode; // Punt this ball for now + Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, type, stream->readString().c_str()); + Serial.flush(); +} + +// Called when there's a warning or error (like a buffer underflow or decode hiccup) +void StatusCallback(void *cbData, int code, const char *string) +{ + const char *ptr = reinterpret_cast(cbData); + Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, string); + Serial.flush(); +} + void setup() { Serial.begin(115200); @@ -41,18 +58,29 @@ void setup() Serial.println("...Connecting to WiFi"); delay(1000); } + Serial.println("Connected"); - file = new AudioFileSourceHTTPStream(URL); + file = new AudioFileSourceICYStream(URL); + file->RegisterMetadataCB(MDCallback, (void*)"ICY"); // Initialize 23LC1024 SPI RAM buffer with chip select ion GPIO4 and ram size of 128KByte buff = new AudioFileSourceSPIRAMBuffer(file, 4, 131072); + buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); out = new AudioOutputI2SNoDAC(); mp3 = new AudioGeneratorMP3(); + mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); mp3->begin(buff, out); } void loop() { + static int lastms = 0; + if (mp3->isRunning()) { + if (millis()-lastms > 1000) { + lastms = millis(); + Serial.printf("Running for %d ms...\n", lastms); + Serial.flush(); + } if (!mp3->loop()) mp3->stop(); } else { Serial.printf("MP3 done\n"); diff --git a/src/AudioFileSource.h b/src/AudioFileSource.h index c8890aea..ea746ef2 100644 --- a/src/AudioFileSource.h +++ b/src/AudioFileSource.h @@ -22,6 +22,7 @@ #define _AUDIOFILESOURCE_H #include +#include "AudioStatus.h" class AudioFileSource { @@ -37,6 +38,13 @@ class AudioFileSource virtual uint32_t getSize() { return 0; }; virtual uint32_t getPos() { return 0; }; virtual bool loop() { return true; }; + + public: + virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } + virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); } + + protected: + AudioStatus cb; }; #endif diff --git a/src/AudioFileSourceBuffer.cpp b/src/AudioFileSourceBuffer.cpp index 132bde87..a2d90083 100644 --- a/src/AudioFileSourceBuffer.cpp +++ b/src/AudioFileSourceBuffer.cpp @@ -78,7 +78,7 @@ uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len) uint32_t bytes = 0; if (!filled) { // Fill up completely before returning any data at all - Serial.println("Buffering..."); + cb.st(STATUS_FILLING, "Refilling buffer"); length = src->read(buffer, buffSize); writePtr = length % buffSize; filled = true; @@ -115,6 +115,7 @@ uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len) writePtr = 0; length = 0; filled = false; + cb.st(STATUS_UNDERFLOW, "Buffer underflow"); } fill(); diff --git a/src/AudioFileSourceBuffer.h b/src/AudioFileSourceBuffer.h index 7f4b5f51..940d64f8 100644 --- a/src/AudioFileSourceBuffer.h +++ b/src/AudioFileSourceBuffer.h @@ -38,6 +38,8 @@ class AudioFileSourceBuffer : public AudioFileSource virtual uint32_t getPos() override; virtual bool loop() override; + enum { STATUS_FILLING=2, STATUS_UNDERFLOW }; + private: virtual void fill(); diff --git a/src/AudioFileSourceHTTPStream.cpp b/src/AudioFileSourceHTTPStream.cpp index 787fbc71..3c12b2fe 100644 --- a/src/AudioFileSourceHTTPStream.cpp +++ b/src/AudioFileSourceHTTPStream.cpp @@ -42,6 +42,7 @@ bool AudioFileSourceHTTPStream::open(const char *url) int code = http.GET(); if (code != HTTP_CODE_OK) { http.end(); + cb.st(STATUS_HTTPFAIL, "Can't open HTTP request"); return false; } size = http.getSize(); @@ -69,21 +70,20 @@ uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool { retry: if (!http.connected()) { - Serial.println("Stream disconnected\n"); - Serial.flush(); + cb.st(STATUS_DISCONNECTED, "Stream disconnected"); http.end(); for (int i = 0; i < reconnectTries; i++) { - Serial.printf("Attempting to reconnect, try %d\n", i); - Serial.flush(); + char buff[32]; + sprintf(buff, "Attempting to reconnect, try %d", i); + cb.st(STATUS_RECONNECTING, buff); delay(reconnectDelayMs); if (open(saveURL)) { - Serial.println("Reconnected to stream"); + cb.st(STATUS_RECONNECTED, "Stream reconnected"); break; } } if (!http.connected()) { - Serial.printf("Unable to reconnect\n"); - Serial.flush(); + cb.st(STATUS_DISCONNECTED, "Unable to reconnect"); return 0; } } @@ -101,7 +101,7 @@ uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool size_t avail = stream->available(); if (!nonBlock && !avail) { - Serial.printf("No stream data available\n"); + cb.st(STATUS_NODATA, "No stream data available"); http.end(); goto retry; } diff --git a/src/AudioFileSourceHTTPStream.h b/src/AudioFileSourceHTTPStream.h index 43760552..3958d253 100644 --- a/src/AudioFileSourceHTTPStream.h +++ b/src/AudioFileSourceHTTPStream.h @@ -45,6 +45,8 @@ class AudioFileSourceHTTPStream : public AudioFileSource virtual uint32_t getPos() override; bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; } + enum { STATUS_HTTPFAIL=2, STATUS_DISCONNECTED, STATUS_RECONNECTING, STATUS_RECONNECTED, STATUS_NODATA }; + private: virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock); HTTPClient http; diff --git a/src/AudioFileSourceICYStream.cpp b/src/AudioFileSourceICYStream.cpp index e242e137..2ff8eb4d 100644 --- a/src/AudioFileSourceICYStream.cpp +++ b/src/AudioFileSourceICYStream.cpp @@ -19,19 +19,19 @@ */ #include "AudioFileSourceICYStream.h" +#include "AudioFileSourcePROGMEM.h" +#include "AudioFileStream.h" AudioFileSourceICYStream::AudioFileSourceICYStream() { pos = 0; reconnectTries = 0; saveURL = NULL; - cb = NULL; } AudioFileSourceICYStream::AudioFileSourceICYStream(const char *url) { saveURL = NULL; - cb = NULL; reconnectTries = 0; open(url); } @@ -47,6 +47,7 @@ bool AudioFileSourceICYStream::open(const char *url) int code = http.GET(); if (code != HTTP_CODE_OK) { http.end(); + cb.st(STATUS_HTTPFAIL, "Can't open HTTP request"); return false; } if (http.hasHeader(hdr[0])) { @@ -73,6 +74,7 @@ class ICYMDReader { stream = str; avail = bytes; ptr = sizeof(buff)+1; // Cause read next time + saved = -1; } ~ICYMDReader() { // Get rid of any remaining bytes in the MD block @@ -84,6 +86,8 @@ class ICYMDReader { stream->readBytes(xxx, avail); } int read(uint8_t *dest, int len) { + if (!len) return 0; + if (saved >= 0) { *(dest++) = (uint8_t)saved; saved = -1; len--; } int ret = 0; while ((len>0) && (avail>0)) { // Always copy from bounce buffer first @@ -104,12 +108,14 @@ class ICYMDReader { return ret; } bool eof() { return (avail==0); } + bool unread(uint8_t c) { if (saved>=0) return false; saved = c; return true; } private: WiFiClient *stream; uint16_t avail; uint8_t ptr; uint8_t buff[16]; + int saved; }; @@ -117,21 +123,20 @@ uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool n { retry: if (!http.connected()) { - Serial.println("Stream disconnected\n"); - Serial.flush(); + cb.st(STATUS_DISCONNECTED, "Stream disconnected"); http.end(); for (int i = 0; i < reconnectTries; i++) { - Serial.printf("Attempting to reconnect, try %d\n", i); - Serial.flush(); + char buff[32]; + sprintf(buff, "Attempting to reconnect, try %d", i); + cb.st(STATUS_RECONNECTING, buff); delay(reconnectDelayMs); if (open(saveURL)) { - Serial.println("Reconnected to stream"); + cb.st(STATUS_RECONNECTED, "Stream reconnected"); break; } } if (!http.connected()) { - Serial.printf("Unable to reconnect\n"); - Serial.flush(); + cb.st(STATUS_DISCONNECTED, "Unable to reconnect"); return 0; } } @@ -149,8 +154,7 @@ uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool n size_t avail = stream->available(); if (!nonBlock && !avail) { - Serial.printf("No stream data available\n"); - Serial.flush(); + cb.st(STATUS_NODATA, "No stream data available"); http.end(); goto retry; } @@ -172,44 +176,54 @@ uint32_t AudioFileSourceICYStream::readInternal(void *data, uint32_t len, bool n uint8_t mdSize; int mdret = stream->readBytes(&mdSize, 1); if ((mdret == 1) && (mdSize > 0)) { - ICYMDReader md(stream, mdSize * 16); + ICYMDReader mdr(stream, mdSize * 16); // Break out (potentially multiple) NAME='xxxx' char type[32]; char value[64]; - while (!md.eof()) { + while (!mdr.eof()) { memset(type, 0, sizeof(type)); memset(value, 0, sizeof(value)); uint8_t c; char *p = type; for (size_t i=0; isrc = src; this->checked = false; - this->cb = NULL; } AudioFileSourceID3::~AudioFileSourceID3() @@ -179,17 +178,18 @@ uint32_t AudioFileSourceID3::read(void *data, uint32_t len) for (int j=0; j +#include "AudioFileStream.h" + + +AudioFileStream::AudioFileStream(AudioFileSource *source, int definedLen) +{ + src = source; + len = definedLen; + ptr = 0; + saved = -1; +} + +AudioFileStream::~AudioFileStream() +{ + // If there's a defined len, read until we're empty + if (len) { + while (ptr++ < len) (void)read(); + } +} + + +int AudioFileStream::available() +{ + if (saved >= 0) return 1; + else if (len) return ptr - len; + else if (src->getSize()) return (src->getPos() - src->getSize()); + else return 1; +} + +int AudioFileStream::read() +{ + uint8_t c; + int r; + if (ptr >= len) return -1; + ptr++; + if (saved >= 0) { + c = (uint8_t)saved; + saved = -1; + r = 1; + } else { + r = src->read(&c, 1); + } + if (r != 1) return -1; + return (int)c; +} + +int AudioFileStream::peek() +{ + uint8_t c; + if ((ptr+1) >= len) return -1; + if (saved >= 0) return saved; + int r = src->read(&c, 1); + if (r<1) return -1; + saved = c; + return saved; +} + +void AudioFileStream::flush() +{ + /* noop? */ +} diff --git a/src/AudioFileStream.h b/src/AudioFileStream.h new file mode 100644 index 00000000..e165751c --- /dev/null +++ b/src/AudioFileStream.h @@ -0,0 +1,48 @@ +/* + AudioFileStream + Convert an AudioFileSource* to a Stream* + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef AUDIOFILESTREAM_H +#define AUDIOFILESTREAM_H + +#include +#include "AudioFileSource.h" + +class AudioFileStream : public Stream +{ +public: + AudioFileStream(AudioFileSource *source, int definedLen); + virtual ~AudioFileStream(); + +public: + // Stream interface - see the Arduino library documentation. + virtual int available() override; + virtual int read() override; + virtual int peek() override; + virtual void flush() override; + virtual size_t write(uint8_t x) override { (void)x; return 0; }; + +private: + AudioFileSource *src; + int saved; + int len; + int ptr; +}; + +#endif diff --git a/src/AudioGenerator.h b/src/AudioGenerator.h index 2ca95638..889b2285 100644 --- a/src/AudioGenerator.h +++ b/src/AudioGenerator.h @@ -22,6 +22,7 @@ #define _AUDIOGENERATOR_H #include +#include "AudioStatus.h" #include "AudioFileSource.h" #include "AudioOutput.h" @@ -35,13 +36,18 @@ class AudioGenerator virtual bool stop() { return false; }; virtual bool isRunning() { return false;}; + public: + virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } + virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); } + protected: bool running; AudioFileSource *file; AudioOutput *output; int16_t lastSample[2]; -}; + protected: + AudioStatus cb; +}; #endif - diff --git a/src/AudioGeneratorAAC.cpp b/src/AudioGeneratorAAC.cpp index e5f20c8b..abc71846 100644 --- a/src/AudioGeneratorAAC.cpp +++ b/src/AudioGeneratorAAC.cpp @@ -108,7 +108,9 @@ bool AudioGeneratorAAC::loop() int ret = AACDecode(hAACDecoder, &inBuff, &bytesLeft, outSample); if (ret) { // Error, skip the frame... - Serial.printf("AAC decode error %d\n", ret); + char buff[48]; + sprintf(buff, "AAC decode error %d", ret); + cb.st(ret, buff); } else { lastFrameEnd = buffValid - bytesLeft; AACFrameInfo fi; diff --git a/src/AudioGeneratorFLAC.cpp b/src/AudioGeneratorFLAC.cpp index 33045d23..9868b165 100644 --- a/src/AudioGeneratorFLAC.cpp +++ b/src/AudioGeneratorFLAC.cpp @@ -191,6 +191,6 @@ void AudioGeneratorFLAC::metadata_cb(const FLAC__StreamDecoder *decoder, const F void AudioGeneratorFLAC::error_cb(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status) { (void) decoder; - Serial.printf("Error:%d\n", status); + cb.st((int)status, FLAC__StreamDecoderErrorStatusString[status]); } diff --git a/src/AudioGeneratorMIDI.cpp b/src/AudioGeneratorMIDI.cpp index 0c8c28b7..970b04d9 100644 --- a/src/AudioGeneratorMIDI.cpp +++ b/src/AudioGeneratorMIDI.cpp @@ -70,6 +70,8 @@ void AudioGeneratorMIDI::midi_error(const char *msg, int curpos) { + cb.st(curpos, msg); +#if 0 int ptr; Serial.printf("---> MIDI file error at position %04X (%d): %s\n", (uint16_t) curpos, (uint16_t) curpos, msg); /* print some bytes surrounding the error */ @@ -82,6 +84,7 @@ void AudioGeneratorMIDI::midi_error(const char *msg, int curpos) Serial.printf((ptr + i) == curpos ? " [%02X] " : "%02X ", (int) c & 0xff); } Serial.printf("\n"); +#endif running = false; } diff --git a/src/AudioGeneratorMP3.cpp b/src/AudioGeneratorMP3.cpp index d3946e5e..05e0c73e 100644 --- a/src/AudioGeneratorMP3.cpp +++ b/src/AudioGeneratorMP3.cpp @@ -67,9 +67,11 @@ bool AudioGeneratorMP3::isRunning() enum mad_flow AudioGeneratorMP3::ErrorToFlow() { char err[64]; + char errLine[128]; strcpy_P(err, mad_stream_errorstr(&stream)); - Serial.printf("Decoding error 0x%04x (%s) at byte offset %d\n", stream.error, err, (stream.this_frame - buff) + lastReadPos); - Serial.flush(); + snprintf(errLine, sizeof(errLine), "Decoding error '%s' at byte offset %d", + err, (stream.this_frame - buff) + lastReadPos); + cb.st(stream.error, errLine); return MAD_FLOW_CONTINUE; } @@ -193,11 +195,14 @@ bool AudioGeneratorMP3::loop() bool AudioGeneratorMP3::begin(AudioFileSource *source, AudioOutput *output) { - if (!source) return false; + if (!source) return false; file = source; if (!output) return false; this->output = output; - if (!file->isOpen()) return false; // Error + if (!file->isOpen()) { + Serial.printf("MP3 source file not open\n"); + return false; // Error + } if (!output->begin()) return false; // Where we are in generating one frame's data, set to invalid so we will run loop on first getsample() diff --git a/src/AudioGeneratorMP3.h b/src/AudioGeneratorMP3.h index f7ff4646..5d00545a 100644 --- a/src/AudioGeneratorMP3.h +++ b/src/AudioGeneratorMP3.h @@ -25,7 +25,7 @@ #include "libmad/config.h" #include "libmad/mad.h" -class AudioGeneratorMP3 : AudioGenerator +class AudioGeneratorMP3 : public AudioGenerator { public: AudioGeneratorMP3(); diff --git a/src/AudioOutput.h b/src/AudioOutput.h index 67621d4f..cc8c0fd7 100644 --- a/src/AudioOutput.h +++ b/src/AudioOutput.h @@ -22,8 +22,9 @@ #define _AUDIOOUTPUT_H #include +#include "AudioStatus.h" -class AudioOutput +class AudioOutput { public: AudioOutput() { }; @@ -38,6 +39,11 @@ class AudioOutput virtual bool stop() { return false; }; virtual bool loop() { return true; }; + public: + virtual bool RegisterMetadataCB(AudioStatus::metadataCBFn fn, void *data) { return cb.RegisterMetadataCB(fn, data); } + virtual bool RegisterStatusCB(AudioStatus::statusCBFn fn, void *data) { return cb.RegisterStatusCB(fn, data); } + + protected: void MakeSampleStereo16(int16_t sample[2]) { // Mono to "stereo" conversion if (channels == 1) @@ -61,6 +67,9 @@ class AudioOutput uint8_t bps; uint8_t channels; uint8_t gainF2P6; // Fixed point 2.6 + + protected: + AudioStatus cb; }; #endif diff --git a/src/AudioStatus.h b/src/AudioStatus.h new file mode 100644 index 00000000..2aa62e1f --- /dev/null +++ b/src/AudioStatus.h @@ -0,0 +1,55 @@ +/* + AudioStatus + Base class for Audio* status/metadata reporting + + Copyright (C) 2017 Earle F. Philhower, III + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _AUDIOSTATUS_H +#define _AUDIOSTATUS_H + +#include + +class AudioStatus +{ + public: + AudioStatus() { ClearCBs(); }; + virtual ~AudioStatus() {}; + + void ClearCBs() { mdFn = NULL; stFn = NULL; }; + + typedef void (*metadataCBFn)(void *cbData, const char *type, bool isUnicode, Stream *stream); + bool RegisterMetadataCB(metadataCBFn f, void *cbData) { mdFn = f; mdData = cbData; return true; } + + // Returns a unique warning/error code, varying by the object. The string may be a PSTR, use _P functions! + typedef void (*statusCBFn)(void *cbData, int code, const char *string); + bool RegisterStatusCB(statusCBFn f, void *cbData) { stFn = f; stData = cbData; return true; } + + // Safely call the md function, if defined + inline void md(const char *type, bool isUnicode, Stream *stream) { if (mdFn) mdFn(mdData, type, isUnicode, stream); } + + // Safely call the st function, if defined + inline void st(int code, const char *string) { if (stFn) stFn(stData, code, string); } + + private: + metadataCBFn mdFn; + void *mdData; + statusCBFn stFn; + void *stData; +}; + +#endif +