// Disable the automatic pin remapping of the API calls in this file #define ARDUINO_CORE_BUILD #include "ESP_I2S.h" #if SOC_I2S_SUPPORTED #include "esp32-hal-periman.h" #include "wav_header.h" #if ARDUINO_HAS_MP3_DECODER #include "mp3dec.h" #endif #if SOC_I2S_HW_VERSION_2 #undef I2S_STD_CLK_DEFAULT_CONFIG #define I2S_STD_CLK_DEFAULT_CONFIG(rate) \ { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, } #endif #define I2S_READ_CHUNK_SIZE 1920 #define I2S_DEFAULT_CFG() \ { \ .id = I2S_NUM_AUTO, .role = I2S_ROLE_MASTER, .dma_desc_num = 6, .dma_frame_num = 240, .auto_clear = true, .auto_clear_before_cb = false, \ .intr_priority = 0 \ } #define I2S_STD_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ { \ .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ .gpio_cfg = { \ .mclk = (gpio_num_t)_mclk, \ .bclk = (gpio_num_t)_bclk, \ .ws = (gpio_num_t)_ws, \ .dout = (gpio_num_t)_dout, \ .din = (gpio_num_t)_din, \ .invert_flags = \ { \ .mclk_inv = _mclk_inv, \ .bclk_inv = _bclk_inv, \ .ws_inv = _ws_inv, \ }, \ }, \ } #if SOC_I2S_SUPPORTS_TDM #define I2S_TDM_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode, _mask) \ { \ .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_TDM_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode, _mask), \ .gpio_cfg = { \ .mclk = (gpio_num_t)_mclk, \ .bclk = (gpio_num_t)_bclk, \ .ws = (gpio_num_t)_ws, \ .dout = (gpio_num_t)_dout, \ .din = (gpio_num_t)_din, \ .invert_flags = \ { \ .mclk_inv = _mclk_inv, \ .bclk_inv = _bclk_inv, \ .ws_inv = _ws_inv, \ }, \ }, \ } #endif #if SOC_I2S_SUPPORTS_PDM_TX #if (SOC_I2S_PDM_MAX_TX_LINES > 1) #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ { \ .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ .gpio_cfg = { \ .clk = (gpio_num_t)_tx_clk, \ .dout = (gpio_num_t)_tx_dout0, \ .dout2 = (gpio_num_t)_tx_dout1, \ .invert_flags = \ { \ .clk_inv = _tx_clk_inv, \ }, \ }, \ } #else #define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ { \ .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ .gpio_cfg = { \ .clk = (gpio_num_t)_tx_clk, \ .dout = (gpio_num_t)_tx_dout0, \ .invert_flags = \ { \ .clk_inv = _tx_clk_inv, \ }, \ }, \ } #endif #endif #if SOC_I2S_SUPPORTS_PDM_RX #if (SOC_I2S_PDM_MAX_RX_LINES > 1) #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ { \ .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ .gpio_cfg = { \ .clk = (gpio_num_t)_rx_clk, \ .dins = \ { \ (gpio_num_t)_rx_din0, \ (gpio_num_t)_rx_din1, \ (gpio_num_t)_rx_din2, \ (gpio_num_t)_rx_din3, \ }, \ .invert_flags = \ { \ .clk_inv = _rx_clk_inv, \ }, \ }, \ } #else #define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode) \ { \ .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \ .gpio_cfg = { \ .clk = (gpio_num_t)_rx_clk, \ .din = (gpio_num_t)_rx_din0, \ .invert_flags = \ { \ .clk_inv = _rx_clk_inv, \ }, \ }, \ } #endif #endif #define I2S_ERROR_CHECK_RETURN(x, r) \ do { \ last_error = (x); \ if (unlikely(last_error != ESP_OK)) { \ log_e("ERROR: %s", esp_err_to_name(last_error)); \ return (r); \ } \ } while (0) #define I2S_ERROR_CHECK_RETURN_FALSE(x) I2S_ERROR_CHECK_RETURN(x, false) // Default read, no resmpling and temp buffer necessary static esp_err_t i2s_channel_read_default(i2s_chan_handle_t handle, char *tmp_buf, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) { return i2s_channel_read(handle, (char *)dst, len, bytes_read, timeout_ms); } // Resample the 32bit SPH0645 microphone data into 16bit. SPH0645 is actually 18 bit, but this trick helps save some space static esp_err_t i2s_channel_read_32_to_16(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) { size_t out_len = 0; size_t read_buff_len = len * 2; if (read_buff == NULL) { log_e("Temp buffer is NULL!"); return ESP_FAIL; } esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); if (err != ESP_OK) { *bytes_read = 0; return err; } out_len /= 4; uint16_t *ds = (uint16_t *)dst; uint32_t *src = (uint32_t *)read_buff; for (size_t i = 0; i < out_len; i++) { ds[i] = src[i] >> 16; } *bytes_read = out_len * 2; return ESP_OK; } // Resample the 16bit stereo microphone data into 16bit mono. static esp_err_t i2s_channel_read_16_stereo_to_mono(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) { size_t out_len = 0; size_t read_buff_len = len * 2; if (read_buff == NULL) { log_e("Temp buffer is NULL!"); return ESP_FAIL; } esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms); if (err != ESP_OK) { *bytes_read = 0; return err; } out_len /= 2; uint16_t *ds = (uint16_t *)dst; uint16_t *src = (uint16_t *)read_buff; for (size_t i = 0; i < out_len; i += 2) { *ds++ = src[i]; } *bytes_read = out_len; return ESP_OK; } I2SClass::I2SClass() { last_error = ESP_OK; _mode = I2S_MODE_MAX; // Initialize to invalid mode to indicate I2S not started tx_chan = NULL; tx_sample_rate = 0; tx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; tx_slot_mode = I2S_SLOT_MODE_STEREO; rx_fn = i2s_channel_read_default; rx_transform = I2S_RX_TRANSFORM_NONE; rx_transform_buf = NULL; rx_transform_buf_len = 0; rx_chan = NULL; rx_sample_rate = 0; rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; rx_slot_mode = I2S_SLOT_MODE_STEREO; _mclk = -1; _bclk = -1; _ws = -1; _dout = -1; _din = -1; _mclk_inv = false; _bclk_inv = false; _ws_inv = false; #if SOC_I2S_SUPPORTS_PDM_TX _tx_clk = -1; _tx_dout0 = -1; _tx_dout1 = -1; _tx_clk_inv = false; #endif #if SOC_I2S_SUPPORTS_PDM_RX _rx_clk = -1; _rx_din0 = -1; _rx_din1 = -1; _rx_din2 = -1; _rx_din3 = -1; _rx_clk_inv = false; #endif } I2SClass::~I2SClass() { end(); } bool I2SClass::i2sDetachBus(void *bus_pointer) { I2SClass *bus = (I2SClass *)bus_pointer; // Only call end() if I2S has been initialized (begin() was called) // _mode is set to I2S_MODE_MAX in constructor and to a valid mode in begin() if (bus->_mode < I2S_MODE_MAX) { bus->end(); } return true; } // Set pins for STD and TDM mode void I2SClass::setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din, int8_t mclk) { _mclk = digitalPinToGPIONumber(mclk); _bclk = digitalPinToGPIONumber(bclk); _ws = digitalPinToGPIONumber(ws); _dout = digitalPinToGPIONumber(dout); _din = digitalPinToGPIONumber(din); } void I2SClass::setInverted(bool bclk, bool ws, bool mclk) { _mclk_inv = mclk; _bclk_inv = bclk; _ws_inv = ws; } // Set pins for PDM TX mode #if SOC_I2S_SUPPORTS_PDM_TX void I2SClass::setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1) { _tx_clk = digitalPinToGPIONumber(clk); _tx_dout0 = digitalPinToGPIONumber(dout0); #if (SOC_I2S_PDM_MAX_TX_LINES > 1) _tx_dout1 = digitalPinToGPIONumber(dout1); #endif } #endif // Set pins for PDM RX mode #if SOC_I2S_SUPPORTS_PDM_RX void I2SClass::setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1, int8_t din2, int8_t din3) { _rx_clk = digitalPinToGPIONumber(clk); _rx_din0 = digitalPinToGPIONumber(din0); #if (SOC_I2S_PDM_MAX_RX_LINES > 1) _rx_din1 = digitalPinToGPIONumber(din1); _rx_din2 = digitalPinToGPIONumber(din2); _rx_din3 = digitalPinToGPIONumber(din3); #endif } #endif #if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX void I2SClass::setInvertedPdm(bool clk) { #if SOC_I2S_SUPPORTS_PDM_TX _tx_clk_inv = clk; #endif #if SOC_I2S_SUPPORTS_PDM_RX _rx_clk_inv = clk; #endif } #endif bool I2SClass::initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) { // Peripheral manager deinit previous peripheral if pin was used if (_mclk >= 0) { if (!perimanClearPinBus(_mclk)) { return false; } } if (_bclk >= 0) { if (!perimanClearPinBus(_bclk)) { return false; } } if (_ws >= 0) { if (!perimanClearPinBus(_ws)) { return false; } } if (_dout >= 0) { if (!perimanClearPinBus(_dout)) { return false; } } if (_din >= 0) { if (!perimanClearPinBus(_din)) { return false; } } // Set peripheral manager detach function for I2S if (_mclk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_MCLK, I2SClass::i2sDetachBus); } if (_bclk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_BCLK, I2SClass::i2sDetachBus); } if (_ws >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_WS, I2SClass::i2sDetachBus); } if (_dout >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_DOUT, I2SClass::i2sDetachBus); } if (_din >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_DIN, I2SClass::i2sDetachBus); } // I2S configuration i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); if (_dout >= 0 && _din >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); } else if (_dout >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); } else if (_din >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); } i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); if (slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask <= I2S_STD_SLOT_BOTH) { i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask; } if (tx_chan != NULL) { tx_sample_rate = rate; tx_data_bit_width = bits_cfg; tx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(tx_chan, &i2s_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); } if (rx_chan != NULL) { rx_sample_rate = rate; rx_data_bit_width = bits_cfg; rx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(rx_chan, &i2s_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); } // Peripheral manager set bus type to I2S if (_mclk >= 0) { if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_STD_MCLK, (void *)(this), -1, -1)) { goto err; } } if (_bclk >= 0) { if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_STD_BCLK, (void *)(this), -1, -1)) { goto err; } } if (_ws >= 0) { if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_STD_WS, (void *)(this), -1, -1)) { goto err; } } if (_dout >= 0) { if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_STD_DOUT, (void *)(this), -1, -1)) { goto err; } } if (_din >= 0) { if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_STD_DIN, (void *)(this), -1, -1)) { goto err; } } return true; err: log_e("Failed to set all pins bus to I2S_STD"); I2SClass::i2sDetachBus((void *)(this)); return false; } #if SOC_I2S_SUPPORTS_TDM bool I2SClass::initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) { // Peripheral manager deinit previous peripheral if pin was used if (_mclk >= 0) { if (!perimanClearPinBus(_mclk)) { return false; } } if (_bclk >= 0) { if (!perimanClearPinBus(_bclk)) { return false; } } if (_ws >= 0) { if (!perimanClearPinBus(_ws)) { return false; } } if (_dout >= 0) { if (!perimanClearPinBus(_dout)) { return false; } } if (_din >= 0) { if (!perimanClearPinBus(_din)) { return false; } } // Set peripheral manager detach function for I2S if (_mclk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_MCLK, I2SClass::i2sDetachBus); } if (_bclk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_BCLK, I2SClass::i2sDetachBus); } if (_ws >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_WS, I2SClass::i2sDetachBus); } if (_dout >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_DOUT, I2SClass::i2sDetachBus); } if (_din >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_DIN, I2SClass::i2sDetachBus); } // I2S configuration i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); if (_dout >= 0 && _din >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); } else if (_dout >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); } else if (_din >= 0) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); } i2s_tdm_config_t i2s_tdm_config = I2S_TDM_CHAN_CFG(rate, bits_cfg, ch, (i2s_tdm_slot_mask_t)slot_mask); if (tx_chan != NULL) { tx_sample_rate = rate; tx_data_bit_width = bits_cfg; tx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(tx_chan, &i2s_tdm_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); } if (rx_chan != NULL) { rx_sample_rate = rate; rx_data_bit_width = bits_cfg; rx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(rx_chan, &i2s_tdm_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); } // Peripheral manager set bus type to I2S if (_mclk >= 0) { if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_TDM_MCLK, (void *)(this), -1, -1)) { goto err; } } if (_bclk >= 0) { if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_TDM_BCLK, (void *)(this), -1, -1)) { goto err; } } if (_ws >= 0) { if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_TDM_WS, (void *)(this), -1, -1)) { goto err; } } if (_dout >= 0) { if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_TDM_DOUT, (void *)(this), -1, -1)) { goto err; } } if (_din >= 0) { if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_TDM_DIN, (void *)(this), -1, -1)) { goto err; } } return true; err: log_e("Failed to set all pins bus to I2S_TDM"); I2SClass::i2sDetachBus((void *)(this)); return false; } #endif #if SOC_I2S_SUPPORTS_PDM_TX bool I2SClass::initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch) { // Peripheral manager deinit previous peripheral if pin was used if (_tx_clk >= 0) { if (!perimanClearPinBus(_tx_clk)) { return false; } } if (_tx_dout0 >= 0) { if (!perimanClearPinBus(_tx_dout0)) { return false; } } if (_tx_dout1 >= 0) { if (!perimanClearPinBus(_tx_dout1)) { return false; } } // Set peripheral manager detach function for I2S if (_tx_clk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_CLK, I2SClass::i2sDetachBus); } if (_tx_dout0 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_DOUT0, I2SClass::i2sDetachBus); } if (_tx_dout1 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_DOUT1, I2SClass::i2sDetachBus); } // I2S configuration i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL)); i2s_pdm_tx_config_t i2s_pdm_tx_config = I2S_PDM_TX_CHAN_CFG(rate, bits_cfg, ch); if (tx_chan != NULL) { tx_sample_rate = rate; tx_data_bit_width = bits_cfg; tx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_tx_mode(tx_chan, &i2s_pdm_tx_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); } // Peripheral manager set bus type to I2S if (_tx_clk >= 0) { if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_I2S_PDM_TX_CLK, (void *)(this), -1, -1)) { goto err; } } if (_tx_dout0 >= 0) { if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_I2S_PDM_TX_DOUT0, (void *)(this), -1, -1)) { goto err; } } if (_tx_dout1 >= 0) { if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_I2S_PDM_TX_DOUT1, (void *)(this), -1, -1)) { goto err; } } return true; err: log_e("Failed to set all pins bus to I2S_TDM"); I2SClass::i2sDetachBus((void *)(this)); return false; } #endif #if SOC_I2S_SUPPORTS_PDM_RX bool I2SClass::initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch) { // Peripheral manager deinit previous peripheral if pin was used if (_rx_clk >= 0) { if (!perimanClearPinBus(_rx_clk)) { return false; } } if (_rx_din0 >= 0) { if (!perimanClearPinBus(_rx_din0)) { return false; } } if (_rx_din1 >= 0) { if (!perimanClearPinBus(_rx_din1)) { return false; } } if (_rx_din2 >= 0) { if (!perimanClearPinBus(_rx_din2)) { return false; } } if (_rx_din3 >= 0) { if (!perimanClearPinBus(_rx_din3)) { return false; } } // Set peripheral manager detach function for I2S if (_rx_clk >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_CLK, I2SClass::i2sDetachBus); } if (_rx_din0 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN0, I2SClass::i2sDetachBus); } if (_rx_din1 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN1, I2SClass::i2sDetachBus); } if (_rx_din2 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN2, I2SClass::i2sDetachBus); } if (_rx_din3 >= 0) { perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN3, I2SClass::i2sDetachBus); } // I2S configuration i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(); I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan)); i2s_pdm_rx_config_t i2s_pdf_rx_config = I2S_PDM_RX_CHAN_CFG(rate, bits_cfg, ch); if (rx_chan != NULL) { rx_sample_rate = rate; rx_data_bit_width = bits_cfg; rx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_rx_mode(rx_chan, &i2s_pdf_rx_config)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); } // Peripheral manager set bus type to I2S if (_rx_clk >= 0) { if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_I2S_PDM_RX_CLK, (void *)(this), -1, -1)) { goto err; } } if (_rx_din0 >= 0) { if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_I2S_PDM_RX_DIN0, (void *)(this), -1, -1)) { goto err; } } if (_rx_din1 >= 0) { if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_I2S_PDM_RX_DIN1, (void *)(this), -1, -1)) { goto err; } } if (_rx_din2 >= 0) { if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_I2S_PDM_RX_DIN2, (void *)(this), -1, -1)) { goto err; } } if (_rx_din3 >= 0) { if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_I2S_PDM_RX_DIN3, (void *)(this), -1, -1)) { goto err; } } return true; err: log_e("Failed to set all pins bus to I2S_TDM"); I2SClass::i2sDetachBus((void *)(this)); return false; } #endif bool I2SClass::begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) { /* Setup I2S peripheral */ if (mode >= I2S_MODE_MAX) { log_e("Invalid I2S mode selected."); return false; } _mode = mode; bool init = false; switch (_mode) { case I2S_MODE_STD: init = initSTD(rate, bits_cfg, ch, slot_mask); break; #if SOC_I2S_SUPPORTS_TDM case I2S_MODE_TDM: init = initTDM(rate, bits_cfg, ch, slot_mask); break; #endif #if SOC_I2S_SUPPORTS_PDM_TX case I2S_MODE_PDM_TX: init = initPDMtx(rate, bits_cfg, ch); break; #endif #if SOC_I2S_SUPPORTS_PDM_RX case I2S_MODE_PDM_RX: init = initPDMrx(rate, bits_cfg, ch); break; #endif default: break; } if (init == false) { log_e("I2S initialization failed."); return false; } return true; } bool I2SClass::end() { // Check if already ended to prevent recursion if (_mode >= I2S_MODE_MAX) { return true; } // Save mode and reset it BEFORE clearing pins to prevent recursive calls // When perimanClearPinBus() is called, it may trigger i2sDetachBus() again i2s_mode_t mode = _mode; _mode = I2S_MODE_MAX; if (tx_chan != NULL) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(tx_chan)); tx_chan = NULL; } if (rx_chan != NULL) { I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(rx_chan)); rx_chan = NULL; } if (rx_transform_buf != NULL) { free(rx_transform_buf); rx_transform_buf = NULL; rx_transform_buf_len = 0; } //Peripheral manager deinit used pins switch (mode) { case I2S_MODE_STD: #if SOC_I2S_SUPPORTS_TDM case I2S_MODE_TDM: #endif if (_mclk >= 0) { perimanClearPinBus(_mclk); } if (_bclk >= 0) { perimanClearPinBus(_bclk); } if (_ws >= 0) { perimanClearPinBus(_ws); } if (_dout >= 0) { perimanClearPinBus(_dout); } if (_din >= 0) { perimanClearPinBus(_din); } break; #if SOC_I2S_SUPPORTS_PDM_TX case I2S_MODE_PDM_TX: perimanClearPinBus(_tx_clk); if (_tx_dout0 >= 0) { perimanClearPinBus(_tx_dout0); } if (_tx_dout1 >= 0) { perimanClearPinBus(_tx_dout1); } break; #endif #if SOC_I2S_SUPPORTS_PDM_RX case I2S_MODE_PDM_RX: perimanClearPinBus(_rx_clk); if (_rx_din0 >= 0) { perimanClearPinBus(_rx_din0); } if (_rx_din1 >= 0) { perimanClearPinBus(_rx_din1); } if (_rx_din2 >= 0) { perimanClearPinBus(_rx_din2); } if (_rx_din3 >= 0) { perimanClearPinBus(_rx_din3); } break; #endif default: break; } return true; } bool I2SClass::configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) { /* Setup I2S channels */ if (tx_chan != NULL) { if (tx_sample_rate == rate && tx_data_bit_width == bits_cfg && tx_slot_mode == ch) { return true; } i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); if (slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask <= I2S_STD_SLOT_BOTH) { i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask; } I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(tx_chan, &i2s_config.clk_cfg)); tx_sample_rate = rate; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(tx_chan, &i2s_config.slot_cfg)); tx_data_bit_width = bits_cfg; tx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan)); return true; } return false; } bool I2SClass::configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform) { /* Setup I2S channels */ if (rx_chan != NULL) { if (rx_sample_rate != rate || rx_data_bit_width != bits_cfg || rx_slot_mode != ch) { i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan)); I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(rx_chan, &i2s_config.clk_cfg)); rx_sample_rate = rate; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg)); rx_data_bit_width = bits_cfg; rx_slot_mode = ch; I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan)); return transformRX(transform); } if (rx_transform != transform) { return transformRX(transform); } return true; } return false; } size_t I2SClass::readBytes(char *buffer, size_t size) { size_t bytes_read = 0; size_t bytes_to_read = 0; size_t total_size = 0; last_error = ESP_FAIL; if (rx_chan == NULL) { return total_size; } while (total_size < size) { bytes_read = 0; bytes_to_read = size - total_size; if (rx_transform_buf != NULL && bytes_to_read > I2S_READ_CHUNK_SIZE) { bytes_to_read = I2S_READ_CHUNK_SIZE; } I2S_ERROR_CHECK_RETURN(rx_fn(rx_chan, rx_transform_buf, (char *)(buffer + total_size), bytes_to_read, &bytes_read, _timeout), 0); total_size += bytes_read; } return total_size; } size_t I2SClass::write(const uint8_t *buffer, size_t size) { size_t written = 0; size_t bytes_sent = 0; size_t bytes_to_send = 0; last_error = ESP_FAIL; if (tx_chan == NULL) { return written; } while (written < size) { bytes_sent = 0; bytes_to_send = size - written; esp_err_t err = i2s_channel_write(tx_chan, (char *)(buffer + written), bytes_to_send, &bytes_sent, _timeout); setWriteError(err); I2S_ERROR_CHECK_RETURN(err, written); written += bytes_sent; } return written; } i2s_chan_handle_t I2SClass::txChan() { return tx_chan; } uint32_t I2SClass::txSampleRate() { return tx_sample_rate; } i2s_data_bit_width_t I2SClass::txDataWidth() { return tx_data_bit_width; } i2s_slot_mode_t I2SClass::txSlotMode() { return tx_slot_mode; } i2s_chan_handle_t I2SClass::rxChan() { return rx_chan; } uint32_t I2SClass::rxSampleRate() { return rx_sample_rate; } i2s_data_bit_width_t I2SClass::rxDataWidth() { return rx_data_bit_width; } i2s_slot_mode_t I2SClass::rxSlotMode() { return rx_slot_mode; } int I2SClass::lastError() { return (int)last_error; } int I2SClass::available() { if (rx_chan == NULL) { return -1; } return I2S_READ_CHUNK_SIZE; // / (rx_data_bit_width/8); }; int I2SClass::peek() { return -1; }; int I2SClass::read() { int out = 0; if (readBytes((char *)&out, rx_data_bit_width / 8) == (rx_data_bit_width / 8)) { return out; } return -1; }; size_t I2SClass::write(uint8_t d) { return write(&d, 1); } bool I2SClass::transformRX(i2s_rx_transform_t transform) { switch (transform) { case I2S_RX_TRANSFORM_NONE: allocTranformRX(0); rx_fn = i2s_channel_read_default; break; case I2S_RX_TRANSFORM_32_TO_16: if (rx_data_bit_width != I2S_DATA_BIT_WIDTH_32BIT) { log_e("Wrong data width. Should be 32bit"); return false; } if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) { return false; } rx_fn = i2s_channel_read_32_to_16; rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT; break; case I2S_RX_TRANSFORM_16_STEREO_TO_MONO: if (rx_slot_mode != I2S_SLOT_MODE_STEREO) { log_e("Wrong slot mode. Should be Stereo"); return false; } if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) { return false; } rx_fn = i2s_channel_read_16_stereo_to_mono; rx_slot_mode = I2S_SLOT_MODE_MONO; break; default: log_e("Unknown RX Transform %d", transform); return false; } rx_transform = transform; return true; } bool I2SClass::allocTranformRX(size_t buf_len) { char *buf = NULL; if (buf_len == 0) { if (rx_transform_buf != NULL) { free(rx_transform_buf); rx_transform_buf = NULL; rx_transform_buf_len = 0; } return true; } if (rx_transform_buf == NULL || rx_transform_buf_len != buf_len) { buf = (char *)malloc(buf_len); if (buf == NULL) { log_e("malloc %u failed!", buf_len); return false; } if (rx_transform_buf != NULL) { free(rx_transform_buf); } rx_transform_buf = buf; rx_transform_buf_len = buf_len; } return true; } const int WAVE_HEADER_SIZE = PCM_WAV_HEADER_SIZE; //Record PCM WAV with current RX settings uint8_t *I2SClass::recordWAV(size_t rec_seconds, size_t *out_size) { uint32_t sample_rate = rxSampleRate(); uint16_t sample_width = (uint16_t)rxDataWidth(); uint16_t num_channels = (uint16_t)rxSlotMode(); size_t rec_size = rec_seconds * ((sample_rate * (sample_width / 8)) * num_channels); const pcm_wav_header_t wav_header = PCM_WAV_HEADER_DEFAULT(rec_size, sample_width, sample_rate, num_channels); *out_size = 0; log_d("Record WAV: rate:%lu, bits:%u, channels:%u, size:%lu", sample_rate, sample_width, num_channels, rec_size); uint8_t *wav_buf = (uint8_t *)malloc(rec_size + WAVE_HEADER_SIZE); if (wav_buf == NULL) { log_e("Failed to allocate WAV buffer with size %u", rec_size + WAVE_HEADER_SIZE); return NULL; } memcpy(wav_buf, &wav_header, WAVE_HEADER_SIZE); size_t wav_size = readBytes((char *)(wav_buf + WAVE_HEADER_SIZE), rec_size); if (wav_size < rec_size) { log_e("Recorded %u bytes from %u", wav_size, rec_size); } else if (lastError()) { log_e("Read Failed! %d", lastError()); } else { *out_size = rec_size + WAVE_HEADER_SIZE; return wav_buf; } free(wav_buf); return NULL; } void I2SClass::playWAV(uint8_t *data, size_t len) { pcm_wav_header_t *header = (pcm_wav_header_t *)data; if (header->fmt_chunk.audio_format != 1) { log_e("Audio format is not PCM!"); return; } wav_data_chunk_t *data_chunk = &header->data_chunk; size_t data_offset = 0; while (memcmp(data_chunk->subchunk_id, "data", 4) != 0) { log_d( "Skip chunk: %c%c%c%c, len: %lu", data_chunk->subchunk_id[0], data_chunk->subchunk_id[1], data_chunk->subchunk_id[2], data_chunk->subchunk_id[3], data_chunk->subchunk_size + 8 ); data_offset += data_chunk->subchunk_size + 8; data_chunk = (wav_data_chunk_t *)(data + WAVE_HEADER_SIZE + data_offset - 8); } log_d( "Play WAV: rate:%lu, bits:%d, channels:%d, size:%lu", header->fmt_chunk.sample_rate, header->fmt_chunk.bits_per_sample, header->fmt_chunk.num_of_channels, data_chunk->subchunk_size ); configureTX(header->fmt_chunk.sample_rate, (i2s_data_bit_width_t)header->fmt_chunk.bits_per_sample, (i2s_slot_mode_t)header->fmt_chunk.num_of_channels); write(data + WAVE_HEADER_SIZE + data_offset, data_chunk->subchunk_size); } #if ARDUINO_HAS_MP3_DECODER bool I2SClass::playMP3(uint8_t *src, size_t src_len) { int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP]; uint8_t *readPtr = NULL; int bytesAvailable = 0, err = 0, offset = 0; MP3FrameInfo frameInfo; HMP3Decoder decoder = NULL; bytesAvailable = src_len; readPtr = src; decoder = MP3InitDecoder(); if (decoder == NULL) { log_e("Could not allocate decoder"); return false; } do { offset = MP3FindSyncWord(readPtr, bytesAvailable); if (offset < 0) { break; } readPtr += offset; bytesAvailable -= offset; err = MP3Decode(decoder, &readPtr, &bytesAvailable, outBuf, 0); if (err) { log_e("Decode ERROR: %d", err); MP3FreeDecoder(decoder); return false; } else { MP3GetLastFrameInfo(decoder, &frameInfo); configureTX(frameInfo.samprate, (i2s_data_bit_width_t)frameInfo.bitsPerSample, (i2s_slot_mode_t)frameInfo.nChans); write((uint8_t *)outBuf, (size_t)((frameInfo.bitsPerSample / 8) * frameInfo.outputSamps)); } } while (true); MP3FreeDecoder(decoder); return true; } #endif #endif /* SOC_I2S_SUPPORTED */