More cleaning of audio for ESP32 (#19577)

This commit is contained in:
s-hadinger 2023-09-23 11:25:58 +02:00 committed by GitHub
parent 63fd3e753a
commit 375c825d32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 373 additions and 220 deletions

View File

@ -132,6 +132,13 @@ class AXP192_M5Stack_Core2 : AXP192
end
end
# respond to audio events
def audio(cmd, idx, payload, raw)
if cmd == "power"
self.set_speaker_enable(idx)
end
end
end
return AXP192_M5Stack_Core2()

View File

@ -142,6 +142,13 @@ class AXP192_M5Stack_Tough : AXP192
end
end
# respond to audio events
def audio(cmd, idx, payload, raw)
if cmd == "power"
self.set_speaker_enable(idx)
end
end
# Touch Panel reset pin
def touch_reset(state)
self.write_gpio(1, state)

View File

@ -38,15 +38,6 @@
#include <layer3.h>
#include <types.h>
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON I2SAudioPower(true);
#define AUDIO_PWR_OFF I2SAudioPower(false);
#define USE_I2S_SAY
#define USE_I2S_SAY_TIME
#define USE_I2S_RTTTL
/*********************************************************************************************\
* Driver Settings in memory
\*********************************************************************************************/
@ -65,11 +56,12 @@ typedef struct{
uint32_t apll : 1 = 1; // bit 2 - will be ignored on unsupported SOC's
uint32_t mono : 1 = 0; // bit 3 0 = stereo, 1 = mono
uint32_t codec : 1 = 0; // bit 4 - S3 box only, unused for now
uint32_t webradio : 1 = 1; // bit 5 - allocate buffer for webradio
uint32_t spare01 : 1 = 1; // bit 6 - request duplex, means RX and TX on 1 slot
uint32_t lsbJustified : 1; // bit 7 - allow supporting LSBJ chips, e.g. TM8211/PT8211
uint32_t slot_config : 3 = 0; // bit 5-7 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
uint32_t volume : 8 = 10; // bit 8-15
uint32_t spare02 : 16; // bit 16-31
uint32_t mclk_inv : 1 = 0; // bit 16 - invert mclk
uint32_t bclk_inv : 1 = 0; // bit 17 - invert bclk
uint32_t ws_inv : 1 = 0; // bit 18 - invert ws
uint32_t spare02 : 13; // bit 19-31
} tx;
struct {
struct{
@ -103,23 +95,14 @@ struct AUDIO_I2S_t {
i2s_chan_handle_t rx_handle = nullptr;
AudioGeneratorMP3 *mp3 = nullptr;
AudioFileSourceFS *file;
AudioFileSourceFS *file = nullptr;
TasmotaAudioOutputI2S *out;
TasmotaAudioOutputI2S *out = nullptr;
AudioFileSourceID3 *id3;
AudioFileSourceID3 *id3 = nullptr;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
// Webradio
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
TaskHandle_t mp3_task_handle;
TaskHandle_t mic_task_handle;
@ -149,22 +132,8 @@ struct AUDIO_I2S_t {
} audio_i2s;
extern FS *ufsp;
extern FS *ffsp;
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
void sayTime(int hour, int minutes);
void Cmndwav2mp3(void);
void Cmd_Time(void);
void Rtttl(char *buffer);
void CmndI2SRtttl(void);
/*********************************************************************************************\
* Class for outputting sound as endpoint for ESP8266Audio library
\*********************************************************************************************/
@ -175,68 +144,105 @@ public:
// Constructor takes no parameter, everything is configured from template and config file
TasmotaAudioOutputI2S() {
hertz = 16000;
i2sOn = false;
bps = I2S_DATA_BIT_WIDTH_16BIT;
mono = audio_i2s.Settings->tx.mono;
lsbJustified = audio_i2s.Settings->tx.lsbJustified;
channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
output_mode = EXTERNAL_I2S;
tx_is_enabled = false;
loadSettings();
}
~TasmotaAudioOutputI2S() {
if(i2sOn){
this->stop();
i2s_del_channel(tx_chan);
}
}
// ------------------------------------------------------------------------------------------
// Setters for configuration parameters
//
// TODO: not sure we still need them since all this should be set at initialiation
// ------------------------------------------------------------------------------------------
bool SetBitsPerSample(int bits) {
virtual bool SetBitsPerSample(int bits) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetBitsPerSample: %i"), bits);
if ( (bits != 16) && (bits != 8) ) { return false; }
this->bps = bits;
return true;
}
bool SetChannels(int channels) {
virtual bool SetChannels(int channels) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetChannels: %i"), channels);
if ((channels < 1) || (channels > 2)) { return false; }
if (channels == (int)this->channels) { return true; }
this->channels = channels;
return true;
}
bool SetRate(int hz) {
if (hz == (int)this->hertz) { return true; }
virtual bool SetRate(int hz) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i"), hz);
if (hz == (int) this->hertz) { return true; }
this->hertz = hz;
if(i2sOn){
if (_i2s_on) {
int result = updateClockConfig();
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result);
}
return true;
}
bool SetPinout() { // TODO is this method still useful?
return this->startI2SChannel();
virtual bool SetGain(float f) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetGain: %_f"), &f);
return AudioOutput::SetGain(f);
}
// ------------------------------------------------------------------------------------------
// Getters
inline uint8_t getTxMode(void) const { return _tx_mode; }
inline bool getTxMono(void) const { return _tx_mono; }
inline bool getTxEnabled(void) const { return _tx_enabled; }
// ------------------------------------------------------------------------------------------
// Setters
inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
inline void setTxMono(bool mono) { _tx_mono = mono; }
inline void setTxEnabled(bool enabled) { _tx_enabled = enabled; }
// ------------------------------------------------------------------------------------------
// AudioOutput has inconsistent case for methods, provide consistent setters for super class
inline void setRate(int hz) { SetRate(hz); }
inline void setBitsPerSample(int bits) { SetBitsPerSample(bits); }
inline void setChannels(int chan) { SetChannels(chan); }
inline void setGain(float f) { SetGain(f); }
bool begin(void);
bool stop(void);
bool ConsumeSample(int16_t sample[2]);
bool startI2SChannel(void);
int updateClockConfig(void);
protected:
enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
int output_mode;
bool i2sOn;
bool mono;
bool lsbJustified;
// The following is now the preferred function
// and allows to send multiple samples at once
//
// Max 128 samples, it is clipped otherwise
// Returns: the number of samples actually consumed
// or -1 if an error occured
//
// The call is non blocking and does not wait
int32_t consumeSamples(int16_t *samples, size_t count);
i2s_chan_handle_t tx_chan;
bool tx_is_enabled;
protected:
void loadSettings(void); // load all settings from Settings file and template - the goal is to have zero touch
protected:
bool _i2s_on = false; // is I2S audio active
// local copy of useful settings for audio
// TX
uint8_t _tx_mode = EXTERNAL_I2S; // EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2
bool _tx_enabled = false; // true = enabled, false = disabled
bool _tx_mono = false; // true = mono, false = stereo
i2s_chan_handle_t _tx_chan; // I2S channel handle, automatically computed
// GPIOs for I2S
gpio_num_t _gpio_mclk = GPIO_NUM_NC; // GPIO for master clock
gpio_num_t _gpio_bclk = GPIO_NUM_NC; // GPIO for bit clock
gpio_num_t _gpio_ws = GPIO_NUM_NC; // GPIO for word select
gpio_num_t _gpio_dout = GPIO_NUM_NC; // GPIO for data out
gpio_num_t _gpio_din = GPIO_NUM_NC; // GPIO for data in
bool _gpio_mclk_inv = false; // invert master clock
bool _gpio_bclk_inv = false; // invert bit clock
bool _gpio_ws_inv = false; // invert word select
};
@ -244,107 +250,160 @@ protected:
// ------------------------------------------------------------------------------------------
// Methods
// ------------------------------------------------------------------------------------------
void TasmotaAudioOutputI2S::loadSettings(void) {
hertz = 16000;
_i2s_on = false;
bps = I2S_DATA_BIT_WIDTH_16BIT;
_tx_mono = audio_i2s.Settings->tx.mono;
channels = _tx_mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
_tx_mode = EXTERNAL_I2S;
_tx_enabled = false;
_gpio_mclk = (gpio_num_t) Pin(GPIO_I2S_MCLK);
_gpio_bclk = (gpio_num_t) Pin(GPIO_I2S_BCLK);
_gpio_ws = (gpio_num_t) Pin(GPIO_I2S_WS);
_gpio_dout = (gpio_num_t) Pin(GPIO_I2S_DOUT);
_gpio_din = (gpio_num_t) Pin(GPIO_I2S_DIN);
_gpio_mclk_inv = audio_i2s.Settings->tx.mclk_inv;
_gpio_bclk_inv = audio_i2s.Settings->tx.bclk_inv;
_gpio_ws_inv = audio_i2s.Settings->tx.ws_inv;
}
bool TasmotaAudioOutputI2S::begin() {
if (tx_is_enabled) { return true; }
if (!i2sOn) {
if (_tx_enabled) { return true; }
if (!_i2s_on) {
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
this->startI2SChannel();
}
}
int result = i2s_channel_enable(tx_chan);
int result = i2s_channel_enable(_tx_chan);
if (result != 0){
AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
return false;
}
tx_is_enabled = true;
_tx_enabled = true;
return true;
}
bool TasmotaAudioOutputI2S::stop() {
i2s_channel_disable(tx_chan);
if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){
i2s_del_channel(tx_chan);
i2sOn = false;
i2s_channel_disable(_tx_chan);
if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
i2s_del_channel(_tx_chan);
_i2s_on = false;
}
tx_is_enabled = false;
_tx_enabled = false;
return true;
}
bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
if (!tx_is_enabled) { return false; }
return consumeSamples(sample, 1);
}
int16_t ms[2];
ms[0] = sample[0];
ms[1] = sample[1];
MakeSampleStereo16(ms);
int32_t TasmotaAudioOutputI2S::consumeSamples(int16_t *samples, size_t count) {
if (!_tx_enabled) { return -1; }
if (count == 0) { return 0; }
if (count > 128) { count = 128; }
if (this->mono) {
int16_t ms[count*2];
for (int32_t i = 0; i < count; i++) {
int16_t left = samples[i*2 + LEFTCHANNEL];
int16_t right = samples[i*2 + RIGHTCHANNEL];
if (this->_tx_mono) {
// Average the two samples and overwrite
int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL];
ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (ttl>>1) & 0xffff;
int32_t ttl = left + right;
left = right = (ttl>>1) & 0xffff;
}
uint32_t s32;
if (output_mode == INTERNAL_DAC) {
int16_t l = Amplify(ms[LEFTCHANNEL]) + 0x8000;
int16_t r = Amplify(ms[RIGHTCHANNEL]) + 0x8000;
s32 = (r << 16) | (l & 0xffff);
if (bps == 8) {
left = (((int16_t)(left & 0xff)) - 128) << 8;
right = (((int16_t)(right & 0xff)) - 128) << 8;
}
if (_tx_mode == INTERNAL_DAC) {
left = Amplify(left) + 0x8000;
right = Amplify(right) + 0x8000;
} else {
s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff);
left = Amplify(left);
right = Amplify(right);
}
ms[i*2 + LEFTCHANNEL] = left;
ms[i*2 + RIGHTCHANNEL] = right;
}
size_t i2s_bytes_written;
i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
esp_err_t err = i2s_channel_write(_tx_chan, ms, sizeof(ms), &i2s_bytes_written, 0);
if (err && err != ESP_ERR_TIMEOUT) {
AddLog(LOG_LEVEL_INFO, "I2S: Could not write samples (count=%i): %i", count, err);
return -1;
}
return i2s_bytes_written;
}
// Initialize I2S channel
// return `true` if successful, `false` otherwise
bool TasmotaAudioOutputI2S::startI2SChannel(void) {
gpio_num_t _DIN = I2S_GPIO_UNUSED;
gpio_num_t _DIN = I2S_GPIO_UNUSED; // no input pin by default
// default dma_desc_num = 6 (DMA buffers), dma_frame_num = 240 (frames per buffer)
i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
if (audio_i2s.Settings->sys.duplex == 1) {
_DIN = (gpio_num_t)Pin(GPIO_I2S_DIN);
i2s_new_channel(&tx_chan_cfg, &tx_chan, &audio_i2s.rx_handle);
_DIN = (gpio_num_t)_gpio_din;
i2s_new_channel(&tx_chan_cfg, &_tx_chan, &audio_i2s.rx_handle);
} else{
i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
i2s_new_channel(&tx_chan_cfg, &_tx_chan, NULL);
}
// by default we configure for MSB 2 slots `I2S_STD_MSB_SLOT_DEFAULT_CONFIG`
i2s_std_config_t tx_std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels),
.gpio_cfg = {
.mclk = (gpio_num_t)Pin(GPIO_I2S_MCLK),
.bclk = (gpio_num_t)Pin(GPIO_I2S_BCLK),
.ws = (gpio_num_t)Pin(GPIO_I2S_WS),
.dout = (gpio_num_t)Pin(GPIO_I2S_DOUT),
.mclk = _gpio_mclk,
.bclk = _gpio_bclk,
.ws = _gpio_ws,
.dout = _gpio_dout,
.din = _DIN,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
.mclk_inv = _gpio_mclk_inv,
.bclk_inv = _gpio_bclk_inv,
.ws_inv = _gpio_ws_inv,
}
}
};
i2sOn = (i2s_channel_init_std_mode(tx_chan, &tx_std_cfg) == 0);
AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel with %i bit width on %i channels initialized", bps, channels);
// change configuration if we are using PCM or PHILIPS
if (audio_i2s.Settings->tx.slot_config == 1) { // PCM
tx_std_cfg.slot_cfg = I2S_STD_PCM_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels);
} else if (audio_i2s.Settings->tx.slot_config == 2) { // PHILIPS
tx_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels);
}
_i2s_on = (i2s_channel_init_std_mode(_tx_chan, &tx_std_cfg) == 0);
AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel with %i bit width on %i channels initialized ok=%i", bps, channels, _i2s_on);
if (audio_i2s.Settings->sys.duplex == 1) {
i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
}
return i2sOn;
return _i2s_on;
}
int TasmotaAudioOutputI2S::updateClockConfig(void) {
i2s_channel_disable(tx_chan);
i2s_channel_disable(_tx_chan);
i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz);
#ifdef SOC_I2S_SUPPORTS_APLL
if(audio_i2s.Settings->tx.apll == 1){
if (audio_i2s.Settings->tx.apll == 1) {
clk_cfg.clk_src = I2S_CLK_SRC_APLL;
}
#endif
int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg );
if(tx_is_enabled) i2s_channel_enable(tx_chan);
int result = i2s_channel_reconfig_std_clock(_tx_chan, &clk_cfg );
if (_tx_enabled) { i2s_channel_enable(_tx_chan); }
AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config, result=%i", result);
return result;
}
#endif // USE_I2S_AUDIO
#endif //ESP_IDF_VERSION_MAJOR >= 5
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5

