From 473a8ee99981545c278acda031ce879a720b918b Mon Sep 17 00:00:00 2001 From: s-hadinger <49731213+s-hadinger@users.noreply.github.com> Date: Tue, 3 Oct 2023 22:39:02 +0200 Subject: [PATCH] Audio refactoring, filters and I2SConfig (#19662) --- .../berry_tasmota/src/be_i2s_audio_lib.c | 16 ++ tasmota/include/i18n.h | 5 + .../xdrv_42_0_i2s_0_config_idf51.ino | 39 ++-- .../xdrv_42_0_i2s_0_lib_idf51.ino | 211 +++++++++++------- .../xdrv_42_0_i2s_audio_idf51.ino | 174 ++++++++++++++- .../xdrv_52_3_berry_audio.ino | 44 ++-- 6 files changed, 364 insertions(+), 125 deletions(-) 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 3a3a8759d..707d98331 100644 --- a/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c +++ b/lib/libesp32/berry_tasmota/src/be_i2s_audio_lib.c @@ -127,6 +127,18 @@ BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_channels, "i", "."); extern float be_audio_input_i2s_get_gain(void* in); BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_gain, "f", "."); +// AudioInputI2S.get_dc_offset() -> float +extern int be_audio_input_i2s_get_dc_offset(void* in); +BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_dc_offset, "i", "."); + +// AudioInputI2S.get_lowpass_alpha() -> float +extern float be_audio_input_i2s_get_lowpass_alpha(void* in); +BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_get_lowpass_alpha, "f", "."); + +// AudioInputI2S.set_lowpass_alpha(alpha:float) -> bool +extern int be_audio_input_i2s_set_lowpass_alpha(void* in, float alpha); +BE_FUNC_CTYPE_DECLARE(be_audio_input_i2s_set_lowpass_alpha, "b", ".f"); + #include "be_fixed_be_class_AudioOutputI2S.h" #include "be_fixed_be_class_AudioGenerator.h" #include "be_fixed_be_class_AudioGeneratorWAV.h" @@ -202,6 +214,10 @@ class be_class_AudioInputI2S (scope: global, name: AudioInputI2S, strings: weak) get_gain, ctype_func(be_audio_input_i2s_get_gain) set_gain, ctype_func(be_audio_input_set_gain) + get_dc_offset, ctype_func(be_audio_input_i2s_get_dc_offset) + + get_lowpass_alpha, ctype_func(be_audio_input_i2s_get_lowpass_alpha) + set_lowpass_alpha, ctype_func(be_audio_input_i2s_set_lowpass_alpha) } @const_object_info_end */ diff --git a/tasmota/include/i18n.h b/tasmota/include/i18n.h index a8a2abb98..4f87a9cce 100644 --- a/tasmota/include/i18n.h +++ b/tasmota/include/i18n.h @@ -763,6 +763,11 @@ #define D_CMND_PING "Ping" #define D_JSON_PING "Ping" +// Commands xdrv_42_audio - I2S Audio +#define D_PRFX_I2S "I2S" +#define D_JSON_I2S_CONFIG "Config" + + // Commands xdrv_52_berry.ino - Berry scripting language #define D_PRFX_BR "Br" #define D_CMND_BR_RUN "" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino index f71bb3b15..b2f1b56ec 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_config_idf51.ino @@ -67,10 +67,11 @@ enum : int8_t { }; #define I2S_SLOTS 2 +#define AUDIO_SETTINGS_VERSION 1 typedef struct{ struct{ - uint8_t version = 0; // B00 + uint8_t version = AUDIO_SETTINGS_VERSION; // 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 @@ -81,7 +82,8 @@ typedef struct{ 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 + uint8_t mp3_preallocate = 0; // B0B - will be ignored without PS-RAM + uint8_t spare[4]; // B0C-0F } sys; struct { uint32_t sample_rate = 16000; // B00-03 @@ -91,26 +93,31 @@ typedef struct{ 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 + uint8_t spare[6]; // B0A-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 + uint16_t gain = 30 * 16; // B04-05 - in Q12.4 + uint8_t mode = I2S_MODE_PDM; // B06 - I2S mode standard, PDM, TDM, DAC + uint8_t slot_mask = I2S_SLOT_NOCHANGE;// B07 - slot mask + uint8_t slot_config = I2S_SLOT_MSB;// B08 - slot configuration MSB = 0, PCM = 1, PHILIPS = 2 + uint8_t channels = 1; // B09 - mono/stereo - 1 is added for both + // filters + uint16_t dc_filter_alpha = 0b0111111011111111; // B0A-B0B - DC_block filter = (1-2^(-7)) Q32:1.31, or `0` if disabled + // low pass filter + // See: https://en.wikipedia.org/wiki/Low-pass_filter#Simple_infinite_impulse_response_filter + // For 3000Hz low pass filter, RC = 0.0000530786 + // dt = 1/16000 = 0.0000625 + // alpha = dt / (RC + dt) = 0.540757545 + // alpha = (b) 0.100010100110111 = 0x4537 + uint16_t lowpass_alpha = 0b0100010100110111; // B0C-B0D - lowpass filter = 3000Hz for 16000Hz sample rate + bool apll = 1; // B0E - will be ignored on unsupported SOC's + uint8_t spare[1]; // B0F } rx; } tI2SSettings; +static_assert(sizeof(tI2SSettings) == 48, "tI2SSettings Size is not correct"); + typedef union { uint8_t data; struct { diff --git a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino index 633048776..34158bb28 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_42_0_i2s_0_lib_idf51.ino @@ -131,9 +131,11 @@ public: 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; } + inline float getRxGain(void) const { return ((float)_rx_gain) / 16; } + inline int32_t getRxDCOffset(void) const { return _rx_dc_offset; } + inline uint16_t getRxLowpassAlpha(void) const { return ((float)_rx_lowpass_alpha / 0x8000); } // ------------------------------------------------------------------------------------------ // Setters @@ -144,6 +146,15 @@ public: 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; } + inline void setRxGain(float f) { _rx_gain = (uint16_t)(f * 16); } + + void setRxLowpassAlpha(float f) { + if (f < 0) { f = 0; } + if (f > 1) { f = 1; } + _rx_lowpass_alpha = (uint16_t)(f * 0x8000); + _rx_lowpass_one_alpha = 0x8000 - _rx_lowpass_alpha; + _rx_lowpass_y_prev = 0; + } // ------------------------------------------------------------------------------------------ // AudioOutput has inconsistent case for methods, provide consistent setters for super class @@ -158,6 +169,8 @@ public: bool startI2SChannel(bool tx, bool rx); int updateClockConfig(void); + int32_t readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass); + // The following is now the preferred function // and allows to send multiple samples at once // @@ -170,11 +183,12 @@ public: // ------------------------------------------------------------------------------------------ // Microphone related methods - uint32_t I2sMicInit(void); - void I2sMicDeinit(void); + uint32_t micInit(void); + void micDeinit(void); protected: - void loadSettings(void); // load all settings from Settings file and template - the goal is to have zero touch + int16_t dcFilter(int16_t pcm_in); + int16_t lowpassFilter(int16_t pcm_in); protected: @@ -202,6 +216,9 @@ protected: uint8_t _rx_slot_config = I2S_SLOT_MSB;// I2S slot configuration uint16_t _rx_freq = 16000; // I2S Rx sampling frequency in Hz + uint16_t _rx_gain = 0x10; // Microphone gain in Q12.4 format (0x10 = 1.0) + int16_t _rx_dc_offset = 0x8000; // DC offset for PCM data, or 0x8000 if not set yet + // 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 @@ -212,6 +229,15 @@ protected: bool _gpio_bclk_inv = false; // invert bit clock bool _gpio_ws_inv = false; // invert word select bool _apll = false; // use APLL instead of PLL + + // DC blocking filter + int16_t _rx_dc_x_prev = 0; + int16_t _rx_dc_y_prev = 0; + + // Low-pass filter + uint16_t _rx_lowpass_alpha = 0; // alpha parameter for lowpass filter Q1.15 + uint16_t _rx_lowpass_one_alpha = 0x8000; // 1 - alpha, Q1.15 + int16_t _rx_lowpass_y_prev = 0; }; @@ -400,6 +426,9 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { } i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + // change to 3 buffers of 512 samples + rx_chan_cfg.dma_desc_num = 3; + rx_chan_cfg.dma_frame_num = 512; 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); @@ -421,7 +450,7 @@ bool TasmotaI2S::startI2SChannel(bool tx, bool rx) { }, }, }; - // if (_rx_slot_mask != I2S_SLOT_NOCHANGE) { rx_pdm_cfg.slot_cfg.slot_mask = (i2s_pdm_slot_mask_t)_rx_slot_mask; } + 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); @@ -489,7 +518,7 @@ int TasmotaI2S::updateClockConfig(void) { * microphone related functions \*********************************************************************************************/ -uint32_t TasmotaI2S::I2sMicInit(void) { +uint32_t TasmotaI2S::micInit(void) { if (!_rx_configured) { return 0; } // nothing configured esp_err_t err = ESP_OK; @@ -498,82 +527,6 @@ uint32_t TasmotaI2S::I2sMicInit(void) { 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); @@ -583,7 +536,7 @@ uint32_t TasmotaI2S::I2sMicInit(void) { } -void TasmotaI2S::I2sMicDeinit(void) { +void TasmotaI2S::micDeinit(void) { esp_err_t err = ESP_OK; gpio_num_t clk_gpio; @@ -599,5 +552,97 @@ void TasmotaI2S::I2sMicDeinit(void) { } } +// Read data into buffer of uint16_t[] +// +// Returns: +// n<0: error occured +// 0: no data available +// n>0: number of bytes read +int32_t TasmotaI2S::readMic(uint8_t *buffer, uint32_t size, bool dc_block, bool apply_gain, bool lowpass) { + if (!audio_i2s.in->getRxRunning()) { return -1; } + + size_t btr = 0; + esp_err_t err = i2s_channel_read(_rx_handle, buffer, size, &btr, 0 /* do not wait */); + if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { return -err; } + if (btr == 0) { return 0; } + int16_t *samples = (int16_t*) buffer; + size_t samples_count = btr / 2; // 16 bits signed samples + + // Apply DC block + if (dc_block) { + if (_rx_dc_x_prev == 0 && _rx_dc_y_prev == 0) { + int32_t sum_samples = 0; + for (uint32_t i=0; i 0x7FFF) { val = 0x7FFF; } + if (val < -0x8000) { val = -0x8000; } + samples[i] = val; + } + } + + // lowpass filter + if (lowpass && _rx_lowpass_alpha != 0) { + // apply lowpass filter + for (uint32_t i=0; i= 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 0dba6f17c..df65b9e2d 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 @@ -34,6 +34,8 @@ #define AUDIO_PWR_ON I2SAudioPower(true); #define AUDIO_PWR_OFF I2SAudioPower(false); +#define AUDIO_CONFIG_FILENAME "/.drvset042" + extern FS *ufsp; extern FS *ffsp; @@ -54,7 +56,7 @@ void I2sWebRadioStopPlaying(void); \*********************************************************************************************/ const char kI2SAudio_Commands[] PROGMEM = "I2S|" - "Gain|Play|Rec|MGain|Stop" + "Gain|Play|Rec|MGain|Stop|Config" #ifdef USE_I2S_DEBUG "|Mic" // debug only #endif // USE_I2S_DEBUG @@ -79,7 +81,7 @@ const char kI2SAudio_Commands[] PROGMEM = "I2S|" ; void (* const I2SAudio_Command[])(void) PROGMEM = { - &CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, + &CmndI2SGain, &CmndI2SPlay, &CmndI2SMicRec, &CmndI2SMicGain, &CmndI2SStop, &CmndI2SConfig, #ifdef USE_I2S_DEBUG &CmndI2SMic, #endif // USE_I2S_DEBUG @@ -103,6 +105,141 @@ void (* const I2SAudio_Command[])(void) PROGMEM = { #endif // I2S_BRIDGE }; + +/*********************************************************************************************\ + * I2S configuration +\*********************************************************************************************/ + +void CmndI2SConfig(void) { + tI2SSettings * cfg = audio_i2s.Settings; + + // if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } + TrimSpace(XdrvMailbox.data); + if (strlen(XdrvMailbox.data) > 0) { + JsonParser parser(XdrvMailbox.data); + JsonParserObject root = parser.getRootObject(); + if (!root) { ResponseCmndChar_P(D_JSON_INVALID_JSON); return; } + + // remove "I2SConfig" primary key if present + JsonParserToken config_tk = root[D_PRFX_I2S D_JSON_I2S_CONFIG]; + if ((bool)config_tk) { + root = config_tk.getObject(); + } + + JsonParserToken sys_tk = root["Sys"]; + if (sys_tk.isObject()) { + JsonParserObject sys = sys_tk.getObject(); + cfg->sys.mclk_inv[0] = sys.getUInt(PSTR("MclkInv0"), cfg->sys.mclk_inv[0]); + cfg->sys.mclk_inv[1] = sys.getUInt(PSTR("MclkInv1"), cfg->sys.mclk_inv[1]); + cfg->sys.bclk_inv[0] = sys.getUInt(PSTR("BclkInv0"), cfg->sys.bclk_inv[0]); + cfg->sys.bclk_inv[1] = sys.getUInt(PSTR("BclkInv1"), cfg->sys.bclk_inv[1]); + cfg->sys.ws_inv[0] = sys.getUInt(PSTR("WsInv0"), cfg->sys.ws_inv[0]); + cfg->sys.ws_inv[1] = sys.getUInt(PSTR("WsInv1"), cfg->sys.ws_inv[1]); + cfg->sys.mp3_preallocate = sys.getUInt(PSTR("Mp3Preallocate"), cfg->sys.mp3_preallocate); + } + + JsonParserToken tx_tk = root["Tx"]; + if (tx_tk.isObject()) { + JsonParserObject tx = tx_tk.getObject(); + // cfg->tx.sample_rate = tx.getUInt(PSTR("SampleRate"), cfg->tx.sample_rate); + cfg->tx.gain = tx.getUInt(PSTR("Gain"), cfg->tx.gain); + cfg->tx.mode = tx.getUInt(PSTR("Mode"), cfg->tx.mode); + cfg->tx.slot_mask = tx.getUInt(PSTR("SlotMask"), cfg->tx.slot_mask); + cfg->tx.slot_config = tx.getUInt(PSTR("SlotConfig"), cfg->tx.slot_config); + cfg->tx.channels = tx.getUInt(PSTR("Channels"), cfg->tx.channels); + cfg->tx.apll = tx.getUInt(PSTR("APLL"), cfg->tx.apll); + } + + JsonParserToken rx_tk = root["Rx"]; + if (rx_tk.isObject()) { + JsonParserObject rx = rx_tk.getObject(); + cfg->rx.sample_rate = rx.getUInt(PSTR("SampleRate"), cfg->rx.sample_rate); + float rx_gain = ((float)cfg->rx.gain) / 16; + rx_gain = rx.getFloat(PSTR("Gain"), rx_gain); + cfg->rx.gain = (uint16_t)(rx_gain * 16); + // cfg->rx.gain = rx.getUInt(PSTR("Gain"), cfg->rx.gain); + cfg->rx.mode = rx.getUInt(PSTR("Mode"), cfg->rx.mode); + cfg->rx.slot_mask = rx.getUInt(PSTR("SlotMask"), cfg->rx.slot_mask); + cfg->rx.slot_config = rx.getUInt(PSTR("SlotConfig"), cfg->rx.slot_config); + cfg->rx.channels = rx.getUInt(PSTR("Channels"), cfg->rx.channels); + cfg->rx.dc_filter_alpha = rx.getUInt(PSTR("DCFilterAlpha"), cfg->rx.dc_filter_alpha); + cfg->rx.lowpass_alpha = rx.getUInt(PSTR("LowpassAlpha"), cfg->rx.lowpass_alpha); + cfg->rx.apll = rx.getUInt(PSTR("APLL"), cfg->rx.apll); + } + I2SSettingsSave(AUDIO_CONFIG_FILENAME); + } + + float mic_gain = ((float)cfg->rx.gain) / 16; + Response_P("{\"" D_PRFX_I2S D_JSON_I2S_CONFIG "\":{" + // Sys + "\"Sys\":{" + "\"Version\":%d," + "\"Duplex\":%d," + "\"Tx\":%d," + "\"Rx\":%d," + "\"Exclusive\":%d," + "\"MclkInv0\":%d," + "\"MclkInv1\":%d," + "\"BclkInv0\":%d," + "\"BclkInv1\":%d," + "\"WsInv0\":%d," + "\"WsInv1\":%d," + "\"Mp3Preallocate\":%d" + "}," + "\"Tx\":{" + "\"SampleRate\":%d," + "\"Gain\":%d," + "\"Mode\":%d," + "\"SlotMask\":%d," + "\"SlotConfig\":%d," + "\"Channels\":%d," + "\"APLL\":%d" + "}," + "\"Rx\":{" + "\"SampleRate\":%d," + "\"Gain\":%_f," + // "\"Gain\":%d," + "\"Mode\":%d," + "\"SlotMask\":%d," + "\"SlotConfig\":%d," + "\"Channels\":%d," + "\"DCFilterAlpha\":%d," + "\"LowpassAlpha\":%d," + "\"APLL\":%d" + "}}}", + cfg->sys.version, + cfg->sys.duplex, + cfg->sys.tx, + cfg->sys.rx, + cfg->sys.exclusive, + cfg->sys.mclk_inv[0], + cfg->sys.mclk_inv[1], + cfg->sys.bclk_inv[0], + cfg->sys.bclk_inv[1], + cfg->sys.ws_inv[0], + cfg->sys.ws_inv[1], + cfg->sys.mp3_preallocate, + // + cfg->tx.sample_rate, + cfg->tx.gain, + cfg->tx.mode, + cfg->tx.slot_mask, + cfg->tx.slot_config, + cfg->tx.channels, + cfg->tx.apll, + // + cfg->rx.sample_rate, + &mic_gain, + // cfg->rx.gain, + cfg->rx.mode, + cfg->rx.slot_mask, + cfg->rx.slot_config, + cfg->rx.channels, + cfg->rx.dc_filter_alpha, + cfg->rx.lowpass_alpha, + cfg->rx.apll + ); +} /*********************************************************************************************\ * microphone related functions \*********************************************************************************************/ @@ -229,7 +366,7 @@ exit: audio_i2s.client.stop(); } - audio_i2s.out->I2sMicDeinit(); + audio_i2s.out->micDeinit(); audio_i2s.mic_stop = 0; audio_i2s.mic_error = error; AddLog(LOG_LEVEL_INFO, PSTR("mp3task result code: %d"), error); @@ -253,7 +390,7 @@ int32_t I2sRecordShine(char *path) { if (audio_i2s.use_stream) { stack = 8000; } - audio_i2s.out->I2sMicInit(); + audio_i2s.out->micInit(); err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1); @@ -299,6 +436,12 @@ void I2SSettingsLoad(const char * config_filename, bool erase) { } else if (TfsLoadFile(config_filename, (uint8_t*)audio_i2s.Settings, sizeof(tI2SSettings))) { AddLog(LOG_LEVEL_INFO, "I2S: config loaded from file '%s'", config_filename); + if ((audio_i2s.Settings->sys.version == 0) || (audio_i2s.Settings->sys.version > AUDIO_SETTINGS_VERSION)) { + delete audio_i2s.Settings; + audio_i2s.Settings = new tI2SSettings(); + AddLog(LOG_LEVEL_DEBUG, "I2S: unsuppoted configuration version, use defaults"); + I2SSettingsSave(config_filename); + } } else { // File system not ready: No flash space reserved for file system @@ -343,7 +486,7 @@ void I2sInit(void) { return; } - I2SSettingsLoad("/.drvset042", false); // load configuration (no-erase) + I2SSettingsLoad(AUDIO_CONFIG_FILENAME, false); // load configuration (no-erase) if (!audio_i2s.Settings) { return; } // fatal error, could not allocate memory for configuration // detect if we need full-duplex on port 0 @@ -469,6 +612,7 @@ void I2sInit(void) { i2s->setRxFreq(audio_i2s.Settings->rx.sample_rate); i2s->setRxChannels(audio_i2s.Settings->rx.channels); i2s->setRate(audio_i2s.Settings->rx.sample_rate); + i2s->setRxGain(audio_i2s.Settings->rx.gain); } if (i2s->startI2SChannel(tx, rx)) { @@ -499,13 +643,13 @@ void I2sInit(void) { } audio_i2s.mp3ram = nullptr; - if (audio_i2s.Settings->rx.mp3_preallocate == 1){ + if (audio_i2s.Settings->sys.mp3_preallocate == 1){ // if (UsePSRAM()) { AddLog(LOG_LEVEL_DEBUG,PSTR("I2S: will allocate buffer for mp3 encoder")); audio_i2s.mp3ram = special_malloc(preallocateCodecSize); // } // else{ - // audio_i2s.Settings->rx.mp3_preallocate = 0; // no PS-RAM -> no MP3 encoding + // audio_i2s.Settings->sys.mp3_preallocate = 0; // no PS-RAM -> no MP3 encoding // } } AddLog(LOG_LEVEL_DEBUG, "I2S: I2sInit done"); @@ -677,14 +821,20 @@ void CmndI2SMic(void) { esp_err_t err = ESP_OK; if (audio_i2s.decoder || audio_i2s.mp3) return; - audio_i2s.in->I2sMicInit(); + audio_i2s.in->micInit(); 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); + int32_t btr = audio_i2s.in->readMic(buf, sizeof(buf), true /*dc_block*/, false /*apply_gain*/, true /*lowpass*/); + if (btr < 0) { + AddLog(LOG_LEVEL_INFO, "I2S: Mic (err:%i)", -btr); + ResponseCmndChar("I2S Mic read error"); + return; + } + // 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 (%i) %*_H", btr, btr, buf); } // err = xTaskCreatePinnedToCore(I2sMicTask, "MIC", stack, NULL, 3, &audio_i2s.mic_task_handle, 1); @@ -765,7 +915,7 @@ void CmndI2SI2SRtttl(void) { } void CmndI2SMicRec(void) { - if (audio_i2s.Settings->rx.mp3_preallocate == 1) { + if (audio_i2s.Settings->sys.mp3_preallocate == 1) { if (XdrvMailbox.data_len > 0) { if (!strncmp(XdrvMailbox.data, "-?", 2)) { Response_P("{\"I2SREC-duration\":%d}", audio_i2s.recdur); 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 d77a71d9c..c2d034e10 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_audio.ino @@ -327,13 +327,13 @@ extern "C" { // 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(); + in->micInit(); return in->getRxRunning(); } // AudioInputI2S.stop() -> bool int be_audio_input_i2s_stop(TasmotaI2S* in) { - in->I2sMicDeinit(); + in->micDeinit(); return true; } @@ -360,10 +360,25 @@ extern "C" { // AudioInputI2S.set_gain(gain:real) -> bool int be_audio_input_set_gain(TasmotaI2S* in, float gain) { - return true; // TODO - // return in->SetGain(gain); + in->setRxGain(gain); + return true; } + // AudioInputI2S.get_dc_offset() -> float + int be_audio_input_i2s_get_dc_offset(TasmotaI2S* in) { + return in->getRxDCOffset(); + } + + // AudioInputI2S.get_lowpass_alpha() -> float + float be_audio_input_i2s_get_lowpass_alpha(TasmotaI2S* in) { + return in->getRxLowpassAlpha(); + } + + // AudioInputI2S.set_lowpass_alpha(alpha:float) -> bool + int be_audio_input_i2s_set_lowpass_alpha(TasmotaI2S* in, float alpha) { + in->setRxLowpassAlpha(alpha); + return true; + } // AudioInputI2S.read_bytes() -> bytes() // @@ -374,15 +389,16 @@ extern "C" { // 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]; + be_getmember(vm, 1, ".p"); + TasmotaI2S *in = (TasmotaI2S*) be_tocomptr(vm, -1); + be_pop(vm, 1); - 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); } + if (!in->getRxRunning()) { be_return_nil(vm); } + + uint16_t buf_audio[512]; + int32_t btr = in->readMic((uint8_t*)buf_audio, sizeof(buf_audio), true /*dc_block*/, true /*apply_gain*/, true /*lowpass*/); + if (btr <= 0) { be_return_nil(vm); } // we received some data if (argc >= 2 && be_isbytes(vm, 2)) { @@ -391,11 +407,11 @@ extern "C" { // resize to btr be_getmember(vm, -1, "resize"); be_pushvalue(vm, -2); - be_pushint(vm, bytes_read); + be_pushint(vm, btr); be_call(vm, 2); be_pop(vm, 3); } else { - be_pushbytes(vm, nullptr, bytes_read); // allocate a buffer of size btr filled with zeros + be_pushbytes(vm, nullptr, btr); // allocate a buffer of size btr filled with zeros } // get the address of the buffer @@ -406,7 +422,7 @@ extern "C" { be_pop(vm, 2); // copy - memmove(buf, buf_audio, bytes_read); + memmove(buf, buf_audio, btr); be_return(vm); /* return code */ }