Skip to content

Commit

Permalink
Add callbacks for metadata and status reports
Browse files Browse the repository at this point in the history
All objects can have a metadata and/or status report callback once created.
They will call the source program with the parsed ID3 tag, ICY StreamTitle,
or text and numeric error ID of any interesting event.  It is up to the end
application to figure out what to do with this.  Normally, you'd just print
it to the LCD or Serial port.  But it is possible to detect, say, a buffer
overflow and then switch to a lower bitrate stream for example.
  • Loading branch information
earlephilhower committed Dec 7, 2017
1 parent 1ab9348 commit 9c120a7
Show file tree
Hide file tree
Showing 22 changed files with 339 additions and 85 deletions.
29 changes: 15 additions & 14 deletions examples/PlayMP3FromSPIFFS/PlayMP3FromSPIFFS.ino
Expand Up @@ -16,21 +16,22 @@ AudioFileSourceSPIFFS *file;
AudioOutputI2SNoDAC *out; AudioOutputI2SNoDAC *out;
AudioFileSourceID3 *id3; 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); (void)cbData;
if (unicode && len > 2) { Serial.printf("ID3 callback for: %s = '", type);
char ign[2];
src->read(&ign, 2); if (isUnicode) {
len -= 2; // Skip byte order marker
stream->read();
stream->read();
} }
while (len) { while (stream->available()) {
char a, b; char a = stream->read();
src->read(&a, 1); if (isUnicode) {
len--; stream->read();
if (unicode && len) {
src->read(&b, 1);
len--;
} }
Serial.printf("%c", a); Serial.printf("%c", a);
} }
Expand All @@ -48,7 +49,7 @@ void setup()
Serial.printf("Sample MP3 playback begins...\n"); Serial.printf("Sample MP3 playback begins...\n");
file = new AudioFileSourceSPIFFS("/pno-cs.mp3"); file = new AudioFileSourceSPIFFS("/pno-cs.mp3");
id3 = new AudioFileSourceID3(file); id3 = new AudioFileSourceID3(file);
id3->setCallback(ID3Callback); id3->RegisterMetadataCB(MDCallback, (void*)"ID3TAG");
out = new AudioOutputI2SNoDAC(); out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3(); mp3 = new AudioGeneratorMP3();
mp3->begin(id3, out); mp3->begin(id3, out);
Expand Down
27 changes: 22 additions & 5 deletions examples/StreamMP3FromHTTP/StreamMP3FromHTTP.ino
@@ -1,4 +1,5 @@
#include <Arduino.h> #include <Arduino.h>

#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include "AudioFileSourceICYStream.h" #include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h" #include "AudioFileSourceBuffer.h"
Expand All @@ -8,8 +9,8 @@
// To run, set your ESP8266 build to 160MHz, update the SSID info, and upload. // To run, set your ESP8266 build to 160MHz, update the SSID info, and upload.


// Enter your WiFi setup here: // Enter your WiFi setup here:
const char *SSID = "....."; const char *SSID = "....";
const char *PASSWORD = "....."; const char *PASSWORD = "....";


// Randomly picked URL // Randomly picked URL
const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US";
Expand All @@ -19,12 +20,24 @@ AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff; AudioFileSourceBuffer *buff;
AudioOutputI2SNoDAC *out; 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<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, type, stream->readString().c_str());
Serial.flush(); 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<const char *>(cbData);
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, string);
Serial.flush();
}


void setup() void setup()
{ {
Serial.begin(115200); Serial.begin(115200);
Expand All @@ -47,15 +60,19 @@ void setup()
Serial.println("...Connecting to WiFi"); Serial.println("...Connecting to WiFi");
delay(1000); delay(1000);
} }
Serial.println("Connected");


file = new AudioFileSourceICYStream(URL); file = new AudioFileSourceICYStream(URL);
file->setCallback(ICYCallback); file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, 2048); buff = new AudioFileSourceBuffer(file, 2048);
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
out = new AudioOutputI2SNoDAC(); out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3(); mp3 = new AudioGeneratorMP3();
mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out); mp3->begin(buff, out);
} }



