Audio refactoring, filters and I2SConfig (#19662)

This commit is contained in:
s-hadinger 2023-10-03 22:39:02 +02:00 committed by GitHub
parent e32c39f21d
commit 473a8ee999
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 364 additions and 125 deletions

View File

@ -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 */

View File

@ -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 ""

View File

@ -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 {

View File

@ -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<samples_count; i++) {
sum_samples += samples[i];
}
_rx_dc_offset = sum_samples / samples_count;
_rx_dc_x_prev = _rx_dc_offset;
_rx_dc_y_prev = 0;
}
// apply dc_blocking filter
for (uint32_t i=0; i<samples_count; i++) {
samples[i] = dcFilter(samples[i]);
}
_rx_dc_offset = _rx_dc_x_prev - _rx_dc_y_prev; // update new offset
}
// apply gain
if (apply_gain) {
for (uint32_t i=0; i<samples_count; i++) {
int32_t val = (((int32_t)samples[i]) * _rx_gain) / 0x10;
// clipping if overflow
if (val > 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<samples_count; i++) {
samples[i] = lowpassFilter(samples[i]);
}
}
return btr;
}
// DC Blocking filter
// 1 - 2^(-7) = 0.9921875
// binary: 0.111111011111111 Q1.15
int16_t TasmotaI2S::dcFilter(int16_t pcm_in) {
#define A1 32511 // (1-2^(-7)) Q32:1.31
int16_t delta_x;
int32_t a1_y_prev;
int16_t pcm_out;
delta_x = pcm_in - _rx_dc_x_prev;
a1_y_prev = (A1 * (int32_t)_rx_dc_y_prev) / 0x8000;
pcm_out = delta_x + (int16_t)a1_y_prev;
_rx_dc_x_prev = pcm_in;
_rx_dc_y_prev = pcm_out;
return pcm_out;
}
// 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
// 1 - alpha = 0x3A9C
int16_t TasmotaI2S::lowpassFilter(int16_t pcm_in) {
// const int32_t alpha = 0x4537;
// const int32_t one_alpha = 0x3A9C;
int32_t a1_y_prev;
int16_t pcm_out;
a1_y_prev = (_rx_lowpass_alpha * _rx_lowpass_y_prev) / 0x8000 + (_rx_lowpass_one_alpha * pcm_in) / 0x8000;
pcm_out = (int16_t)a1_y_prev;
_rx_lowpass_y_prev = pcm_out;
return pcm_out;
}
#endif // USE_I2S_AUDIO
#endif // defined(ESP32) && ESP_IDF_VERSION_MAJOR >= 5

View File

@ -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);

View File

@ -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 */
}