Merge branch 'arendst:development' into ModbusBridgeTcp

This commit is contained in:
Jeroen 2022-07-26 20:25:08 +02:00 committed by GitHub
commit 3504819074
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1263 additions and 1135 deletions

View File

@ -106,8 +106,16 @@ shine_global_config *shine_initialise(shine_config_t *pub_config) {
for (x = 0; x < MAX_CHANNELS; x++) { for (x = 0; x < MAX_CHANNELS; x++) {
for (y = 0; y < MAX_GRANULES; y++) { for (y = 0; y < MAX_GRANULES; y++) {
// 2 * 2 * 576 each // 2 * 2 * 576 each
config->l3_enc[x][y] = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //Significant performance hit in IRAM config->l3_enc[x][y] = (int*)heap_caps_malloc_prefer(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //Significant performance hit in IRAM
config->mdct_freq[x][y] = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //OK 1% if (!config->l3_enc[x][y]) {
// error should never occur because of spiram size
//config->l3_enc[x][y] = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE,MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT);
}
config->mdct_freq[x][y] = (int*)heap_caps_malloc_prefer(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //OK 1%
if (!config->mdct_freq[x][y]) {
// error
//config->mdct_freq[x][y] = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT);
}
} }
} }
#ifdef SHINE_DEBUG #ifdef SHINE_DEBUG
@ -117,8 +125,16 @@ shine_global_config *shine_initialise(shine_config_t *pub_config) {
#ifdef SHINE_DEBUG #ifdef SHINE_DEBUG
printf("xrsq & xrabs each: %d\n", sizeof(int32_t)*GRANULE_SIZE); printf("xrsq & xrabs each: %d\n", sizeof(int32_t)*GRANULE_SIZE);
#endif #endif
config->l3loop->xrsq = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //OK 0.5% config->l3loop->xrsq = (int*)heap_caps_malloc_prefer(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //OK 0.5%
config->l3loop->xrabs = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //OK 0.5% if (!config->l3loop->xrsq) {
// error
//config->l3loop->xrsq = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //OK 0.5%
}
config->l3loop->xrabs = (int*)heap_caps_malloc_prefer(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //OK 0.5%
if (!config->l3loop->xrabs) {
//config->l3loop->xrabs = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_SPIRAM|MALLOC_CAP_32BIT); //OK 0.5%
}
/*typedef struct { /*typedef struct {
int32_t *xr; int32_t *xr;
@ -273,7 +289,6 @@ void shine_close(shine_global_config *config) {
} }
} }
config->l3loop = (l3loop_t*)heap_caps_malloc(sizeof(l3loop_t), MALLOC_CAP_SPIRAM);
if (config->l3loop) { if (config->l3loop) {
free(config->l3loop); free(config->l3loop);
} }

View File

@ -1142,29 +1142,3 @@ BERRY_API bbool be_isge(bvm *vm)
be_assert(vm->reg + 2 <= vm->top); be_assert(vm->reg + 2 <= vm->top);
return be_vm_isge(vm, vm->top - 2, vm->top - 1); return be_vm_isge(vm, vm->top - 2, vm->top - 1);
} }
BERRY_API int be_register(bvm *vm, int index)
{
bvalue *v;
if (!vm->registry) {
vm->registry = be_list_new(vm);
be_list_pool_init(vm, vm->registry);
}
be_assert(vm->registry != NULL);
v = be_indexof(vm, index);
return be_list_pool_alloc(vm, vm->registry, v);
}
BERRY_API void be_unregister(bvm *vm, int id)
{
be_assert(vm->registry != NULL);
be_list_pool_free(vm->registry, id);
}
BERRY_API void be_getregister(bvm *vm, int id)
{
blist *reg = vm->registry;
be_assert(reg && id > 0 && id < be_list_count(reg));
var_setval(vm->top, be_list_at(reg, id));
be_incrtop(vm);
}

View File

@ -370,7 +370,6 @@ static void premark_internal(bvm *vm)
mark_gray(vm, gc_object(vm->module.loaded)); mark_gray(vm, gc_object(vm->module.loaded));
mark_gray(vm, gc_object(vm->module.path)); mark_gray(vm, gc_object(vm->module.path));
mark_gray(vm, gc_object(vm->ntvclass)); mark_gray(vm, gc_object(vm->ntvclass));
mark_gray(vm, gc_object(vm->registry));
#if BE_USE_DEBUG_HOOK #if BE_USE_DEBUG_HOOK
if (be_isgcobj(&vm->hook)) { if (be_isgcobj(&vm->hook)) {
mark_gray(vm, gc_object(var_toobj(&vm->hook))); mark_gray(vm, gc_object(var_toobj(&vm->hook)));

View File

@ -102,7 +102,6 @@ struct bvm {
struct bstringtable strtab; /* short string table */ struct bstringtable strtab; /* short string table */
bstack tracestack; /* call state trace-stack */ bstack tracestack; /* call state trace-stack */
bmap *ntvclass; /* native class table */ bmap *ntvclass; /* native class table */
blist *registry; /* registry list */
struct bgc gc; struct bgc gc;
bctypefunc ctypefunc; /* handler to ctype_func */ bctypefunc ctypefunc; /* handler to ctype_func */
bbyte compopt; /* compilation options */ bbyte compopt; /* compilation options */

View File

@ -580,11 +580,6 @@ BERRY_API void be_module_path_set(bvm *vm, const char *path);
BERRY_API void* be_pushbytes(bvm *vm, const void *buf, size_t len); BERRY_API void* be_pushbytes(bvm *vm, const void *buf, size_t len);
BERRY_API const void* be_tobytes(bvm *vm, int index, size_t *len); BERRY_API const void* be_tobytes(bvm *vm, int index, size_t *len);
/* registry operation */
BERRY_API int be_register(bvm *vm, int index);
BERRY_API void be_unregister(bvm *vm, int id);
BERRY_API void be_getregister(bvm *vm, int id);
/* debug APIs */ /* debug APIs */
BERRY_API void be_sethook(bvm *vm, const char *mask); BERRY_API void be_sethook(bvm *vm, const char *mask);
BERRY_API void be_setntvhook(bvm *vm, bntvhook hook, void *data, int mask); BERRY_API void be_setntvhook(bvm *vm, bntvhook hook, void *data, int mask);

View File

@ -78,6 +78,10 @@ int32_t web_send_file(char mc, char *file);
#define SPECIAL_EEPMODE_SIZE 6200 #define SPECIAL_EEPMODE_SIZE 6200
#ifndef STASK_STACK
#define STASK_STACK 8192-2048
#endif
#ifdef USE_UFILESYS #ifdef USE_UFILESYS
#undef USE_SCRIPT_FATFS #undef USE_SCRIPT_FATFS
@ -2318,17 +2322,21 @@ chknext:
if (!strncmp(lp, "adc(", 4)) { if (!strncmp(lp, "adc(", 4)) {
lp = GetNumericArgument(lp + 4, OPER_EQU, &fvar, gv); lp = GetNumericArgument(lp + 4, OPER_EQU, &fvar, gv);
while (*lp==' ') lp++; while (*lp==' ') lp++;
float fvar1 = 1; float pin = 1;
if (*lp!=')') { if (*lp!=')') {
lp = GetNumericArgument(lp, OPER_EQU, &fvar1, gv); lp = GetNumericArgument(lp, OPER_EQU, &pin, gv);
if (fvar1<32 || fvar1>39) fvar1 = 32; #ifdef CONFIG_IDF_TARGET_ESP32S3
if (pin<1 || pin>20) pin = 1;
#else
if (pin<32 || pin>39) pin = 32;
#endif
} }
lp++; lp++;
if (fvar > 7) fvar = 7; if (fvar > 7) fvar = 7;
#ifdef ESP32 #ifdef ESP32
// ESP32 // ESP32
#ifdef USE_ADC #ifdef USE_ADC
fvar = AdcRead(fvar1, fvar); fvar = AdcRead(pin, fvar);
#else #else
fvar = 999.999; fvar = 999.999;
#endif // USE_ADC #endif // USE_ADC
@ -2466,11 +2474,17 @@ chknext:
while (*lp==' ') lp++; while (*lp==' ') lp++;
float fvar2; float fvar2;
lp = GetNumericArgument(lp, OPER_EQU, &fvar2, gv); lp = GetNumericArgument(lp, OPER_EQU, &fvar2, gv);
SCRIPT_SKIP_SPACES
float prio = STASK_PRIO; float prio = STASK_PRIO;
if (*lp!=')') { if (*lp!=')') {
lp = GetNumericArgument(lp, OPER_EQU, &prio, gv); lp = GetNumericArgument(lp, OPER_EQU, &prio, gv);
} }
fvar = scripter_create_task(fvar, fvar1, fvar2, prio); SCRIPT_SKIP_SPACES
float stack = STASK_STACK;
if (*lp!=')') {
lp = GetNumericArgument(lp, OPER_EQU, &stack, gv);
}
fvar = scripter_create_task(fvar, fvar1, fvar2, prio, stack);
goto nfuncexit; goto nfuncexit;
} }
#endif //USE_SCRIPT_TASK #endif //USE_SCRIPT_TASK
@ -3794,16 +3808,34 @@ chknext:
} }
goto nfuncexit; goto nfuncexit;
} }
#if defined(ESP32) && (defined(USE_M5STACK_CORE2)) /*
#if defined(ESP32) && (defined(USE_I2S_AUDIO) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC))
if (!strncmp(lp, "rec(", 4)) { if (!strncmp(lp, "rec(", 4)) {
char str[SCRIPT_MAXSSIZE]; char str[SCRIPT_MAXSSIZE];
lp = GetStringArgument(lp + 4, OPER_EQU, str, 0); lp = GetStringArgument(lp + 4, OPER_EQU, str, 0);
SCRIPT_SKIP_SPACES //SCRIPT_SKIP_SPACES
lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv); //lp = GetNumericArgument(lp, OPER_EQU, &fvar, gv);
fvar = i2s_record(str, fvar); fvar = i2s_record_shine(str);
len++; len++;
goto exit; goto exit;
} }
#endif
*/
#ifdef ESP32
if (!strncmp(lp, "rr(", 3)) {
lp+=4;
len = 0;
const char *cp = GetResetReason().c_str();
if (sp) {
if (cp) {
strlcpy(sp, cp, glob_script_mem.max_ssize);
} else {
strlcpy(sp, "-", glob_script_mem.max_ssize);
}
}
goto strexit;
}
#endif #endif
break; break;
@ -4883,6 +4915,14 @@ extern char *SML_GetSVal(uint32_t index);
fvar = !TasmotaGlobal.global_state.wifi_down; fvar = !TasmotaGlobal.global_state.wifi_down;
goto exit; goto exit;
} }
#ifdef xUSE_SHINE
if (!strncmp(vname, "wav2mp3(", 8)) {
char path[SCRIPT_MAXSSIZE];
lp = GetStringArgument(lp + 8, OPER_EQU, path, 0);
fvar = wav2mp3(path);
goto nfuncexit;
}
#endif
break; break;
case 'y': case 'y':
if (!strncmp(vname, "year", 4)) { if (!strncmp(vname, "year", 4)) {
@ -8208,6 +8248,81 @@ String ScriptUnsubscribe(const char * data, int data_len)
#endif // SUPPORT_MQTT_EVENT #endif // SUPPORT_MQTT_EVENT
#if defined(ESP32) && defined(USE_UFILESYS) && defined(USE_SCRIPT_ALT_DOWNLOAD)
ESP8266WebServer *http82_Server;
bool download82_busy;
void script_download_task82(void *path) {
SendFile_sub((char*) path, 1);
free(path);
download82_busy = false;
//AddLog(LOG_LEVEL_INFO, PSTR("UFS 82: Download finished"));
vTaskDelete( NULL );
}
void ScriptServeFile82(void) {
String stmp = http82_Server->uri();
char *cp = strstr_P(stmp.c_str(), PSTR("/ufs/"));
if (cp) {
cp += 4;
if (ufsp) {
if (ufsp->exists(cp)) {
if (download82_busy == true) {
AddLog(LOG_LEVEL_INFO, PSTR("UFS 82: Download is busy"));
return;
}
download82_busy = true;
char *path = (char*)malloc(128);
strcpy(path, cp);
xTaskCreatePinnedToCore(script_download_task82, "DT", 6000, (void*)path, 3, NULL, 1);
//AddLog(LOG_LEVEL_INFO, PSTR("Sendfile 82 started"));
return;
}
}
}
Handle82NotFound();
}
void Handle82NotFound(void) {
Send82Header(404, "not found");
}
void Handle82Root(void) {
Send82Header(403, "forbidden");
}
void WebServer82Loop(void) {
if (http82_Server != nullptr) {
http82_Server->handleClient();
}
}
void Send82Header(uint32_t type, const char *message) {
http82_Server->client().printf_P(PSTR("HTTP/1.1 %d\r\n"), type);
http82_Server->client().printf_P(PSTR("Content-type: text/plain\r\n\r\n"));
http82_Server->client().printf_P(PSTR("%s\n"), message);
}
void WebServer82Init(void) {
if (http82_Server != nullptr) {
return;
}
http82_Server = new ESP8266WebServer(82);
if (http82_Server != nullptr) {
http82_Server->on(UriGlob("/ufs/*"), HTTP_GET, ScriptServeFile82);
http82_Server->on("/", HTTP_GET, Handle82Root);
http82_Server->onNotFound(Handle82NotFound);
http82_Server->begin();
AddLog(LOG_LEVEL_INFO, PSTR("HTTP Server 82 started"));
} else {
AddLog(LOG_LEVEL_INFO, PSTR("HTTP Server 82 failed"));
}
}
#endif // USE_SCRIPT_ALT_DOWNLOAD
#ifdef USE_SCRIPT_WEB_DISPLAY #ifdef USE_SCRIPT_WEB_DISPLAY
@ -8251,7 +8366,7 @@ bool script_download_busy;
void SendFile(char *fname) { void SendFile(char *fname) {
#ifdef ESP8266 #ifdef ESP8266
SendFile_sub(fname); SendFile_sub(fname, 0);
#endif // ESP8266 #endif // ESP8266
#ifdef ESP32 #ifdef ESP32
@ -8265,7 +8380,7 @@ void SendFile(char *fname) {
strcpy(path, fname); strcpy(path, fname);
xTaskCreatePinnedToCore(script_download_task, "DT", 6000, (void*)path, 3, NULL, 1); xTaskCreatePinnedToCore(script_download_task, "DT", 6000, (void*)path, 3, NULL, 1);
#else #else
SendFile_sub(fname); SendFile_sub(fname, 0);
#endif #endif
#endif // ESP32 #endif // ESP32
@ -8273,7 +8388,7 @@ void SendFile(char *fname) {
#ifdef USE_DLTASK #ifdef USE_DLTASK
void script_download_task(void *path) { void script_download_task(void *path) {
SendFile_sub((char*) path); SendFile_sub((char*) path, 0);
free(path); free(path);
script_download_busy = false; script_download_busy = false;
vTaskDelete( NULL ); vTaskDelete( NULL );
@ -8282,26 +8397,29 @@ void script_download_task(void *path) {
#define REVERT_M5EPD #define REVERT_M5EPD
void SendFile_sub(char *fname) { void SendFile_sub(char *path, uint8_t stype) {
char buff[512]; char buff[512];
uint8_t sflg = 0; WiFiClient client;
uint8_t sflg = 0;
File file;
uint32_t fsize;
#ifdef USE_DISPLAY_DUMP #ifdef USE_DISPLAY_DUMP
char *sbmp = strstr_P(fname, PSTR("scrdmp.bmp")); char *sbmp = strstr_P(path, PSTR("scrdmp.bmp"));
if (sbmp) { if (sbmp) {
sflg = 1; sflg = 1;
} }
#endif // USE_DISPLAY_DUMP #endif // USE_DISPLAY_DUMP
if ( strstr_P(fname, PSTR(".jpg"))) { if ( strstr_P(path, PSTR(".jpg"))) {
strcpy_P(buff,PSTR("image/jpeg")); strcpy_P(buff,PSTR("image/jpeg"));
} else if (strstr_P(fname, PSTR(".bmp"))) { } else if (strstr_P(path, PSTR(".bmp"))) {
strcpy_P(buff,PSTR("image/bmp")); strcpy_P(buff,PSTR("image/bmp"));
} else if (strstr_P(fname, PSTR(".html"))) { } else if (strstr_P(path, PSTR(".html"))) {
strcpy_P(buff,PSTR("text/html")); strcpy_P(buff,PSTR("text/html"));
} else if (strstr_P(fname, PSTR(".txt"))) { } else if (strstr_P(path, PSTR(".txt"))) {
strcpy_P(buff,PSTR("text/plain")); strcpy_P(buff,PSTR("text/plain"));
} else if (strstr_P(fname, PSTR(".pdf"))) { } else if (strstr_P(path, PSTR(".pdf"))) {
strcpy_P(buff,PSTR("application/pdf")); strcpy_P(buff,PSTR("application/pdf"));
} else { } else {
strcpy_P(buff,PSTR("text/plain")); strcpy_P(buff,PSTR("text/plain"));
@ -8309,9 +8427,33 @@ char buff[512];
if (!buff[0]) return; if (!buff[0]) return;
WSContentSend_P(HTTP_SCRIPT_MIMES, buff); if (!sflg) {
WSContentFlush(); file = ufsp->open(path, FS_FILE_READ);
fsize = file.size();
}
if (0 == stype) {
WSContentSend_P(HTTP_SCRIPT_MIMES, buff);
WSContentFlush();
client = Webserver->client();
} else {
#ifdef USE_SCRIPT_ALT_DOWNLOAD
client = http82_Server->client();
#else
client = Webserver->client();
#endif
client.printf_P(PSTR("HTTP/1.1 200 OK\r\n"));
char *cp = path;
for (uint32_t cnt = strlen(path) - 1; cnt >= 0; cnt--) {
if (path[cnt] == '/') {
cp = &path[cnt + 1];
break;
}
}
client.printf_P(PSTR("Content-Disposition: attachment; filename=\"%s\"\r\n"), cp);
client.printf_P(PSTR("Content-Length: %d\r\n"), fsize);
client.printf_P(PSTR("Content-type: application/octet-stream\r\n\r\n"));
}
if (sflg) { if (sflg) {
#ifdef USE_DISPLAY_DUMP #ifdef USE_DISPLAY_DUMP
@ -8335,10 +8477,10 @@ char buff[512];
uint8_t *lbp; uint8_t *lbp;
uint8_t fileHeader[fileHeaderSize]; uint8_t fileHeader[fileHeaderSize];
createBitmapFileHeader(Settings->display_height , Settings->display_width , fileHeader); createBitmapFileHeader(Settings->display_height , Settings->display_width , fileHeader);
Webserver->client().write((uint8_t *)fileHeader, fileHeaderSize); client.write((uint8_t *)fileHeader, fileHeaderSize);
uint8_t infoHeader[infoHeaderSize]; uint8_t infoHeader[infoHeaderSize];
createBitmapInfoHeader(Settings->display_height, Settings->display_width, infoHeader ); createBitmapInfoHeader(Settings->display_height, Settings->display_width, infoHeader );
Webserver->client().write((uint8_t *)infoHeader, infoHeaderSize); client.write((uint8_t *)infoHeader, infoHeaderSize);
if (bpp < 0) { if (bpp < 0) {
for (uint32_t lins = Settings->display_height - 1; lins >= 0 ; lins--) { for (uint32_t lins = Settings->display_height - 1; lins >= 0 ; lins--) {
lbp = lbuf; lbp = lbuf;
@ -8351,7 +8493,7 @@ char buff[512];
*lbp++ = pixel; *lbp++ = pixel;
*lbp++ = pixel; *lbp++ = pixel;
} }
Webserver->client().write((const char*)lbuf, Settings->display_width * 3); client.write((const char*)lbuf, Settings->display_width * 3);
} }
} else { } else {
for (uint32_t lins = 0; lins < Settings->display_height; lins++) { for (uint32_t lins = 0; lins < Settings->display_height; lins++) {
@ -8421,25 +8563,23 @@ char buff[512];
bp++; bp++;
} }
} }
Webserver->client().write((const char*)lbuf, Settings->display_width * 3); client.write((const char*)lbuf, Settings->display_width * 3);
} }
} }
if (lbuf) free(lbuf); if (lbuf) free(lbuf);
Webserver->client().stop(); client.stop();
} }
#endif // USE_DISPLAY_DUMP #endif // USE_DISPLAY_DUMP
} else { } else {
File file = ufsp->open(fname, FS_FILE_READ);
uint32_t siz = file.size();
uint32_t len = sizeof(buff); uint32_t len = sizeof(buff);
while (siz > 0) { while (fsize > 0) {
if (len > siz) len = siz; if (len > fsize) len = fsize;
file.read((uint8_t *)buff, len); file.read((uint8_t *)buff, len);
Webserver->client().write((const char*)buff, len); client.write((const char*)buff, len);
siz -= len; fsize -= len;
} }
file.close(); file.close();
Webserver->client().stop(); client.stop();
} }
} }
#endif // USE_UFILESYS #endif // USE_UFILESYS
@ -9994,9 +10134,7 @@ int32_t retval;
#ifdef ESP32 #ifdef ESP32
#ifdef USE_SCRIPT_TASK #ifdef USE_SCRIPT_TASK
#ifndef STASK_STACK
#define STASK_STACK 8192-2048
#endif
struct ESP32_Task { struct ESP32_Task {
uint16_t task_timer; uint16_t task_timer;
@ -10039,7 +10177,7 @@ void script_task2(void *arg) {
} }
} }
} }
uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core, int32_t prio) { uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core, int32_t prio, int32_t stack) {
//return 0; //return 0;
BaseType_t res = 0; BaseType_t res = 0;
if (core > 1) { core = 1; } if (core > 1) { core = 1; }
@ -10056,12 +10194,12 @@ uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core, int32_
if (!num) { if (!num) {
if (Run_Scripter1(">t1", -3, 0) == 99) { if (Run_Scripter1(">t1", -3, 0) == 99) {
sp = glob_script_mem.section_ptr + 2; sp = glob_script_mem.section_ptr + 2;
res = xTaskCreatePinnedToCore(script_task1, "T1", STASK_STACK, NULL, prio, &esp32_tasks[num].task_t, core); res = xTaskCreatePinnedToCore(script_task1, "T1", stack, NULL, prio, &esp32_tasks[num].task_t, core);
} }
} else { } else {
if (Run_Scripter1(">t2", -3, 0) == 99) { if (Run_Scripter1(">t2", -3, 0) == 99) {
sp = glob_script_mem.section_ptr + 2; sp = glob_script_mem.section_ptr + 2;
res = xTaskCreatePinnedToCore(script_task2, "T2", STASK_STACK, NULL, prio, &esp32_tasks[num].task_t, core); res = xTaskCreatePinnedToCore(script_task2, "T2", stack, NULL, prio, &esp32_tasks[num].task_t, core);
} }
} }
esp32_tasks[num].tstart = sp; esp32_tasks[num].tstart = sp;
@ -11044,8 +11182,11 @@ bool Xdrv10(uint8_t function)
#if defined(USE_UFILESYS) && defined(USE_SCRIPT_WEB_DISPLAY) #if defined(USE_UFILESYS) && defined(USE_SCRIPT_WEB_DISPLAY)
Webserver->on(UriGlob("/ufs/*"), HTTP_GET, ScriptServeFile); Webserver->on(UriGlob("/ufs/*"), HTTP_GET, ScriptServeFile);
#endif #endif
#endif // USE_WEBSERVER #if defined(USE_UFILESYS) && defined(USE_SCRIPT_ALT_DOWNLOAD)
WebServer82Init();
#endif // USE_SCRIPT_ALT_DOWNLOAD
break; break;
#endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART: case FUNC_SAVE_BEFORE_RESTART:
if (bitRead(Settings->rule_enabled, 0)) { if (bitRead(Settings->rule_enabled, 0)) {
@ -11094,11 +11235,14 @@ bool Xdrv10(uint8_t function)
break; break;
#endif //USE_BUTTON_EVENT #endif //USE_BUTTON_EVENT
#ifdef USE_SCRIPT_GLOBVARS
case FUNC_LOOP: case FUNC_LOOP:
#ifdef USE_SCRIPT_GLOBVARS
Script_PollUdp(); Script_PollUdp();
break;
#endif //USE_SCRIPT_GLOBVARS #endif //USE_SCRIPT_GLOBVARS
#ifdef USE_SCRIPT_ALT_DOWNLOAD
WebServer82Loop();
#endif
break;
} }
return result; return result;

View File

@ -0,0 +1,668 @@
/*
xdrv_42_i2s_audio.ino - Audio dac support for Tasmota
Copyright (C) 2021 Gerhard Mutz and Theo Arends
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 <http://www.gnu.org/licenses/>.
*/
#if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX))
/*********************************************************************************************\
* I2S support using an external DAC or a speaker connected to GPIO03 using a transistor
*
* Uses fixed GPIOs for ESP8266:
* I2S Out Data GPIO03 (Rx)
* I2S Out Bit Clock GPIO15
* I2S Out Word Select GPIO02
* I2S In Data GPIO12
* I2S In Bit Clock GPIO13
* I2S In Word Select GPIO14
\*********************************************************************************************/
#define XDRV_42 42
#define USE_I2S_EXTERNAL_DAC 1
//#define USE_I2S_NO_DAC // Add support for transistor-based output without DAC
//#define USE_I2S_WEBRADIO // Add support for web radio
//#define USE_I2S_SAY_TIME // Add support for english speaking clock
#include "AudioFileSourcePROGMEM.h"
#include "AudioFileSourceID3.h"
#include "AudioGeneratorMP3.h"
#ifdef USE_I2S_NO_DAC
#include "AudioOutputI2SNoDAC.h" // Transistor-driven lower quality connected to RX pin
#else
#include "AudioOutputI2S.h" // External I2S DAC IC
#endif // USE_I2S_NO_DAC
#include <ESP8266SAM.h>
#include "AudioFileSourceFS.h"
#ifdef USE_I2S_SAY_TIME
#include "AudioGeneratorTalkie.h"
#endif // USE_I2S_SAY_TIME
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorAAC.h"
#ifdef ESP32
#include <driver/i2s.h>
#endif
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON
#define AUDIO_PWR_OFF
#ifdef ESP8266
#define i2s_port_t uint8_t
#endif
#ifdef ESP32
#define MODE_MIC 0
#define MODE_SPK 1
#ifndef MICSRATE
#define MICSRATE 16000
#endif
#endif // ESP32
struct AUDIO_I2S_t {
uint8_t is2_volume; // should be in settings
i2s_port_t i2s_port;
int8_t mclk = -1;
int8_t bclk = -1;
int8_t ws = -1;
int8_t dout = -1;
int8_t din = -1;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceFS *file;
#ifdef USE_I2S_NO_DAC
AudioOutputI2SNoDAC *out;
#else
AudioOutputI2S *out;
#endif // USE_I2S_NO_DAC
AudioFileSourceID3 *id3;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
#ifdef USE_I2S_WEBRADIO
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
#endif // USE_I2S_WEBRADIO
#ifdef ESP32
TaskHandle_t mp3_task_h;
TaskHandle_t mic_task_h;
uint32_t mic_size;
uint32_t mic_rate;
uint8_t *mic_buff;
char mic_path[32];
uint8_t mic_channels;
File fwp;
uint8_t mic_stop;
int8_t mic_error;
int8_t mic_mclk = -1;
int8_t mic_bclk = -1;
int8_t mic_ws = -1;
int8_t mic_din = -1;
int8_t mic_dout = -1;
bool use_stream = false;
i2s_port_t mic_port;
#endif // ESP32
#ifdef USE_SHINE
uint32_t recdur;
uint8_t stream_active;
uint8_t stream_enable;
WiFiClient client;
ESP8266WebServer *MP3Server;
#endif
} audio_i2s;
#define MIC_CHANNELS 1
#ifdef USE_TTGO_WATCH
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON TTGO_audio_power(true);
#define AUDIO_PWR_OFF TTGO_audio_power(false);
#endif // USE_TTGO_WATCH
#ifdef USE_M5STACK_CORE2
// leave this predefined currently
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON Core2AudioPower(true);
#define AUDIO_PWR_OFF Core2AudioPower(false);
#undef DAC_IIS_BCK
#undef DAC_IIS_WS
#undef DAC_IIS_DOUT
#undef DAC_IIS_DIN
#define DAC_IIS_BCK 12
#define DAC_IIS_WS 0
#define DAC_IIS_DOUT 2
#define DAC_IIS_DIN 34
#undef MICSRATE
#define MICSRATE 32000
#undef MIC_CHANNELS
#define MIC_CHANNELS 1
#endif // USE_M5STACK_CORE2
#ifdef ESP32S3_BOX
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON S3boxAudioPower(true);
#define AUDIO_PWR_OFF S3boxAudioPower(false);
#undef MIC_CHANNELS
#define MIC_CHANNELS 2
#endif // ESP32S3_BOX
extern FS *ufsp;
extern FS *ffsp;
#ifdef ESP8266
const int preallocateBufferSize = 5*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
#endif // ESP8266
#ifdef ESP32
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
#endif // ESP32
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
#ifdef ESP8266
#define I2S_MCLK_MULTIPLE_128 0
#endif
void sayTime(int hour, int minutes);
void Cmd_MicRec(void);
void Cmd_wav2mp3(void);
void Cmd_Time(void);
int32_t I2S_Init_0(void) {
audio_i2s.i2s_port = (i2s_port_t)0;
audio_i2s.mic_port = (i2s_port_t)0;
#if USE_I2S_EXTERNAL_DAC
// use i2s
#if (defined(USE_I2S_NO_DAC) && defined(DAC_IIS_DOUT)) || (defined(DAC_IIS_BCK) && defined(DAC_IIS_WS) && defined(DAC_IIS_DOUT))
audio_i2s.i2s_port = (i2s_port_t)0;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
audio_i2s.out = new AudioOutputI2S();
#endif
audio_i2s.bclk = DAC_IIS_BCK;
audio_i2s.ws = DAC_IIS_WS;
audio_i2s.dout = DAC_IIS_DOUT;
audio_i2s.din = DAC_IIS_DIN;
#else
#ifdef USE_I2S_NO_DAC
if (PinUsed(GPIO_I2S_DOUT)) {
#else
if (PinUsed(GPIO_I2S_BCLK) && PinUsed(GPIO_I2S_WS) && PinUsed(GPIO_I2S_DOUT)) {
#endif // USE_I2S_NO_DAC
audio_i2s.i2s_port = (i2s_port_t)0;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
#endif // USE_I2S_NO_DAC
audio_i2s.mclk = Pin(GPIO_I2S_MCLK);
audio_i2s.bclk = Pin(GPIO_I2S_BCLK);
audio_i2s.ws = Pin(GPIO_I2S_WS);
audio_i2s.dout = Pin(GPIO_I2S_DOUT);
audio_i2s.din = Pin(GPIO_I2S_DIN);
audio_i2s.mic_mclk = audio_i2s.mclk;
audio_i2s.mic_bclk = audio_i2s.bclk;
audio_i2s.mic_ws = audio_i2s.ws;
audio_i2s.mic_dout = audio_i2s.dout;
audio_i2s.mic_din = audio_i2s.din;
audio_i2s.mic_port = (i2s_port_t)0;
// check if 2 ports used, use second for micro
if (PinUsed(GPIO_I2S_BCLK, 1) && PinUsed(GPIO_I2S_WS, 1) && PinUsed(GPIO_I2S_DIN, 1)) {
audio_i2s.mic_bclk = -1;
audio_i2s.mic_bclk = Pin(GPIO_I2S_BCLK, 1);
audio_i2s.mic_ws = Pin(GPIO_I2S_WS, 1);
audio_i2s.mic_dout = -1;
audio_i2s.mic_din = Pin(GPIO_I2S_DIN, 1);
audio_i2s.mic_port = (i2s_port_t)1;
}
} else if (PinUsed(GPIO_I2S_BCLK, 1) && PinUsed(GPIO_I2S_WS, 1) && PinUsed(GPIO_I2S_DOUT, 1)) {
audio_i2s.i2s_port = (i2s_port_t)1;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
#endif // USE_I2S_NO_DAC
audio_i2s.mclk = Pin(GPIO_I2S_MCLK, 1);
audio_i2s.bclk = Pin(GPIO_I2S_BCLK, 1);
audio_i2s.ws = Pin(GPIO_I2S_WS, 1);
audio_i2s.dout = Pin(GPIO_I2S_DOUT, 1);
audio_i2s.din = Pin(GPIO_I2S_DIN, 1);
audio_i2s.mic_mclk = audio_i2s.mclk;
audio_i2s.mic_bclk = audio_i2s.bclk;
audio_i2s.mic_ws = audio_i2s.ws;
audio_i2s.mic_dout = audio_i2s.dout;
audio_i2s.mic_din = audio_i2s.din;
audio_i2s.mic_port = (i2s_port_t)1;
} else {
return -1;
}
#ifdef ESP8266
// esp8266 have fixed pins
if ((audio_i2s.bclk != 15) || (audio_i2s.ws != 2) || (audio_i2s.dout != 3)) {
return -2;
}
#endif // ESP8266
#endif // defined(DAC_IIS_BCK)
audio_i2s.out->SetPinout(audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
AddLog(LOG_LEVEL_INFO, PSTR("Init audio I2S: port=%d, bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), audio_i2s.i2s_port, audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
if (audio_i2s.mic_port != 0) {
AddLog(LOG_LEVEL_INFO, PSTR("Init audio I2S mic: port=%d, bclk=%d, ws=%d, din=%d"), audio_i2s.mic_port, audio_i2s.mic_bclk, audio_i2s.mic_ws, audio_i2s.mic_din);
}
#else
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
audio_i2s.out = new AudioOutputI2S(0, 1); // Internal DAC port 0
#endif // USE_I2S_NO_DAC
#endif // USE_I2S_EXTERNAL_DAC
return 0;
}
void I2S_Init(void) {
#if defined(ESP32) && defined(ESP32S3_BOX)
S3boxInit();
#endif
if (I2S_Init_0()) {
return;
}
audio_i2s.is2_volume=10;
audio_i2s.out->SetGain(((float)audio_i2s.is2_volume/100.0)*4.0);
audio_i2s.out->stop();
audio_i2s.mp3ram = nullptr;
#ifdef ESP32
if (UsePSRAM()) {
audio_i2s.mp3ram = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
#ifdef USE_I2S_WEBRADIO
if (UsePSRAM()) {
audio_i2s.preallocateBuffer = heap_caps_malloc(preallocateBufferSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
audio_i2s.preallocateCodec = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
audio_i2s.preallocateBuffer = malloc(preallocateBufferSize);
audio_i2s.preallocateCodec = malloc(preallocateCodecSize);
}
if (!audio_i2s.preallocateBuffer || !audio_i2s.preallocateCodec) {
//Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
}
#endif // USE_I2S_WEBRADIO
audio_i2s.mic_channels = MIC_CHANNELS;
audio_i2s.mic_rate = MICSRATE;
#endif // ESP32
}
#ifdef ESP32
void mp3_task(void *arg) {
while (1) {
while (audio_i2s.mp3->isRunning()) {
if (!audio_i2s.mp3->loop()) {
audio_i2s.mp3->stop();
mp3_delete();
audio_i2s.out->stop();
if (audio_i2s.mp3_task_h) {
vTaskDelete(audio_i2s.mp3_task_h);
audio_i2s.mp3_task_h = 0;
}
//mp3_task_h=nullptr;
}
delay(1);
}
}
}
#endif // ESP32
#ifdef USE_I2S_WEBRADIO
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
(void) ptr;
if (strstr_P(type, PSTR("Title"))) {
strncpy(audio_i2s.wr_title, str, sizeof(audio_i2s.wr_title));
audio_i2s.wr_title[sizeof(audio_i2s.wr_title)-1] = 0;
//AddLog(LOG_LEVEL_INFO,PSTR("WR-Title: %s"),wr_title);
} else {
// Who knows what to do? Not me!
}
}
void StatusCallback(void *cbData, int code, const char *string) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) code;
(void) ptr;
//strncpy_P(status, string, sizeof(status)-1);
//status[sizeof(status)-1] = 0;
}
void Webradio(const char *url) {
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
AUDIO_PWR_ON
audio_i2s.ifile = new AudioFileSourceICYStream(url);
audio_i2s.ifile->RegisterMetadataCB(MDCallback, NULL);
audio_i2s.buff = new AudioFileSourceBuffer(audio_i2s.ifile, audio_i2s.preallocateBuffer, preallocateBufferSize);
audio_i2s.buff->RegisterStatusCB(StatusCallback, NULL);
audio_i2s.decoder = new AudioGeneratorMP3(audio_i2s.preallocateCodec, preallocateCodecSize);
audio_i2s.decoder->RegisterStatusCB(StatusCallback, NULL);
audio_i2s.decoder->begin(audio_i2s.buff, audio_i2s.out);
if (!audio_i2s.decoder->isRunning()) {
// Serial.printf_P(PSTR("Can't connect to URL"));
StopPlaying();
// strcpy_P(status, PSTR("Unable to connect to URL"));
audio_i2s.retryms = millis() + 2000;
}
xTaskCreatePinnedToCore(mp3_task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_h, 1);
}
void mp3_task2(void *arg){
while (1) {
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
if (!audio_i2s.decoder->loop()) {
StopPlaying();
//retryms = millis() + 2000;
}
delay(1);
}
}
}
void StopPlaying() {
if (audio_i2s.mp3_task_h) {
vTaskDelete(audio_i2s.mp3_task_h);
audio_i2s.mp3_task_h = nullptr;
}
if (audio_i2s.decoder) {
audio_i2s.decoder->stop();
delete audio_i2s.decoder;
audio_i2s.decoder = NULL;
}
if (audio_i2s.buff) {
audio_i2s.buff->close();
delete audio_i2s.buff;
audio_i2s.buff = NULL;
}
if (audio_i2s.ifile) {
audio_i2s.ifile->close();
delete audio_i2s.ifile;
audio_i2s.ifile = NULL;
}
AUDIO_PWR_OFF
}
void Cmd_WebRadio(void) {
if (!audio_i2s.out) return;
if (audio_i2s.decoder) {
StopPlaying();
}
if (XdrvMailbox.data_len > 0) {
Webradio(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
} else {
ResponseCmndChar_P(PSTR("Stopped"));
}
}
#ifdef USE_WEBSERVER
const char HTTP_WEBRADIO[] PROGMEM =
"{s}" "I2S_WR-Title" "{m}%s{e}";
void I2S_WR_Show(bool json) {
if (audio_i2s.decoder) {
if (json) {
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), audio_i2s.wr_title);
} else {
WSContentSend_PD(HTTP_WEBRADIO,audio_i2s.wr_title);
}
}
}
#endif // USE_WEBSERVER
#endif // USE_I2S_WEBRADIO
#ifdef ESP32
void Play_mp3(const char *path) {
#ifdef USE_UFILESYS
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
bool I2S_Task;
if (*path=='+') {
I2S_Task = true;
path++;
} else {
I2S_Task = false;
}
FS *mp3fsp = ufsp;
if (!strncmp(path, "/ffs", 4)) {
path += 4;
mp3fsp = ffsp;
}
if (!mp3fsp->exists(path)) {
return;
}
AUDIO_PWR_ON
audio_i2s.file = new AudioFileSourceFS(*mp3fsp, path);
audio_i2s.id3 = new AudioFileSourceID3(audio_i2s.file);
if (audio_i2s.mp3ram) {
audio_i2s.mp3 = new AudioGeneratorMP3(audio_i2s.mp3ram, preallocateCodecSize);
} else {
audio_i2s.mp3 = new AudioGeneratorMP3();
}
audio_i2s.mp3->begin(audio_i2s.id3, audio_i2s.out);
if (I2S_Task) {
xTaskCreatePinnedToCore(mp3_task, "MP3", 8192, NULL, 3, &audio_i2s.mp3_task_h, 1);
} else {
while (audio_i2s.mp3->isRunning()) {
if (!audio_i2s.mp3->loop()) {
audio_i2s.mp3->stop();
break;
}
OsWatchLoop();
}
audio_i2s.out->stop();
mp3_delete();
}
#endif // USE_UFILESYS
}
void mp3_delete(void) {
delete audio_i2s.file;
delete audio_i2s.id3;
delete audio_i2s.mp3;
audio_i2s.mp3=nullptr;
AUDIO_PWR_OFF
}
#endif // ESP32
void Say(char *text) {
if (!audio_i2s.out) return;
AUDIO_PWR_ON
audio_i2s.out->begin();
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audio_i2s.out, text);
delete sam;
audio_i2s.out->stop();
AUDIO_PWR_OFF
}
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Say|Gain"
#ifdef USE_I2S_SAY_TIME
"|Time"
#endif // USE_I2S_SAY_TIME
#ifdef ESP32
"|Play"
#ifdef USE_I2S_WEBRADIO
"|WR"
#endif // USE_I2S_WEBRADIO
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
"|REC"
#ifdef MP3_MIC_STREAM
"|STREAM"
#endif // MP3_MIC_STREAM
#endif // USE_SHINE
#endif // ESP32
;
void (* const I2SAudio_Command[])(void) PROGMEM = {
&Cmd_Say, &Cmd_Gain
#ifdef USE_I2S_SAY_TIME
,&Cmd_Time
#endif // USE_I2S_SAY_TIME
#ifdef ESP32
,&Cmd_Play
#ifdef USE_I2S_WEBRADIO
,&Cmd_WebRadio
#endif // USE_I2S_WEBRADIO
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
,&Cmd_MicRec
#ifdef MP3_MIC_STREAM
,&Cmd_MP3Stream
#endif // MP3_MIC_STREAM
#endif // USE_SHINE
#endif // ESP32
};
void Cmd_Play(void) {
if (XdrvMailbox.data_len > 0) {
Play_mp3(XdrvMailbox.data);
}
ResponseCmndChar(XdrvMailbox.data);
}
void Cmd_Gain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
if (audio_i2s.out) {
audio_i2s.is2_volume=XdrvMailbox.payload;
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
}
}
ResponseCmndNumber(audio_i2s.is2_volume);
}
void Cmd_Say(void) {
if (XdrvMailbox.data_len > 0) {
Say(XdrvMailbox.data);
}
ResponseCmndChar(XdrvMailbox.data);
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
void i2s_mp3_loop(void);
void i2s_mp3_init(void);
void MP3ShowStream(void);
bool Xdrv42(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_COMMAND:
result = DecodeCommand(kI2SAudio_Commands, I2SAudio_Command);
break;
case FUNC_INIT:
I2S_Init();
break;
#if defined(MP3_MIC_STREAM)
//#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
case FUNC_WEB_ADD_MAIN_BUTTON:
//MP3ShowStream();
break;
case FUNC_LOOP:
i2s_mp3_loop();
break;
case FUNC_WEB_ADD_HANDLER:
audio_i2s.stream_enable = 1;
i2s_mp3_init(1);
break;
#endif
#ifdef USE_WEBSERVER
#ifdef USE_I2S_WEBRADIO
case FUNC_WEB_SENSOR:
I2S_WR_Show(false);
break;
#endif // USE_I2S_WEBRADIO
#endif // USE_WEBSERVER
#ifdef USE_I2S_WEBRADIO
case FUNC_JSON_APPEND:
I2S_WR_Show(true);
break;
#endif // USE_I2S_WEBRADIO
}
return result;
}
#endif // USE_I2S_AUDIO