void loop() void loop()
{ {
static int lastms = 0; static int lastms = 0;
Expand Down
34 changes: 31 additions & 3 deletions examples/StreamMP3FromHTTP_SPIRAM/StreamMP3FromHTTP_SPIRAM.ino
@@ -1,6 +1,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include "AudioFileSourceHTTPStream.h" #include "AudioFileSourceICYStream.h"
#include "AudioFileSourceSPIRAMBuffer.h" #include "AudioFileSourceSPIRAMBuffer.h"
#include "AudioGeneratorMP3.h" #include "AudioGeneratorMP3.h"
#include "AudioOutputI2SNoDAC.h" #include "AudioOutputI2SNoDAC.h"
Expand All @@ -15,10 +15,27 @@ const char *PASSWORD = ".....";
const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US"; const char *URL="http://streaming.shoutcast.com/80sPlanet?lang=en-US";


AudioGeneratorMP3 *mp3; AudioGeneratorMP3 *mp3;
AudioFileSourceHTTPStream *file; AudioFileSourceICYStream *file;
AudioFileSourceSPIRAMBuffer *buff; AudioFileSourceSPIRAMBuffer *buff;
AudioOutputI2SNoDAC *out; 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<const char *>(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<const char *>(cbData);
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, string);
Serial.flush();
}

void setup() void setup()
{ {
Serial.begin(115200); Serial.begin(115200);
Expand All @@ -41,18 +58,29 @@ void setup()
Serial.println("...Connecting to WiFi"); Serial.println("...Connecting to WiFi");
delay(1000); 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 // Initialize 23LC1024 SPI RAM buffer with chip select ion GPIO4 and ram size of 128KByte
buff = new AudioFileSourceSPIRAMBuffer(file, 4, 131072); buff = new AudioFileSourceSPIRAMBuffer(file, 4, 131072);
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
out = new AudioOutputI2SNoDAC(); out = new AudioOutputI2SNoDAC();
mp3 = new AudioGeneratorMP3(); mp3 = new AudioGeneratorMP3();
mp3->RegisterStatusCB(StatusCallback, (void*)"mp3");
mp3->begin(buff, out); mp3->begin(buff, out);
} }


void loop() void loop()
{ {
static int lastms = 0;

if (mp3->isRunning()) { if (mp3->isRunning()) {
if (millis()-lastms > 1000) {
lastms = millis();
Serial.printf("Running for %d ms...\n", lastms);
Serial.flush();
}
if (!mp3->loop()) mp3->stop(); if (!mp3->loop()) mp3->stop();
} else { } else {
Serial.printf("MP3 done\n"); Serial.printf("MP3 done\n");
Expand Down
8 changes: 8 additions & 0 deletions src/AudioFileSource.h
Expand Up @@ -22,6 +22,7 @@
#define _AUDIOFILESOURCE_H #define _AUDIOFILESOURCE_H


#include <Arduino.h> #include <Arduino.h>
#include "AudioStatus.h"


class AudioFileSource class AudioFileSource
{ {
Expand All @@ -37,6 +38,13 @@ class AudioFileSource
virtual uint32_t getSize() { return 0; }; virtual uint32_t getSize() { return 0; };
virtual uint32_t getPos() { return 0; }; virtual uint32_t getPos() { return 0; };
virtual bool loop() { return true; }; 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 #endif
Expand Down
3 changes: 2 additions & 1 deletion src/AudioFileSourceBuffer.cpp
Expand Up @@ -78,7 +78,7 @@ uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len)
uint32_t bytes = 0; uint32_t bytes = 0;
if (!filled) { if (!filled) {
// Fill up completely before returning any data at all // Fill up completely before returning any data at all
Serial.println("Buffering..."); cb.st(STATUS_FILLING, "Refilling buffer");
length = src->read(buffer, buffSize); length = src->read(buffer, buffSize);
writePtr = length % buffSize; writePtr = length % buffSize;
filled = true; filled = true;
Expand Down Expand Up @@ -115,6 +115,7 @@ uint32_t AudioFileSourceBuffer::read(void *data, uint32_t len)
writePtr = 0; writePtr = 0;
length = 0; length = 0;
filled = false; filled = false;
cb.st(STATUS_UNDERFLOW, "Buffer underflow");
} }


fill(); fill();
Expand Down
2 changes: 2 additions & 0 deletions src/AudioFileSourceBuffer.h
Expand Up @@ -38,6 +38,8 @@ class AudioFileSourceBuffer : public AudioFileSource
virtual uint32_t getPos() override; virtual uint32_t getPos() override;
virtual bool loop() override; virtual bool loop() override;


enum { STATUS_FILLING=2, STATUS_UNDERFLOW };

private: private:
virtual void fill(); virtual void fill();


Expand Down
16 changes: 8 additions & 8 deletions src/AudioFileSourceHTTPStream.cpp
Expand Up @@ -42,6 +42,7 @@ bool AudioFileSourceHTTPStream::open(const char *url)
int code = http.GET(); int code = http.GET();
if (code != HTTP_CODE_OK) { if (code != HTTP_CODE_OK) {
http.end(); http.end();
cb.st(STATUS_HTTPFAIL, "Can't open HTTP request");
return false; return false;
} }
size = http.getSize(); size = http.getSize();
Expand Down Expand Up @@ -69,21 +70,20 @@ uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool
{ {
retry: retry:
if (!http.connected()) { if (!http.connected()) {
Serial.println("Stream disconnected\n"); cb.st(STATUS_DISCONNECTED, "Stream disconnected");
Serial.flush();
http.end(); http.end();
for (int i = 0; i < reconnectTries; i++) { for (int i = 0; i < reconnectTries; i++) {
Serial.printf("Attempting to reconnect, try %d\n", i); char buff[32];
Serial.flush(); sprintf(buff, "Attempting to reconnect, try %d", i);
cb.st(STATUS_RECONNECTING, buff);
delay(reconnectDelayMs); delay(reconnectDelayMs);
if (open(saveURL)) { if (open(saveURL)) {
Serial.println("Reconnected to stream"); cb.st(STATUS_RECONNECTED, "Stream reconnected");
break; break;
} }
} }
if (!http.connected()) { if (!http.connected()) {
Serial.printf("Unable to reconnect\n"); cb.st(STATUS_DISCONNECTED, "Unable to reconnect");
Serial.flush();
return 0; return 0;
} }
} }
Expand All @@ -101,7 +101,7 @@ uint32_t AudioFileSourceHTTPStream::readInternal(void *data, uint32_t len, bool


size_t avail = stream->available(); size_t avail = stream->available();
if (!nonBlock && !avail) { if (!nonBlock && !avail) {
Serial.printf("No stream data available\n"); cb.st(STATUS_NODATA, "No stream data available");
http.end(); http.end();
goto retry; goto retry;
} }
Expand Down
2 changes: 2 additions & 0 deletions src/AudioFileSourceHTTPStream.h
Expand Up @@ -45,6 +45,8 @@ class AudioFileSourceHTTPStream : public AudioFileSource
virtual uint32_t getPos() override; virtual uint32_t getPos() override;
bool SetReconnect(int tries, int delayms) { reconnectTries = tries; reconnectDelayMs = delayms; return true; } 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: private:
virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock); virtual uint32_t readInternal(void *data, uint32_t len, bool nonBlock);
HTTPClient http; HTTPClient http;
Expand Down

0 comments on commit 9c120a7

Please sign in to comment.