11 KiB
11 KiB
AEC语音打断功能移植方案:Airhub_Rtc_h → Kapi_Rtc
1. 可行性总结
经过全面分析,在不启用ADF架构的情况下,将Airhub_Rtc_h项目中基于双麦克风和ES7210实现的AEC语音打断功能移植到Kapi_Rtc项目是技术可行的。主要依据如下:
- Kapi_Rtc项目已集成ESP-SR组件,提供了独立于ADF的AEC和VAD API
- 两个项目均支持ESP32_S3_KORVO2_V3开发板,硬件兼容性良好
- Kapi_Rtc已有AudioProcessor类可扩展以支持AEC功能
- 两个项目都支持ES7210编解码器,配置方式可兼容
2. 移植实现方案
2.1 ES7210双麦克风配置移植
-
创建ES7210配置初始化模块:
// 在Kapi_Rtc中创建es7210_mic_config.h/cpp文件 #include "es7210_adc.h" #define ES7210_MIC_COMBO_MIC1_MIC3 (ES7210_INPUT_MIC1 | ES7210_INPUT_MIC3) // 双麦克风配置 esp_err_t init_es7210_with_dual_mic(const audio_codec_ctrl_if_t *ctrl_if) { es7210_codec_cfg_t es7210_cfg = { .ctrl_if = ctrl_if, .master_mode = false, .mic_selected = ES7210_MIC_COMBO_MIC1_MIC3, .mclk_src = ES7210_MCLK_FROM_PAD, .mclk_div = 256 }; const audio_codec_if_t *es7210_codec = es7210_codec_new(&es7210_cfg); if (es7210_codec == NULL) { return ESP_FAIL; } // 设置麦克风增益为30dB es7210_codec->set_mic_gain(es7210_codec, GAIN_30DB); return ESP_OK; } -
在AudioCodec基类中增加ES7210配置支持:
// 修改AudioCodec类,增加双麦克风配置接口 class AudioCodec { public: // 现有接口... virtual bool configureDualMicrophone() = 0; virtual bool setMicrophoneGain(int gain_db) = 0; }; -
在具体编解码器实现中添加ES7210配置:
bool Es7210AudioCodec::configureDualMicrophone() { return (init_es7210_with_dual_mic(ctrl_if_) == ESP_OK); }
2.2 AEC功能集成到AudioProcessor
-
扩展AudioProcessor类:
// 修改audio_processor.h class AudioProcessor { private: aec_handle_t *aec_instance_ = nullptr; std::vector<int16_t> reference_buffer_; public: // 现有方法... // 添加AEC相关方法 void InitializeAEC(int sample_rate, int channels); void ProcessWithAEC(const std::vector<int16_t>& mic_data, const std::vector<int16_t>& ref_data, std::vector<int16_t>& out_data); void SetReferenceAudio(const std::vector<int16_t>& ref_data); }; -
实现AEC功能:
// 在audio_processor.cc中实现 void AudioProcessor::InitializeAEC(int sample_rate, int channels) { // 使用ESP-SR的AEC API,不依赖ADF aec_instance_ = aec_create(sample_rate, 4, channels, AEC_MODE_SR_LOW_COST); if (aec_instance_ == nullptr) { ESP_LOGE(TAG, "Failed to initialize AEC"); } else { ESP_LOGI(TAG, "AEC initialized successfully"); } } void AudioProcessor::SetReferenceAudio(const std::vector<int16_t>& ref_data) { reference_buffer_ = ref_data; } void AudioProcessor::ProcessWithAEC(const std::vector<int16_t>& mic_data, const std::vector<int16_t>& ref_data, std::vector<int16_t>& out_data) { if (aec_instance_ == nullptr) { // 如果AEC初始化失败,直接返回麦克风数据 out_data = mic_data; return; } // 确保输出缓冲区足够大 out_data.resize(mic_data.size()); // 调用AEC处理函数 size_t processed_size = aec_process(aec_instance_, mic_data.data(), ref_data.data(), out_data.data(), mic_data.size() / sizeof(int16_t)); if (processed_size == 0) { ESP_LOGW(TAG, "AEC processing failed"); out_data = mic_data; // 失败时返回原始数据 } } -
修改音频处理流程:
void AudioProcessor::AudioProcessorTask() { // 现有代码... while (true) { // 等待运行标志 xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY); // 获取麦克风数据 auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY); if (res == nullptr || res->ret_value == ESP_FAIL) { continue; } // 准备数据 std::vector<int16_t> mic_data((int16_t*)res->data, (int16_t*)res->data + res->data_size / sizeof(int16_t)); std::vector<int16_t> processed_data; // AEC处理 if (aec_instance_ != nullptr && !reference_buffer_.empty()) { ProcessWithAEC(mic_data, reference_buffer_, processed_data); } else { processed_data = mic_data; // 无AEC时使用原始数据 } // 后续VAD处理和回调... // 智能语音打断逻辑(使用处理后的数据) } }
2.3 语音打断功能实现
-
实现智能语音确认机制:
// 在audio_processor.h中添加 struct VoiceInterruptConfig { int min_speech_duration_ms = 200; // 最小语音时长,防止回声误触发 float volume_threshold = 0.05f; // 音量阈值 }; class AudioProcessor { private: // 现有成员... VoiceInterruptConfig interrupt_config_; int speech_duration_count_ = 0; bool is_voice_interrupt_active_ = false; public: // 现有方法... void ConfigureVoiceInterrupt(const VoiceInterruptConfig& config); bool DetectVoiceInterrupt(const std::vector<int16_t>& audio_data); }; -
实现语音检测逻辑:
// 在audio_processor.cc中实现 void AudioProcessor::ConfigureVoiceInterrupt(const VoiceInterruptConfig& config) { interrupt_config_ = config; } bool AudioProcessor::DetectVoiceInterrupt(const std::vector<int16_t>& audio_data) { // 计算音频能量 float energy = 0.0f; for (int16_t sample : audio_data) { float normalized = (float)sample / 32768.0f; energy += normalized * normalized; } energy /= audio_data.size(); // 音量检测 bool volume_detected = (energy > interrupt_config_.volume_threshold * interrupt_config_.volume_threshold); if (volume_detected) { speech_duration_count_ += 16; // 假设每帧16ms } else { speech_duration_count_ = 0; } // 语音确认:持续时间超过阈值 bool voice_confirmed = (speech_duration_count_ >= interrupt_config_.min_speech_duration_ms); if (voice_confirmed && !is_voice_interrupt_active_) { is_voice_interrupt_active_ = true; return true; // 触发语音打断 } else if (!volume_detected) { is_voice_interrupt_active_ = false; } return false; } -
集成到处理流程:
void AudioProcessor::AudioProcessorTask() { // 现有代码... while (true) { // 获取并处理音频数据... // 调用语音打断检测 if (DetectVoiceInterrupt(processed_data)) { ESP_LOGI(TAG, "Voice interrupt detected!"); // 触发打断回调 if (voice_interrupt_callback_) { voice_interrupt_callback_(); } } } }
2.4 内存优化策略
-
SPIRAM使用优化:
// 修改缓冲区分配方式 void AudioProcessor::Initialize(int sample_rate, int channels) { // 现有初始化代码... // 使用SPIRAM分配大缓冲区 size_t buffer_size = sample_rate * channels * 2; // 2秒的缓冲区 reference_buffer_.resize(buffer_size); // 确保AEC使用正确的内存 // AEC内部会使用heap_caps_aligned_alloc,需要确保配置正确 } -
配置调整:
- 修改
sdkconfig.defaults以优化SPIRAM使用:
# 启用SPIRAM CONFIG_SPIRAM=y # 优化内存分配策略 CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=8192 CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 # 增加任务栈大小 CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 - 修改
3. 注意事项
3.1 硬件兼容性注意事项
- 麦克风选择:确保正确配置ES7210的MIC1和MIC3(或其他麦克风组合)
- 增益设置:根据实际硬件调整麦克风增益,建议从30dB开始测试
- 参考音频通道:确保提供正确的扬声器参考音频给AEC算法
3.2 软件实现注意事项
- 采样率一致性:确保AEC、麦克风输入和参考音频使用相同的采样率
- 内存对齐:AEC处理需要16字节对齐的内存,使用
heap_caps_aligned_alloc - 实时性能:AEC处理较为耗时,确保设置适当的任务优先级
- 错误处理:添加完善的错误处理和回退机制
3.3 调优建议
-
AEC参数调优:
- 调整
filter_length参数(推荐值:4) - 根据实际使用场景选择合适的AEC模式
- 调整
-
VAD参数调优:
- 调整语音打断的最小持续时间(建议200ms以上)
- 根据环境噪声调整音量阈值
-
性能监控:
- 添加CPU使用率监控
- 监控内存使用情况,避免内存泄漏
4. 测试方案
-
基础功能测试:
- 验证ES7210双麦克风配置是否正确
- 确认AEC初始化和处理无错误
-
AEC性能测试:
- 在不同距离测试回声消除效果
- 在不同音量下测试性能
-
语音打断测试:
- 测试在设备播放时的语音打断功能
- 测试不同距离和音量下的打断灵敏度
-
稳定性测试:
- 长时间运行测试,监控内存泄漏
- 测试在各种环境噪声条件下的稳定性
5. 总结
本移植方案通过直接使用ESP-SR组件提供的AEC API,无需启用完整的ADF架构,即可实现从Airhub_Rtc_h到Kapi_Rtc的AEC语音打断功能移植。方案保留了Kapi_Rtc现有的音频处理架构,通过扩展AudioProcessor类和相关接口,实现了双麦克风配置、AEC处理和智能语音打断功能。
通过合理的内存优化和参数调优,可以在保证系统稳定性的同时,实现良好的回声消除和语音打断效果。