View File

@ -0,0 +1,297 @@
/*
xdrv_42_i2s_audio.ino - Audio dac support for Tasmota
Copyright (C) 2021 Gerhard Mutz and Theo Arends
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 <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
#define MP3_BOUNDARY "e8b8c539-047d-4777-a985-fbba6edff11e"
uint32_t SpeakerMic(uint8_t spkr) {
esp_err_t err = ESP_OK;
if (spkr == MODE_SPK) {
if (audio_i2s.mic_port == 0) {
I2S_Init_0();
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
audio_i2s.out->stop();
}
return 0;
}
// set micro
if (audio_i2s.mic_port == 0) {
// close audio out
if (audio_i2s.out) {
audio_i2s.out->stop();
delete audio_i2s.out;
audio_i2s.out = nullptr;
}
i2s_driver_uninstall(audio_i2s.i2s_port);
}
// config mic
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER),
.sample_rate = audio_i2s.mic_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
//.dma_buf_len = 128,
.dma_buf_len = 1024,
.use_apll = 0, // Use audio PLL
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // I2S_MCLK_MULTIPLE_128
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
};
#ifdef ESP32S3_BOX
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
#ifdef USE_I2S_MIC
// mic select to GND
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX);
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
#ifdef USE_M5STACK_CORE2
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
#endif
if (audio_i2s.mic_channels == 1) {
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
} else {
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
}
err += i2s_driver_install(audio_i2s.mic_port, &i2s_config, 0, NULL);
i2s_pin_config_t tx_pin_config;
tx_pin_config.mck_io_num = audio_i2s.mic_mclk;
tx_pin_config.bck_io_num = audio_i2s.mic_bclk;
tx_pin_config.ws_io_num = audio_i2s.mic_ws;
tx_pin_config.data_out_num = audio_i2s.mic_dout;
tx_pin_config.data_in_num = audio_i2s.mic_din;
err += i2s_set_pin(audio_i2s.mic_port, &tx_pin_config);
i2s_channel_t mode = I2S_CHANNEL_MONO;
if (audio_i2s.mic_channels > 1) {
mode = I2S_CHANNEL_STEREO;
}
err += i2s_set_clk(audio_i2s.mic_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, mode);
return err;
}
#ifdef USE_SHINE
#include <layer3.h>
#include <types.h>
#define MP3HANDLECLIENT audio_i2s.MP3Server->handleClient();
// micro to mp3 file or stream
void mic_task(void *arg){
int8_t error = 0;
uint8_t *ucp;
int written;
shine_config_t config;
shine_t s = nullptr;
uint16_t samples_per_pass;
File mp3_out = (File)nullptr;
int16_t *buffer = nullptr;
uint16_t bytesize;
uint16_t bwritten;
uint32_t ctime;
if (!audio_i2s.use_stream) {
mp3_out = ufsp->open(audio_i2s.mic_path, "w");
if (!mp3_out) {
error = 1;
goto exit;
}
} else {
if (!audio_i2s.stream_active) {
error = 2;
audio_i2s.use_stream = 0;
goto exit;
}
audio_i2s.client.flush();
audio_i2s.client.setTimeout(3);
audio_i2s.client.print("HTTP/1.1 200 OK\r\n"
"Content-Type: audio/mpeg;\r\n\r\n");
MP3HANDLECLIENT
}
shine_set_config_mpeg_defaults(&config.mpeg);
if (audio_i2s.mic_channels == 1) {
config.mpeg.mode = MONO;
} else {
config.mpeg.mode = STEREO;
}
config.mpeg.bitr = 128;
config.wave.samplerate = audio_i2s.mic_rate;
config.wave.channels = (channels)audio_i2s.mic_channels;
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
error = 3;
goto exit;
}
s = shine_initialise(&config);
if (!s) {
error = 4;
goto exit;
}
samples_per_pass = shine_samples_per_pass(s);
bytesize = samples_per_pass * 2 * audio_i2s.mic_channels;
buffer = (int16_t*)malloc(bytesize);
if (!buffer) {
error = 5;
goto exit;
}
ctime = TasmotaGlobal.uptime;
while (!audio_i2s.mic_stop) {
uint32_t bytes_read;
i2s_read(audio_i2s.mic_port, (char *)buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS));
ucp = shine_encode_buffer_interleaved(s, buffer, &written);
if (!audio_i2s.use_stream) {
bwritten = mp3_out.write(ucp, written);
if (bwritten != written) {
break;
}
} else {
audio_i2s.client.write((const char*)ucp, written);
MP3HANDLECLIENT
if (!audio_i2s.client.connected()) {
break;
}
}
audio_i2s.recdur = TasmotaGlobal.uptime - ctime;
}
ucp = shine_flush(s, &written);
if (!audio_i2s.use_stream) {
mp3_out.write(ucp, written);
} else {
audio_i2s.client.write((const char*)ucp, written);
MP3HANDLECLIENT
}
exit:
if (s) {
shine_close(s);
}
if (mp3_out) {
mp3_out.close();
}
if (buffer) {
free(buffer);
}
if (audio_i2s.use_stream) {
audio_i2s.client.stop();
MP3HANDLECLIENT
}
SpeakerMic(MODE_SPK);
audio_i2s.mic_stop = 0;
audio_i2s.mic_error = error;
AddLog(LOG_LEVEL_INFO, PSTR("mp3task error: %d"), error);
audio_i2s.mic_task_h = 0;
audio_i2s.recdur = 0;
audio_i2s.stream_active = 0;
vTaskDelete(NULL);
}
int32_t i2s_record_shine(char *path) {
esp_err_t err = ESP_OK;
if (audio_i2s.mic_port == 0) {
if (audio_i2s.decoder || audio_i2s.mp3) return 0;
}
err = SpeakerMic(MODE_MIC);
if (err) {
if (audio_i2s.mic_port == 0) {
SpeakerMic(MODE_SPK);
}
AddLog(LOG_LEVEL_INFO, PSTR("mic init error: %d"), err);
return err;
}
strlcpy(audio_i2s.mic_path, path, sizeof(audio_i2s.mic_path));
audio_i2s.mic_stop = 0;
uint32_t stack = 4096;
audio_i2s.use_stream = !strcmp(audio_i2s.mic_path, "stream.mp3");
if (audio_i2s.use_stream) {
stack = 8000;
}
err = xTaskCreatePinnedToCore(mic_task, "MIC", stack, NULL, 3, &audio_i2s.mic_task_h, 1);
return err;
}
void Cmd_MicRec(void) {
if (XdrvMailbox.data_len > 0) {
if (!strncmp(XdrvMailbox.data, "-?", 2)) {
Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur);
} else {
i2s_record_shine(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
}
} else {
if (audio_i2s.mic_task_h) {
// stop task
audio_i2s.mic_stop = 1;
while (audio_i2s.mic_stop) {
delay(1);
}
ResponseCmndChar_P(PSTR("Stopped"));
}
}
}
#endif // USE_SHINE
#endif // USE_I2S_AUDIO
#endif // ESP32

View File

@ -0,0 +1,58 @@
#ifdef ESP32
#if defined(USE_SHINE) && ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) )
#ifdef MP3_MIC_STREAM
void Stream_mp3(void) {
if (!audio_i2s.stream_enable) {
return;
}
if (audio_i2s.stream_active) {
return;
}
AddLog(LOG_LEVEL_INFO, PSTR("I2S: Handle mp3server"));
audio_i2s.stream_active = 1;
audio_i2s.client = audio_i2s.MP3Server->client();
AddLog(LOG_LEVEL_INFO, PSTR("I2S: Create client"));
i2s_record_shine((char*)"stream.mp3");
}
void i2s_mp3_loop(void) {
if (audio_i2s.MP3Server) {
audio_i2s.MP3Server->handleClient();
}
}
void i2s_mp3_init(uint32_t on) {
if (on) {
if (!audio_i2s.MP3Server) {
audio_i2s.MP3Server = new ESP8266WebServer(81);
audio_i2s.MP3Server->on(PSTR("/stream.mp3"), Stream_mp3);
audio_i2s.MP3Server->begin();
AddLog(LOG_LEVEL_INFO, PSTR("MP3: server created"));
}
} else {
if (audio_i2s.MP3Server) {
audio_i2s.MP3Server->stop();
delete audio_i2s.MP3Server;
audio_i2s.MP3Server = nullptr;
AddLog(LOG_LEVEL_INFO, PSTR("MP3: server deleted"));
}
}
}
void Cmd_MP3Stream(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
audio_i2s.stream_enable = XdrvMailbox.payload;
}
i2s_mp3_init(audio_i2s.stream_enable);
ResponseCmndNumber(audio_i2s.stream_enable);
}
#endif // MP3_MIC_STREAM
#endif // USE_SHINE
#endif // ESP32

View File

@ -1,7 +1,8 @@
/*
xdrv_42_i2s_audio.ino - Audio dac support for Tasmota
Copyright (C) 2021 Gerhard Mutz and Theo Arends /*
audio is2 say time
Copyright (C) 2022 Gerhard Mutz and Theo Arends
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -16,164 +17,7 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX)) #if (defined(USE_I2S_AUDIO) || defined(USE_TTGO_WATCH) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX))
/*********************************************************************************************\
* I2S support using an external DAC or a speaker connected to GPIO03 using a transistor
*
* Uses fixed GPIOs for ESP8266:
* I2S Out Data GPIO03 (Rx)
* I2S Out Bit Clock GPIO15
* I2S Out Word Select GPIO02
* I2S In Data GPIO12
* I2S In Bit Clock GPIO13
* I2S In Word Select GPIO14
\*********************************************************************************************/
#define XDRV_42 42
#define USE_I2S_EXTERNAL_DAC 1
//#define USE_I2S_NO_DAC // Add support for transistor-based output without DAC
//#define USE_I2S_WEBRADIO // Add support for web radio
//#define USE_I2S_SAY_TIME // Add support for english speaking clock
#include "AudioFileSourcePROGMEM.h"
#include "AudioFileSourceID3.h"
#include "AudioGeneratorMP3.h"
#ifdef USE_I2S_NO_DAC
#include "AudioOutputI2SNoDAC.h" // Transistor-driven lower quality connected to RX pin
#else
#include "AudioOutputI2S.h" // External I2S DAC IC
#endif // USE_I2S_NO_DAC
#include <ESP8266SAM.h>
#include "AudioFileSourceFS.h"
#ifdef USE_I2S_SAY_TIME
#include "AudioGeneratorTalkie.h"
#endif // USE_I2S_SAY_TIME
#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorAAC.h"
#ifdef ESP32
#include <driver/i2s.h>
#endif
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON
#define AUDIO_PWR_OFF
#ifdef ESP8266
#define i2s_port_t uint8_t
#endif
#ifdef ESP32
#define MODE_MIC 0
#define MODE_SPK 1
#ifndef MICSRATE
#define MICSRATE 16000
#endif
#endif // ESP32
struct AUDIO_I2S_t {
uint8_t is2_volume; // should be in settings
i2s_port_t i2s_port;
int8_t mclk = -1;
int8_t bclk = -1;
int8_t ws = -1;
int8_t dout = -1;
int8_t din = -1;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceFS *file;
#ifdef USE_I2S_NO_DAC
AudioOutputI2SNoDAC *out;
#else
AudioOutputI2S *out;
#endif // USE_I2S_NO_DAC
AudioFileSourceID3 *id3;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
#ifdef USE_I2S_WEBRADIO
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
#endif // USE_I2S_WEBRADIO
#ifdef ESP32
TaskHandle_t mp3_task_h;
TaskHandle_t mic_task_h;
uint32_t mic_size;
uint32_t mic_rate;
uint8_t *mic_buff;
char mic_path[32];
uint8_t mic_channels;
File fwp;
uint8_t mic_stop;
int8_t mic_error;
#endif // ESP32
} audio_i2s;
// because S3 box mclk severly disturbs WLAN
// we must slow down after each sound
#ifdef ESP32S3_BOX
#undef DOWNRATE
#define DOWNRATE audio_i2s.out->SetRate(1000);
#else
#undef DOWNRATE
#define DOWNRATE
#endif
#define MIC_CHANNELS 1
#ifdef USE_TTGO_WATCH
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON TTGO_audio_power(true);
#define AUDIO_PWR_OFF TTGO_audio_power(false);
#endif // USE_TTGO_WATCH
#ifdef USE_M5STACK_CORE2
// leave this predefined currently
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON Core2AudioPower(true);
#define AUDIO_PWR_OFF Core2AudioPower(false);
#undef DAC_IIS_BCK
#undef DAC_IIS_WS
#undef DAC_IIS_DOUT
#define DAC_IIS_BCK 12
#define DAC_IIS_WS 0
#define DAC_IIS_DOUT 2
#endif // USE_M5STACK_CORE2
#ifdef ESP32S3_BOX
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON S3boxAudioPower(true);
#define AUDIO_PWR_OFF S3boxAudioPower(false);
#undef MIC_CHANNELS
#define MIC_CHANNELS 2
#endif // ESP32S3_BOX
extern FS *ufsp;
#ifdef ESP8266
const int preallocateBufferSize = 5*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
#endif // ESP8266
#ifdef ESP32
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
#endif // ESP32
#ifdef USE_I2S_SAY_TIME #ifdef USE_I2S_SAY_TIME
long timezone = 2; long timezone = 2;
byte daysavetime = 1; byte daysavetime = 1;
@ -272,778 +116,13 @@ AudioGeneratorTalkie *talkie = nullptr;
} }
delete talkie; delete talkie;
audio_i2s.out->stop(); audio_i2s.out->stop();
DOWNRATE
AUDIO_PWR_OFF AUDIO_PWR_OFF
} }
#endif // USE_I2S_SAY_TIME
enum : int { APLL_AUTO = -1, APLL_ENABLE = 1, APLL_DISABLE = 0 };
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
#ifdef ESP8266
#define I2S_MCLK_MULTIPLE_128 0
#endif
int32_t I2S_Init_0(void) {
audio_i2s.i2s_port = (i2s_port_t)0;
#if USE_I2S_EXTERNAL_DAC
// use i2s
#if (defined(USE_I2S_NO_DAC) && defined(DAC_IIS_DOUT)) || (defined(DAC_IIS_BCK) && defined(DAC_IIS_WS) && defined(DAC_IIS_DOUT))
audio_i2s.i2s_port = (i2s_port_t)0;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
audio_i2s.out = new AudioOutputI2S();
#endif
audio_i2s.bclk = DAC_IIS_BCK;
audio_i2s.ws = DAC_IIS_WS;
audio_i2s.dout = DAC_IIS_DOUT;
#else
#ifdef USE_I2S_NO_DAC
if (PinUsed(GPIO_I2S_DOUT)) {
#else
if (PinUsed(GPIO_I2S_BCLK) && PinUsed(GPIO_I2S_WS) && PinUsed(GPIO_I2S_DOUT)) {
#endif // USE_I2S_NO_DAC
audio_i2s.i2s_port = (i2s_port_t)0;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
#endif // USE_I2S_NO_DAC
audio_i2s.mclk = Pin(GPIO_I2S_MCLK);
audio_i2s.bclk = Pin(GPIO_I2S_BCLK);
audio_i2s.ws = Pin(GPIO_I2S_WS);
audio_i2s.dout = Pin(GPIO_I2S_DOUT);
audio_i2s.din = Pin(GPIO_I2S_DIN);
} else if (PinUsed(GPIO_I2S_BCLK, 1) && PinUsed(GPIO_I2S_WS, 1) && PinUsed(GPIO_I2S_DOUT), 1) {
audio_i2s.i2s_port = (i2s_port_t)1;
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
//audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port);
audio_i2s.out = new AudioOutputI2S(audio_i2s.i2s_port, EXTERNAL_I2S, 8, APLL_DISABLE, I2S_MCLK_MULTIPLE_128, 12000000);
#endif // USE_I2S_NO_DAC
audio_i2s.mclk = Pin(GPIO_I2S_MCLK, 1);
audio_i2s.bclk = Pin(GPIO_I2S_BCLK, 1);
audio_i2s.ws = Pin(GPIO_I2S_WS, 1);
audio_i2s.dout = Pin(GPIO_I2S_DOUT, 1);
audio_i2s.din = Pin(GPIO_I2S_DIN, 1);
} else {
return -1;
}
#ifdef ESP8266
// esp8266 have fixed pins
if ((audio_i2s.bclk != 15) || (audio_i2s.ws != 2) || (audio_i2s.dout != 3)) {
return -2;
}
#endif // ESP8266
#endif // defined(DAC_IIS_BCK)
audio_i2s.out->SetPinout(audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
AddLog(LOG_LEVEL_INFO, PSTR("Init audio I2S: port=%d, bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), audio_i2s.i2s_port, audio_i2s.bclk, audio_i2s.ws, audio_i2s.dout, audio_i2s.mclk, audio_i2s.din);
#else
#ifdef USE_I2S_NO_DAC
audio_i2s.out = new AudioOutputI2SNoDAC();
#else
audio_i2s.out = new AudioOutputI2S(0, 1); // Internal DAC port 0
#endif // USE_I2S_NO_DAC
#endif // USE_I2S_EXTERNAL_DAC
return 0;
}
void I2S_Init(void) {
#if defined(ESP32) && defined(ESP32S3_BOX)
S3boxInit();
#endif
if (I2S_Init_0()) {
return;
}
DOWNRATE
audio_i2s.is2_volume=10;
audio_i2s.out->SetGain(((float)audio_i2s.is2_volume/100.0)*4.0);
audio_i2s.out->stop();
audio_i2s.mp3ram = nullptr;
#ifdef ESP32
if (UsePSRAM()) {
audio_i2s.mp3ram = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
#ifdef USE_I2S_WEBRADIO
if (UsePSRAM()) {
audio_i2s.preallocateBuffer = heap_caps_malloc(preallocateBufferSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
audio_i2s.preallocateCodec = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
audio_i2s.preallocateBuffer = malloc(preallocateBufferSize);
audio_i2s.preallocateCodec = malloc(preallocateCodecSize);
}
if (!audio_i2s.preallocateBuffer || !audio_i2s.preallocateCodec) {
//Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
}
#endif // USE_I2S_WEBRADIO
audio_i2s.mic_channels = MIC_CHANNELS;
audio_i2s.mic_rate = MICSRATE;
#endif // ESP32
}
#ifdef ESP32
uint32_t SpeakerMic(uint8_t spkr) {
esp_err_t err = ESP_OK;
if (audio_i2s.out) {
audio_i2s.out->stop();
delete audio_i2s.out;
audio_i2s.out = nullptr;
}
i2s_driver_uninstall(audio_i2s.i2s_port);
if (spkr == MODE_SPK) {
I2S_Init_0();
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
audio_i2s.out->stop();
DOWNRATE
} else {
// config mic
i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER),
.sample_rate = audio_i2s.mic_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 2,
//.dma_buf_len = 128,
.dma_buf_len = 1024,
.use_apll = 0, // Use audio PLL
.tx_desc_auto_clear = true,
.fixed_mclk = 0,
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, // I2S_MCLK_MULTIPLE_128
.bits_per_chan = I2S_BITS_PER_CHAN_16BIT
};
#ifdef ESP32S3_BOX
i2s_config.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT;
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX);
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
#ifdef USE_I2S_MIC
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX);
// mic select to GND
i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
i2s_config.communication_format = I2S_COMM_FORMAT_STAND_I2S;
#endif
#ifdef USE_M5STACK_CORE2
i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
#endif
err += i2s_driver_install(audio_i2s.i2s_port, &i2s_config, 0, NULL);
i2s_pin_config_t tx_pin_config;
#ifdef ESP32S3_BOX
tx_pin_config.mck_io_num = audio_i2s.mclk;
#else
tx_pin_config.mck_io_num = I2S_PIN_NO_CHANGE;
#endif
tx_pin_config.bck_io_num = audio_i2s.bclk;
tx_pin_config.ws_io_num = audio_i2s.ws;
tx_pin_config.data_out_num = audio_i2s.dout;
tx_pin_config.data_in_num = audio_i2s.din;
err += i2s_set_pin(audio_i2s.i2s_port, &tx_pin_config);
#ifdef ESP32S3_BOX
err += i2s_set_clk(audio_i2s.i2s_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_STEREO);
#else
err += i2s_set_clk(audio_i2s.i2s_port, audio_i2s.mic_rate, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
#endif
}
return err;
}
#endif //ESP32
#ifdef ESP32
#ifdef USE_SHINE
#include <layer3.h>
#include <types.h>
// micro to mp3 file
void mic_task(void *arg){
int8_t error = 0;
uint8_t *ucp;
int written;
shine_config_t config;
shine_t s = nullptr;
uint16_t samples_per_pass;
File mp3_out = (File)nullptr;
int16_t *buffer = nullptr;
uint16_t bytesize;
uint16_t bwritten;
mp3_out = ufsp->open(audio_i2s.mic_path, "w");
if (!mp3_out) {
error = -1;
goto exit;
}
shine_set_config_mpeg_defaults(&config.mpeg);
if (audio_i2s.mic_channels == 1) {
config.mpeg.mode = MONO;
} else {
config.mpeg.mode = STEREO;
}
config.mpeg.bitr = 128;
config.wave.samplerate = audio_i2s.mic_rate;
config.wave.channels = (channels)audio_i2s.mic_channels;
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
error = -3;
goto exit;
}
s = shine_initialise(&config);
if (!s) {
error = -4;
goto exit;
}
samples_per_pass = shine_samples_per_pass(s);
bytesize = samples_per_pass * 2 * audio_i2s.mic_channels;
buffer = (int16_t*)malloc(bytesize);
if (!buffer) {
error = -5;
goto exit;
}
while (!audio_i2s.mic_stop) {
uint32_t bytes_read;
i2s_read(audio_i2s.i2s_port, (char *)buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS));
ucp = shine_encode_buffer_interleaved(s, buffer, &written);
bwritten = mp3_out.write(ucp, written);
if (bwritten != written) {
break;
}
}
ucp = shine_flush(s, &written);
mp3_out.write(ucp, written);
exit:
if (s) {
shine_close(s);
}
if (mp3_out) {
mp3_out.close();
}
if (buffer) {
free(buffer);
}
SpeakerMic(MODE_SPK);
audio_i2s.mic_stop = 0;
audio_i2s.mic_error = error;
AddLog(LOG_LEVEL_INFO, PSTR("task error: %d"), error);
audio_i2s.mic_task_h = 0;
vTaskDelete(NULL);
}
int32_t i2s_record_shine(char *path) {
esp_err_t err = ESP_OK;
if (audio_i2s.decoder || audio_i2s.mp3) return 0;
err = SpeakerMic(MODE_MIC);
if (err) {
SpeakerMic(MODE_SPK);
return err;
}
strlcpy(audio_i2s.mic_path, path, sizeof(audio_i2s.mic_path));
audio_i2s.mic_stop = 0;
err = xTaskCreatePinnedToCore(mic_task, "MIC", 4096, NULL, 3, &audio_i2s.mic_task_h, 1);
return err;
}
#else
// micro to wav file
#define DATA_SIZE 1024
void mic_task(void *arg){
uint32_t data_offset = 0;
while (1) {
uint32_t bytes_read;
i2s_read(audio_i2s.i2s_port, (char *)(audio_i2s.mic_buff + data_offset), DATA_SIZE, &bytes_read, (100 / portTICK_RATE_MS));
if (bytes_read != DATA_SIZE) break;
data_offset += DATA_SIZE;
if (data_offset >= audio_i2s.mic_size-DATA_SIZE) break;
}
SpeakerMic(MODE_SPK);
SaveWav(audio_i2s.mic_path, audio_i2s.mic_buff, audio_i2s.mic_size);
free(audio_i2s.mic_buff);
vTaskDelete(audio_i2s.mic_task_h);
}
uint32_t i2s_record(char *path, uint32_t secs) {
esp_err_t err = ESP_OK;
if (audio_i2s.decoder || audio_i2s.mp3) return 0;
err = SpeakerMic(MODE_MIC);
if (err) {
SpeakerMic(MODE_SPK);
return err;
}
audio_i2s.mic_size = secs * audio_i2s.mic_rate * 2 * audio_i2s.mic_channels;
audio_i2s.mic_buff = (uint8_t*)heap_caps_malloc(audio_i2s.mic_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (!audio_i2s.mic_buff) return 2;
if (*path=='+') {
path++;
strlcpy(audio_i2s.mic_path, path , sizeof(audio_i2s.mic_path));
xTaskCreatePinnedToCore(mic_task, "MIC", 4096, NULL, 3, &audio_i2s.mic_task_h, 1);
return 0;
}
uint32_t data_offset = 0;
uint32_t stime=millis();
while (1) {
uint32_t bytes_read;
i2s_read(audio_i2s.i2s_port, (char *)(audio_i2s.mic_buff + data_offset), DATA_SIZE, &bytes_read, (100 / portTICK_RATE_MS));
if (bytes_read != DATA_SIZE) break;
data_offset += DATA_SIZE;
if (data_offset >= audio_i2s.mic_size-DATA_SIZE) break;
delay(0);
}
//AddLog(LOG_LEVEL_INFO, PSTR("rectime: %d ms"), millis()-stime);
SpeakerMic(MODE_SPK);
// save to path
SaveWav(path, audio_i2s.mic_buff, audio_i2s.mic_size);
free(audio_i2s.mic_buff);
return 0;
}
static const uint8_t wavHTemplate[] PROGMEM = { // Hardcoded simple WAV header with 0xffffffff lengths all around
0x52, 0x49, 0x46, 0x46, 0xff, 0xff, 0xff, 0xff, 0x57, 0x41, 0x56, 0x45,
0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x22, 0x56, 0x00, 0x00, 0x88, 0x58, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00,
0x64, 0x61, 0x74, 0x61, 0xff, 0xff, 0xff, 0xff };
bool SaveWav(char *path, uint8_t *buff, uint32_t size) {
File fwp = ufsp->open(path, "w");
if (!fwp) return false;
uint8_t wavHeader[sizeof(wavHTemplate)];
memcpy_P(wavHeader, wavHTemplate, sizeof(wavHTemplate));
uint8_t channels = audio_i2s.mic_channels;
uint32_t hertz = audio_i2s.mic_rate;
uint8_t bps = 16;
wavHeader[22] = channels & 0xff;
wavHeader[23] = 0;
wavHeader[24] = hertz & 0xff;
wavHeader[25] = (hertz >> 8) & 0xff;
wavHeader[26] = (hertz >> 16) & 0xff;
wavHeader[27] = (hertz >> 24) & 0xff;
int byteRate = hertz * bps * channels / 8;
wavHeader[28] = byteRate & 0xff;
wavHeader[29] = (byteRate >> 8) & 0xff;
wavHeader[30] = (byteRate >> 16) & 0xff;
wavHeader[31] = (byteRate >> 24) & 0xff;
wavHeader[32] = channels * bps / 8;
wavHeader[33] = 0;
wavHeader[34] = bps;
wavHeader[35] = 0;
fwp.write(wavHeader, sizeof(wavHeader));
fwp.write(buff, size);
fwp.close();
return true;
}
#endif // USE_SHINE
#endif // ESP32
#ifdef ESP32
void mp3_task(void *arg) {
while (1) {
while (audio_i2s.mp3->isRunning()) {
if (!audio_i2s.mp3->loop()) {
audio_i2s.mp3->stop();
mp3_delete();
audio_i2s.out->stop();
if (audio_i2s.mp3_task_h) {
vTaskDelete(audio_i2s.mp3_task_h);
audio_i2s.mp3_task_h = 0;
}
//mp3_task_h=nullptr;
}
delay(1);
}
}
}
#endif // ESP32
#ifdef USE_I2S_WEBRADIO
void MDCallback(void *cbData, const char *type, bool isUnicode, const char *str) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) isUnicode; // Punt this ball for now
(void) ptr;
if (strstr_P(type, PSTR("Title"))) {
strncpy(audio_i2s.wr_title, str, sizeof(audio_i2s.wr_title));
audio_i2s.wr_title[sizeof(audio_i2s.wr_title)-1] = 0;
//AddLog(LOG_LEVEL_INFO,PSTR("WR-Title: %s"),wr_title);
} else {
// Who knows what to do? Not me!
}
}
void StatusCallback(void *cbData, int code, const char *string) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) code;
(void) ptr;
//strncpy_P(status, string, sizeof(status)-1);
//status[sizeof(status)-1] = 0;
}
void Webradio(const char *url) {
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
AUDIO_PWR_ON
audio_i2s.ifile = new AudioFileSourceICYStream(url);
audio_i2s.ifile->RegisterMetadataCB(MDCallback, NULL);
audio_i2s.buff = new AudioFileSourceBuffer(audio_i2s.ifile, audio_i2s.preallocateBuffer, preallocateBufferSize);
audio_i2s.buff->RegisterStatusCB(StatusCallback, NULL);
audio_i2s.decoder = new AudioGeneratorMP3(audio_i2s.preallocateCodec, preallocateCodecSize);
audio_i2s.decoder->RegisterStatusCB(StatusCallback, NULL);
audio_i2s.decoder->begin(audio_i2s.buff, audio_i2s.out);
if (!audio_i2s.decoder->isRunning()) {
// Serial.printf_P(PSTR("Can't connect to URL"));
StopPlaying();
// strcpy_P(status, PSTR("Unable to connect to URL"));
audio_i2s.retryms = millis() + 2000;
}
xTaskCreatePinnedToCore(mp3_task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_h, 1);
}
void mp3_task2(void *arg){
while (1) {
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
if (!audio_i2s.decoder->loop()) {
StopPlaying();
//retryms = millis() + 2000;
}
delay(1);
}
}
}
void StopPlaying() {
if (audio_i2s.mp3_task_h) {
vTaskDelete(audio_i2s.mp3_task_h);
audio_i2s.mp3_task_h = nullptr;
}
if (audio_i2s.decoder) {
audio_i2s.decoder->stop();
delete audio_i2s.decoder;
audio_i2s.decoder = NULL;
}
if (audio_i2s.buff) {
audio_i2s.buff->close();
delete audio_i2s.buff;
audio_i2s.buff = NULL;
}
if (audio_i2s.ifile) {
audio_i2s.ifile->close();
delete audio_i2s.ifile;
audio_i2s.ifile = NULL;
}
DOWNRATE
AUDIO_PWR_OFF
}
void Cmd_WebRadio(void) {
if (audio_i2s.decoder) {
StopPlaying();
}
if (XdrvMailbox.data_len > 0) {
Webradio(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
} else {
ResponseCmndChar_P(PSTR("Stopped"));
}
}
#ifdef USE_WEBSERVER
const char HTTP_WEBRADIO[] PROGMEM =
"{s}" "I2S_WR-Title" "{m}%s{e}";
void I2S_WR_Show(bool json) {
if (audio_i2s.decoder) {
if (json) {
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), audio_i2s.wr_title);
} else {
WSContentSend_PD(HTTP_WEBRADIO,audio_i2s.wr_title);
}
}
}
#endif // USE_WEBSERVER
#endif // USE_I2S_WEBRADIO
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
#ifdef USE_SHINE
void Cmd_MicRec(void) {
if (audio_i2s.mic_task_h) {
// stop task
audio_i2s.mic_stop = 1;
while (audio_i2s.mic_stop) {
delay(1);
}
ResponseCmndChar_P(PSTR("Stopped"));
}
if (XdrvMailbox.data_len > 0) {
i2s_record_shine(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
}
}
#else
void Cmd_MicRec(void) {
if (audio_i2s.mic_task_h) {
// stop task
vTaskDelete(audio_i2s.mic_task_h);
audio_i2s.mic_task_h = nullptr;
ResponseCmndChar_P(PSTR("Stopped"));
}
if (XdrvMailbox.data_len > 0) {
uint16 time = 10;
char *cp = strchr(XdrvMailbox.data, ':');
if (cp) {
time = atoi(cp + 1);
*cp = 0;
}
if (time<10) time = 10;
if (time>30) time = 30;
i2s_record(XdrvMailbox.data, time);
ResponseCmndChar(XdrvMailbox.data);
}
}
#endif // USE_SHINE
#endif // USE_M5STACK_CORE2
#ifdef ESP32
void Play_mp3(const char *path) {
#ifdef USE_UFILESYS
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
bool I2S_Task;
if (*path=='+') {
I2S_Task = true;
path++;
} else {
I2S_Task = false;
}
if (!ufsp->exists(path)) {
return;
}
AUDIO_PWR_ON
audio_i2s.file = new AudioFileSourceFS(*ufsp, path);
audio_i2s.id3 = new AudioFileSourceID3(audio_i2s.file);
if (audio_i2s.mp3ram) {
audio_i2s.mp3 = new AudioGeneratorMP3(audio_i2s.mp3ram, preallocateCodecSize);
} else {
audio_i2s.mp3 = new AudioGeneratorMP3();
}
audio_i2s.mp3->begin(audio_i2s.id3, audio_i2s.out);
if (I2S_Task) {
xTaskCreatePinnedToCore(mp3_task, "MP3", 8192, NULL, 3, &audio_i2s.mp3_task_h, 1);
} else {
while (audio_i2s.mp3->isRunning()) {
if (!audio_i2s.mp3->loop()) {
audio_i2s.mp3->stop();
break;
}
OsWatchLoop();
}
audio_i2s.out->stop();
mp3_delete();
}
#endif // USE_UFILESYS
}
void mp3_delete(void) {
delete audio_i2s.file;
delete audio_i2s.id3;
delete audio_i2s.mp3;
audio_i2s.mp3=nullptr;
DOWNRATE
AUDIO_PWR_OFF
}
#endif // ESP32
void Say(char *text) {
if (!audio_i2s.out) return;
AUDIO_PWR_ON
audio_i2s.out->begin();
ESP8266SAM *sam = new ESP8266SAM;
sam->Say(audio_i2s.out, text);
delete sam;
audio_i2s.out->stop();
DOWNRATE
AUDIO_PWR_OFF
}
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Say|Gain|Time"
#ifdef ESP32
"|Play"
#ifdef USE_I2S_WEBRADIO
"|WR"
#endif // USE_I2S_WEBRADIO
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
"|REC"
#ifdef WAV2MP3
"|W2M"
#endif
#endif // USE_M5STACK_CORE2
#endif // ESP32
;
void (* const I2SAudio_Command[])(void) PROGMEM = {
&Cmd_Say, &Cmd_Gain, &Cmd_Time
#ifdef ESP32
,&Cmd_Play
#ifdef USE_I2S_WEBRADIO
,&Cmd_WebRadio
#endif // USE_I2S_WEBRADIO
#if defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) || defined(USE_I2S_MIC)
,&Cmd_MicRec
#ifdef WAV2MP3
,&Cmd_wav2mp3
#endif
#endif // USE_M5STACK_CORE2
#endif // ESP32
};
void Cmd_Play(void) {
if (XdrvMailbox.data_len > 0) {
Play_mp3(XdrvMailbox.data);
}
ResponseCmndChar(XdrvMailbox.data);
}
void Cmd_Gain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
if (audio_i2s.out) {
audio_i2s.is2_volume=XdrvMailbox.payload;
audio_i2s.out->SetGain(((float)(audio_i2s.is2_volume-2)/100.0)*4.0);
}
}
ResponseCmndNumber(audio_i2s.is2_volume);
}
#ifdef WAV2MP3
void Cmd_wav2mp3(void) {
if (XdrvMailbox.data_len > 0) {
#ifdef USE_SHINE
wav2mp3(XdrvMailbox.data);
#endif // USE_SHINE
}
ResponseCmndChar(XdrvMailbox.data);
}
#endif // WAV2MP3
void Cmd_Say(void) {
if (XdrvMailbox.data_len > 0) {
Say(XdrvMailbox.data);
}
ResponseCmndChar(XdrvMailbox.data);
}
void Cmd_Time(void) { void Cmd_Time(void) {
#ifdef USE_I2S_SAY_TIME if (!audio_i2s.out) return;
sayTime(RtcTime.hour, RtcTime.minute); sayTime(RtcTime.hour, RtcTime.minute);
#endif // USE_I2S_SAY_TIME
ResponseCmndDone(); ResponseCmndDone();
} }
#endif // USE_I2S_SAY_TIME
/*********************************************************************************************\ #endif // is2audio
* Interface
\*********************************************************************************************/
bool Xdrv42(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_COMMAND:
result = DecodeCommand(kI2SAudio_Commands, I2SAudio_Command);
break;
case FUNC_INIT:
I2S_Init();
break;
#ifdef USE_WEBSERVER
#ifdef USE_I2S_WEBRADIO
case FUNC_WEB_SENSOR:
I2S_WR_Show(false);
break;
#endif // USE_I2S_WEBRADIO
#endif // USE_WEBSERVER
#ifdef USE_I2S_WEBRADIO
case FUNC_JSON_APPEND:
I2S_WR_Show(true);
break;
#endif // USE_I2S_WEBRADIO
}
return result;
}
#endif // USE_I2S_AUDIO

