From 795a194d6552a6f799cad87883976cf083723d8e Mon Sep 17 00:00:00 2001
From: s-hadinger <49731213+s-hadinger@users.noreply.github.com>
Date: Mon, 2 Oct 2023 09:18:53 +0200
Subject: [PATCH] Audio prepare for Arduino3 (#19637)
---
.../berry_tasmota/src/be_i2s_audio_lib.c | 40 ++
.../xdrv_42_0_i2s_0_config_idf51.ino | 168 +++++
.../xdrv_42_0_i2s_0_lib_idf51.ino | 603 ++++++++++++++++++
.../xdrv_42_0_i2s__lib_idf51.ino | 428 -------------
.../xdrv_42_0_i2s_audio_idf51.ino | 461 +++++++------
.../xdrv_52_3_berry_audio.ino | 108 +++-
.../xdrv_52_3_berry_udp.ino | 2 +-
7 files changed, 1189 insertions(+), 621 deletions(-)
create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino
create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino
delete 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.c b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
index 081f56868..3a3a8759d 100644
--- a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
+++ b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c
@@ -97,6 +97,35 @@ BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_init, "+.p", "");
extern void* be_audio_input_i2s_deinit(void* instance);
BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_deinit, "", ".");
+// AudioInputI2S.begin() -> bool
+extern int be_audio_input_i2s_begin(bvm *vm, void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_begin, "b", "@.");
+
+// AudioInputI2S.stop() -> bool
+extern int be_audio_input_i2s_stop(void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_stop, "b", ".");
+
+extern int be_audio_input_i2s_read_bytes(bvm *vm);
+
+// AudioInputI2S.set_gain(gain:real) -> bool
+extern int be_audio_input_set_gain(void* in, float gain);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_set_gain, "b", ".f");
+
+// AudioInputI2S.get_rate() -> int
+extern int be_audio_input_i2s_get_rate(void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_rate, "i", ".");
+
+// AudioInputI2S.get_bits_per_sample() -> int
+extern int be_audio_input_i2s_get_bits_per_sample(void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_bits_per_sample, "i", ".");
+
+// AudioInputI2S.get_channels() -> int
+extern int be_audio_input_i2s_get_channels(void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_channels, "i", ".");
+
+// AudioInputI2S.get_gain() -> real
+extern float be_audio_input_i2s_get_gain(void* in);
+BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_gain, "f", ".");
#include "be_fixed_be_class_AudioOutputI2S.h"
#include "be_fixed_be_class_AudioGenerator.h"
@@ -162,6 +191,17 @@ class be_class_AudioInputI2S (scope: global, name: AudioInputI2S, strings: weak)
.p, var
init, ctype_func(be_audio_input_i2s_init)
deinit, ctype_func(be_audio_input_i2s_deinit)
+
+ begin, ctype_func(be_audio_input_i2s_begin)
+ stop, ctype_func(be_audio_input_i2s_stop)
+ read_bytes, func(be_audio_input_i2s_read_bytes)
+
+ get_rate, ctype_func(be_audio_input_i2s_get_rate)
+ get_bits_per_sample, ctype_func(be_audio_input_i2s_get_bits_per_sample)
+ get_channels, ctype_func(be_audio_input_i2s_get_channels)
+ get_gain, ctype_func(be_audio_input_i2s_get_gain)
+
+ set_gain, ctype_func(be_audio_input_set_gain)
}
@const_object_info_end */
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
new file mode 100644
index 000000000..f71bb3b15
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino
@@ -0,0 +1,168 @@
+/*
+ xdrv_42_0_i2s_0_config_idf51.ino - Simplified Audio library, general configuration
+
+ 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 "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
+\*********************************************************************************************/
+
+// I2S communication mode
+enum : int8_t {
+ I2S_MODE_STD = 0, // I2S mode standard
+ I2S_MODE_PDM = 1, // I2S mode PDM
+ I2S_MODE_TDM = 2, // I2S mode TDM
+ I2S_MODE_DAC = 3, // Using internal DAC - only available on ESP32
+};
+
+// I2S slot mask (left, right, both)
+enum : int8_t {
+ I2S_SLOT_NOCHANGE = 0, // don't change default
+ I2S_SLOT_LEFT = 1, // left
+ I2S_SLOT_RIGHT = 2, // right
+ I2S_SLOT_BOTH = 3, // both
+};
+
+// I2S slot configuration
+enum : int8_t {
+ I2S_SLOT_MSB = 0, // MSB
+ I2S_SLOT_PCM = 1, // PCM
+ I2S_SLOT_PHILIPS = 2, // Philips
+};
+
+#define I2S_SLOTS 2
+
+typedef struct{
+ struct{
+ uint8_t version = 0; // B00
+
+ // runtime options, will be saved but ignored on setting read
+ bool duplex = 0; // B01 - depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
+ bool tx = 0; // B02 - depends on GPIO setting
+ bool rx = 0; // B03 - depends on GPIO setting
+ bool exclusive = 0; // B04 - depends on GPIO setting, if WS is shared between 2 ports, drivers needs to be reinstalled before being used (Yuck... but we don't have a choice)
+
+ bool mclk_inv[I2S_SLOTS] = {0}; // B05-06 - invert mclk
+ bool bclk_inv[I2S_SLOTS] = {0}; // B07-08 - invert bclk
+ bool ws_inv[I2S_SLOTS] = {0}; // B09-0A - invert ws
+ uint8_t spare[5]; // B0B-0F
+ } sys;
+ struct {
+ uint32_t sample_rate = 16000; // B00-03
+ uint8_t gain = 10; // B04 - was `volume`
+ uint8_t mode = I2S_MODE_STD; // B05 - I2S mode standard, PDM, TDM, DAC
+ uint8_t slot_mask = I2S_SLOT_NOCHANGE;// B06 - slot mask
+ uint8_t slot_config = I2S_SLOT_MSB;// B07 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
+ uint8_t channels = 2; // B08 - mono/stereo - 1 is added for both
+ bool apll = 1; // B09 - will be ignored on unsupported SOC's
+ // device specific
+ uint8_t mp3_preallocate = 0; // B0A - preallocate MP3 buffer for mp3 playing
+ uint8_t codec = 0; // B0B - S3 box only, unused for now
+ uint8_t spare[4]; // B0C-0F
+ } tx;
+ struct {
+ uint32_t sample_rate = 16000; // B00-03
+ uint8_t gain = 30; // B04
+ uint8_t mode = I2S_MODE_PDM; // B05 - I2S mode standard, PDM, TDM, DAC
+ uint8_t slot_mask = I2S_SLOT_NOCHANGE;// B06 - slot mask
+ uint8_t slot_config = I2S_SLOT_MSB;// B07 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
+ uint8_t channels = 1; // B08 - mono/stereo - 1 is added for both
+ bool apll = 1; // B09 - will be ignored on unsupported SOC's
+ // device specific
+ uint8_t codec = 0; // B0A - S3 box only, unused for now
+ uint8_t mp3_preallocate = 0; // B0B - will be ignored without PS-RAM
+ uint8_t spare[4]; // B0C-0F
+ } 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 TasmotaI2S;
+
+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
+#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
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
new file mode 100644
index 000000000..633048776
--- /dev/null
+++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino
@@ -0,0 +1,603 @@
+/*
+ xdrv_42_0_i2s_0_lib_idf51.ino - Simplified Audio library, core class
+
+ 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
+
+/*********************************************************************************************\
+ * Driver Settings in memory
+\*********************************************************************************************/
+
+/*********************************************************************************************\
+ * Reminder of esp_err codes
+ *
+ * #define ESP_OK 0 - esp_err_t value indicating success (no error)
+ * #define ESP_FAIL -1 - Generic esp_err_t code indicating failure
+ *
+ * #define ESP_ERR_NO_MEM 0x101 257 - Out of memory
+ * #define ESP_ERR_INVALID_ARG 0x102 258 - Invalid argument
+ * #define ESP_ERR_INVALID_STATE 0x103 259 - Invalid state
+ * #define ESP_ERR_INVALID_SIZE 0x104 260 - Invalid size
+ * #define ESP_ERR_NOT_FOUND 0x105 261 - Requested resource not found
+ * #define ESP_ERR_NOT_SUPPORTED 0x106 262 - Operation or feature not supported
+ * #define ESP_ERR_TIMEOUT 0x107 263 - Operation timed out
+ * #define ESP_ERR_INVALID_RESPONSE 0x108 264 - Received response was invalid
+ * #define ESP_ERR_INVALID_CRC 0x109 265 - CRC or checksum was invalid
+ * #define ESP_ERR_INVALID_VERSION 0x10A 266 - Version was invalid
+ * #define ESP_ERR_INVALID_MAC 0x10B 267 - MAC address was invalid
+ * #define ESP_ERR_NOT_FINISHED 0x10C 268 - Operation has not fully completed
+\*********************************************************************************************/
+
+/*********************************************************************************************\
+ * This is the central class to acccess I2S in (rx) or out (tx)
+ *
+ * It inherits from AudioOutput so it can be used as output instance for ESP8266Audio library
+ *
+ * It also supports microphone input
+ *
+\*********************************************************************************************/
+
+class TasmotaI2S : public AudioOutput
+{
+public:
+
+ // Constructor takes no parameter, everything is configured from template and config file
+ TasmotaI2S() {
+ // set some defaults
+ hertz = 16000;
+ bps = I2S_DATA_BIT_WIDTH_16BIT;
+ channels = 2;
+ gainF2P6 = 32; // equivalent of 0.5
+ }
+
+ ~TasmotaI2S() {
+ this->stop();
+ }
+
+ // Settings
+ void setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, int32_t din,
+ bool mclk_inv = false, bool bclk_inv = false, bool ws_inv = false, bool apll = false);
+
+ void setSlotConfig(i2s_port_t i2s_port, uint8_t tx_slot_config, uint8_t rx_slot_config,
+ uint8_t tx_slot_mask, uint8_t rx_slot_mask) {
+ _i2s_port = i2s_port;
+ _tx_slot_config = tx_slot_config;
+ _rx_slot_config = rx_slot_config;
+ }
+ void setRxFreq(uint16_t freq) { _rx_freq = freq; }
+
+ // ------------------------------------------------------------------------------------------
+ // Setters for configuration parameters
+ //
+ // TODO: not sure we still need them since all this should be set at initialiation
+ // ------------------------------------------------------------------------------------------
+ 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;
+ }
+
+ 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;
+ }
+
+ virtual bool SetRate(int hz) {
+ AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i was %i on=%i"), hz, this->hertz, _i2s_on);
+ if (hz == (int) this->hertz) { return true; }
+ this->hertz = hz;
+ if (_i2s_on) {
+ int result = updateClockConfig();
+ }
+ return true;
+ }
+
+ virtual bool SetGain(float f) {
+ AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetGain: %_f"), &f);
+ return AudioOutput::SetGain(f);
+ }
+
+ // ------------------------------------------------------------------------------------------
+ // Getters
+ inline bool isDuplex(void) const { return _tx_configured && _rx_configured; }
+
+ inline bool getExclusive(void) const {return _exclusive; }
+ inline uint8_t getTxMode(void) const { return _tx_mode; }
+ 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; }
+
+ inline uint8_t getRxMode(void) const { return _rx_mode; }
+ inline uint8_t getRxBitsPerSample(void) const { return 16; } // TODO - hardcoded to 16 bits for recording
+ inline uint16_t getRxRate(void) const { return _rx_freq; }
+ inline uint8_t getRxChannels(void) const { return _rx_channels; }
+ inline float getRxGain(void) const { return 1.0f; } // TODO - hardcoded to 1.0 for recording
+ inline bool getRxRunning(void) const { return _rx_running; }
+ inline i2s_chan_handle_t getRxHandle(void) const { return _rx_handle; }
+
+ // ------------------------------------------------------------------------------------------
+ // Setters
+ inline void setExclusive(bool exclusive) { _exclusive = exclusive; }
+ inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
+ 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; }
+ inline void setRxChannels(uint8_t channels) { _rx_channels = channels; }
+ inline void setRxRunning(bool running) { _rx_running = running; }
+
+ // ------------------------------------------------------------------------------------------
+ // 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(bool tx, bool rx);
+ int updateClockConfig(void);
+
+ // 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);
+
+ // ------------------------------------------------------------------------------------------
+ // Microphone related methods
+ uint32_t I2sMicInit(void);
+ void I2sMicDeinit(void);
+
+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
+ bool _exclusive = false; // in exclusive mode, stopping this instance needs to uninstall driver, and reinstall for next use
+ i2s_port_t _i2s_port = I2S_NUM_AUTO; // I2S port, I2S_NUM_0/I2S_NUM_1/I2S_NUM_AUTO
+
+ // local copy of useful settings for audio
+ // TX
+ bool _tx_configured = false; // true = configured, false = not configured
+ uint8_t _tx_mode = I2S_MODE_STD; // I2S_MODE_STD / I2S_MODE_PDM / I2S_MODE_TDM / I2S_MODE_DAC
+ uint8_t _tx_slot_mask = I2S_SLOT_NOCHANGE;
+ bool _tx_running = false; // true = enabled, false = disabled
+ // uint8_t _tx_channels = 2; // number of channels, 1 = mono, 2 = stereo -- `channels`
+ i2s_chan_handle_t _tx_handle = nullptr; // I2S channel handle, automatically computed
+ uint8_t _tx_slot_config = I2S_SLOT_MSB;// I2S slot configuration
+
+ // RX
+ bool _rx_configured = false; // true = configured, false = not configured
+ uint8_t _rx_mode = I2S_MODE_STD; // I2S_MODE_STD / I2S_MODE_PDM / I2S_MODE_TDM / I2S_MODE_DAC
+ uint8_t _rx_slot_mask = I2S_SLOT_NOCHANGE;
+ bool _rx_running = false; // true = enabled, false = disabled
+ uint8_t _rx_channels = 2; // number of channels, 1 = mono, 2 = stereo
+ i2s_chan_handle_t _rx_handle = nullptr; // I2S channel handle, automatically computed
+ uint8_t _rx_slot_config = I2S_SLOT_MSB;// I2S slot configuration
+ uint16_t _rx_freq = 16000; // I2S Rx sampling frequency in Hz
+
+ // 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
+ bool _apll = false; // use APLL instead of PLL
+};
+
+
+// ------------------------------------------------------------------------------------------
+// Methods
+// ------------------------------------------------------------------------------------------
+
+void TasmotaI2S::setPinout(int32_t bclk, int32_t ws, int32_t dout, int32_t mclk, int32_t din,
+ bool mclk_inv, bool bclk_inv, bool ws_inv, bool apll) {
+ _gpio_mclk = (gpio_num_t) mclk;
+ _gpio_bclk = (gpio_num_t) bclk;
+ _gpio_ws = (gpio_num_t) ws;
+ _gpio_dout = (gpio_num_t) dout;
+ _gpio_din = (gpio_num_t) din;
+ _gpio_mclk_inv = mclk_inv;
+ _gpio_bclk_inv = bclk_inv;
+ _gpio_ws_inv = ws_inv;
+ _apll = apll;
+
+ _tx_configured = (_gpio_dout != GPIO_NUM_NC);
+ _rx_configured = (_gpio_din != GPIO_NUM_NC);
+
+ AddLog(LOG_LEVEL_DEBUG, "I2S: setPinout: gpios[%i,%i,%i,%i,%i] inv[%i,%i,%i] apll:%i _tx_configured:%i _rx_configured:%i",
+ _gpio_mclk, _gpio_bclk, _gpio_ws, _gpio_dout, _gpio_din,
+ _gpio_mclk_inv, _gpio_bclk_inv, _gpio_ws_inv, _apll,
+ _tx_configured, _rx_configured);
+}
+
+bool TasmotaI2S::begin() {
+ AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running:%i _i2s_on:%i", _tx_running, _i2s_on);
+ if (_tx_running) { return true; }
+ // if (!_i2s_on) {
+ // if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ?
+ // this->startI2SChannel();
+ // }
+ // }
+ int result = i2s_channel_enable(_tx_handle);
+ if (result != 0){
+ AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
+ return false;
+ }
+ _tx_running = true;
+ AddLog(LOG_LEVEL_DEBUG, "I2S: begin _tx_running succeeded");
+ return true;
+}
+
+bool TasmotaI2S::stop() {
+ i2s_channel_disable(_tx_handle);
+ if ((!_rx_configured || !_tx_configured) && _rx_configured) { // not duplex -- TODO ?
+ i2s_del_channel(_tx_handle);
+ _i2s_on = false;
+ AddLog(LOG_LEVEL_INFO, "I2S: stop: I2S channel disabled");
+ }
+ _tx_running = false;
+ return true;
+}
+
+bool TasmotaI2S::ConsumeSample(int16_t sample[2]) {
+ return consumeSamples(sample, 1);
+}
+
+int32_t TasmotaI2S::consumeSamples(int16_t *samples, size_t count) {
+ if (!_tx_running) { return -1; }
+ if (count == 0) { return 0; }
+ if (count > 128) { count = 128; }
+
+ 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 (channels == 1) { // if mono, average the two samples
+ // 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 == I2S_MODE_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;
+ }
+
+ // 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);
+ 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 TasmotaI2S::startI2SChannel(bool tx, bool rx) {
+ if (!tx) { _tx_configured = false; }
+ if (!rx) { _rx_configured = false; }
+ if (!_tx_configured && !_rx_configured) { return false; } // nothing configured
+
+ esp_err_t err = ESP_OK;
+ gpio_num_t _DIN = I2S_GPIO_UNUSED; // no input pin by default
+
+ if (_tx_configured) {
+ // 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);
+
+ 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);
+
+ if (_tx_configured && _rx_configured) {
+ _DIN = (gpio_num_t)_gpio_din;
+ err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, &audio_i2s.out->_rx_handle);
+ } else{
+ err = i2s_new_channel(&tx_chan_cfg, &_tx_handle, NULL);
+ }
+
+ AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_new_channel err:%i", err);
+
+ // 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_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 (_tx_slot_config == I2S_SLOT_PCM) { // 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 (_tx_slot_config == I2S_SLOT_PHILIPS) { // PHILIPS
+ 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; }
+
+ // AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_chan_cfg=%*_H", sizeof(tx_chan_cfg), &tx_chan_cfg);
+ // AddLog(LOG_LEVEL_INFO, ">>>: I2S tx_std_cfg=%*_H", sizeof(tx_std_cfg), &tx_std_cfg);
+
+ err = i2s_channel_init_std_mode(_tx_handle, &tx_std_cfg);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: TX channel bits:%i channels:%i hertz:%i initialized err=0x%04X", bps, channels, hertz, err);
+ if (err != ERR_OK) {
+ _i2s_on = false;
+ return false;
+ }
+
+ _i2s_on = true;
+ if (_rx_configured) { // full duplex mode
+ err = i2s_channel_init_std_mode(audio_i2s.out->_rx_handle, &tx_std_cfg);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: i2s_channel_init_std_mode err:%i", err);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel added in full duplex mode");
+ }
+ } // if (tx)
+
+ // configure Rx Microphone
+ if (_rx_configured && !_tx_configured) { // if Microphone and not duplex
+ gpio_num_t clk_gpio;
+
+ 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);
+
+ if (_tx_configured && _rx_running) { // duplex mode, mic was already initialized
+ AddLog(LOG_LEVEL_DEBUG, "I2S: mic init exit Rx already enabled");
+ return 0; // no need to en- or disable when in full duplex mode and already initialized
+ }
+
+ i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
+
+ AddLog(LOG_LEVEL_DEBUG, "I2S: rx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i",
+ rx_chan_cfg.id, rx_chan_cfg.role, rx_chan_cfg.dma_desc_num, rx_chan_cfg.dma_frame_num, rx_chan_cfg.auto_clear);
+
+ err = i2s_new_channel(&rx_chan_cfg, NULL, &_rx_handle);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
+ switch (_rx_mode){
+ case I2S_MODE_PDM:
+ {
+ i2s_pdm_rx_config_t rx_pdm_cfg = {
+ .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_rx_freq),
+ /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
+ .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, (i2s_slot_mode_t)_rx_channels),
+ .gpio_cfg = {
+ .clk = _gpio_ws,
+ .din = _gpio_din,
+ .invert_flags = {
+ .clk_inv = _gpio_ws_inv,
+ },
+ },
+ };
+ // if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_pdm_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)_rx_slot_mask; }
+
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg clk_cfg sample_rate_hz:%i clk_src:%i mclk_multiple:%i dn_sample_mode:%i",
+ // rx_pdm_cfg.clk_cfg.sample_rate_hz, rx_pdm_cfg.clk_cfg.clk_src, rx_pdm_cfg.clk_cfg.mclk_multiple, rx_pdm_cfg.clk_cfg.dn_sample_mode);
+
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg slot_cfg data_bit_width:%i slot_bit_width:%i slot_mode:%i slot_mask:%i",
+ // rx_pdm_cfg.slot_cfg.data_bit_width, rx_pdm_cfg.slot_cfg.slot_bit_width, rx_pdm_cfg.slot_cfg.slot_mode, rx_pdm_cfg.slot_cfg.slot_mask);
+
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg gpio_cfg clk:%i din:%i clk_inv:%i",
+ // rx_pdm_cfg.gpio_cfg.clk, rx_pdm_cfg.gpio_cfg.din, rx_pdm_cfg.gpio_cfg.invert_flags.clk_inv);
+
+ err = i2s_channel_init_pdm_rx_mode(_rx_handle, &rx_pdm_cfg);
+ AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X"),
+ _gpio_ws, _gpio_din, _rx_channels, err);
+ _i2s_on = true;
+ }
+ break;
+ case I2S_MODE_STD:
+ {
+ i2s_std_config_t rx_std_cfg = {
+ .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_rx_freq),
+ .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
+ .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 = I2S_GPIO_UNUSED,
+ .din = (gpio_num_t)Pin(GPIO_I2S_DIN),
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false,
+ },
+ },
+ };
+ if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_rx_slot_mask; }
+
+ err = i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: RX i2s_channel_init_std_mode err:%i", err);
+ AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized", slot_mode);
+ _i2s_on = true;
+ }
+ break;
+ default:
+ AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", _rx_mode);
+ }
+ }
+ return true;
+}
+
+int TasmotaI2S::updateClockConfig(void) {
+ i2s_channel_disable(_tx_handle);
+ 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
+ int result = i2s_channel_reconfig_std_clock(_tx_handle, &clk_cfg );
+ if (_tx_running) { i2s_channel_enable(_tx_handle); }
+ AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config");
+ return result;
+}
+
+/*********************************************************************************************\
+ * microphone related functions
+\*********************************************************************************************/
+
+uint32_t TasmotaI2S::I2sMicInit(void) {
+ if (!_rx_configured) { return 0; } // nothing configured
+
+ esp_err_t err = ESP_OK;
+ gpio_num_t clk_gpio;
+
+ 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);
+
+ // if (_tx_configured && _rx_running) { // duplex mode, mic was already initialized
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: mic init exit Rx already enabled");
+ // return 0; // no need to en- or disable when in full duplex mode and already initialized
+ // }
+
+ // if (_rx_handle == nullptr){
+ // i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
+
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_chan_cfg id:%i role:%i dma_desc_num:%i dma_frame_num:%i auto_clear:%i",
+ // rx_chan_cfg.id, rx_chan_cfg.role, rx_chan_cfg.dma_desc_num, rx_chan_cfg.dma_frame_num, rx_chan_cfg.auto_clear);
+
+ // err = i2s_new_channel(&rx_chan_cfg, NULL, &_rx_handle);
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
+ // switch (_rx_mode){
+ // case I2S_MODE_PDM:
+ // {
+ // i2s_pdm_rx_config_t rx_pdm_cfg = {
+ // .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_rx_freq),
+ // /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
+ // .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
+ // // .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
+ // .gpio_cfg = {
+ // .clk = _gpio_ws,
+ // .din = _gpio_din,
+ // .invert_flags = {
+ // .clk_inv = _gpio_ws_inv,
+ // },
+ // },
+ // };
+ // if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_pdm_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)_rx_slot_mask; }
+
+ // // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg clk_cfg sample_rate_hz:%i clk_src:%i mclk_multiple:%i dn_sample_mode:%i",
+ // // rx_pdm_cfg.clk_cfg.sample_rate_hz, rx_pdm_cfg.clk_cfg.clk_src, rx_pdm_cfg.clk_cfg.mclk_multiple, rx_pdm_cfg.clk_cfg.dn_sample_mode);
+
+ // // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg slot_cfg data_bit_width:%i slot_bit_width:%i slot_mode:%i slot_mask:%i",
+ // // rx_pdm_cfg.slot_cfg.data_bit_width, rx_pdm_cfg.slot_cfg.slot_bit_width, rx_pdm_cfg.slot_cfg.slot_mode, rx_pdm_cfg.slot_cfg.slot_mask);
+
+ // // AddLog(LOG_LEVEL_DEBUG, "I2S: rx_pdm_cfg gpio_cfg clk:%i din:%i clk_inv:%i",
+ // // rx_pdm_cfg.gpio_cfg.clk, rx_pdm_cfg.gpio_cfg.din, rx_pdm_cfg.gpio_cfg.invert_flags.clk_inv);
+
+ // err = i2s_channel_init_pdm_rx_mode(_rx_handle, &rx_pdm_cfg);
+ // AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: 0x%04X"),
+ // _gpio_ws, _gpio_din, slot_mode, err);
+ // }
+ // break;
+ // case I2S_MODE_STD:
+ // {
+ // i2s_std_config_t rx_std_cfg = {
+ // .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_rx_freq),
+ // .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
+ // .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 = I2S_GPIO_UNUSED,
+ // .din = (gpio_num_t)Pin(GPIO_I2S_DIN),
+ // .invert_flags = {
+ // .mclk_inv = false,
+ // .bclk_inv = false,
+ // .ws_inv = false,
+ // },
+ // },
+ // };
+ // if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)_rx_slot_mask; }
+
+ // err = i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: RX i2s_channel_init_std_mode err:%i", err);
+ // AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized", slot_mode);
+ // }
+ // break;
+ // default:
+ // AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", _rx_mode);
+ // return -1;
+ // }
+ // }
+
+ if (!_rx_running) {
+ err = i2s_channel_enable(_rx_handle);
+ AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel enable err:0x%04X"),err);
+ _rx_running = true;
+ }
+ return err;
+}
+
+
+void TasmotaI2S::I2sMicDeinit(void) {
+ esp_err_t err = ESP_OK;
+ gpio_num_t clk_gpio;
+
+ AddLog(LOG_LEVEL_DEBUG, "I2S: mic deinit rx_running:%i rx_handle:%p", _rx_running, _rx_handle);
+ if (!_rx_handle) { return; }
+
+ if (!_tx_configured || !_rx_configured) { // if duplex mode, there is no mic channel - TODO check this
+ int err = i2s_channel_disable(_rx_handle);
+ i2s_del_channel(_rx_handle);
+ _rx_handle = nullptr;
+ _rx_running = false;
+ AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel disable: %i", err);
+ }
+}
+
+#endif // USE_I2S_AUDIO
+#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5
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
deleted file mode 100644
index 3102b3d92..000000000
--- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s__lib_idf51.ino
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- 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
-
-/*********************************************************************************************\
- * Driver Settings in memory
-\*********************************************************************************************/
-
-// I2S communication mode
-enum : int8_t {
- I2S_MODE_STD = 0, // I2S mode standard
- I2S_MODE_PDM = 1, // I2S mode PDM
- I2S_MODE_TDM = 2, // I2S mode TDM
- I2S_MODE_DAC = 3, // Using internal DAC - only available on ESP32
-};
-
-// I2S slot mask (left, right, both)
-enum : int8_t {
- I2S_SLOT_LEFT = 1, // left
- I2S_SLOT_RIGHT = 2, // right
- I2S_SLOT_BOTH = 3, // both
-};
-
-// I2S slot configuration
-enum : int8_t {
- I2S_SLOT_MSB = 0, // MSB
- I2S_SLOT_PCM = 1, // PCM
- I2S_SLOT_PHILIPS = 2, // Philips
-};
-
-typedef struct{
- struct{
- uint8_t version = 0; // B00
-
- // runtime options, will be saved but ignored on setting read
- bool duplex = 0; // B01 - depends on GPIO setting and SOC caps, DIN and DOUT on same port in GPIO means -> try to use duplex if possible
- bool tx = 0; // B02 - depends on GPIO setting
- bool rx = 0; // B03 - depends on GPIO setting
- uint32_t spare01; // B04-07
- } sys;
- struct {
- uint8_t mode = I2S_MODE_STD; // B00 - I2S mode standard, PDM, TDM, DAC
- bool apll = 1; // B01 - will be ignored on unsupported SOC's
- uint8_t channels = 2; // B02 - 1 = mono, 2 = stereo
- uint8_t codec = 0; // B03 - S3 box only, unused for now
- uint8_t slot_config = I2S_SLOT_MSB;// B04 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2
- uint8_t volume = 10; // B05
- bool mclk_inv = 0; // B06 - invert mclk
- bool bclk_inv = 0; // B07 - invert bclk
- bool ws_inv = 0; // B08 - invert ws
- uint8_t spare[7]; // B09-0F
- } tx;
- struct {
- uint32_t sample_rate = 16000; // B00-03
- uint8_t gain = 30; // B04
- uint8_t mode = I2S_MODE_STD; // B05 - I2S mode standard, PDM, TDM, DAC
- uint8_t slot_mask = I2S_SLOT_LEFT;// B06 - slot mask
- uint8_t slot_mode = 0; // B07 - mono/stereo - 1 is added for both
- uint8_t codec = 0; // B08 - unused for now
- uint8_t mp3_encoder = 1; // B09 - will be ignored without PS-RAM
- uint8_t spare[6]; // B0A-0F
- } 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 = nullptr;
-
- TasmotaAudioOutputI2S *out = nullptr;
-
- 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;
-
-/*********************************************************************************************\
- * 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() {
- loadSettings();
- }
-
- ~TasmotaAudioOutputI2S() {
- this->stop();
- }
-
- // ------------------------------------------------------------------------------------------
- // Setters for configuration parameters
- //
- // TODO: not sure we still need them since all this should be set at initialiation
- // ------------------------------------------------------------------------------------------
- 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;
- }
-
- 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;
- }
-
- virtual bool SetRate(int hz) {
- AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: SetRate: %i was %i on=%i"), hz, this->hertz, _i2s_on);
- if (hz == (int) this->hertz) { return true; }
- this->hertz = hz;
- if (_i2s_on) {
- int result = updateClockConfig();
- }
- return true;
- }
-
- 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 uint8_t getTxChannels(void) const { return _tx_channels; }
- inline bool getTxEnabled(void) const { return _tx_enabled; }
-
- // ------------------------------------------------------------------------------------------
- // Setters
- inline void setTxMode(uint8_t mode) { _tx_mode = mode; }
- inline void setTxChannels(uint8_t channels) { _tx_channels = channels; }
- 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);
-
- // 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);
-
-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 = I2S_MODE_STD; // EXTERNAL_I2S = 0, INTERNAL_DAC = 1, INTERNAL_PDM = 2
- bool _tx_enabled = false; // true = enabled, false = disabled
- uint8_t _tx_channels = 2; // 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
-
-};
-
-
-// ------------------------------------------------------------------------------------------
-// Methods
-// ------------------------------------------------------------------------------------------
-void TasmotaAudioOutputI2S::loadSettings(void) {
- hertz = 16000;
- _i2s_on = false;
- bps = I2S_DATA_BIT_WIDTH_16BIT;
- _tx_channels = audio_i2s.Settings->tx.channels;
- if (_tx_channels == 0) { _tx_channels = 2; } // if zero channel default to stereo
- if (_tx_channels > 2) { _tx_channels = 2; } // if > 2 channels default to stereo
- channels = (_tx_channels == 1) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
- _tx_mode = I2S_MODE_STD;
- _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_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);
- if (result != 0){
- AddLog(LOG_LEVEL_INFO, "I2S: Could not enable i2s_channel: %i", result);
- return false;
- }
- _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);
- _i2s_on = false;
- AddLog(LOG_LEVEL_INFO, "I2S: stop: I2S channel disabled");
- }
- _tx_enabled = false;
- return true;
-}
-
-bool TasmotaAudioOutputI2S::ConsumeSample(int16_t sample[2]) {
- return consumeSamples(sample, 1);
-}
-
-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; }
-
- 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_channels == 1) { // if mono, average the two samples
- // 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 == I2S_MODE_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;
- 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; // 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)_gpio_din;
- i2s_new_channel(&tx_chan_cfg, &_tx_chan, &audio_i2s.rx_handle);
- } else{
- 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_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 == I2S_SLOT_PCM) { // 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 == I2S_SLOT_PHILIPS) { // 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 bits width on %i channels initialized i2s_on=%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 _i2s_on;
-}
-
-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_enabled) { i2s_channel_enable(_tx_chan); }
- AddLog(LOG_LEVEL_DEBUG, "I2S: Updating clock config");
- return result;
-}
-
-#endif // USE_I2S_AUDIO
-#endif // defined(ESP32) && 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 72f5f3e13..0dba6f17c 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
@@ -26,6 +26,7 @@
#define USE_I2S_SAY_TIME
#define USE_I2S_RTTTL
#define USE_I2S_WEBRADIO
+#define USE_I2S_DEBUG // remove before flight
// Macros used in audio sub-functions
#undef AUDIO_PWR_ON
@@ -54,6 +55,9 @@ void I2sWebRadioStopPlaying(void);
const char kI2SAudio_Commands[] PROGMEM = "I2S|"
"Gain|Play|Rec|MGain|Stop"
+#ifdef USE_I2S_DEBUG
+ "|Mic" // debug only
+#endif // USE_I2S_DEBUG
#ifdef USE_I2S_WEBRADIO
"|WR"
#endif // USE_I2S_WEBRADIO
@@ -76,6 +80,9 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|"
void (* const I2SAudio_Command[])(void) PROGMEM = {
&CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop,
+#ifdef USE_I2S_DEBUG
+ &CmndI2SMic,
+#endif // USE_I2S_DEBUG
#ifdef USE_I2S_WEBRADIO
&CmndI2SWebRadio,
#endif // USE_I2S_WEBRADIO
@@ -100,94 +107,6 @@ void (* const I2SAudio_Command[])(void) PROGMEM = {
* microphone related functions
\*********************************************************************************************/
-uint32_t I2sMicInit(void) {
- esp_err_t err = ESP_OK;
- gpio_num_t clk_gpio;
-
- i2s_slot_mode_t slot_mode = (audio_i2s.Settings->rx.slot_mode == 0) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO;
- AddLog(LOG_LEVEL_DEBUG, "I2S: mic init rx_channels=%i", slot_mode);
-
- if (audio_i2s.Settings->sys.duplex == 1 && audio_i2s.rx_handle){
- return 0; // no need to en- or disable when in full duplex mode and already initialized
- }
-
- if (audio_i2s.rx_handle == nullptr){
- i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
-
- err = i2s_new_channel(&chan_cfg, NULL, &audio_i2s.rx_handle);
- AddLog(LOG_LEVEL_DEBUG, "I2S: mic init i2s_new_channel err=%i", err);
- switch (audio_i2s.Settings->rx.mode){
- case I2S_MODE_PDM:
- {
- clk_gpio = (gpio_num_t)Pin(GPIO_I2S_WS,1); //legacy setting for Core2, might be wrong
- if (clk_gpio == -1){
- clk_gpio = (gpio_num_t)Pin(GPIO_I2S_WS); //fallback to other port, might be wrong
- }
- i2s_pdm_rx_config_t pdm_rx_cfg = {
- .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(audio_i2s.Settings->rx.sample_rate),
- /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */
- .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
- .gpio_cfg = {
- .clk = clk_gpio,
- .din = (gpio_num_t)Pin(GPIO_I2S_DIN),
- .invert_flags = {
- .clk_inv = false,
- },
- },
- };
- pdm_rx_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)audio_i2s.Settings->rx.slot_mask;
- err = i2s_channel_init_pdm_rx_mode(audio_i2s.rx_handle, &pdm_rx_cfg);
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in PDM mode, CLK: %i, DIN: %i, 16 bit width, %i channel(s), err code: %u"),clk_gpio, Pin(GPIO_I2S_DIN), slot_mode, err);
- }
- break;
- case I2S_MODE_STD:
- {
- i2s_std_config_t rx_std_cfg = {
- .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(audio_i2s.Settings->rx.sample_rate),
- .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, slot_mode),
- .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 = I2S_GPIO_UNUSED,
- .din = (gpio_num_t)Pin(GPIO_I2S_DIN),
- .invert_flags = {
- .mclk_inv = false,
- .bclk_inv = false,
- .ws_inv = false,
- },
- },
- };
- rx_std_cfg.slot_cfg.slot_mask = (i2s_std_slot_mask_t)audio_i2s.Settings->rx.slot_mask;
- i2s_channel_init_std_mode(audio_i2s.rx_handle, &rx_std_cfg);
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel in standard mode with 16 bit width on %i channel(s) initialized"),slot_mode);
- }
- break;
- default:
- AddLog(LOG_LEVEL_INFO, "I2S: invalid rx mode=%i", audio_i2s.Settings->rx.mode);
- }
- }
-
- err = i2s_channel_enable(audio_i2s.rx_handle);
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX channel enable: %i"),err);
- return err;
-}
-
-
-void I2sMicDeinit(void) {
- esp_err_t err = ESP_OK;
- gpio_num_t clk_gpio;
-
- if (!audio_i2s.rx_handle) { return; }
-
- if (!audio_i2s.Settings->sys.duplex) { // if duplex mode, there is no mic channel - TODO check this
- int err = i2s_channel_disable(audio_i2s.rx_handle);
- i2s_del_channel(audio_i2s.rx_handle);
- audio_i2s.rx_handle = nullptr;
- AddLog(LOG_LEVEL_DEBUG, "I2S: RX channel disable: %i", err);
- }
-}
-
// micro to mp3 file or stream
void I2sMicTask(void *arg){
int8_t error = 0;
@@ -226,14 +145,14 @@ void I2sMicTask(void *arg){
shine_set_config_mpeg_defaults(&config.mpeg);
- if (audio_i2s.Settings->rx.slot_mode == 0) {
+ if (audio_i2s.Settings->rx.channels == 1) {
config.mpeg.mode = MONO;
} else {
config.mpeg.mode = STEREO;
}
config.mpeg.bitr = 128;
config.wave.samplerate = audio_i2s.Settings->rx.sample_rate;
- config.wave.channels = (channels)(audio_i2s.Settings->rx.slot_mode + 1);
+ config.wave.channels = (channels)(audio_i2s.Settings->rx.channels);
if (shine_check_config(config.wave.samplerate, config.mpeg.bitr) < 0) {
error = 3;
@@ -247,7 +166,7 @@ void I2sMicTask(void *arg){
}
samples_per_pass = shine_samples_per_pass(s);
- bytesize = samples_per_pass * 2 * (audio_i2s.Settings->rx.slot_mode + 1);
+ bytesize = samples_per_pass * 2 * (audio_i2s.Settings->rx.channels);
buffer = (int16_t*)malloc(bytesize);
if (!buffer) {
@@ -310,7 +229,7 @@ exit:
audio_i2s.client.stop();
}
- I2sMicDeinit();
+ audio_i2s.out->I2sMicDeinit();
audio_i2s.mic_stop = 0;
audio_i2s.mic_error = error;
AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error);
@@ -334,7 +253,7 @@ int32_t I2sRecordShine(char *path) {
if (audio_i2s.use_stream) {
stack = 8000;
}
- I2sMicInit();
+ audio_i2s.out->I2sMicInit();
err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1);
@@ -347,7 +266,8 @@ int32_t I2sRecordShine(char *path) {
// error codes
enum {
I2S_OK = 0,
- I2S_ERR_OUTPUT_NOT_CONFIGURE,
+ I2S_ERR_OUTPUT_NOT_CONFIGURED,
+ I2S_ERR_INPUT_NOT_CONFIGURED,
I2S_ERR_DECODER_IN_USE,
I2S_ERR_FILE_NOT_FOUND,
};
@@ -369,7 +289,6 @@ void I2SSettingsLoad(const char * config_filename, bool erase) {
AddLog(LOG_LEVEL_ERROR, "I2S: ERROR memory allocation failed");
return;
}
-
if (!config_filename) { return; } // if no filename, use defaults
#ifndef USE_UFILESYS
@@ -406,53 +325,20 @@ void I2SSettingsSave(const char * config_filename) {
* Driver init
\*********************************************************************************************/
-//
-// I2sCheckCfg
-//
-// Multiple checks
-void I2sCheckCfg(void){
- // din and dout must be configured on port 0 for full duplex
- bool useDuplexMode = ((Pin(GPIO_I2S_DIN) != -1) && (Pin(GPIO_I2S_DOUT) != -1));
- // AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: DIN %i , DOUT %i"),Pin(GPIO_I2S_DIN),Pin(GPIO_I2S_DOUT) );
-
- if (useDuplexMode){
- if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){
- useDuplexMode = false;
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: PDM forbids full duplex mode"));
- }
- audio_i2s.Settings->sys.duplex = useDuplexMode ? 1 : 0;
- if(useDuplexMode){
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
- }
- }
- if (Pin(GPIO_I2S_DIN) != -1 || Pin(GPIO_I2S_DIN, 1) != -1){ // micro could be port 0 or 1
- audio_i2s.Settings->sys.rx = 1;
- AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: RX config = mode: %i, channels: %i, gain: %i, sample rate: %i"), audio_i2s.Settings->rx.mode, (uint8_t)(audio_i2s.Settings->rx.slot_mode + 1), audio_i2s.Settings->rx.gain, audio_i2s.Settings->rx.sample_rate);
- }
- else{
- audio_i2s.Settings->sys.rx = 0;
- audio_i2s.Settings->rx.mp3_encoder = 0; // do not allocate buffer
- }
- if (Pin(GPIO_I2S_DOUT) != -1){ // output is only supported on port 0
- audio_i2s.Settings->sys.tx = 1;
- }
- else{
- audio_i2s.Settings->sys.tx = 0;
- }
-
- AddLog(LOG_LEVEL_INFO, PSTR("I2S: init pins bclk=%d, ws=%d, dout=%d, mclk=%d, din=%d [tx=%i, rx=%i, duplex=%i]"),
- Pin(GPIO_I2S_BCLK) , Pin(GPIO_I2S_WS), Pin(GPIO_I2S_DOUT), Pin(GPIO_I2S_MCLK), Pin(GPIO_I2S_DIN),
- audio_i2s.Settings->sys.tx, audio_i2s.Settings->sys.tx, audio_i2s.Settings->sys.duplex);
-
-}
-
//
// I2sInit
//
// Initialize I2S driver for input and output
void I2sInit(void) {
+ int32_t gpio_din_0 = Pin(GPIO_I2S_DIN, 0);
+ int32_t gpio_din_1 = Pin(GPIO_I2S_DIN, 1);
+ 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);
+
// we need at least one pin configured
- if(Pin(GPIO_I2S_DIN) + Pin(GPIO_I2S_DIN,1) + Pin(GPIO_I2S_DOUT) + Pin(GPIO_I2S_DOUT,1) == -4){
+ // 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)) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: no pin configured"));
return;
}
@@ -460,37 +346,204 @@ void I2sInit(void) {
I2SSettingsLoad("/.drvset042", false); // load configuration (no-erase)
if (!audio_i2s.Settings) { return; } // fatal error, could not allocate memory for configuration
- // check configuration is valid
- I2sCheckCfg();
-
- // TODO
- int result = 0;
- if (audio_i2s.Settings->sys.rx == 1 && audio_i2s.Settings->sys.duplex == 0){
- I2sMicDeinit();
+ // detect if we need full-duplex on port 0
+ bool duplex = false;
+ if ((gpio_din_0 >= 0) && (gpio_dout_0 >= 0)) {
+ // conditions are potentially favorable for duplex
+ if (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM ){
+ AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: PDM forbids full duplex mode, ignoring 'I2S DIN 1'"));
+ gpio_din_0 = -1; // hence deconfigure DIN_0 which can't be used
+ } else {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
+ duplex = true;
+ }
}
- if(audio_i2s.Settings->sys.tx == 1){
- audio_i2s.out = new TasmotaAudioOutputI2S;
- int err = audio_i2s.out->startI2SChannel() ? 0 : 1;
- result += err;
+ // 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));
+
+ audio_i2s.Settings->sys.duplex = false;
+ audio_i2s.Settings->sys.tx = false;
+ audio_i2s.Settings->sys.rx = false;
+ audio_i2s.Settings->sys.exclusive = false;
+ bool exclusive = false; // signals that in/out have a shared GPIO and need to un/install driver before use
+
+ for (uint32_t port = 0; port < SOC_I2S_NUM; port++) {
+ int32_t bclk = Pin(GPIO_I2S_BCLK, port);
+ int32_t ws = Pin(GPIO_I2S_WS, port);
+ int32_t dout = Pin(GPIO_I2S_DOUT, port);
+ int32_t mclk = Pin(GPIO_I2S_MCLK, port);
+ int32_t din = Pin(GPIO_I2S_DIN, port);
+ bool tx = false; // is Tx enabled for this port
+ bool rx = false; // is Rx enabled for this port
+
+ 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; }
+
+ const char *err_msg = nullptr; // to save code, we indicate an error with a message configured
+ bool duplex = (din >= 0) && (dout >= 0);
+ bool dac_mode = false;
+ if (din >= 0 || dout >= 0) {
+ // we have regular I2S configuration
+ // do multiple checks
+ // 1. check that WS is configured
+ if (ws < 0) {
+ // WS may be shared between both ports, so if it is configured on port 0, we accept it on port 1
+ int32_t ws0 = Pin(GPIO_I2S_WS, 0);
+ if (ws0 >= 0) {
+ ws = ws0;
+ AddLog(LOG_LEVEL_DEBUG, "I2S: I2S%i WS is shared, using WS from port 0 (%i)", port, ws);
+ exclusive = true;
+ }
+ if (ws < 0) {
+ err_msg = "no WS pin configured";
+ }
+ }
+ // 2. check that DAC mode is not enabled for output(incompatible with DIN/DOUT)
+ else if (dout >= 0 && audio_i2s.Settings->tx.mode == I2S_MODE_DAC) {
+ err_msg = "DAC mode is not compatible with DOUT";
+ }
+ // 3. check that ADC mode is not enabled for output (incompatible with DIN/DOUT)
+ else if (din >= 0 && audio_i2s.Settings->rx.mode == I2S_MODE_DAC) {
+ err_msg = "ADC mode is not compatible with DIN";
+ }
+ // 4. check that output is not already configured
+ else if (dout >= 0 && audio_i2s.out) {
+ err_msg = "output already configured";
+ }
+ // 5. check that input is not already configured
+ else if (din >= 0 && audio_i2s.in) {
+ err_msg = "input already configured";
+ }
+ // 6. check that we don't try PDM on port 1
+ else if (port != 0 && din >= 0 && audio_i2s.Settings->rx.mode == I2S_MODE_PDM) {
+ err_msg = "PDM Rx is not supported";
+ }
+ // 7. check that we don't try PDM on port 1
+ else if (port != 0 && dout >= 0 && audio_i2s.Settings->tx.mode == I2S_MODE_PDM) {
+ err_msg = "PDM Tx is not supported";
+ }
+ // 8. check that we don't try full-duplex with PDM in either direction
+ else if (duplex && (audio_i2s.Settings->rx.mode == I2S_MODE_PDM || audio_i2s.Settings->tx.mode == I2S_MODE_PDM )) {
+ err_msg = "PDM forbids full duplex mode";
+ duplex = false;
+ din = -1; // deconfigure DIN_0 which can't be used
+ }
+ } 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) {
+ err_msg = "DAC mode is not enabled";
+ }
+ }
+
+ // is there any error?
+ if (err_msg) {
+ AddLog(LOG_LEVEL_DEBUG, "I2S: Error: %s for I2S%i, skipping", err_msg, port);
+ continue; // skip this port
+ }
+
+ tx = (dout >= 0) || dac_mode;
+ rx = (din >= 0);
+
+ if (duplex) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("I2S: will try to use full duplex mode"));
+ }
+
+ TasmotaI2S * i2s = new TasmotaI2S;
+ i2s->setPinout(bclk, ws, dout, mclk, din,
+ audio_i2s.Settings->sys.mclk_inv[0], audio_i2s.Settings->sys.bclk_inv[0],
+ audio_i2s.Settings->sys.ws_inv[0], audio_i2s.Settings->tx.apll);
+ i2s->setSlotConfig((i2s_port_t)port, audio_i2s.Settings->tx.slot_config, audio_i2s.Settings->rx.slot_config,
+ audio_i2s.Settings->tx.slot_mask, audio_i2s.Settings->rx.slot_mask);
+ if (tx) {
+ i2s->setTxMode(audio_i2s.Settings->tx.mode);
+ i2s->setTxChannels(audio_i2s.Settings->tx.channels);
+ i2s->setRate(audio_i2s.Settings->tx.sample_rate);
+ }
+ if (rx) {
+ i2s->setRxMode(audio_i2s.Settings->rx.mode);
+ i2s->setRxFreq(audio_i2s.Settings->rx.sample_rate);
+ i2s->setRxChannels(audio_i2s.Settings->rx.channels);
+ i2s->setRate(audio_i2s.Settings->rx.sample_rate);
+ }
+
+ if (i2s->startI2SChannel(tx, rx)) {
+ // succesful, register handlers
+ if (tx) {
+ audio_i2s.out = i2s;
+ audio_i2s.Settings->sys.tx = true;
+ }
+ if (rx) {
+ audio_i2s.in = i2s;
+ audio_i2s.Settings->sys.rx = true;
+ }
+ if (duplex) {
+ audio_i2s.Settings->sys.duplex = true;
+ }
+ }
}
+ // do we have exclusive mode?
+ audio_i2s.Settings->sys.exclusive = exclusive;
+ if (audio_i2s.out) { audio_i2s.out->setExclusive(exclusive); }
+ if (audio_i2s.in) { audio_i2s.in->setExclusive(exclusive); }
+
if(audio_i2s.out != nullptr){
- audio_i2s.out->SetGain(((float)audio_i2s.Settings->tx.volume / 100.0) * 4.0);
+ audio_i2s.out->SetGain(((float)audio_i2s.Settings->tx.gain / 100.0) * 4.0);
audio_i2s.out->begin();
audio_i2s.out->stop();
}
audio_i2s.mp3ram = nullptr;
- if(audio_i2s.Settings->rx.mp3_encoder == 1){
+ if (audio_i2s.Settings->rx.mp3_preallocate == 1){
+ // if (UsePSRAM()) {
AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder"));
- if (UsePSRAM()) {
- audio_i2s.mp3ram = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
- }
- else{
- audio_i2s.Settings->rx.mp3_encoder = 0; // no PS-RAM -> no MP3 encoding
- }
+ audio_i2s.mp3ram = special_malloc(preallocateCodecSize);
+ // }
+ // else{
+ // audio_i2s.Settings->rx.mp3_preallocate = 0; // no PS-RAM -> no MP3 encoding
+ // }
}
+ AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done");
+}
+
+/*********************************************************************************************\
+ * General functions
+\*********************************************************************************************/
+
+//
+// I2SPrepareTx() -> int
+//
+// Prepare I2S for output, handle exclusive access if necessary
+//
+// Returns `I2S_OK` if ok to send to output or error code
+int32_t I2SPrepareTx(void) {
+ AddLog(LOG_LEVEL_DEBUG, "I2S: I2SPrepareTx out=%p", audio_i2s.out);
+ if (!audio_i2s.out) { return I2S_ERR_OUTPUT_NOT_CONFIGURED; }
+
+ if (audio_i2s.Settings->sys.exclusive) {
+ // TODO - deconfigure input driver
+ }
+ return I2S_OK;
+}
+
+//
+// I2SPrepareRx() -> int
+//
+// Prepare I2S for input, handle exclusive access if necessary
+//
+// Returns `I2S_OK` if ok to record input or error code
+int32_t I2SPrepareRx(void) {
+ if (!audio_i2s.in) return I2S_ERR_OUTPUT_NOT_CONFIGURED;
+
+ if (audio_i2s.Settings->sys.exclusive) {
+ // TODO - deconfigure input driver
+ }
+ return I2S_OK;
}
/*********************************************************************************************\
@@ -558,7 +611,8 @@ void I2sStopPlaying() {
//
// Returns I2S_error_t
int32_t I2SPlayMp3(const char *path) {
- if (!audio_i2s.out) return I2S_ERR_OUTPUT_NOT_CONFIGURE;
+ 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;
// check if the filename starts with '/', if not add it
@@ -598,7 +652,7 @@ void mp3_delete(void) {
void Say(char *text) {
- if (!audio_i2s.out) return;
+ if (I2SPrepareTx()) { return; }
I2SAudioPower(true);
@@ -615,12 +669,44 @@ void Say(char *text) {
* Commands
\*********************************************************************************************/
+void CmndI2SMic(void) {
+ if (I2SPrepareRx()) {
+ ResponseCmndChar("I2S Mic not configured");
+ return;
+ }
+
+ esp_err_t err = ESP_OK;
+ if (audio_i2s.decoder || audio_i2s.mp3) return;
+ audio_i2s.in->I2sMicInit();
+ if (audio_i2s.in->getRxRunning()) {
+ uint8_t buf[128];
+
+ size_t bytes_read = 0;
+ esp_err_t err = i2s_channel_read(audio_i2s.in->getRxHandle(), buf, sizeof(buf), &bytes_read, 0);
+ if (err == ESP_ERR_TIMEOUT) { err = ESP_OK; }
+ AddLog(LOG_LEVEL_INFO, "I2S: Mic (err:%i size:%i) %*_H", err, bytes_read, bytes_read, buf);
+ }
+
+ // err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1);
+
+ ResponseCmndDone();
+}
+
+
void CmndI2SStop(void) {
+ if (!I2SPrepareTx()) {
+ ResponseCmndChar("I2S output not configured");
+ return;
+ }
I2sStopPlaying();
ResponseCmndDone();
}
void CmndI2SPlay(void) {
+ if (I2SPrepareTx()) {
+ ResponseCmndChar("I2S output not configured");
+ return;
+ }
if (XdrvMailbox.data_len > 0) {
int32_t err = I2SPlayMp3(XdrvMailbox.data);
// display return message
@@ -628,7 +714,7 @@ void CmndI2SPlay(void) {
case I2S_OK:
ResponseCmndDone();
break;
- case I2S_ERR_OUTPUT_NOT_CONFIGURE:
+ case I2S_ERR_OUTPUT_NOT_CONFIGURED:
ResponseCmndChar("I2S output not configured");
break;
case I2S_ERR_DECODER_IN_USE:
@@ -649,14 +735,18 @@ void CmndI2SPlay(void) {
void CmndI2SGain(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
if (audio_i2s.out) {
- audio_i2s.Settings->tx.volume = XdrvMailbox.payload;
- audio_i2s.out->SetGain(((float)(audio_i2s.Settings->tx.volume-2)/100.0)*4.0);
+ audio_i2s.Settings->tx.gain = XdrvMailbox.payload;
+ audio_i2s.out->SetGain(((float)(audio_i2s.Settings->tx.gain-2)/100.0)*4.0);
}
}
- ResponseCmndNumber(audio_i2s.Settings->tx.volume);
+ ResponseCmndNumber(audio_i2s.Settings->tx.gain);
}
void CmndI2SSay(void) {
+ if (I2SPrepareTx()) {
+ ResponseCmndChar("I2S output not configured");
+ return;
+ }
if (XdrvMailbox.data_len > 0) {
Say(XdrvMailbox.data);
}
@@ -664,6 +754,10 @@ void CmndI2SSay(void) {
}
void CmndI2SI2SRtttl(void) {
+ if (I2SPrepareTx()) {
+ ResponseCmndChar("I2S output not configured");
+ return;
+ }
if (XdrvMailbox.data_len > 0) {
Rtttl(XdrvMailbox.data);
}
@@ -671,37 +765,38 @@ void CmndI2SI2SRtttl(void) {
}
void CmndI2SMicRec(void) {
-if (audio_i2s.Settings->rx.mp3_encoder == 1) {
- if (XdrvMailbox.data_len > 0) {
- if (!strncmp(XdrvMailbox.data, "-?", 2)) {
- Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur);
- } else {
- I2sRecordShine(XdrvMailbox.data);
- ResponseCmndChar(XdrvMailbox.data);
- }
- } else {
- if (audio_i2s.mic_task_handle) {
- // stop task
- audio_i2s.mic_stop = 1;
- while (audio_i2s.mic_stop) {
- delay(1);
+ if (audio_i2s.Settings->rx.mp3_preallocate == 1) {
+ if (XdrvMailbox.data_len > 0) {
+ if (!strncmp(XdrvMailbox.data, "-?", 2)) {
+ Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur);
+ } else {
+ I2sRecordShine(XdrvMailbox.data);
+ ResponseCmndChar(XdrvMailbox.data);
+ }
+ } else {
+ if (audio_i2s.mic_task_handle) {
+ // stop task
+ audio_i2s.mic_stop = 1;
+ while (audio_i2s.mic_stop) {
+ delay(1);
+ }
+ ResponseCmndChar_P(PSTR("Stopped"));
}
- ResponseCmndChar_P(PSTR("Stopped"));
}
}
-}
-else{
- if(audio_i2s.Settings->sys.rx == 1){
- ResponseCmndChar_P(PSTR("need PSRAM for MP3 recording"));
- }
else{
- ResponseCmndChar_P(PSTR("no mic configured"));
+ if (audio_i2s.in){
+ ResponseCmndChar_P(PSTR("need PSRAM for MP3 recording"));
+ }
+ else{
+ ResponseCmndChar_P(PSTR("no mic configured"));
+ }
}
}
-}
// mic gain in factor not percent
void CmndI2SMicGain(void) {
+ // TODO - does nothing for now
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 256)) {
audio_i2s.Settings->rx.gain = XdrvMailbox.payload;
}
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 3e25ac097..d77a71d9c 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino
@@ -40,21 +40,22 @@ extern "C" {
// AudioOutputI2S.init() -> instance
void* be_audio_output_i2s_init(void) {
- return audio_i2s.out; // return the singleton of TasmotaAudioOutputI2S which is already initialized
+ return audio_i2s.out; // return the singleton of TasmotaI2S which is already initialized
}
// AudioOutputI2S.deinit()-> void
- void be_audio_output_i2s_deinit(TasmotaAudioOutputI2S * out) {
+ void be_audio_output_i2s_deinit(TasmotaI2S * out) {
out->stop();
}
// AudioOutputI2S.begin() -> bool
- int be_audio_output_i2s_begin(TasmotaAudioOutputI2S* out) {
+ int be_audio_output_i2s_begin(TasmotaI2S* out) {
+ if (I2SPrepareTx()) { return false; }
return out->begin();
}
// AudioOutputI2S.stop() -> bool
- int be_audio_output_i2s_stop(TasmotaAudioOutputI2S* out) {
+ int be_audio_output_i2s_stop(TasmotaI2S* out) {
return out->stop();
}
@@ -125,7 +126,7 @@ extern "C" {
// AudioOutputI2S()
//
int i2s_output_i2s_init(bvm *vm) {
- TasmotaAudioOutputI2S * audio = new TasmotaAudioOutputI2S();
+ TasmotaI2S * audio = new TasmotaI2S();
be_pushcomptr(vm, (void*) audio);
be_setmember(vm, 1, ".p");
be_return_nil(vm);
@@ -134,7 +135,7 @@ extern "C" {
int i2s_output_i2s_deinit(bvm *vm) {
int argc = be_top(vm);
be_getmember(vm, 1, ".p");
- TasmotaAudioOutputI2S * audio = (TasmotaAudioOutputI2S *) be_tocomptr(vm, -1);
+ TasmotaI2S * audio = (TasmotaI2S *) be_tocomptr(vm, -1);
if (audio) {
delete audio;
// clear
@@ -177,6 +178,7 @@ extern "C" {
int i2s_generator_wav_begin(bvm *vm) {
int argc = be_top(vm);
if (argc > 2) {
+ if (I2SPrepareTx()) { be_raisef(vm, "internal_error", "I2SPrepareTx() failed"); be_return_nil(vm); }
AudioGeneratorWAV * wav = i2s_generator_wav_get(vm);
be_getmember(vm, 2, ".p");
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
@@ -244,6 +246,7 @@ extern "C" {
int i2s_generator_mp3_begin(bvm *vm) {
int argc = be_top(vm);
if (argc > 2) {
+ if (I2SPrepareTx()) { be_raisef(vm, "internal_error", "I2SPrepareTx() failed"); be_return_nil(vm); }
AudioGeneratorMP3 * mp3 = i2s_generator_mp3_get(vm);
be_getmember(vm, 2, ".p");
AudioFileSource * source = (AudioFileSource*) be_tocomptr(vm, -1);
@@ -313,12 +316,99 @@ extern "C" {
// ----------------------------------------------------------------------
// AudioInputI2S.init() -> instance
void* be_audio_input_i2s_init(void) {
- return audio_i2s.out; // return the singleton of TasmotaAudioOutputI2S which is already initialized
+ return audio_i2s.in; // return the singleton of TasmotaI2S which is already initialized
}
// AudioInputI2S.deinit()-> void
- void be_audio_input_i2s_deinit(TasmotaAudioOutputI2S * out) {
- out->stop();
+ void be_audio_input_i2s_deinit(TasmotaI2S * in) {
+ in->stop();
+ }
+
+ // AudioInputI2S.begin() -> bool
+ int be_audio_input_i2s_begin(bvm *vm, TasmotaI2S* in) {
+ if (I2SPrepareRx()) { be_raisef(vm, "internal_error", "I2SPrepareRx() failed"); be_return_nil(vm); }
+ in->I2sMicInit();
+ return in->getRxRunning();
+ }
+
+ // AudioInputI2S.stop() -> bool
+ int be_audio_input_i2s_stop(TasmotaI2S* in) {
+ in->I2sMicDeinit();
+ return true;
+ }
+
+
+ // AudioInputI2S.get_rate() -> int
+ int be_audio_input_i2s_get_rate(TasmotaI2S* in) {
+ return in->getRxRate();
+ }
+
+ // AudioInputI2S.get_bits_per_sample() -> int
+ int be_audio_input_i2s_get_bits_per_sample(TasmotaI2S* in) {
+ return in->getRxBitsPerSample();
+ }
+
+ // AudioInputI2S.get_channels() -> int
+ int be_audio_input_i2s_get_channels(TasmotaI2S* in) {
+ return in->getRxChannels();
+ }
+
+ // AudioInputI2S.get_gain() -> real
+ float be_audio_input_i2s_get_gain(TasmotaI2S* in) {
+ return in->getRxGain();
+ }
+
+ // AudioInputI2S.set_gain(gain:real) -> bool
+ int be_audio_input_set_gain(TasmotaI2S* in, float gain) {
+ return true; // TODO
+ // return in->SetGain(gain);
+ }
+
+
+ // AudioInputI2S.read_bytes() -> bytes()
+ //
+ // Reads bytes in the input buffer
+ // Pre-condition: microphone but be recording (call begin() first)
+ //
+ // Returns `nil` if not configured or buffer empty
+ // Returns `bytes()` instance with 16-bits audio
+ int be_audio_input_i2s_read_bytes(bvm *vm) {
+ int argc = be_top(vm);
+ if (!audio_i2s.in->getRxRunning()) { be_return_nil(vm); }
+
+ uint16_t buf_audio[256];
+
+ size_t bytes_read = 0;
+ esp_err_t err = i2s_channel_read(audio_i2s.in->getRxHandle(), buf_audio, sizeof(buf_audio), &bytes_read, 0);
+ // AddLog(LOG_LEVEL_DEBUG, "BRY: be_audio_input_i2s_read_bytes - err=%i bytes=%i", err, bytes_read);
+ if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { be_return_nil(vm); }
+ if (bytes_read == 0) { be_return_nil(vm); }
+
+ // we received some data
+ if (argc >= 2 && be_isbytes(vm, 2)) {
+ // we have already a bytes() buffer
+ be_pushvalue(vm, 2); // push on top
+ // resize to btr
+ be_getmember(vm, -1, "resize");
+ be_pushvalue(vm, -2);
+ be_pushint(vm, bytes_read);
+ be_call(vm, 2);
+ be_pop(vm, 3);
+ } else {
+ be_pushbytes(vm, nullptr, bytes_read); // allocate a buffer of size btr filled with zeros
+ }
+
+ // get the address of the buffer
+ be_getmember(vm, -1, "_buffer");
+ be_pushvalue(vm, -2);
+ be_call(vm, 1);
+ uint8_t * buf = (uint8_t*) be_tocomptr(vm, -2);
+ be_pop(vm, 2);
+
+ // copy
+ memmove(buf, buf_audio, bytes_read);
+
+ be_return(vm); /* return code */
}
}
diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_udp.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_udp.ino
index b48d35417..ab80735a7 100644
--- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_udp.ino
+++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_udp.ino
@@ -136,7 +136,7 @@ extern "C" {
be_setmember(vm, 1, "remote_port");
be_pop(vm, 1);
- be_return(vm); /* return code */
+ be_return(vm);
} else {
be_return_nil(vm);
}