You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

268 lines
7.6 KiB

#include "mp3.h"
#include <HTTPClient.h>
#include <SD.h>
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourcePROGMEM.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioGeneratorRTTTL.h"
#include "AudioOutputI2S.h"
//#include "driver/gpio.h"
//#include "driver/periph_ctrl.h"
//#include "soc/gpio_sig_map.h"
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"
#include "esp_system.h"
#include <math.h>
#include "HTTPClient.h"
#include "alarmsound.h"
char titleStr[64];
// convert mp3 files with `xxd -i alarm.wav > alarmsound.h`
// has to be: const unsigned char alarmsound[] PROGMEM = ...
MP3::MP3() {
strncpy_P(URL, "http://streaming.shoutcast.com/80sPlanet?lang=en-US", sizeof(URL));
//const char *URL="http://swr-swr1-bw.cast.addradio.de/swr/swr1/bw/mp3/64/stream.mp3";
}
/*gpio_set_direction(GPIO_NUM_13,GPIO_MODE_OUTPUT); // GPIO_MODE_DEF_OUTPUT
gpio_set_direction(GPIO_NUM_12,GPIO_MODE_OUTPUT);
gpio_set_direction(GPIO_NUM_22,GPIO_MODE_OUTPUT);
gpio_matrix_out(13, I2S0O_BCK_OUT_IDX, 0, 0);
gpio_matrix_out(12, I2S0O_WS_OUT_IDX, 0, 0);
gpio_matrix_out(22, I2S0O_DATA_OUT0_IDX, 0, 0);
//periph_module_reset( PERIPH_I2S0_MODULE );
periph_module_disable(PERIPH_I2S0_MODULE);
delay(100);
periph_module_enable(PERIPH_I2S0_MODULE);*/
void MP3::cTaskWrapper(void* parameters) {
static_cast<MP3*>(parameters)->mp3_decoder_task(NULL);
}
void MP3::stop() {
Serial.println("Stopping mp3 playback");
strcpy(titleStr, "stopping..");
playing = false;
aborted = false;
// while (audioTaskHandle && eTaskGetState(audioTaskHandle) != eDeleted) {
// Serial.println("waiting for audio to finish");
delay(100);
// }
}
void MP3::start(const char* url) {
strncpy(URL, url, sizeof(URL));
URL[sizeof(URL)-1] = '\0';
fromProgmem = false;
start();
}
void MP3::start() {
if (playing) {
stop();
}
Serial.println("Starting mp3 playback");
Serial.printf("%x\n", audioTaskHandle);
aborted = false;
if (playing == false) {
xTaskCreate(
&cTaskWrapper, /* Task function. */
"audioTask", /* String with name of task. */
3072, /* Stack size in words. */
this, /* Parameter passed as input of the task */
tskIDLE_PRIORITY+1, /* Priority of the task. */
&audioTaskHandle); /* Task handle. */
}
}
void MP3::start_Progmem() {
fromProgmem = true;
start();
}
void MP3::playRTTTL(const char* str, size_t len) {
rtttl = str;
rtttl_len = len;
volume = 2;
start();
}
void MP3::setVolume(int volume) {
if (volume > 0 && volume <= 100) {
this->volume = volume;
volumeChanged = true;
}
}
int MP3::getVolume() {
return volume;
}
uint32_t MP3::getBuffFill() {
if (audiobuffer) return ((100U*audiobuffer->getFillLevel())/preallocateBufferSize);
else return 0;
}
void MP3::mp3_decoder_task(void *pvParameters) {
AudioGenerator *decoder = nullptr;
AudioFileSource *file = nullptr;
AudioFileSourceBuffer *buff = nullptr;
AudioOutputI2S *out = nullptr;
// strcpy(titleStr, "loading...");
if (fromProgmem) file = new AudioFileSourcePROGMEM(alarmsound, alarmsound_len);
else if (rtttl_len > 0) file = new AudioFileSourcePROGMEM( rtttl, rtttl_len );
else {
HTTPClient http;
http.begin(URL);
const char* headerNames[] = { "Location", "Last-Modified" };
http.collectHeaders(headerNames, sizeof(headerNames)/sizeof(headerNames[0]));
int httpCode = http.GET();
if (httpCode == HTTP_CODE_FOUND || httpCode == HTTP_CODE_SEE_OTHER) { // check for redirect
Serial.println("redirect!!!!!!!!!!!!!!!");
if (http.hasHeader("Location")) {
Serial.println(http.header("Location"));
http.header("Location").toCharArray(URL, 80);
}
}
http.end();
file = new AudioFileSourceICYStream(URL);
}
file->RegisterMetadataCB(MDCallback, (void*)"ICY");
buff = new AudioFileSourceBuffer(file, preallocateBuffer, preallocateBufferSize);
audiobuffer = buff;
buff->RegisterStatusCB(StatusCallback, (void*)"buffer");
if (out == NULL) {
out = new AudioOutputI2S(I2S_NUM_0, AudioOutputI2S::EXTERNAL_I2S, 32, AudioOutputI2S::APLL_DISABLE);
out->SetPinout(12, 13, 25);
//out->SetRate(44100);
//out->SetBitsPerSample(I2S_BITS_PER_SAMPLE_16BIT);
//out->SetChannels(I2S_CHANNEL_STEREO);
//out->SetOutputModeMono(true);
}
if (rtttl_len > 0) {
decoder = new AudioGeneratorRTTTL();
Serial.println(rtttl_len); Serial.println(FPSTR(rtttl));
rtttl_len = 0;
} else {
decoder = new AudioGeneratorMP3(preallocateCodec, preallocateCodecSize);
}
decoder->RegisterStatusCB(StatusCallback, (void*)"mp3");
decoder->begin(buff, out);
if (volume > 0 && volume <= 100) {
out->SetGain(((float)volume)/100.0);
} else {
out->SetGain(0.2);
volume = 20;
}
playing = true;
while(decoder->isRunning()) {
if (volumeChanged) {
volumeChanged = false;
out->SetGain(((float)volume)/(100.0/1.50)); // gain from 0.1 to 4.0
}
if (!decoder->loop()) {
aborted = true;
break;
}
if (!playing) break;
vTaskDelay(20 / portTICK_PERIOD_MS);
}
playing = false;
i2s_zero_dma_buffer(I2S_NUM_0);
Serial.printf("MP3 done\n");
strcpy(titleStr, "stopped");
if (decoder) {
decoder->stop();
delete decoder;
decoder = nullptr;
}
if (buff) {
buff->close();
delete buff;
buff = nullptr;
audiobuffer = nullptr;
}
if (file) {
file->close();
delete file;
file = nullptr;
}
vTaskDelete(NULL);
// audioTaskHandle = NULL;
}
bool MP3::begin() {
strcpy(titleStr, "ready");
// First, preallocate all the memory needed for the buffering and codecs, never to be freed
preallocateBuffer = malloc(preallocateBufferSize);
preallocateCodec = malloc(preallocateCodecSize);
if (!preallocateBuffer || !preallocateCodec) {
Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
return false;
}
/*
I2S0.clkm_conf.clka_en = 1; // was 0
I2S0.clkm_conf.clkm_div_a = 1; // was 63;
I2S0.clkm_conf.clkm_div_b = 0; // was clkmDecimals;
I2S0.clkm_conf.clkm_div_num = 1; // was clkmInteger;
// periph_module_enable(PERIPH_EMAC_MODULE);
// rtc_clk_apll_enable(1,0,73,7,8);
// i2s_set_clk((i2s_port_t)portNo, 44100, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
i2s_set_clk(I2S_NUM, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
*/
return true;
}
// Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc.
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
// Note that the type and string may be in PROGMEM, so copy them to RAM for printf
char s1[32], s2[64];
strncpy_P(s1, type, sizeof(s1));
s1[sizeof(s1)-1]=0;
strncpy_P(s2, string, sizeof(s2));
s2[sizeof(s2)-1]=0;
Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2);
Serial.flush();
if (strcmp("StreamTitle", type)==0) {
strncpy_P(titleStr, string, sizeof(titleStr));
s2[sizeof(s2)-1]=0;
}
}
// 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);
// Note that the string may be in PROGMEM, so copy it to RAM for printf
char s1[64];
strncpy_P(s1, string, sizeof(s1));
s1[sizeof(s1)-1]=0;
Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1);
Serial.flush();
}