Files
xiaozhi-esp32/main/boards/esp-hi/adc_pdm_audio_codec.cc
T
3e78cd73bd v2.4.0: Migrate to IDF 6.0 (#2120)
* Migrate board builds to ESP-IDF 6.0.1

* Document upstream IDF 6 CI blockers

* Use IDF 6 patched registry components

* Fix board-specific ESP-IDF 6 build errors

* Fix remaining ESP-IDF 6 board builds

* Document final ESP-IDF 6 matrix results [skip ci]

* update IDF 6 component releases

* rebase IDF 6 migration and use upstream SSCMA

* Enhance audio input management in AudioService

- Introduced AS_EVENT_AUDIO_INPUT_STOP_REQUEST to manage audio input stopping more effectively.
- Updated AudioService::Start() to clear the new stop request event.
- Modified AudioService::AudioInputTask() to handle the stop request and ensure proper input disabling.
- Adjusted AdcPdmAudioCodec::Start() to reflect lazy input opening, ensuring start/stop operations run in the same task.

* Refactor audio codec management and configuration

- Added output_device_opened_ flag to track the state of the output device in Es8388 and Es8389 codecs.
- Updated EnableOutput method to prevent unnecessary device reopening and added mute functionality to manage audio output more effectively.
- Enhanced error handling in Es8311AudioCodec by ensuring proper closure and deletion of the codec device.
- Adjusted sample rates in board configurations for atk-dnesp32s3-box2 to 24000 Hz and introduced AUDIO_CODEC_USE_MCLK for improved clock management.

* Update build configurations and documentation for ESP-IDF 6 compatibility

- Added container specification for the build workflow using espressif/idf:v6.0.1.
- Updated the version of the espressif/esp_video component to ^2.3.0.
- Enhanced documentation to clarify the support status of ESP32-P4 variants, specifying that legacy Rev < 3 variants are excluded from the IDF 6 matrix.
- Adjusted board configurations to include IDF version constraints and necessary SDK configurations for legacy support.
- Improved handling of YUV formats in EspVideo to maintain compatibility with previous versions.

* Implement playback drained event handling in Application

- Added MAIN_EVENT_PLAYBACK_DRAINED to manage playback state transitions.
- Introduced callbacks for playback drained events in AudioService to trigger listening state changes.
- Refactored Application::Run() to handle deferred listening start based on playback queue status.
- Enhanced audio processing logic to prevent audio truncation during state changes.
- Updated related methods to ensure proper wake word detection configuration during listening mode.

* Fix variant selection shell in CI

* Update project version to 2.4.0 and adjust component dependencies

- Bump project version in CMakeLists.txt to 2.4.0.
- Change espressif/esp_video component version to ^2.0.1 in idf_component.yml.
- Modify AUDIO_INPUT_REFERENCE setting in config.h to false for m5stack-core-s3.
- Remove unnecessary infinite loops in xmini_c3_board.cc during initialization.

---------

Co-authored-by: Xiaoxia <terrence.huang@tenclass.com>
2026-07-17 13:41:52 +08:00

252 lines
9.1 KiB
C++

#include "adc_pdm_audio_codec.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <driver/i2c.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#include "driver/i2s_pdm.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "hal/rtc_io_hal.h"
#include "hal/gpio_ll.h"
#include "settings.h"
#include "config.h"
static const char TAG[] = "AdcPdmAudioCodec";
#define BSP_I2S_GPIO_CFG(_dout) \
{ \
.clk = GPIO_NUM_NC, \
.dout = _dout, \
.invert_flags = { \
.clk_inv = false, \
}, \
}
/**
* @brief Mono Duplex I2S configuration structure
*
* This configuration is used by default in bsp_audio_init()
*/
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
{ \
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
.gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
}
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
input_reference_ = false;
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
audio_codec_adc_cfg_t cfg = {};
cfg.handle = NULL;
cfg.continuous_cfg.max_store_buf_size = 1024 * 2;
cfg.continuous_cfg.conv_frame_size = 1024;
cfg.continuous_cfg.sample_freq_hz = (uint32_t)input_sample_rate;
cfg.continuous_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1;
cfg.continuous_cfg.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2;
cfg.continuous_cfg.pattern_num = 1;
cfg.continuous_cfg.cfg_mode = AUDIO_CODEC_ADC_CFG_MODE_SINGLE_UNIT;
cfg.continuous_cfg.cfg.single_unit.unit_id = ADC_UNIT_1;
cfg.continuous_cfg.cfg.single_unit.atten = ADC_ATTEN_DB_12;
cfg.continuous_cfg.cfg.single_unit.bit_width = ADC_BITWIDTH_12;
cfg.continuous_cfg.cfg.single_unit.channel_id[0] = (uint8_t)adc_mic_channel;
const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
esp_codec_dev_cfg_t codec_dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.data_if = adc_if,
};
input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
if (!input_dev_) {
ESP_LOGE(TAG, "Failed to create codec device");
return;
}
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = NULL,
.tx_handle = tx_handle_,
};
const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
codec_dev_cfg.codec_if = NULL;
codec_dev_cfg.data_if = i2s_data_if;
output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
output_volume_ = 100;
if(pa_ctl != GPIO_NUM_NC) {
pa_ctrl_pin_ = pa_ctl;
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
}
gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
if(pdm_speak_n != GPIO_NUM_NC){
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO);
gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
}
// 初始化输出定时器
esp_timer_create_args_t output_timer_args = {
.callback = &AdcPdmAudioCodec::OutputTimerCallback,
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "output_timer"
};
ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
}
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
// 删除定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_delete(output_timer_);
output_timer_ = nullptr;
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
}
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void AdcPdmAudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
.sample_rate = (uint32_t)input_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void AdcPdmAudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
// 强制按板卡配置重配PDM TX时钟,覆盖第三方库在set_fmt中的默认up_sample_fs
// 若通道已启用,先禁用再重配,最后再启用
ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 1);
}
// 启用输出时启动定时器
if (output_timer_) {
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
} else {
// 禁用输出时停止定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
}
if(pa_ctrl_pin_ != GPIO_NUM_NC){
gpio_set_level(pa_ctrl_pin_, 0);
}
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
}
AudioCodec::EnableOutput(enable);
}
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
// 重置输出定时器
if (output_timer_) {
esp_timer_stop(output_timer_);
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
}
}
return samples;
}
void AdcPdmAudioCodec::Start() {
Settings settings("audio", false);
output_volume_ = settings.GetInt("output_volume", output_volume_);
if (output_volume_ <= 0) {
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
output_volume_ = 10;
}
// Input is opened lazily by AudioService::AudioInputTask. ESP-IDF's ADC
// continuous driver requires start and stop to run in the same task.
EnableOutput(true);
ESP_LOGI(TAG, "Audio codec started");
}
// 定时器回调函数实现
void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
if (codec && codec->output_enabled_) {
codec->EnableOutput(false);
}
}