diff --git a/tasmota/berry/drivers/AXP192_M5Stack_Core2.be b/tasmota/berry/drivers/AXP192_M5Stack_Core2.be index 309006987..a7293ba8e 100644 --- a/tasmota/berry/drivers/AXP192_M5Stack_Core2.be +++ b/tasmota/berry/drivers/AXP192_M5Stack_Core2.be @@ -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() diff --git a/tasmota/berry/drivers/AXP192_M5Stack_Tough.be b/tasmota/berry/drivers/AXP192_M5Stack_Tough.be index 5444b9eda..d70d56f0c 100644 --- a/tasmota/berry/drivers/AXP192_M5Stack_Tough.be +++ b/tasmota/berry/drivers/AXP192_M5Stack_Tough.be @@ -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) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino index 5d6c6a0de..bfcbf3e25 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino @@ -38,15 +38,6 @@ #include #include -#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); - } + this->stop(); } // ------------------------------------------------------------------------------------------ // 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) { - // Average the two samples and overwrite - int32_t ttl = ms[LEFTCHANNEL] + ms[RIGHTCHANNEL]; - ms[LEFTCHANNEL] = ms[RIGHTCHANNEL] = (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); - } else { - s32 = ((Amplify(ms[RIGHTCHANNEL])) << 16) | (Amplify(ms[LEFTCHANNEL]) & 0xffff); + 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 = left + right; + left = right = (ttl>>1) & 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 { + 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), - .din = _DIN, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; - 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); + .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_mclk, + .bclk = _gpio_bclk, + .ws = _gpio_ws, + .dout = _gpio_dout, + .din = _DIN, + .invert_flags = { + .mclk_inv = _gpio_mclk_inv, + .bclk_inv = _gpio_bclk_inv, + .ws_inv = _gpio_ws_inv, + } + } + }; + + // 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 // USE_I2S_AUDIO +#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino index a83b3a03f..616465717 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio.ino @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#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 @@ -764,5 +764,5 @@ bool Xdrv42(uint32_t function) { return result; } -#endif // USE_I2S_AUDIO -#endif // ESP_IDF_VERSION_MAJOR < 5 \ No newline at end of file +#endif // USE_I2S_AUDIO +#endif // ESP8266 || (ESP_IDF_VERSION_MAJOR < 5) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino index e31d6b40f..9529b0a75 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_audio_idf51.ino @@ -17,17 +17,46 @@ along with this program. If not, see . */ -#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(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(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); @@ -784,5 +730,5 @@ bool Xdrv42(uint32_t function) { return result; } -#endif // USE_I2S_AUDIO -#endif //ESP_IDF_VERSION_MAJOR >= 5 +#endif // USE_I2S_AUDIO +#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino new file mode 100644 index 000000000..294a7e865 --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino @@ -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 . +*/ + +#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(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