Merge pull request #16090 from gemu2015/audio-update

Audio update
This commit is contained in:
Theo Arends 2022-07-26 17:27:18 +02:00 committed by GitHub
commit 1b99b3aa38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1076 additions and 1059 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 (y = 0; y < MAX_GRANULES; y++) {
// 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->mdct_freq[x][y] = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //OK 1%
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
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
@ -117,8 +125,16 @@ shine_global_config *shine_initialise(shine_config_t *pub_config) {
#ifdef SHINE_DEBUG
printf("xrsq & xrabs each: %d\n", sizeof(int32_t)*GRANULE_SIZE);
#endif
config->l3loop->xrsq = (int*)heap_caps_malloc(sizeof(int32_t)*GRANULE_SIZE, MALLOC_CAP_32BIT); //OK 0.5%
config->l3loop->xrabs = (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%
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 {
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) {
free(config->l3loop);
}

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
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
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;
#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
long timezone = 2;
byte daysavetime = 1;
@ -272,778 +116,13 @@ AudioGeneratorTalkie *talkie = nullptr;
}
delete talkie;
audio_i2s.out->stop();
DOWNRATE
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) {
#ifdef USE_I2S_SAY_TIME
if (!audio_i2s.out) return;
sayTime(RtcTime.hour, RtcTime.minute);
#endif // USE_I2S_SAY_TIME
ResponseCmndDone();
}
/*********************************************************************************************\
* 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
#endif // USE_I2S_SAY_TIME
#endif // is2audio

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 ESP32S3_BOX
#include <driver/i2s.h>
@ -7,9 +26,11 @@
#include <es7243e.h>
#include <es7210.h>
#define S3BOX_APWR_GPIO 46
void S3boxAudioPower(uint8_t pwr) {
pinMode(46 , OUTPUT);
digitalWrite(46, pwr);
digitalWrite(S3BOX_APWR_GPIO, pwr);
}
// box lite dac init
@ -111,130 +132,9 @@ void S3boxInit() {
// box full
ES8311_init();
es7210_init();
pinMode(S3BOX_APWR_GPIO , OUTPUT);
}
}
#endif // ESP32S3_BOX
#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
#endif // ESP32