输入关键词搜索

配置 AEC 方案

更新时间: 2025/09/04 11:01:41

本文详细介绍 AI 对话功能中的音频回声消除(AEC)方案,包括技术原理、实现方式以及不同方案的选择指导。

前提条件

根据本文操作前,请确保您已经完成了 基础实现流程

技术名词

本文涉及的技术名词如下:

  • 回声消除(Acoustic Echo Cancellation,AEC):指从麦克风采集的音频流中去除回声信号,保留纯净的用户语音。

    在 AI 对话场景中,设备的麦克风(MIC)采集到的声音包含两部分:

    • 近端信号:用户的说话声音
    • 回声信号:从扬声器播放出来的 AI 语音
  • 音频参考帧(Audio Reference Frame):是 AEC 算法的关键输入,它提供了扬声器播放内容的精确副本。

    通过比较参考帧和麦克风采集的信号,AEC 算法可以:

    • 识别信号中的回声成分
    • 自适应调整滤波器参数
    • 实现精准的回声消除
  • 音频 PCM 流:指经过 PCM(Pulse Code Modulation,脉冲编码调制)格式处理的音频数据流。PCM 是将模拟音频信号转换为数字信号的标准方法。

    在 AEC 场景中,PCM 流用作音频参考帧,因为 AEC 算法需要精确的原始音频数据来进行回声消除计算。

  • OPUS 编码:是一种开源的音频压缩编码格式,专为实时音频通信而设计。OPUS 结合了 SILK(语音编码)和 CELT(音频编码)两种编码技术的优势。

    在 AI 对话场景中,用于压缩用户语音数据,减少网络传输带宽。云端返回的 AI 语音通常也采用 OPUS 编码。相比 PCM,OPUS 编码可将音频数据压缩至原始大小的 10-20% 左右。

AEC 方案

网易云信嵌入式 NERTC SDK 根据设备性能、隐私性要求、实时性要求,提供三种 AEC 方案,您可以参考下表选择其中一种方案:

方案 处理位置 设备性能要求 数据传输 回声消除效果 自动打断支持 手动打断支持
云端 AEC 云端服务器 需传输参考帧 优秀
本地 AEC 设备端 中等 仅传输处理后音频 良好
无 AEC - 最低 仅传输原始音频

云端 AEC 方案

此方案将 AEC 处理放在云端进行,对端侧设备性能要求较低,但需要额外传输一路音频参考帧。

  • 发送
    • 调用 nertc_push_audio_frame 发送本地麦克风采集的音频 PCM 流。
    • 调用 nertc_push_audio_reference_frame 发送正在播放的远端音频流作为参考帧。
  • 接收:通过 on_audio_encoded_data 回调接收云端返回的 AI 音频流。
  • 打断:支持自动打断(用户说话时,AI 自动停止播放),也可调用 nertc_ai_manual_interrupt 手动打断。
sequenceDiagram
    participant Device as 设备
    participant Cloud as 云端AEC
    participant AI as AI服务
    
    Device->>Cloud: 调用 `nertc_push_audio_frame` <br> 发送本地麦克风采集的音频 PCM 流
    Device->>Cloud: 调用 `nertc_push_audio_reference_frame` <br> 发送正在播放的远端音频流作为参考帧
    Cloud->>Cloud: 执行AEC处理
    Cloud->>AI: 发送处理后的音频
    AI->>Cloud: 返回AI音频响应
    Cloud->>Device: 通过 `on_audio_encoded_data` <br> 回调接收云端返回的 AI 音频流
    Device->>Device: 播放 AI 音频
C++// 发送麦克风采集的音频流
void MyAPPClass::SendAudioData(void* data, int data_len) {
    nertc_sdk_audio_encoded_frame_t encoded_frame;
    encoded_frame.data = const_cast<unsigned char*>(packet.payload.data());
    encoded_frame.length = packet.payload.size();
    nertc_sdk_audio_config audio_config = {16000, 1, 160};
    nertc_push_audio_encoded_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO,
                                    &audio_config, 100, &encoded_frame);
}

// 接收并处理云端AI音频,同时发送参考帧
void MyAPPClass::OnAudioEncodedData(const nertc_sdk_callback_context_t* ctx,
                                    uint64_t uid,
                                    nertc_sdk_media_stream_e stream_type,
                                    nertc_sdk_audio_encoded_frame_t* encoded_frame,
                                    bool is_mute_packet) {
    ESP_LOGI(TAG, "NERtcSDK OnAudioEncodedData");

    std::vector<uint8_t> payload_vector;
    if(encoded_frame->data) {
        payload_vector.assign(encoded_frame->data, encoded_frame->data + encoded_frame->length);
        // 解码云端返回的音频数据
        std::vector<int16_t> pcm_payload;
        AudioDecode(payload_vector, pcm_payload);

        // 播放解码后的PCM数据
        PlayAudio(pcm_payload);

        // 关键步骤:将刚播放的PCM数据作为参考帧发送给SDK
        nertc_sdk_audio_frame_t audio_frame;
        audio_frame.type = NERTC_SDK_AUDIO_PCM_16;
        audio_frame.data = pcm_payload.data();
        audio_frame.length = pcm_payload.size();
        nertc_push_audio_reference_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO, 
                                       encoded_frame, &audio_frame);
    }
}