View File

@ -16,7 +16,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 ESP_IDF_VERSION_MAJOR < 5
#if ESP8266 || (ESP_IDF_VERSION_MAJOR < 5)
#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
@ -765,4 +765,4 @@ bool Xdrv42(uint32_t function) {
}
#endif // USE_I2S_AUDIO
#endif // ESP_IDF_VERSION_MAJOR < 5
#endif // ESP8266 || (ESP_IDF_VERSION_MAJOR < 5)

View File

@ -17,17 +17,46 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#if ESP_IDF_VERSION_MAJOR >= 5
#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
#if defined(USE_I2S_AUDIO)
#define XDRV_42 42
#define USE_I2S_SAY
#define USE_I2S_SAY_TIME
#define USE_I2S_RTTTL
#define USE_I2S_WEBRADIO
// Macros used in audio sub-functions
#undef AUDIO_PWR_ON
#undef AUDIO_PWR_OFF
#define AUDIO_PWR_ON I2SAudioPower(true);
#define AUDIO_PWR_OFF I2SAudioPower(false);
extern FS *ufsp;
extern FS *ffsp;
const int preallocateBufferSize = 16*1024;
const int preallocateCodecSize = 29192; // MP3 codec max mem needed
//const int preallocateCodecSize = 85332; // AAC+SBR codec max mem needed
void sayTime(int hour, int minutes);
void Cmndwav2mp3(void);
void Cmd_Time(void);
void Rtttl(char *buffer);
void CmndI2SRtttl(void);
void I2sWebRadioStopPlaying(void);
/*********************************************************************************************\
* Commands definitions
\*********************************************************************************************/
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Gain|Play|WR|Rec|MGain|Stop"
"Gain|Play|Rec|MGain|Stop"
#ifdef USE_I2S_WEBRADIO
"|WR"
#endif // USE_I2S_WEBRADIO
#ifdef USE_I2S_SAY
"|Say"
#endif // USE_I2S_SAY
@ -46,7 +75,10 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
;
void (* const I2SAudio_Command[])(void) PROGMEM = {
&CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
&CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
#ifdef USE_I2S_WEBRADIO
&CmndI2SWebRadio,
#endif // USE_I2S_WEBRADIO
#ifdef USE_I2S_SAY
&CmndI2SSay,
#endif // USE_I2S_SAY
@ -378,7 +410,6 @@ void I2sCheckCfg(void){
}
else{
audio_i2s.Settings->sys.tx = 0;
audio_i2s.Settings->tx.webradio = 0; // do not allocate buffer
}
AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), Pin(GPIO_I2S_BCLK) , Pin(GPIO_I2S_WS), Pin(GPIO_I2S_DOUT), Pin(GPIO_I2S_MCLK), Pin(GPIO_I2S_DIN));
@ -401,7 +432,7 @@ bool I2sPinInit(void) {
if(audio_i2s.Settings->sys.tx == 1){
audio_i2s.out = new TasmotaAudioOutputI2S;
int err = audio_i2s.out->SetPinout() ? 0 : 1;
int err = audio_i2s.out->startI2SChannel() ? 0 : 1;
result += err;
}
@ -431,17 +462,6 @@ void I2sInit(void) {
audio_i2s.Settings->rx.mp3_encoder = 0; // no PS-RAM -> no MP3 encoding
}
}
if(audio_i2s.Settings->tx.webradio == 1){
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for 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);
}
}
}
/*********************************************************************************************\
@ -466,19 +486,6 @@ void I2sMp3Task(void *arg) {
}
}
void I2sMDCallback(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 I2sStatusCallback(void *cbData, int code, const char *string) {
const char *ptr = reinterpret_cast<const char *>(cbData);
(void) code;
@ -487,29 +494,6 @@ void I2sStatusCallback(void *cbData, int code, const char *string) {
//status[sizeof(status)-1] = 0;
}
void Webradio(const char *url) {
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
if (audio_i2s.Settings->tx.webradio == 0) return;
I2SAudioPower(true);
audio_i2s.ifile = new AudioFileSourceICYStream(url);
audio_i2s.ifile->RegisterMetadataCB(I2sMDCallback, NULL);
audio_i2s.buff = new AudioFileSourceBuffer(audio_i2s.ifile, audio_i2s.preallocateBuffer, preallocateBufferSize);
audio_i2s.buff->RegisterStatusCB(I2sStatusCallback, NULL);
audio_i2s.decoder = new AudioGeneratorMP3(audio_i2s.preallocateCodec, preallocateCodecSize);
audio_i2s.decoder->RegisterStatusCB(I2sStatusCallback, 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"));
I2sStopPlaying();
// strcpy_P(status, PSTR("Unable to connect to URL"));
audio_i2s.retryms = millis() + 2000;
}
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task"));
xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1);
}
void I2sMp3Task2(void *arg){
while (1) {
if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) {
@ -534,37 +518,13 @@ void I2sStopPlaying() {
delete audio_i2s.decoder;
audio_i2s.decoder = NULL;
}
#ifdef USE_I2S_WEBRADIO
I2sWebRadioStopPlaying();
#endif
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;
}
I2SAudioPower(false);
}
#ifdef USE_WEBSERVER
const char HTTP_WEBRADIO[] PROGMEM =
"{s}" "I2S_WR-Title" "{m}%s{e}";
void I2sWrShow(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
// Play_mp3 - Play a MP3 file from filesystem
//
// Returns I2S_error_t
@ -631,20 +591,6 @@ void CmndI2SStop(void) {
ResponseCmndDone();
}
void CmndI2SWebRadio(void) {
if (!audio_i2s.out) return;
if (audio_i2s.decoder) {
I2sStopPlaying();
}
if (XdrvMailbox.data_len > 0) {
Webradio(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
} else {
ResponseCmndChar_P(PSTR("Stopped"));
}
}
void CmndI2SPlay(void) {
if (XdrvMailbox.data_len > 0) {
int32_t err = I2SPlayMp3(XdrvMailbox.data);
@ -785,4 +731,4 @@ bool Xdrv42(uint32_t function) {
}
#endif // USE_I2S_AUDIO
#endif //ESP_IDF_VERSION_MAJOR >= 5
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5

View File

@ -0,0 +1,134 @@
/*
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(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
#if defined(USE_I2S_AUDIO) && defined(USE_I2S_WEBRADIO)
struct AUDIO_I2S_WEBRADIO_t {
// Webradio
AudioFileSourceICYStream *ifile = NULL;
AudioFileSourceBuffer *buff = NULL;
char wr_title[64];
void *preallocateBuffer = NULL;
void *preallocateCodec = NULL;
uint32_t retryms = 0;
} Audio_webradio;
void I2sMDCallback(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_webradio.wr_title, str, sizeof(Audio_webradio.wr_title));
Audio_webradio.wr_title[sizeof(Audio_webradio.wr_title)-1] = 0;
//AddLog(LOG_LEVEL_INFO,PSTR("WR-Title: %s"),wr_title);
} else {
// Who knows what to do? Not me!
}
}
void Webradio(const char *url) {
// allocate buffers if not already done
if (Audio_webradio.preallocateBuffer == NULL) {
Audio_webradio.preallocateBuffer = special_malloc(preallocateBufferSize);
}
if (Audio_webradio.preallocateCodec == NULL) {
Audio_webradio.preallocateCodec = special_malloc(preallocateCodecSize);
}
// check if we have buffers
if (Audio_webradio.preallocateBuffer == NULL || Audio_webradio.preallocateCodec == NULL) {
AddLog(LOG_LEVEL_INFO, "I2S: cannot allocate buffers");
if (Audio_webradio.preallocateBuffer != NULL) {
free(Audio_webradio.preallocateBuffer);
Audio_webradio.preallocateBuffer = NULL;
}
if (Audio_webradio.preallocateCodec != NULL) {
free(Audio_webradio.preallocateCodec);
Audio_webradio.preallocateCodec = NULL;
}
return;
}
if (audio_i2s.decoder || audio_i2s.mp3) return;
if (!audio_i2s.out) return;
I2SAudioPower(true);
Audio_webradio.ifile = new AudioFileSourceICYStream(url);
Audio_webradio.ifile->RegisterMetadataCB(I2sMDCallback, NULL);
Audio_webradio.buff = new AudioFileSourceBuffer(Audio_webradio.ifile, Audio_webradio.preallocateBuffer, preallocateBufferSize);
Audio_webradio.buff->RegisterStatusCB(I2sStatusCallback, NULL);
audio_i2s.decoder = new AudioGeneratorMP3(Audio_webradio.preallocateCodec, preallocateCodecSize);
audio_i2s.decoder->RegisterStatusCB(I2sStatusCallback, NULL);
audio_i2s.decoder->begin(Audio_webradio.buff, audio_i2s.out);
if (!audio_i2s.decoder->isRunning()) {
// Serial.printf_P(PSTR("Can't connect to URL"));
I2sStopPlaying();
// strcpy_P(status, PSTR("Unable to connect to URL"));
Audio_webradio.retryms = millis() + 2000;
}
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task"));
xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1);
}
#ifdef USE_WEBSERVER
const char HTTP_WEBRADIO[] PROGMEM =
"{s}" "I2S_WR-Title" "{m}%s{e}";
void I2sWrShow(bool json) {
if (audio_i2s.decoder) {
if (json) {
ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), Audio_webradio.wr_title);
} else {
WSContentSend_PD(HTTP_WEBRADIO,Audio_webradio.wr_title);
}
}
}
#endif // USE_WEBSERVER
void CmndI2SWebRadio(void) {
if (!audio_i2s.out) return;
if (audio_i2s.decoder) {
I2sStopPlaying();
}
if (XdrvMailbox.data_len > 0) {
Webradio(XdrvMailbox.data);
ResponseCmndChar(XdrvMailbox.data);
} else {
ResponseCmndChar_P(PSTR("Stopped"));
}
}
void I2sWebRadioStopPlaying() {
if (Audio_webradio.buff) {
Audio_webradio.buff->close();
delete Audio_webradio.buff;
Audio_webradio.buff = NULL;
}
if (Audio_webradio.ifile) {
Audio_webradio.ifile->close();
delete Audio_webradio.ifile;
Audio_webradio.ifile = NULL;
}
}
#endif // defined(USE_I2S_AUDIO) && defined(USE_I2S_WEBRADIO)
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5