From 7de25acac0a8ddd171dd6a745bde377fc2d5d4e1 Mon Sep 17 00:00:00 2001
From: s-hadinger <49731213+s-hadinger@users.noreply.github.com>
Date: Thu, 21 Sep 2023 09:00:28 +0200
Subject: [PATCH] Minor refactoring of audio code for Arduino3 (#19559)
---
...e_i2s_audio_lib.cpp => be_i2s_audio_lib.c} | 123 ++----
.../xdrv_42_0_i2s__lib_idf51.ino | 350 ++++++++++++++++
.../xdrv_42_0_i2s_audio_idf51.ino | 387 ++----------------
.../xdrv_52_3_berry_audio.ino | 121 ++++--
4 files changed, 504 insertions(+), 477 deletions(-)
rename lib/libesp32/berry_tasmota/src/{be_i2s_audio_lib.cpp => be_i2s_audio_lib.c} (56%)
create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino
diff --git a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
similarity index 56%
rename from lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp
rename to lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
index 40322c08f..f347e13f4 100644
--- a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.cpp
+++ b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
@@ -9,134 +9,80 @@
#ifdef USE_I2S_AUDIO_BERRY
#include "be_mapping.h"
-#include "AudioOutput.h"
-extern "C" void berry_log_C(const char * berry_buf, ...);
+extern int i2s_output_i2s_init(bvm *vm);
+extern int i2s_output_i2s_deinit(bvm *vm);
+extern int i2s_output_i2s_stop(bvm *vm);
-extern "C" {
- extern int i2s_output_i2s_init(bvm *vm);
- extern int i2s_output_i2s_deinit(bvm *vm);
- extern int i2s_output_i2s_stop(bvm *vm);
+extern int i2s_generator_wav_init(bvm *vm);
+extern int i2s_generator_wav_deinit(bvm *vm);
+extern int i2s_generator_wav_begin(bvm *vm);
+extern int i2s_generator_wav_loop(bvm *vm);
+extern int i2s_generator_wav_stop(bvm *vm);
+extern int i2s_generator_wav_isrunning(bvm *vm);
- extern int i2s_generator_wav_init(bvm *vm);
- extern int i2s_generator_wav_deinit(bvm *vm);
- extern int i2s_generator_wav_begin(bvm *vm);
- extern int i2s_generator_wav_loop(bvm *vm);
- extern int i2s_generator_wav_stop(bvm *vm);
- extern int i2s_generator_wav_isrunning(bvm *vm);
-
- extern int i2s_generator_mp3_init(bvm *vm);
- extern int i2s_generator_mp3_deinit(bvm *vm);
- extern int i2s_generator_mp3_begin(bvm *vm);
- extern int i2s_generator_mp3_loop(bvm *vm);
- extern int i2s_generator_mp3_stop(bvm *vm);
- extern int i2s_generator_mp3_isrunning(bvm *vm);
+extern int i2s_generator_mp3_init(bvm *vm);
+extern int i2s_generator_mp3_deinit(bvm *vm);
+extern int i2s_generator_mp3_begin(bvm *vm);
+extern int i2s_generator_mp3_loop(bvm *vm);
+extern int i2s_generator_mp3_stop(bvm *vm);
+extern int i2s_generator_mp3_isrunning(bvm *vm);
#ifdef USE_UFILESYS
- extern int i2s_file_source_fs_init(bvm *vm);
- extern int i2s_file_source_fs_deinit(bvm *vm);
+extern int i2s_file_source_fs_init(bvm *vm);
+extern int i2s_file_source_fs_deinit(bvm *vm);
#endif // USE_UFILESYS
-}
+
// AudioOutput.set_rate(rate_hz:int) -> bool
-AudioOutput* be_audio_output_init(void) {
- return new AudioOutput();
-}
+extern void* be_audio_output_init(void);
BE_FUNC_CTYPE_DECLARE(be_audio_output_init, "+.p", "");
// AudioOutput.set_rate(rate_hz:int) -> bool
-int be_audio_output_set_rate(AudioOutput* out, int hz) {
- return out->SetRate(hz);
-}
+extern int be_audio_output_set_rate(void* out, int hz);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_rate, "b", ".i");
// AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool
-int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) {
- return out->SetBitsPerSample(bps);
-}
+extern int be_audio_output_set_bits_per_sample(void* out, int bps);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_bits_per_sample, "b", ".i");
// AudioOutput.set_channels(channels:int) -> bool
-int be_audio_output_set_channels(AudioOutput* out, int channels) {
- return out->SetChannels(channels);
-}
+extern int be_audio_output_set_channels(void* out, int channels);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_channels, "b", ".i");
// AudioOutput.set_gain(gain:real) -> bool
-int be_audio_output_set_gain(AudioOutput* out, float gain) {
- return out->SetGain(gain);
-}
+extern int be_audio_output_set_gain(void* out, float gain);
BE_FUNC_CTYPE_DECLARE(be_audio_output_set_gain, "b", ".f");
// AudioOutput.begin() -> bool
-int be_audio_output_begin(AudioOutput* out) {
- return out->begin();
-}
+extern int be_audio_output_begin(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_begin, "b", ".");
// AudioOutput.stop() -> bool
-int be_audio_output_stop(AudioOutput* out) {
- return out->stop();
-}
+extern int be_audio_output_stop(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_stop, "b", ".");
// AudioOutput.flush() -> bool
-void be_audio_output_flush(AudioOutput* out) {
- out->flush();
-}
+extern void be_audio_output_flush(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_flush, "", ".");
// AudioOutput.consume_mono(bytes) -> int
-int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
- int pcm_len = bytes_len / 2;
- int n;
- // berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
- for (n = 0; index + n < pcm_len; n++) {
- int16_t ms[2];
- ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n];
- if (!out->ConsumeSample(ms)) { break; }
- }
- return n;
-}
+extern int be_audio_output_consume_mono(void* out, uint16_t *pcm, int bytes_len, int index);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_mono, "i", ".(bytes)~i");
// AudioOutput.consume_stereo(bytes) -> int
-int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
- int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each
- int n;
- // berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
- for (n = 0; index + n < pcm_len; n++) {
- int16_t ms[2];
- ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n];
- ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1];
- if (!out->ConsumeSample(ms)) { break; }
- }
- return n;
-}
+extern int be_audio_output_consume_stereo(void* out, uint16_t *pcm, int bytes_len, int index);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_stereo, "i", ".(bytes)~i");
// AudioOutput.consume_silence() -> int, push silence frames
-int be_audio_output_consume_silence(AudioOutput* out) {
- int n = 0;
- int16_t ms[2] = {0, 0};
- while (true) {
- if (!out->ConsumeSample(ms)) { break; }
- n++;
- }
- return n;
-}
+extern int be_audio_output_consume_silence(void* out);
BE_FUNC_CTYPE_DECLARE(be_audio_output_consume_silence, "i", ".");
-#include "AudioOutputI2S.h"
-
// AudioOutputI2S.set_lsb_justified(gain:real) -> nil
-int i2s_output_i2s_set_lsb_justified(AudioOutputI2S* out, bbool lsbJustified) {
- return out->SetLsbJustified(lsbJustified);
-}
+extern int i2s_output_i2s_set_lsb_justified(void* out, bbool lsbJustified);
BE_FUNC_CTYPE_DECLARE(i2s_output_i2s_set_lsb_justified, "b", ".b");
-extern "C" {
-
+
#include "be_fixed_be_class_AudioOutput.h"
#include "be_fixed_be_class_AudioOutputI2S.h"
#include "be_fixed_be_class_AudioGenerator.h"
@@ -145,7 +91,6 @@ extern "C" {
#include "be_fixed_be_class_AudioFileSource.h"
#include "be_fixed_be_class_AudioFileSourceFS.h"
-}
/* @const_object_info_begin
class be_class_AudioOutput (scope: global, name: AudioOutput, strings: weak) {
@@ -175,15 +120,9 @@ class be_class_AudioFileSource (scope: global, name: AudioFileSource, strings: w
}
class be_class_AudioOutputI2S (scope: global, name: AudioOutputI2S, super: be_class_AudioOutput, strings: weak) {
- EXTERNAL_I2S, int(AudioOutputI2S::EXTERNAL_I2S)
- INTERNAL_DAC, int(AudioOutputI2S::INTERNAL_DAC)
- INTERNAL_PDM, int(AudioOutputI2S::INTERNAL_PDM)
-
init, func(i2s_output_i2s_init)
deinit, func(i2s_output_i2s_deinit)
stop, func(i2s_output_i2s_stop)
-
- set_lsb_justified, ctype_func(i2s_output_i2s_set_lsb_justified)
}
class be_class_AudioGeneratorWAV (scope: global, name: AudioGeneratorWAV, super: be_class_AudioGenerator, strings: weak) {
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
new file mode 100644
index 000000000..5d6c6a0de
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino
@@ -0,0 +1,350 @@
+/*
+ xdrv_42_0_i2s__lib_idf51.ino - Simplified Audio library
+
+ Copyright (C) 2021 Gerhard Mutz, Theo Arends, Staars, Stephan Hadinger
+
+ 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
+
+#include "driver/i2s_std.h"
+#include "driver/i2s_pdm.h"
+#include "driver/gpio.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
+
+#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
+\*********************************************************************************************/
+
+typedef struct{
+ struct{
+ uint32_t version : 8 = 0;
+
+ // runtime options, will be saved but ignored on setting read
+ uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
+ uint32_t tx : 1 = 0; // depends on GPIO setting
+ uint32_t rx : 1 = 0; // depends on GPIO setting
+ } sys;
+ struct {
+ uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2
+ 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 volume : 8 = 10; // bit 8-15
+ uint32_t spare02 : 16; // bit 16-31
+ } tx;
+ struct {
+ struct{
+ uint16_t sample_rate = 32000;
+ uint8_t gain = 30;
+ uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2
+
+ uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3
+ uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both
+ uint8_t codec : 1 = 0; // unused for now
+ uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM
+ };
+ } rx;
+} tI2SSettings;
+
+typedef union {
+ uint8_t data;
+ struct {
+ uint8_t master : 1;
+ uint8_t enabled : 1;
+ uint8_t swap_mic : 1;
+ uint8_t mode : 2;
+ };
+} BRIDGE_MODE;
+
+class TasmotaAudioOutputI2S;
+
+struct AUDIO_I2S_t {
+ tI2SSettings *Settings;
+
+ i2s_chan_handle_t rx_handle = nullptr;
+
+ AudioGeneratorMP3 *mp3 = nullptr;
+ AudioFileSourceFS *file;
+
+ TasmotaAudioOutputI2S *out;
+
+ AudioFileSourceID3 *id3;
+ 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;
+
+ uint32_t mic_size;
+ uint8_t *mic_buff;
+ char mic_path[32];
+ File fwp;
+ 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;
+
+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
+\*********************************************************************************************/
+
+class TasmotaAudioOutputI2S : public AudioOutput
+{
+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;
+ }
+
+ ~TasmotaAudioOutputI2S() {
+ if(i2sOn){
+ this->stop();
+ i2s_del_channel(tx_chan);
+ }
+ }
+
+ // ------------------------------------------------------------------------------------------
+ // Setters for configuration parameters
+ // ------------------------------------------------------------------------------------------
+ bool SetBitsPerSample(int bits) {
+ if ( (bits != 16) && (bits != 8) ) { return false; }
+ this->bps = bits;
+ return true;
+ }
+
+ bool SetChannels(int 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; }
+ this->hertz = hz;
+ if(i2sOn){
+ 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();
+ }
+
+ 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;
+
+ i2s_chan_handle_t tx_chan;
+ bool tx_is_enabled;
+
+};
+
+
+// ------------------------------------------------------------------------------------------
+// Methods
+// ------------------------------------------------------------------------------------------
+bool TasmotaAudioOutputI2S::begin() {
+ if (tx_is_enabled) { return true; }
+ if (!i2sOn) {
+ if (audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1) {
+ this->startI2SChannel();
+ }
+ }
+ 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;
+ 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;
+ }
+ tx_is_enabled = false;
+ return true;
+}
+
+bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
+ if (!tx_is_enabled) { return false; }
+
+ int16_t ms[2];
+ ms[0] = sample[0];
+ ms[1] = sample[1];
+ MakeSampleStereo16(ms);
+
+ 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);
+ }
+
+ size_t i2s_bytes_written;
+ i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
+ return i2s_bytes_written;
+}
+
+bool TasmotaAudioOutputI2S::startI2SChannel(void) {
+ gpio_num_t _DIN = I2S_GPIO_UNUSED;
+
+ 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);
+ } else{
+ i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
+ }
+
+ 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);
+ 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;
+}
+
+int TasmotaAudioOutputI2S::updateClockConfig(void) {
+ 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){
+ 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);
+ return result;
+}
+
+#endif // USE_I2S_AUDIO
+#endif //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 355928874..e31d6b40f 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
@@ -22,327 +22,52 @@
#define XDRV_42 42
-#include "driver/i2s_std.h"
-#include "driver/i2s_pdm.h"
-#include "driver/gpio.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
-
-#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
+ * Commands definitions
\*********************************************************************************************/
-typedef struct{
- struct{
- uint32_t version : 8 = 0;
-
- // runtime options, will be saved but ignored on setting read
- uint32_t duplex : 1 = 0; // depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
- uint32_t tx : 1 = 0; // depends on GPIO setting
- uint32_t rx : 1 = 0; // depends on GPIO setting
- } sys;
- struct {
- uint32_t mode : 2 = 0; // bit 0+1 STD = 0, PDM = 1, TDM = 2
- 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 volume : 8 = 10; // bit 8-15
- uint32_t spare02 : 16; // bit 16-31
- } tx;
- struct {
- struct{
- uint16_t sample_rate = 32000;
- uint8_t gain = 30;
- uint8_t mode = 0; //STD = 0, PDM = 1, TDM = 2
-
- uint8_t slot_mask : 2 = 1; // left = 1 /right = 2 /both = 3
- uint8_t slot_mode : 1 = 0; // mono/stereo - 1 is added for both
- uint8_t codec : 1 = 0; // unused for now
- uint8_t mp3_encoder : 1 = 1; // will be ignored without PS-RAM
- };
- } rx;
-} tI2SSettings;
-
-typedef union {
- uint8_t data;
- struct {
- uint8_t master : 1;
- uint8_t enabled : 1;
- uint8_t swap_mic : 1;
- uint8_t mode : 2;
- };
-} BRIDGE_MODE;
-
-class TasmotaAudioOutputI2S;
-
-struct AUDIO_I2S_t {
- tI2SSettings *Settings;
-
- i2s_chan_handle_t rx_handle = nullptr;
-
- AudioGeneratorMP3 *mp3 = nullptr;
- AudioFileSourceFS *file;
-
- TasmotaAudioOutputI2S *out;
-
- AudioFileSourceID3 *id3;
- 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;
-
- uint32_t mic_size;
- uint8_t *mic_buff;
- char mic_path[32];
- File fwp;
- 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;
-
-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
-\*********************************************************************************************/
-
-class TasmotaAudioOutputI2S : public AudioOutput
-{
- public:
- TasmotaAudioOutputI2S(){
- hertz = 16000;
- i2sOn = false;
- bps = I2S_DATA_BIT_WIDTH_16BIT;
- mono = (audio_i2s.Settings->tx.mono == 1);
- channels = mono ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
- output_mode = EXTERNAL_I2S;
- tx_is_enabled = false;
- }
-
- ~TasmotaAudioOutputI2S(){
- if(i2sOn){
- this->stop();
- i2s_del_channel(tx_chan);
- }
- }
-
- bool SetBitsPerSample(int bits)
- {
- if ( (bits != 16) && (bits != 8) ) return false;
- this->bps = bits;
- return true;
- }
-
- bool SetChannels(int 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;
- this->hertz = hz;
- if(i2sOn){
- int result = updateClockConfig();
- AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i - %i"),hz, result);
- }
- return true;
- }
-
- bool SetPinout(){
- return this->startI2SChannel();
- }
-
- bool begin(){
- if(tx_is_enabled) return true;
- if(i2sOn == false){
- if(audio_i2s.Settings->sys.duplex == 0 && audio_i2s.Settings->sys.rx == 1){
- this->startI2SChannel();
- }
- }
- int result = i2s_channel_enable(tx_chan);
- if(result != 0){
- AddLog(LOG_LEVEL_INFO,PSTR("I2S: Could not enable i2s_channel: %i"), result);
- return false;
- }
- tx_is_enabled = true;
- return true;
- }
- bool 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;
- }
- tx_is_enabled = false;
- return true;
- }
-
- bool ConsumeSample(int16_t sample[2])
- {
- if (!tx_is_enabled)
- return false;
-
- int16_t ms[2];
-
- ms[0] = sample[0];
- ms[1] = sample[1];
- MakeSampleStereo16( ms );
-
- 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);
- }
-
- size_t i2s_bytes_written;
- i2s_channel_write(tx_chan, (const void*)&s32, sizeof(uint32_t), &i2s_bytes_written, 0);
- return i2s_bytes_written;
- }
-
- private:
- enum : int { EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2 };
- int output_mode;
- bool i2sOn;
- bool mono;
-
- i2s_chan_handle_t tx_chan;
- bool tx_is_enabled;
-
- bool startI2SChannel(){
- gpio_num_t _DIN = I2S_GPIO_UNUSED;
-
- 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);
- }
- else{
- i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL);
- }
-
- 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, PSTR("I2S: TX channel with %i bit width on %i channels initialized"),bps, channels);
- if(audio_i2s.Settings->sys.duplex == 1){
- i2s_channel_init_std_mode(audio_i2s.rx_handle, &tx_std_cfg);
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel added in full duplex mode"));
- }
- return i2sOn;
- }
-
- int updateClockConfig(){
- 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){
- clk_cfg.clk_src = I2S_CLK_SRC_APLL;
- }
+const char kI2SAudio_Commands[] PROGMEM = "I2S|"
+ "Gain|Play|WR|Rec|MGain|Stop"
+#ifdef USE_I2S_SAY
+ "|Say"
+#endif // USE_I2S_SAY
+#ifdef USE_I2S_SAY_TIME
+ "|Time"
+#endif // USE_I2S_SAY_TIME
+#ifdef USE_I2S_RTTTL
+ "|Rtttl"
#endif
- int result = i2s_channel_reconfig_std_clock(tx_chan, &clk_cfg );
- if(tx_is_enabled) i2s_channel_enable(tx_chan);
- return result;
- }
+#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
+ "|Stream"
+#endif // MP3_MIC_STREAM
+#ifdef I2S_BRIDGE
+ "|Bridge"
+#endif // I2S_BRIDGE
+;
+
+void (* const I2SAudio_Command[])(void) PROGMEM = {
+ &CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
+#ifdef USE_I2S_SAY
+ &CmndI2SSay,
+#endif // USE_I2S_SAY
+#ifdef USE_I2S_SAY_TIME
+ &Cmd_Time,
+#endif // USE_I2S_SAY_TIME
+#ifdef USE_I2S_RTTTL
+ &CmndI2SI2SRtttl,
+#endif
+#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
+ &CmndI2SMP3Stream,
+#endif // MP3_MIC_STREAM
+#ifdef I2S_BRIDGE
+ &CmndI2SI2SBridge,
+#endif // I2S_BRIDGE
};
/*********************************************************************************************\
* microphone related functions
\*********************************************************************************************/
-
uint32_t I2sMicInit(uint8_t enable) {
esp_err_t err = ESP_OK;
i2s_slot_mode_t slot_mode = (audio_i2s.Settings->rx.slot_mode == 0) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
@@ -901,46 +626,6 @@ void Say(char *text) {
* Commands
\*********************************************************************************************/
-const char kI2SAudio_Commands[] PROGMEM = "I2S|"
- "Gain|Play|WR|Rec|MGain|Stop"
-#ifdef USE_I2S_SAY
- "|Say"
-#ifdef USE_I2S_SAY_TIME
- "|Time"
-#endif // USE_I2S_SAY_TIME
-#endif // USE_I2S_SAY
-
-#ifdef USE_I2S_RTTTL
- "|Rtttl"
-#endif
-#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
- "|Stream"
-#endif // MP3_MIC_STREAM
-#ifdef I2S_BRIDGE
- "|Bridge"
-#endif // I2S_BRIDGE
-;
-
-void (* const I2SAudio_Command[])(void) PROGMEM = {
- &CmndI2SGain, &CmndI2SPlay, &CmndI2SWebRadio, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
-#ifdef USE_I2S_SAY
- &CmndI2SSay,
-#ifdef USE_I2S_SAY_TIME
- &Cmd_Time,
-#endif // USE_I2S_SAY_TIME
-#endif // USE_I2S_SAY
-
-#ifdef USE_I2S_RTTTL
- &CmndI2SI2SRtttl,
-#endif
-#if defined(USE_SHINE) && defined(MP3_MIC_STREAM)
- &CmndI2SMP3Stream,
-#endif // MP3_MIC_STREAM
-#ifdef I2S_BRIDGE
- &CmndI2SI2SBridge,
-#endif // I2S_BRIDGE
-};
-
void CmndI2SStop(void) {
I2sStopPlaying();
ResponseCmndDone();
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino
index 06957bced..932da058f 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino
@@ -22,15 +22,15 @@
#ifdef USE_I2S_AUDIO_BERRY
-// #include "AudioFileSourceSPIFFS.h"
-// #include "AudioFileSourceID3.h"
-#include "AudioOutputI2S.h"
#include "AudioGeneratorWAV.h"
#include "AudioGeneratorMP3.h"
#include "AudioFileSourceFS.h"
#include
+#if ESP_IDF_VERSION_MAJOR < 5
+ #error "USE_I2S_AUDIO_BERRY is only supported for ESP-IDF 5.1 or later"
+#endif
/*********************************************************************************************\
* AudioOutput class
@@ -38,44 +38,97 @@
\*********************************************************************************************/
extern "C" {
+ // AudioOutput.set_rate(rate_hz:int) -> bool
+ void* be_audio_output_init(void) {
+ return new AudioOutput();
+ }
+ // AudioOutput.set_rate(rate_hz:int) -> bool
+ int be_audio_output_set_rate(AudioOutput* out, int hz) {
+ return out->SetRate(hz);
+ }
+
+ // AudioOutput.set_bits_per_sample(bits_per_sample:int) -> bool
+ int be_audio_output_set_bits_per_sample(AudioOutput* out, int bps) {
+ return out->SetBitsPerSample(bps);
+ }
+
+ // AudioOutput.set_channels(channels:int) -> bool
+ int be_audio_output_set_channels(AudioOutput* out, int channels) {
+ return out->SetChannels(channels);
+ }
+
+ // AudioOutput.set_gain(gain:real) -> bool
+ int be_audio_output_set_gain(AudioOutput* out, float gain) {
+ return out->SetGain(gain);
+ }
+
+ // AudioOutput.begin() -> bool
+ int be_audio_output_begin(AudioOutput* out) {
+ return out->begin();
+ }
+
+ // AudioOutput.stop() -> bool
+ int be_audio_output_stop(AudioOutput* out) {
+ return out->stop();
+ }
+
+ // AudioOutput.flush() -> bool
+ void be_audio_output_flush(AudioOutput* out) {
+ out->flush();
+ }
+
+ // AudioOutput.consume_mono(bytes) -> int
+ int be_audio_output_consume_mono(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
+ int pcm_len = bytes_len / 2;
+ int n;
+ // berry_log_C("be_audio_output_consume_mono_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
+ for (n = 0; index + n < pcm_len; n++) {
+ int16_t ms[2];
+ ms[AudioOutput::LEFTCHANNEL] = ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n];
+ if (!out->ConsumeSample(ms)) { break; }
+ }
+ return n;
+ }
+
+ // AudioOutput.consume_stereo(bytes) -> int
+ int be_audio_output_consume_stereo(AudioOutput* out, uint16_t *pcm, int bytes_len, int index) {
+ int pcm_len = bytes_len / 4; // 2 samples LEFT+RIGHT of 2 bytes each
+ int n;
+ // berry_log_C("be_audio_output_consume_stereo_ntv out=%p pcm=%p bytes_len=%i index=%i", out, pcm, bytes_len, index);
+ for (n = 0; index + n < pcm_len; n++) {
+ int16_t ms[2];
+ ms[AudioOutput::LEFTCHANNEL] = pcm[index + n + n];
+ ms[AudioOutput::RIGHTCHANNEL] = pcm[index + n + n + 1];
+ if (!out->ConsumeSample(ms)) { break; }
+ }
+ return n;
+ }
+
+ // AudioOutput.consume_silence() -> int, push silence frames
+ int be_audio_output_consume_silence(AudioOutput* out) {
+ int n = 0;
+ int16_t ms[2] = {0, 0};
+ while (true) {
+ if (!out->ConsumeSample(ms)) { break; }
+ n++;
+ }
+ return n;
+ }
+
//
- // AudioOutputI2S(bclkPin: int, wclkPin: int, doutPin: int[, port:int, dmabuf:int, mode: int])
+ // AudioOutputI2S()
//
int i2s_output_i2s_init(bvm *vm) {
- int argc = be_top(vm);
- if (argc > 3) {
- int bclkPin = be_toint(vm, 2);
- int wclkPin = be_toint(vm, 3);
- int doutPin = be_toint(vm, 4);
- int port = 0;
- if (argc > 4) {
- port = be_toint(vm, 5);
- }
- int dma_buf_count = 8; // number of dma buffers of 64 bytes
- if (argc > 5) {
- dma_buf_count = be_toint(vm, 6);
- }
- int mode = 0; // EXTERNAL_I2S
- if (argc > 6) {
- mode = be_toint(vm, 7);
- }
- // AudioOutputI2S(int port=0, int output_mode=EXTERNAL_I2S, int dma_buf_count = 8, int use_apll=APLL_DISABLE);
- AudioOutputI2S * audio = new AudioOutputI2S(port, mode, dma_buf_count);
- if (0 == mode) {
- audio->SetPinout(bclkPin, wclkPin, doutPin); // return value has no useful information for us
- }
- be_pushcomptr(vm, (void*) audio);
- be_setmember(vm, 1, ".p");
- be_return_nil(vm);
- }
-
- be_raise(vm, kTypeError, nullptr);
+ TasmotaAudioOutputI2S * audio = new TasmotaAudioOutputI2S();
+ be_pushcomptr(vm, (void*) audio);
+ be_setmember(vm, 1, ".p");
+ be_return_nil(vm);
}
int i2s_output_i2s_deinit(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
- AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1);
+ TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
if (audio) {
delete audio;
// clear
@@ -89,7 +142,7 @@ extern "C" {
int i2s_output_i2s_stop(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
- AudioOutputI2S * audio = (AudioOutputI2S *) be_tocomptr(vm, -1);
+ TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
if (audio) {
audio->stop();
}