// 支持自动打断,同时支持手动打断
void MyAPPClass::ManualInterrupt() {
    nertc_ai_manual_interrupt(engine_);
}

本地 AEC 方案

此方案在设备端完成 AEC 处理,需要设备具备足够的 CPU 处理能力。处理流程相对简单,无需发送参考帧。

  • 发送:调用 nertc_push_audio_encoded_frame 发送经过本地 AEC 处理和 OPUS 编码后的音频流。
  • 接收:通过 on_audio_encoded_data 回调接收云端返回的 AI 音频流。
  • 打断:支持自动打断,也可调用 nertc_ai_manual_interrupt 手动打断。
C++// 发送本地AEC处理和编码后的音频
void MyAPPClass::SendAudioData(void* opus_data, int data_len) {
    nertc_sdk_audio_encoded_frame_t encoded_frame;
    encoded_frame.data = (unsigned char*)opus_data;
    encoded_frame.length = data_len;
    nertc_sdk_audio_config audio_config = {MY_AUDIO_SAMPLERATE, MY_AUDIO_CHANNELS, MY_AUDIO_SAMPLES_PER_CHANNEL};
    nertc_push_audio_encoded_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO, 
                                 &audio_config, 100, &encoded_frame);
}

// 接收AI音频并播放
void MyAPPClass::OnAudioEncodedData(const nertc_sdk_callback_context_t* ctx,
                                    uint64_t uid,
                                    nertc_sdk_media_stream_e stream_type,
                                    nertc_sdk_audio_encoded_frame_t* encoded_frame,
                                    bool is_mute_packet) {
    RTC_LOGI(TAG, "NERtcSDK OnAudioEncodedData");

    std::vector<uint8_t> payload_vector;
    if(encoded_frame->data) {
       payload_vector.assign(encoded_frame->data, encoded_frame->data + encoded_frame->length);
       std::vector<int16_t> pcm_vector;
       AudioDecode(payload_vector, pcm_vector);

       // 直接播放,无需发送参考帧
       PlayAudio(pcm_payload);
    }
}

// 支持自动打断,同时支持手动打断
void MyAPPClass::ManualInterrupt() {
    nertc_ai_manual_interrupt(engine_);
}

无 AEC 方案

此方案不进行任何 AEC 处理,适用于没有扬声器播放远端声音的场景(如使用耳机),或者对回声不敏感的业务。

  • 发送:调用 nertc_push_audio_encoded_frame 发送 OPUS 编码后的音频流。
  • 接收:通过 on_audio_encoded_data 回调接收音频流。
  • 打断:只能通过 nertc_ai_manual_interrupt 手动打断。不支持 自动打断。
C++// 发送原始编码音频
void MyAPPClass::SendAudioData(void* data, int data_len) {
    nertc_sdk_audio_encoded_frame_t encoded_frame;
    encoded_frame.data = (unsigned char*)data;
    encoded_frame.length = data_len;
    nertc_sdk_audio_config audio_config = {MY_AUDIO_SAMPLERATE, MY_AUDIO_CHANNELS, MY_AUDIO_SAMPLES_PER_CHANNEL};
    nertc_push_audio_encoded_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO, 
                                 &audio_config, 100, &encoded_frame);
}

// 接收
void MyAPPClass::OnAudioEncodedData(const nertc_sdk_callback_context_t* ctx,
                                    uint64_t uid,
                                    nertc_sdk_media_stream_e stream_type,
                                    nertc_sdk_audio_encoded_frame_t* encoded_frame,
                                    bool is_mute_packet) {
    RTC_LOGI(TAG, "NERtcSDK OnAudioEncodedData");

    std::vector<uint8_t> payload_vector;
    if(encoded_frame->data) {
       payload_vector.assign(encoded_frame->data, encoded_frame->data + encoded_frame->length);
       std::vector<int16_t> pcm_vector;
       AudioDecode(payload_vector, pcm_vector);

       // 直接播放
       PlayAudio(pcm_payload);
    }
}

// 手动打断(无 AEC 方案的唯一打断方式)
void MyAPPClass::ManualInterrupt() {
    nertc_ai_manual_interrupt(engine_);
}

下一步

了解 AEC 方案后,您可以继续学习 AI 打断服务端控制,掌握完整的 AI 对话功能实现。

此文档是否对你有帮助?
有帮助
去反馈
  • 前提条件
  • 技术名词
  • AEC 方案
  • 云端 AEC 方案
  • 本地 AEC 方案
  • 无 AEC 方案
  • 下一步