diff --git a/tasmota/berry/include/be_gpio_defines.h b/tasmota/berry/include/be_gpio_defines.h index f037d9480..9c5a131cc 100644 --- a/tasmota/berry/include/be_gpio_defines.h +++ b/tasmota/berry/include/be_gpio_defines.h @@ -101,6 +101,7 @@ const be_const_member_t lv_gpio_constants[] = { { "HX711_SCK", (int32_t) GPIO_HX711_SCK }, { "I2C_SCL", (int32_t) GPIO_I2C_SCL }, { "I2C_SDA", (int32_t) GPIO_I2C_SDA }, + { "I2S_DAC", (int32_t) GPIO_I2S_DAC }, { "I2S_IN_CLK", (int32_t) GPIO_I2S_BCLK_IN }, { "I2S_IN_DATA", (int32_t) GPIO_I2S_DIN }, { "I2S_IN_SLCT", (int32_t) GPIO_I2S_WS_IN }, diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h index b6ea5bec8..260e59a46 100644 --- a/tasmota/include/tasmota_template.h +++ b/tasmota/include/tasmota_template.h @@ -213,6 +213,7 @@ enum UserSelectablePins { GPIO_DINGTIAN_OE, // New version of Dingtian relay board where PL is not shared with OE GPIO_HDMI_CEC, // Support for HDMI CEC GPIO_HC8_RXD, // HC8 Serial interface + GPIO_I2S_DAC, // Audio DAC support for ESP32 and ESP32S2 GPIO_SENSOR_END }; // Error as warning to rethink GPIO usage with max 2045 @@ -473,6 +474,7 @@ const char kSensorNames[] PROGMEM = D_GPIO_DINGTIAN_OE "|" D_SENSOR_HDMI_CEC "|" D_SENSOR_HC8_RX "|" + D_SENSOR_I2S_DAC "|" ; const char kSensorNamesFixed[] PROGMEM = @@ -578,9 +580,10 @@ const uint16_t kGpioNiceList[] PROGMEM = { #if defined(USE_I2S_AUDIO) || defined (USE_I2S) AGPIO(GPIO_I2S_MCLK) + MAX_I2S, // I2S master clock AGPIO(GPIO_I2S_BCLK) + MAX_I2S, // I2S bit clock + AGPIO(GPIO_I2S_DOUT) + MAX_I2S, // I2S Out Data + AGPIO(GPIO_I2S_DAC) + 2, // I2S DAC Output AGPIO(GPIO_I2S_WS) + MAX_I2S, // I2S word select AGPIO(GPIO_I2S_DIN) + MAX_I2S, // I2S IN Data - AGPIO(GPIO_I2S_DOUT) + MAX_I2S, // I2S Out Data #endif #ifdef USE_I2S AGPIO(GPIO_I2S_BCLK_IN) + MAX_I2S, // I2S bit clock in diff --git a/tasmota/language/af_AF.h b/tasmota/language/af_AF.h index 29feac9c0..c196f224f 100644 --- a/tasmota/language/af_AF.h +++ b/tasmota/language/af_AF.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Speler" diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index b4f78d202..1702d9c4e 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/ca_AD.h b/tasmota/language/ca_AD.h index 686516f4e..e1681305b 100644 --- a/tasmota/language/ca_AD.h +++ b/tasmota/language/ca_AD.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "Reproductor MP3" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 5c724a1cb..def1b6937 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index a3b79886c..cb54016ee 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index fb00043ee..72f2cbe21 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index e87e497f6..77f067bca 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 71ddb6870..4186ef707 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index ae3a5a5f1..f1f10d63c 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS In" #define D_SENSOR_I2S_DIN "I2S DIn" #define D_SENSOR_I2S_DOUT "I2S DOut" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/fy_NL.h b/tasmota/language/fy_NL.h index 8d9044092..a023615f9 100644 --- a/tasmota/language/fy_NL.h +++ b/tasmota/language/fy_NL.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Speler" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 153ce075a..2474c88b1 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "נגן מוזיקה" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index 45d1599f3..817645cd8 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 lejátszó" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 455c87815..7affc8999 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S - WS IN" #define D_SENSOR_I2S_DIN "I2S - DIN" #define D_SENSOR_I2S_DOUT "I2S - DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "Riproduttore MP3" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index 858233864..6c5585d54 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index 31ebb5a90..4ed85c94d 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Speler" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index 4e0b0b911..6665faecf 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "Odtwarzacz MP3" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index 6a025b02d..d4b07da40 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index ed986fe56..c2cc25392 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "Leitor de MP3" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 6b83f73ef..ce868f944 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 27b360472..28c0f754c 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -658,6 +658,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 66fa14bca..c124beb66 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index aa0ca90cf..dec707a3b 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 spelare" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index fdec2ae69..175fa7e64 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index 4e4ba7ec1..b8ea15e14 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h index 719fa66bb..c3327ae49 100644 --- a/tasmota/language/vi_VN.h +++ b/tasmota/language/vi_VN.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index ea1ed682e..651afcb15 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index a6a7fabac..897fad62f 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -657,6 +657,7 @@ #define D_SENSOR_I2S_BCLK_IN "I2S WS IN" #define D_SENSOR_I2S_DIN "I2S DIN" #define D_SENSOR_I2S_DOUT "I2S DOUT" +#define D_SENSOR_I2S_DAC "I2S DAC" #define D_SENSOR_HDMI_CEC "HDMI CEC" #define D_SENSOR_WS2812 "WS2812" #define D_SENSOR_DFR562 "MP3 Player" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino index b2f1b56ec..f724da1f0 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino @@ -25,20 +25,6 @@ #include "driver/gpio.h" #include "soc/soc_caps.h" -#include "AudioFileSourcePROGMEM.h" -#include "AudioFileSourceID3.h" -#include "AudioGeneratorMP3.h" - -#include -#include "AudioFileSourceFS.h" -#include "AudioGeneratorTalkie.h" -#include "AudioFileSourceICYStream.h" -#include "AudioFileSourceBuffer.h" -#include "AudioGeneratorAAC.h" - -#include -#include - /*********************************************************************************************\ * Driver Settings in memory \*********************************************************************************************/ @@ -134,41 +120,8 @@ struct AUDIO_I2S_t { tI2SSettings *Settings; i2s_chan_handle_t rx_handle = nullptr; - - AudioGeneratorMP3 *mp3 = nullptr; - AudioFileSourceFS *file = nullptr; - TasmotaI2S *out = nullptr; // instance used for I2S output, or `nullptr` if none TasmotaI2S *in = nullptr; // instance used for I2S input, or `nullptr` if none (it can be the same as `out` in case of full duplex) - - AudioFileSourceID3 *id3 = nullptr; - AudioGeneratorMP3 *decoder = NULL; - void *mp3ram = NULL; - - TaskHandle_t mp3_task_handle; - TaskHandle_t mic_task_handle; - - char mic_path[32]; - uint8_t mic_stop; - int8_t mic_error; - bool use_stream = false; - - -// SHINE - uint32_t recdur; - uint8_t stream_active; - uint8_t stream_enable; - WiFiClient client; - ESP8266WebServer *MP3Server; - -// I2S_BRIDGE - BRIDGE_MODE bridge_mode; - WiFiUDP i2s_bridge_udp; - WiFiUDP i2s_bridgec_udp; - IPAddress i2s_bridge_ip; - TaskHandle_t i2s_bridge_h; - int8_t ptt_pin = -1; - } audio_i2s; #endif // USE_I2S_AUDIO diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino index 40fa3bba6..d19ddba7f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino @@ -20,6 +20,17 @@ #if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 #ifdef USE_I2S_AUDIO +#include "AudioOutput.h" +#include "driver/dac_continuous.h" + +// If DAC is not supported, proide some placeholders +#ifndef SOC_DAC_SUPPORTED + #define dac_continuous_enable(...) (0xFF) + #define dac_continuous_disable(...) (0xFF) + #define dac_continuous_del_channels(...) (0xFF) + #define dac_continuous_write(...) (0xFF) +#endif + /*********************************************************************************************\ * Driver Settings in memory \*********************************************************************************************/ @@ -67,7 +78,8 @@ public: } ~TasmotaI2S() { - this->stopTx(); + delRxHandle(); + delTxHandle(); } /*********************************************************************************************\ @@ -137,6 +149,11 @@ public: inline uint8_t getTxChannels(void) const { return channels; } inline bool getTxRunning(void) const { return _tx_running; } inline i2s_chan_handle_t getTxHandle(void) const { return _tx_handle; } +#ifdef SOC_DAC_SUPPORTED + inline bool isDACMode(void) const { return (_tx_mode == I2S_MODE_DAC); } +#else + inline bool isDACMode(void) const { return false; } +#endif inline uint8_t getRxMode(void) const { return _rx_mode; } inline uint8_t getRxBitsPerSample(void) const { return 16; } // TODO - hardcoded to 16 bits for recording @@ -151,7 +168,12 @@ public: // ------------------------------------------------------------------------------------------ // Setters inline void setExclusive(bool exclusive) { _exclusive = exclusive; } - inline void setTxMode(uint8_t mode) { _tx_mode = mode; } + inline void setTxMode(uint8_t mode) { + _tx_mode = mode; + if (_tx_mode == I2S_MODE_DAC) { + _tx_configured = true; + } + } inline void setTxChannels(uint8_t channels) { SetChannels(channels); } inline void setTxRunning(bool running) { _tx_running = running; } inline void setRxMode(uint8_t mode) { _rx_mode = mode; } @@ -181,7 +203,6 @@ public: bool stopTx(void); bool ConsumeSample(int16_t sample[2]); bool startI2SChannel(bool tx, bool rx); - bool updateClockConfig(void); int32_t readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass, uint32_t *peak_ptr); @@ -203,6 +224,10 @@ public: protected: int16_t dcFilter(int16_t pcm_in); int16_t lowpassFilter(int16_t pcm_in); + bool updateClockConfig(void); + + bool delTxHandle(void); // remove handle + bool delRxHandle(void); protected: @@ -289,7 +314,12 @@ bool TasmotaI2S::beginTx(void) { } } - esp_err_t err = i2s_channel_enable(_tx_handle); + esp_err_t err = ESP_OK; + if (isDACMode()) { + err = dac_continuous_enable((dac_continuous_handle_t) _tx_handle); + } else { + err = i2s_channel_enable(_tx_handle); + } AddLog(LOG_LEVEL_INFO, "I2S: Tx i2s_channel_enable err=0x%04X", err); if (err != ERR_OK){ return false; @@ -300,17 +330,26 @@ bool TasmotaI2S::beginTx(void) { } bool TasmotaI2S::stopTx() { + esp_err_t err = ESP_OK; AddLog(LOG_LEVEL_DEBUG, "I2S: calling stopTx() tx_running:%i tx_handle:%p", _tx_running, _tx_handle); if (!_tx_configured) { return false; } // invalid action if (!_tx_handle) { return true; } // nothing to do if (_tx_running) { - esp_err_t err = i2s_channel_disable(_tx_handle); + if (isDACMode()) { + err = dac_continuous_disable((dac_continuous_handle_t) _tx_handle); + } else { + err = i2s_channel_disable(_tx_handle); + } AddLog(LOG_LEVEL_DEBUG, "I2S: stopTx i2s_channel_disable err=0x%04X", err); _tx_running = false; } if (_exclusive) { // exclusive mode, deregister channel if (_tx_handle) { - esp_err_t err = i2s_del_channel(_tx_handle); + if (isDACMode()) { + err = dac_continuous_del_channels((dac_continuous_handle_t) _tx_handle); + } else { + err = i2s_del_channel(_tx_handle); + } AddLog(LOG_LEVEL_DEBUG, "I2S: stopTx i2s_del_channel err=0x%04X", err); _tx_handle = nullptr; } @@ -319,6 +358,36 @@ bool TasmotaI2S::stopTx() { return true; } +bool TasmotaI2S::delTxHandle(void) { + esp_err_t err = ESP_OK; + AddLog(LOG_LEVEL_DEBUG, "I2S: calling delTxHandle() tx_running:%i tx_handle:%p", _tx_running, _tx_handle); + if (!_tx_configured) { return false; } // invalid action + if (!_tx_handle) { return true; } // nothing to do + if (_tx_running) { stopTx(); } + + if (isDACMode()) { + err = dac_continuous_del_channels((dac_continuous_handle_t) _tx_handle); + } else { + err = i2s_del_channel(_tx_handle); + } + AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_del_channel Tx err=0x%04X", err); + _tx_handle = nullptr; + return true; +} + +bool TasmotaI2S::delRxHandle(void) { + esp_err_t err = ESP_OK; + AddLog(LOG_LEVEL_DEBUG, "I2S: calling delRxHandle() rx_running:%i rx_handle:%p", _rx_running, _rx_handle); + if (!_rx_configured) { return false; } // invalid action + if (!_rx_handle) { return true; } // nothing to do + if (_rx_running) { stopRx(); } + + err = i2s_del_channel(_rx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_del_channel Rx err=0x%04X", err); + _rx_handle = nullptr; + return true; +} + bool TasmotaI2S::stopRx(void) { AddLog(LOG_LEVEL_DEBUG, "I2S: calling stopRx() rx_running:%i rx_handle:%p", _rx_running, _rx_handle); if (!_rx_configured) { return false; } // nothing configured @@ -364,7 +433,7 @@ int32_t TasmotaI2S::consumeSamples(int16_t *samples, size_t count) { right = (((int16_t)(right & 0xff)) - 128) << 8; } - if (_tx_mode == I2S_MODE_DAC) { + if (isDACMode()) { left = Amplify(left) + 0x8000; right = Amplify(right) + 0x8000; } else { @@ -379,7 +448,14 @@ int32_t TasmotaI2S::consumeSamples(int16_t *samples, size_t count) { // AddLog(LOG_LEVEL_DEBUG, "I2S: consumeSamples: left=%i right=%i", ms[0], ms[1]); size_t i2s_bytes_written; - esp_err_t err = i2s_channel_write(_tx_handle, ms, sizeof(ms), &i2s_bytes_written, 0); + esp_err_t err = ESP_OK; + if (isDACMode()) { + err = dac_continuous_write((dac_continuous_handle_t) _tx_handle, (uint8_t*) ms, sizeof(ms), &i2s_bytes_written, -1); + // Serial.printf("."); Serial.flush(); + // AddLog(LOG_LEVEL_DEBUG, "I2S: dac_continuous_write err=0x%04X bytes_written=%i buf=%*_H", err, i2s_bytes_written, sizeof(ms), (uint8_t*) ms); + } else { + err = i2s_channel_write(_tx_handle, 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; @@ -417,15 +493,19 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { esp_err_t err = ESP_OK; gpio_num_t _DIN = rx ? (gpio_num_t)_gpio_din : I2S_GPIO_UNUSED; // no input pin if no Rx - if (tx) { + if (tx && !isDACMode()) { // 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_1, I2S_ROLE_MASTER); + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(_i2s_port, I2S_ROLE_MASTER); AddLog(LOG_LEVEL_DEBUG, "I2S: tx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i", tx_chan_cfg.id, tx_chan_cfg.role, tx_chan_cfg.dma_desc_num, tx_chan_cfg.dma_frame_num, tx_chan_cfg.auto_clear); err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, rx ? &_rx_handle : NULL); // configure Rx only in duplex non-exclusive AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel Tx err:0x%04X", err); + if (err != ERR_OK) { + _tx_handle = nullptr; + return false; + } // by default we configure for MSB 2 slots `I2S_STD_MSB_SLOT_DEFAULT_CONFIG` i2s_std_config_t tx_std_cfg = { @@ -452,6 +532,7 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { tx_std_cfg.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG((i2s_data_bit_width_t)bps, (i2s_slot_mode_t)channels); } if (_tx_slot_mask != I2S_SLOT_NOCHANGE) { tx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_tx_slot_mask; } + if (_apll) { tx_std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; } err = i2s_channel_init_std_mode(_tx_handle, &tx_std_cfg); AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_channel_init_std_mode TX channel bits:%i channels:%i hertz:%i err=0x%04X", bps, channels, hertz, err); @@ -467,6 +548,29 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { } } // if (tx) +#ifdef SOC_DAC_SUPPORTED + if (tx && isDACMode()) { + dac_continuous_config_t dac_chan_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 6, + .buf_size = 240, + .freq_hz = hertz, + .offset = 0, + .clk_src = DAC_DIGI_CLK_SRC_APLL, /*DAC_DIGI_CLK_SRC_DEFAULT*/ + .chan_mode = DAC_CHANNEL_MODE_SIMUL, + }; + // AddLog(LOG_LEVEL_DEBUG, "I2S: dac_chan_cfg chan_mask:%i clk_src:%i chan_mode:%i", + // dac_chan_cfg.chan_mask, dac_chan_cfg.clk_src, dac_chan_cfg.chan_mode); + + err = dac_continuous_new_channels(&dac_chan_cfg, (dac_continuous_handle_t*) &_tx_handle); + AddLog(LOG_LEVEL_DEBUG, "I2S: dac_new_channel Tx err:0x%04X", err); + if (err != ERR_OK) { + _tx_handle = nullptr; + return false; + } + } // if (tx) && DAC +#endif // SOC_DAC_SUPPORTED + // configure Rx Microphone if (rx) { gpio_num_t clk_gpio; @@ -474,7 +578,7 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { i2s_slot_mode_t slot_mode = (_rx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels:%i rx_running:%i rx_handle:%p", slot_mode, _rx_running, _rx_handle); - i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(_i2s_port, I2S_ROLE_MASTER); // change to 3 buffers of 512 samples rx_chan_cfg.dma_desc_num = 3; rx_chan_cfg.dma_frame_num = 512; @@ -555,26 +659,39 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { return true; } +// called only if Tx frequency is changed bool TasmotaI2S::updateClockConfig(void) { if (!_tx_handle) { return true; } - if (_tx_running) { - esp_err_t err = i2s_channel_disable(_tx_handle); - AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_disable err=0x%04X", err); + + // I2S mode + if (_tx_mode != I2S_MODE_DAC) { + if (_tx_running) { + esp_err_t err = i2s_channel_disable(_tx_handle); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_disable err=0x%04X", err); + } + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz); + #ifdef SOC_I2S_SUPPORTS_APLL + if (_apll) { + clk_cfg.clk_src = I2S_CLK_SRC_APLL; + } + #endif + esp_err_t result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_reconfig_std_clock err=0x%04X", result); + if (_tx_running) { + esp_err_t err = i2s_channel_enable(_tx_handle); + AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_enable err=0x%04X", err); + } + AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config"); + return result == ESP_OK; + } else { + + // DAC mode + // It looks like you can't change the DAC frequency without removing and recreating the DAC diver + delTxHandle(); + AddLog(LOG_LEVEL_DEBUG, "I2S: Updating DAC clock config"); + return startI2SChannel(true, false); } - i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(hertz); -#ifdef SOC_I2S_SUPPORTS_APLL - if (_apll) { - clk_cfg.clk_src = I2S_CLK_SRC_APLL; - } -#endif - esp_err_t result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg ); - AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_reconfig_std_clock err=0x%04X", result); - if (_tx_running) { - esp_err_t err = i2s_channel_enable(_tx_handle); - AddLog(LOG_LEVEL_INFO, "I2S: updateClockConfig i2s_channel_enable err=0x%04X", err); - } - AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config"); - return result == ESP_OK; + } /*********************************************************************************************\ 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 c9e4f4cd3..6263586d3 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,8 +17,22 @@ along with this program. If not, see . */ -#if defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5 -#if defined(USE_I2S_AUDIO) +#ifdef ESP32 +#if defined(USE_I2S_AUDIO) && ESP_IDF_VERSION_MAJOR >= 5 + +#include + +#include "AudioFileSourcePROGMEM.h" +#include "AudioFileSourceID3.h" +#include "AudioGeneratorMP3.h" + +#include "AudioFileSourceFS.h" +#include "AudioGeneratorTalkie.h" +#include "AudioFileSourceICYStream.h" +#include "AudioFileSourceBuffer.h" +#include "AudioGeneratorAAC.h" + +#include #define XDRV_42 42 @@ -26,6 +40,7 @@ #define USE_I2S_SAY_TIME #define USE_I2S_RTTTL #define USE_I2S_WEBRADIO +#define USE_I2S_MP3 #define USE_I2S_DEBUG // remove before flight // Macros used in audio sub-functions @@ -51,12 +66,57 @@ void Rtttl(char *buffer); void CmndI2SRtttl(void); void I2sWebRadioStopPlaying(void); +/*********************************************************************************************\ + * More structures +\*********************************************************************************************/ + +struct AUDIO_I2S_MP3_t { +#ifdef USE_I2S_MP3 + AudioGeneratorMP3 *mp3 = nullptr; + AudioFileSourceFS *file = nullptr; + AudioFileSourceID3 *id3 = nullptr; + + void *mp3ram = NULL; +#endif // USE_I2S_MP3 + +#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) + AudioGeneratorMP3 *decoder = NULL; + TaskHandle_t mp3_task_handle; + TaskHandle_t mic_task_handle; +#endif // defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) + + char mic_path[32]; + uint8_t mic_stop; + int8_t mic_error; + bool use_stream = false; + + +// SHINE + uint32_t recdur; + uint8_t stream_active; + uint8_t stream_enable; + WiFiClient client; + ESP8266WebServer *MP3Server; + +// I2S_BRIDGE + BRIDGE_MODE bridge_mode; + WiFiUDP i2s_bridge_udp; + WiFiUDP i2s_bridgec_udp; + IPAddress i2s_bridge_ip; + TaskHandle_t i2s_bridge_h; + int8_t ptt_pin = -1; + +} audio_i2s_mp3; + /*********************************************************************************************\ * Commands definitions \*********************************************************************************************/ const char kI2SAudio_Commands[] PROGMEM = "I2S|" - "Gain|Play|Rec|MGain|Stop|Config" + "Gain|Rec|MGain|Stop|Config" +#ifdef USE_I2S_MP3 + "|Play" +#endif #ifdef USE_I2S_DEBUG "|Mic" // debug only #endif // USE_I2S_DEBUG @@ -81,7 +141,10 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|" ; void (* const I2SAudio_Command[])(void) PROGMEM = { - &CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, &CmndI2SConfig, + &CmndI2SGain, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, &CmndI2SConfig, +#ifdef USE_I2S_MP3 + &CmndI2SPlay, +#endif #ifdef USE_I2S_DEBUG &CmndI2SMic, #endif // USE_I2S_DEBUG @@ -141,7 +204,7 @@ void CmndI2SConfig(void) { JsonParserToken tx_tk = root["Tx"]; if (tx_tk.isObject()) { JsonParserObject tx = tx_tk.getObject(); - // cfg->tx.sample_rate = tx.getUInt(PSTR("SampleRate"), cfg->tx.sample_rate); + cfg->tx.sample_rate = tx.getUInt(PSTR("SampleRate"), cfg->tx.sample_rate); cfg->tx.gain = tx.getUInt(PSTR("Gain"), cfg->tx.gain); cfg->tx.mode = tx.getUInt(PSTR("Mode"), cfg->tx.mode); cfg->tx.slot_mask = tx.getUInt(PSTR("SlotMask"), cfg->tx.slot_mask); @@ -259,21 +322,21 @@ void I2sMicTask(void *arg){ uint32_t ctime; uint32_t gain = audio_i2s.Settings->rx.gain; - if (!audio_i2s.use_stream) { - mp3_out = ufsp->open(audio_i2s.mic_path, "w"); + if (!audio_i2s_mp3.use_stream) { + mp3_out = ufsp->open(audio_i2s_mp3.mic_path, "w"); if (!mp3_out) { error = 1; goto exit; } } else { - if (!audio_i2s.stream_active) { + if (!audio_i2s_mp3.stream_active) { error = 2; - audio_i2s.use_stream = 0; + audio_i2s_mp3.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" + audio_i2s_mp3.client.flush(); + audio_i2s_mp3.client.setTimeout(3); + audio_i2s_mp3.client.print("HTTP/1.1 200 OK\r\n" "Content-Type: audio/mpeg;\r\n\r\n"); // Webserver->send(200, "application/octet-stream", ""); @@ -314,7 +377,7 @@ void I2sMicTask(void *arg){ ctime = TasmotaGlobal.uptime; - while (!audio_i2s.mic_stop) { + while (!audio_i2s_mp3.mic_stop) { size_t bytes_read; i2s_channel_read(audio_i2s.rx_handle, (void*)buffer, bytesize, &bytes_read, (100 / portTICK_PERIOD_MS)); @@ -326,27 +389,27 @@ void I2sMicTask(void *arg){ } ucp = shine_encode_buffer_interleaved(s, buffer, &written); - if (!audio_i2s.use_stream) { + if (!audio_i2s_mp3.use_stream) { bwritten = mp3_out.write(ucp, written); if (bwritten != written) { break; } } else { - audio_i2s.client.write((const char*)ucp, written); + audio_i2s_mp3.client.write((const char*)ucp, written); - if (!audio_i2s.client.connected()) { + if (!audio_i2s_mp3.client.connected()) { break; } } - audio_i2s.recdur = TasmotaGlobal.uptime - ctime; + audio_i2s_mp3.recdur = TasmotaGlobal.uptime - ctime; } ucp = shine_flush(s, &written); - if (!audio_i2s.use_stream) { + if (!audio_i2s_mp3.use_stream) { mp3_out.write(ucp, written); } else { - audio_i2s.client.write((const char*)ucp, written); + audio_i2s_mp3.client.write((const char*)ucp, written); } @@ -362,17 +425,17 @@ exit: free(buffer); } - if (audio_i2s.use_stream) { - audio_i2s.client.stop(); + if (audio_i2s_mp3.use_stream) { + audio_i2s_mp3.client.stop(); } audio_i2s.out->stopRx(); - audio_i2s.mic_stop = 0; - audio_i2s.mic_error = error; + audio_i2s_mp3.mic_stop = 0; + audio_i2s_mp3.mic_error = error; AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error); - audio_i2s.mic_task_handle = 0; - audio_i2s.recdur = 0; - audio_i2s.stream_active = 0; + audio_i2s_mp3.mic_task_handle = 0; + audio_i2s_mp3.recdur = 0; + audio_i2s_mp3.stream_active = 0; vTaskDelete(NULL); } @@ -380,19 +443,21 @@ exit: int32_t I2sRecordShine(char *path) { esp_err_t err = ESP_OK; - if (audio_i2s.decoder || audio_i2s.mp3) return 0; +#ifdef USE_I2S_MP3 + if (audio_i2s_mp3.decoder || audio_i2s_mp3.mp3) return 0; +#endif - strlcpy(audio_i2s.mic_path, path, sizeof(audio_i2s.mic_path)); - audio_i2s.mic_stop = 0; + strlcpy(audio_i2s_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path)); + audio_i2s_mp3.mic_stop = 0; uint32_t stack = 4096; - audio_i2s.use_stream = !strcmp(audio_i2s.mic_path, "stream.mp3"); + audio_i2s_mp3.use_stream = !strcmp(audio_i2s_mp3.mic_path, "stream.mp3"); - if (audio_i2s.use_stream) { + if (audio_i2s_mp3.use_stream) { stack = 8000; } audio_i2s.out->startRx(); - err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1); + err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s_mp3.mic_task_handle, 1); return err; } @@ -479,10 +544,12 @@ void I2sInit(void) { int32_t gpio_dout_0 = Pin(GPIO_I2S_DOUT, 0); int32_t gpio_dout_1 = Pin(GPIO_I2S_DOUT, 1); int32_t gpio_ws_0 = Pin(GPIO_I2S_WS, 0); + int32_t gpio_dac_0 = Pin(GPIO_I2S_DAC, 0); // DAC-1 needs to be comfigured if DAC-2 is needed as well + int32_t gpio_dac_1 = Pin(GPIO_I2S_DAC, 1); // DAC-1 needs to be comfigured if DAC-2 is needed as well // we need at least one pin configured // Note: in case of ESP32 DAC output we may have only WS_0 configured. DAC is only supported on port 0 - if ((gpio_din_0 < 0) && (gpio_din_1 < 0) && (gpio_dout_0 < 0) && (gpio_dout_1 < 0) && (gpio_ws_0 < 0)) { + if ((gpio_din_0 < 0) && (gpio_din_1 < 0) && (gpio_dout_0 < 0) && (gpio_dout_1 < 0) && (gpio_ws_0 < 0) && (gpio_dac_0 < 0)) { AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: no pin configured")); return; } @@ -492,6 +559,10 @@ void I2sInit(void) { bool duplex = false; // the same ports are used for input and output bool exclusive = false; // signals that in/out have a shared GPIO and need to un/install driver before use + bool dac_mode = (gpio_dac_0 >= 0); + if (dac_mode) { + audio_i2s.Settings->tx.mode = I2S_MODE_DAC; + } // AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d"), // Pin(GPIO_I2S_BCLK, 0) , Pin(GPIO_I2S_WS, 0), Pin(GPIO_I2S_DOUT, 0), Pin(GPIO_I2S_MCLK, 0), Pin(GPIO_I2S_DIN, 0)); @@ -513,18 +584,17 @@ void I2sInit(void) { AddLog(LOG_LEVEL_DEBUG, "I2S: I2S%i bclk=%i, ws=%i, dout=%i, mclk=%i, din=%i", port, bclk, ws, dout, mclk, din); // if neither input, nor output, nor DAC/ADC skip (WS could is only needed for DAC but supports only port 0) - if (din < 0 && dout < 0 && (ws < 0 || port !=0)) { continue; } + if (din < 0 && dout < 0 && (!(dac_mode && port == 0))) { continue; } duplex = (din >= 0) && (dout >= 0); if (duplex) { if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){ exclusive = true; } - AddLog(LOG_LEVEL_DEBUG, "I2S: enabling duplex mode, exclusive;%i", exclusive); + AddLog(LOG_LEVEL_DEBUG, "I2S: enabling duplex mode, exclusive:%i", exclusive); } const char *err_msg = nullptr; // to save code, we indicate an error with a message configured - bool dac_mode = false; if (din >= 0 || dout >= 0) { // we have regular I2S configuration // do multiple checks @@ -566,10 +636,11 @@ void I2sInit(void) { err_msg = "PDM Tx is not supported"; } } else { - dac_mode = true; // no DIN/DOUT, try DAC mode // 1. Check that tx.mode is I2S_MODE_DAC - if (audio_i2s.Settings->tx.mode != I2S_MODE_DAC) { + if (dac_mode) { + AddLog(LOG_LEVEL_DEBUG, "I2S: Configuraing DAC mode DAC0=%i DAC1=%i", gpio_dac_0, gpio_dac_1); + } else { err_msg = "DAC mode is not enabled"; } } @@ -646,13 +717,14 @@ void I2sInit(void) { audio_i2s.out->beginTx(); // TODO - useful? audio_i2s.out->stopTx(); } - audio_i2s.mp3ram = nullptr; - +#ifdef USE_I2S_MP3 + audio_i2s_mp3.mp3ram = nullptr; if (audio_i2s.Settings->sys.mp3_preallocate == 1){ // if (UsePSRAM()) { AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder")); - audio_i2s.mp3ram = special_malloc(preallocateCodecSize); + audio_i2s_mp3.mp3ram = special_malloc(preallocateCodecSize); } +#endif // USE_I2S_MP3 AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done"); } @@ -693,16 +765,17 @@ int32_t I2SPrepareRx(void) { * Driver features and commands \*********************************************************************************************/ +#if defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) void I2sMp3Task(void *arg) { while (1) { - while (audio_i2s.mp3->isRunning()) { - if (!audio_i2s.mp3->loop()) { - audio_i2s.mp3->stop(); + while (audio_i2s_mp3.mp3->isRunning()) { + if (!audio_i2s_mp3.mp3->loop()) { + audio_i2s_mp3.mp3->stop(); mp3_delete(); audio_i2s.out->stop(); - if (audio_i2s.mp3_task_handle) { - vTaskDelete(audio_i2s.mp3_task_handle); - audio_i2s.mp3_task_handle = 0; + if (audio_i2s_mp3.mp3_task_handle) { + vTaskDelete(audio_i2s_mp3.mp3_task_handle); + audio_i2s_mp3.mp3_task_handle = 0; } //mp3_task_handle=nullptr; } @@ -710,6 +783,7 @@ void I2sMp3Task(void *arg) { } } } +#endif // defined(USE_I2S_MP3) || defined(USE_I2S_WEBRADIO) void I2sStatusCallback(void *cbData, int code, const char *string) { const char *ptr = reinterpret_cast(cbData); @@ -719,10 +793,11 @@ void I2sStatusCallback(void *cbData, int code, const char *string) { //status[sizeof(status)-1] = 0; } +#ifdef USE_I2S_MP3 void I2sMp3Task2(void *arg){ while (1) { - if (audio_i2s.decoder && audio_i2s.decoder->isRunning()) { - if (!audio_i2s.decoder->loop()) { + if (audio_i2s_mp3.decoder && audio_i2s_mp3.decoder->isRunning()) { + if (!audio_i2s_mp3.decoder->loop()) { I2sStopPlaying(); //retryms = millis() + 2000; } @@ -730,33 +805,38 @@ void I2sMp3Task2(void *arg){ } } } +void I2SStopMP3Play(void) { + if (audio_i2s_mp3.mp3_task_handle) { + vTaskDelete(audio_i2s_mp3.mp3_task_handle); + audio_i2s_mp3.mp3_task_handle = nullptr; + } + + if (audio_i2s_mp3.decoder) { + audio_i2s_mp3.decoder->stop(); + delete audio_i2s_mp3.decoder; + audio_i2s_mp3.decoder = NULL; + } +} +#endif // USE_I2S_MP3 void I2sStopPlaying() { - - if (audio_i2s.mp3_task_handle) { - vTaskDelete(audio_i2s.mp3_task_handle); - audio_i2s.mp3_task_handle = nullptr; - } - - if (audio_i2s.decoder) { - audio_i2s.decoder->stop(); - delete audio_i2s.decoder; - audio_i2s.decoder = NULL; - } +#ifdef USE_I2S_MP3 + I2SStopMP3Play(); +#endif // USE_I2S_MP3 #ifdef USE_I2S_WEBRADIO I2sWebRadioStopPlaying(); #endif - I2SAudioPower(false); } +#ifdef USE_I2S_MP3 // Play_mp3 - Play a MP3 file from filesystem // // Returns I2S_error_t int32_t I2SPlayMp3(const char *path) { int32_t i2s_err = I2S_OK; if ((i2s_err = I2SPrepareTx()) != I2S_OK) { return i2s_err; } - if (audio_i2s.decoder || audio_i2s.mp3) return I2S_ERR_DECODER_IN_USE; + if (audio_i2s_mp3.decoder || audio_i2s_mp3.mp3) return I2S_ERR_DECODER_IN_USE; // check if the filename starts with '/', if not add it char fname[64]; @@ -769,44 +849,30 @@ int32_t I2SPlayMp3(const char *path) { I2SAudioPower(true); - audio_i2s.file = new AudioFileSourceFS(*ufsp, fname); + audio_i2s_mp3.file = new AudioFileSourceFS(*ufsp, fname); - audio_i2s.id3 = new AudioFileSourceID3(audio_i2s.file); + audio_i2s_mp3.id3 = new AudioFileSourceID3(audio_i2s_mp3.file); - if (audio_i2s.mp3ram) { - audio_i2s.mp3 = new AudioGeneratorMP3(audio_i2s.mp3ram, preallocateCodecSize); + if (audio_i2s_mp3.mp3ram) { + audio_i2s_mp3.mp3 = new AudioGeneratorMP3(audio_i2s_mp3.mp3ram, preallocateCodecSize); } else { - audio_i2s.mp3 = new AudioGeneratorMP3(); + audio_i2s_mp3.mp3 = new AudioGeneratorMP3(); } - audio_i2s.mp3->begin(audio_i2s.id3, audio_i2s.out); + audio_i2s_mp3.mp3->begin(audio_i2s_mp3.id3, audio_i2s.out); // Always use a task - xTaskCreatePinnedToCore(I2sMp3Task, "MP3", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1); + xTaskCreatePinnedToCore(I2sMp3Task, "MP3", 8192, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1); return I2S_OK; } void mp3_delete(void) { - delete audio_i2s.file; - delete audio_i2s.id3; - delete audio_i2s.mp3; - audio_i2s.mp3=nullptr; - I2SAudioPower(false); -} - -void Say(char *text) { - - if (I2SPrepareTx()) { return; } - - I2SAudioPower(true); - - ESP8266SAM *sam = new ESP8266SAM; - - sam->Say(audio_i2s.out, text); - delete sam; - audio_i2s.out->stopTx(); - + delete audio_i2s_mp3.file; + delete audio_i2s_mp3.id3; + delete audio_i2s_mp3.mp3; + audio_i2s_mp3.mp3=nullptr; I2SAudioPower(false); } +#endif // USE_I2S_MP3 /*********************************************************************************************\ * Commands @@ -819,7 +885,6 @@ void CmndI2SMic(void) { } esp_err_t err = ESP_OK; - if (audio_i2s.decoder || audio_i2s.mp3) return; audio_i2s.in->startRx(); if (audio_i2s.in->getRxRunning()) { uint8_t buf[128]; @@ -836,7 +901,7 @@ void CmndI2SMic(void) { AddLog(LOG_LEVEL_INFO, "I2S: Mic (%i) %*_H", btr, btr, buf); } - // err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1); + // err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s_mp3.mic_task_handle, 1); ResponseCmndDone(); } @@ -851,6 +916,7 @@ void CmndI2SStop(void) { ResponseCmndDone(); } +#ifdef USE_I2S_MP3 void CmndI2SPlay(void) { if (I2SPrepareTx()) { ResponseCmndChar("I2S output not configured"); @@ -883,6 +949,7 @@ void CmndI2SPlay(void) { ResponseCmndChar("Missing filename"); } } +#endif // USE_I2S_MP3 void CmndI2SGain(void) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { @@ -895,12 +962,18 @@ void CmndI2SGain(void) { } void CmndI2SSay(void) { - if (I2SPrepareTx()) { - ResponseCmndChar("I2S output not configured"); - return; - } if (XdrvMailbox.data_len > 0) { - Say(XdrvMailbox.data); + if (I2SPrepareTx()) { + ResponseCmndChar("I2S output not configured"); + return; + } else { + I2SAudioPower(true); + ESP8266SAM sam; + sam.Say(audio_i2s.out, XdrvMailbox.data); + audio_i2s.out->stopTx(); + I2SAudioPower(false); + // end of scope, ESP8266SAM is destroyed + } } ResponseCmndChar(XdrvMailbox.data); } @@ -920,16 +993,16 @@ void CmndI2SMicRec(void) { if (audio_i2s.Settings->sys.mp3_preallocate == 1) { if (XdrvMailbox.data_len > 0) { if (!strncmp(XdrvMailbox.data, "-?", 2)) { - Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur); + Response_P("{\"I2SREC-duration\":%d}", audio_i2s_mp3.recdur); } else { I2sRecordShine(XdrvMailbox.data); ResponseCmndChar(XdrvMailbox.data); } } else { - if (audio_i2s.mic_task_handle) { + if (audio_i2s_mp3.mic_task_handle) { // stop task - audio_i2s.mic_stop = 1; - while (audio_i2s.mic_stop) { + audio_i2s_mp3.mic_stop = 1; + while (audio_i2s_mp3.mic_stop) { delay(1); } ResponseCmndChar_P(PSTR("Stopped")); diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino index f7af2479e..d283abcaa 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic.ino @@ -18,7 +18,7 @@ */ -#ifdef ESP32 +#if ESP32 && (ESP_IDF_VERSION_MAJOR < 5) #if ( (defined(USE_I2S_AUDIO) && defined(USE_I2S_MIC)) || defined(USE_M5STACK_CORE2) || defined(ESP32S3_BOX) ) uint32_t SpeakerMic(uint8_t spkr) { @@ -332,4 +332,4 @@ void Cmd_MicGain(void) { } #endif // USE_I2S_AUDIO -#endif // ESP32 +#endif // ESP32 && (ESP_IDF_VERSION_MAJOR < 5) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic_idf51.ino new file mode 100644 index 000000000..cfc9ba166 --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_1_i2s_mp3mic_idf51.ino @@ -0,0 +1,238 @@ +/* + 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 +#ifdef USE_I2S_AUDIO + +uint32_t SpeakerMic(uint8_t spkr) { +esp_err_t err = ESP_OK; + +// audio_i2s.mode = spkr; + return err; +} + +#ifdef USE_SHINE + +#include +#include + +// 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_mp3.use_stream) { + mp3_out = ufsp->open(audio_i2s_mp3.mic_path, "w"); + if (!mp3_out) { + error = 1; + goto exit; + } + } else { + if (!audio_i2s_mp3.stream_active) { + error = 2; + audio_i2s_mp3.use_stream = 0; + goto exit; + } + audio_i2s_mp3.client.flush(); + audio_i2s_mp3.client.setTimeout(3); + audio_i2s_mp3.client.print("HTTP/1.1 200 OK\r\n" + "Content-Type: audio/mpeg;\r\n\r\n"); + + // Webserver->send(200, "application/octet-stream", ""); + //"Content-Type: audio/mp3;\r\n\r\n"); + } + + 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_mp3.mic_stop) { + uint32_t bytes_read; + i2s_read(audio_i2s.mic_port, (char *)buffer, bytesize, &bytes_read, (100 / portTICK_RATE_MS)); + + if (audio_i2s.mic_gain > 1) { + // set gain + for (uint32_t cnt = 0; cnt < bytes_read / 2; cnt++) { + buffer[cnt] *= audio_i2s.mic_gain; + } + } + ucp = shine_encode_buffer_interleaved(s, buffer, &written); + + if (!audio_i2s_mp3.use_stream) { + bwritten = mp3_out.write(ucp, written); + if (bwritten != written) { + break; + } + } else { + audio_i2s_mp3.client.write((const char*)ucp, written); + + if (!audio_i2s_mp3.client.connected()) { + break; + } + } + audio_i2s_mp3.recdur = TasmotaGlobal.uptime - ctime; + } + + ucp = shine_flush(s, &written); + + if (!audio_i2s_mp3.use_stream) { + mp3_out.write(ucp, written); + } else { + audio_i2s_mp3.client.write((const char*)ucp, written); + } + + +exit: + if (s) { + shine_close(s); + } + if (mp3_out) { + mp3_out.close(); + } + if (buffer) { + free(buffer); + } + + if (audio_i2s_mp3.use_stream) { + audio_i2s_mp3.client.stop(); + } + + SpeakerMic(MODE_SPK); + audio_i2s_mp3.mic_stop = 0; + audio_i2s_mp3.mic_error = error; + AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error); + audio_i2s.mic_task_h = 0; + audio_i2s_mp3.recdur = 0; + audio_i2s_mp3.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_mp3.decoder || audio_i2s_mp3.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_mp3.mic_path, path, sizeof(audio_i2s_mp3.mic_path)); + + audio_i2s_mp3.mic_stop = 0; + + uint32_t stack = 4096; + + audio_i2s_mp3.use_stream = !strcmp(audio_i2s_mp3.mic_path, "stream.mp3"); + + if (audio_i2s_mp3.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_mp3.recdur); + } else { + i2s_record_shine(XdrvMailbox.data); + ResponseCmndChar(XdrvMailbox.data); + } + } else { + if (audio_i2s.mic_task_h) { + // stop task + audio_i2s_mp3.mic_stop = 1; + while (audio_i2s_mp3.mic_stop) { + delay(1); + } + ResponseCmndChar_P(PSTR("Stopped")); + } + } + +} +#endif // USE_SHINE + + +// mic gain in factor not percent +void Cmd_MicGain(void) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 256)) { + if (audio_i2s.in) { + audio_i2s.in->setRxGain(XdrvMailbox.payload); + } + if (audio_i2s.Settings) { + audio_i2s.Settings->rx.gain = XdrvMailbox.payload * 16; + } + I2SSettingsSave(AUDIO_CONFIG_FILENAME); + } + ResponseCmndNumber(audio_i2s.Settings->rx.gain / 16); +} + +#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 index 294a7e865..df28b0e8f 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_7_i2s_webradio_idf51.ino @@ -65,17 +65,17 @@ void Webradio(const char *url) { return; } - if (audio_i2s.decoder || audio_i2s.mp3) return; + // if (audio_i2s_mp3.decoder || audio_i2s_mp3.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()) { + audio_i2s_mp3.decoder = new AudioGeneratorMP3(Audio_webradio.preallocateCodec, preallocateCodecSize); + audio_i2s_mp3.decoder->RegisterStatusCB(I2sStatusCallback, NULL); + audio_i2s_mp3.decoder->begin(Audio_webradio.buff, audio_i2s.out); + if (!audio_i2s_mp3.decoder->isRunning()) { // Serial.printf_P(PSTR("Can't connect to URL")); I2sStopPlaying(); // strcpy_P(status, PSTR("Unable to connect to URL")); @@ -83,7 +83,7 @@ void Webradio(const char *url) { } AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will launch webradio task")); - xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s.mp3_task_handle, 1); + xTaskCreatePinnedToCore(I2sMp3Task2, "MP3-2", 8192, NULL, 3, &audio_i2s_mp3.mp3_task_handle, 1); } #ifdef USE_WEBSERVER @@ -91,7 +91,7 @@ const char HTTP_WEBRADIO[] PROGMEM = "{s}" "I2S_WR-Title" "{m}%s{e}"; void I2sWrShow(bool json) { - if (audio_i2s.decoder) { + if (audio_i2s_mp3.decoder) { if (json) { ResponseAppend_P(PSTR(",\"WebRadio\":{\"Title\":\"%s\"}"), Audio_webradio.wr_title); } else { @@ -104,7 +104,7 @@ void I2sWrShow(bool json) { void CmndI2SWebRadio(void) { if (!audio_i2s.out) return; - if (audio_i2s.decoder) { + if (audio_i2s_mp3.decoder) { I2sStopPlaying(); } if (XdrvMailbox.data_len > 0) {