View File

@ -1,4 +1,23 @@
/*
audio is2 support for ESP32-S3 box and box lite
Copyright (C) 2022 Gerhard Mutz and Theo Arends
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 <http://www.gnu.org/licenses/>.
*/
#ifdef ESP32 #ifdef ESP32
#ifdef ESP32S3_BOX #ifdef ESP32S3_BOX
#include <driver/i2s.h> #include <driver/i2s.h>
@ -7,9 +26,11 @@
#include <es7243e.h> #include <es7243e.h>
#include <es7210.h> #include <es7210.h>
#define S3BOX_APWR_GPIO 46
void S3boxAudioPower(uint8_t pwr) { void S3boxAudioPower(uint8_t pwr) {
pinMode(46 , OUTPUT); digitalWrite(S3BOX_APWR_GPIO, pwr);
digitalWrite(46, pwr);
} }
// box lite dac init // box lite dac init
@ -111,130 +132,9 @@ void S3boxInit() {
// box full // box full
ES8311_init(); ES8311_init();
es7210_init(); es7210_init();
pinMode(S3BOX_APWR_GPIO , OUTPUT);
} }
} }
#endif // ESP32S3_BOX #endif // ESP32S3_BOX
#endif // ESP32
#ifdef USE_SHINE
#ifdef WAV2MP3
#include <layer3.h>
#include <types.h>
typedef uint8_t mp3buf_t;
// min freq = 16 KHz Stereo or 32 KHz Mono
int32_t wav2mp3(char *path) {
int32_t error = 0;
shine_config_t config;
shine_t s = nullptr;
File wav_in = (File)nullptr;
File mp3_out = (File)nullptr;
uint8_t *ucp;
int written;
int16_t *buffer = nullptr;
uint32_t bread;
uint16_t samples_per_pass;
char mpath[64];
char *cp;
uint8_t chans = 1;
uint32_t sfreq = 16000;
strlcpy(mpath, path, sizeof(mpath));
wav_in = ufsp->open(mpath, FS_FILE_READ);
if (!wav_in) {
error = -1;
goto exit;
}
// script>wav2mp3("/test2.wav")
uint8_t wavHeader[sizeof(wavHTemplate)];
wav_in.read((uint8_t*)wavHeader, sizeof(wavHTemplate));
chans = wavHeader[22];
sfreq = wavHeader[24]|(wavHeader[25]<<8)|(wavHeader[26]<<16)|(wavHeader[27]<<24);
cp = strchr(mpath, '.');
if (!cp) {
error = -6;
goto exit;
}
strcpy(cp, ".mp3");
mp3_out = ufsp->open(mpath, FS_FILE_WRITE);
if (!mp3_out) {
error = -2;
goto exit;
}
shine_set_config_mpeg_defaults(&config.mpeg);
if (chans == 1) {
config.mpeg.mode = MONO;
} else {
config.mpeg.mode = STEREO;
}
config.mpeg.bitr = 128;
config.wave.samplerate = sfreq;
config.wave.channels = (channels)chans;
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
error = -3;
goto exit;
}
s = shine_initialise(&config);
if (!s) {
error = -4;
goto exit;
}
samples_per_pass = shine_samples_per_pass(s);
buffer = (int16_t*)malloc(samples_per_pass * 2 * chans);
if (!buffer) {
error = -5;
goto exit;
}
AddLog(LOG_LEVEL_INFO, PSTR("mp3 encoding %d channels with freq %d Hz"), chans, sfreq);
while (1) {
bread = wav_in.read((uint8_t*)buffer, samples_per_pass * 2 * chans);
if (!bread) {
break;
}
ucp = shine_encode_buffer_interleaved(s, buffer, &written);
mp3_out.write(ucp, written);
}
ucp = shine_flush(s, &written);
mp3_out.write(ucp, written);
exit:
if (s) {
shine_close(s);
}
if (wav_in) {
wav_in.close();
}
if (mp3_out) {
mp3_out.close();
}
if (buffer) {
free(buffer);
}
AddLog(LOG_LEVEL_INFO, PSTR("mp3 encoding exit with code: %d"), error);
return error;
}
#endif // WAV2MP3
#endif // USE_SHINE
#endif