toy-Kapi_Rtc/03AEC_VOICE_INTERRUPT_PORTING_PLAN.md
2026-01-20 16:55:17 +08:00

327 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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双麦克风配置移植
1. **创建ES7210配置初始化模块**
```cpp
// 在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;
}
```
2. **在AudioCodec基类中增加ES7210配置支持**
```cpp
// 修改AudioCodec类增加双麦克风配置接口
class AudioCodec {
public:
// 现有接口...
virtual bool configureDualMicrophone() = 0;
virtual bool setMicrophoneGain(int gain_db) = 0;
};
```
3. **在具体编解码器实现中添加ES7210配置**
```cpp
bool Es7210AudioCodec::configureDualMicrophone() {
return (init_es7210_with_dual_mic(ctrl_if_) == ESP_OK);
}
```
### 2.2 AEC功能集成到AudioProcessor
1. **扩展AudioProcessor类**
```cpp
// 修改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);
};
```
2. **实现AEC功能**
```cpp
// 在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; // 失败时返回原始数据
}
}
```
3. **修改音频处理流程**
```cpp
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 语音打断功能实现
1. **实现智能语音确认机制**
```cpp
// 在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);
};
```
2. **实现语音检测逻辑**
```cpp
// 在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;
}
```
3. **集成到处理流程**
```cpp
void AudioProcessor::AudioProcessorTask() {
// 现有代码...
while (true) {
// 获取并处理音频数据...
// 调用语音打断检测
if (DetectVoiceInterrupt(processed_data)) {
ESP_LOGI(TAG, "Voice interrupt detected!");
// 触发打断回调
if (voice_interrupt_callback_) {
voice_interrupt_callback_();
}
}
}
}
```
### 2.4 内存优化策略
1. **SPIRAM使用优化**
```cpp
// 修改缓冲区分配方式
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需要确保配置正确
}
```
2. **配置调整**
- 修改`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 调优建议
1. **AEC参数调优**
- 调整`filter_length`参数推荐值4
- 根据实际使用场景选择合适的AEC模式
2. **VAD参数调优**
- 调整语音打断的最小持续时间建议200ms以上
- 根据环境噪声调整音量阈值
3. **性能监控**
- 添加CPU使用率监控
- 监控内存使用情况,避免内存泄漏
## 4. 测试方案
1. **基础功能测试**
- 验证ES7210双麦克风配置是否正确
- 确认AEC初始化和处理无错误
2. **AEC性能测试**
- 在不同距离测试回声消除效果
- 在不同音量下测试性能
3. **语音打断测试**
- 测试在设备播放时的语音打断功能
- 测试不同距离和音量下的打断灵敏度
4. **稳定性测试**
- 长时间运行测试,监控内存泄漏
- 测试在各种环境噪声条件下的稳定性
## 5. 总结
本移植方案通过直接使用ESP-SR组件提供的AEC API无需启用完整的ADF架构即可实现从Airhub_Rtc_h到Kapi_Rtc的AEC语音打断功能移植。方案保留了Kapi_Rtc现有的音频处理架构通过扩展AudioProcessor类和相关接口实现了双麦克风配置、AEC处理和智能语音打断功能。
通过合理的内存优化和参数调优,可以在保证系统稳定性的同时,实现良好的回声消除和语音打断效果。