输入关键词搜索

基础实现流程

更新时间: 2026/05/28 17:08:52

本文介绍使用网易云信嵌入式 SDK(NERTC SDK)完成 AI 对话功能的核心实现步骤,包括 SDK 的初始化、事件监听、建立与结束 AI 对话(session)等关键操作。

名词解释

  • NERTC SDK:网易云信嵌入式实时通信软件开发工具包,专为硬件设备设计的轻量级音视频通信解决方案,提供实时音频传输、AI 对话、语音识别等核心功能。
  • 引擎:NERTC SDK 的核心控制实例,负责管理整个 AI 对话 session 的生命周期,包括初始化配置、session 建立、媒体流处理等所有底层操作的统一入口。
  • AI 对话 session:设备与云端 AI 服务之间的一次实时对话会话,由 nertc_join 建立底层会话后,再通过 nertc_start_ai_with_config 正式开启。
  • 用户:AI 对话 session 中的参与者,由唯一的用户 ID(uid)标识。设备侧用户通过音频流与 AI 服务进行实时交互。
  • AI 服务:云端智能对话服务,提供语音识别、自然语言理解、对话生成和语音合成等 AI 能力,实现与用户的智能语音交互。
  • ASR 服务:自动语音识别(Automatic Speech Recognition)服务,将用户的语音实时转换为文字字幕,为用户提供可视化的语音内容显示。
  • 安全模式:AI 对话 session 建立前的身份验证机制。安全模式下需要通过业务服务器生成的有效凭证(Token)进行身份验证。与安全模式相对的是调试模式,调试模式下可直接建立 session,但存在串 session 风险,仅适用于概念验证的原型阶段。更多有关两种模式对比的详情,请参考《音视频通话 2.0》基础 Token 鉴权

    此处的 Token 是指会话身份凭证,与大语言模型的自然语言文本基本单位 token 概念不同。

准备工作

根据本文操作前,请确保您已经完成了以下工作:

  1. 获取 NERTC SDK
  2. 获取设备授权码 License
  3. 在测试开发阶段,建议在 网易云信控制台 中为您的应用开通 调试模式,此时建立 AI 对话 session 无需进行凭证(Token)校验。应用正式上线前,请务必切换为 安全模式,以保障业务安全。详细说明请参考《音视频通话 2.0》Token 鉴权

流程概述

本文中实现 AI 对话功能实现的完整时序如下:

%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#AFDDFF', 'primaryTextColor': '#5409DA', 'primaryBorderColor': '#4E71FF', 'lineColor': '#4E71FF', 'secondaryColor': '#FF9149', 'tertiaryColor': '#F8FAFC' }}}%%

sequenceDiagram
    participant App as 应用程序
    participant SDK as 嵌入式 NERTC SDK
    participant Engine as NERTC 引擎
    participant Server as 云端服务
    participant AI as AI 服务

    Note over App, AI: 第一步:创建和初始化
    App->>SDK: 配置 SDK 参数<br> (app_key, device_id, event_handler)
    App->>SDK: 配置音频参数
    App->>SDK: nertc_create_engine_with_config()
    SDK-->>App: 返回引擎实例
    App->>Engine: nertc_init_engine()
    Engine-->>App: 初始化结果

    Note over App, AI: 第二步:实现监听函数
    App->>Engine: 设置回调函数

    Note over App, AI: 第三步:建立 AI 对话 session
    App->>Engine: nertc_join(token, channel_name, uid)
    Engine->>Server: 发送 session 建立请求
    Server->>Engine: 验证并处理建立请求

    Note over App, AI: 第四步:开启 AI 对话
    App->>Engine: nertc_start_ai(ai_config)
    Engine->>AI: 连接 AI 服务
    AI-->>Engine: AI 服务就绪
    Engine->>App: AI 开启结果

    opt 开启实时字幕
        App->>Engine: nertc_start_asr_caption(asr_config)
        Engine->>Server: 连接 ASR 服务
        Server-->>Engine: ASR 服务就绪
        Engine->>App: ASR 开启结果
    end

    Note over App, AI: 第五步:结束 AI 对话 session
    App->>Engine: nertc_leave()
    Engine->>Server: 发送 session 结束请求
    Engine->>AI: 断开 AI 服务连接
    Server-->>Engine: 确认结束
    AI-->>Engine: 断开连接确认
    Engine->>App: session 结束完成

第一步:创建和初始化

在进行任何操作之前,您必须先创建一个 NERTC SDK 引擎实例。通过调用 nertc_create_engine_with_confignertc_init_engine 方法来完成创建和初始化。此过程在应用的生命周期中通常仅需执行一次。

初始化时,您需要在 nertc_sdk_configuration_t 结构体中配置以下关键信息:

  • app_key:填入您在准备工作中获取到的 App Key。

  • device_id:设备的唯一标识符,由客户端负责生成和维护。该标识符需要确保在设备级别的唯一性和持久性。

  • event_handler:在引擎初始化阶段通过 nertc_sdk_engine_config_t 配置 nertc_sdk_event_handle_t 类型的事件回调句柄,用于接收 SDK 在运行过程中的各类事件通知。

  • audio_config:如果您计划使用 本地 AEC 方案,需要在此配置音频的采集参数,如采样率、声道数等。

  • licence_cfg:设置用于设备鉴权的 Licence

    C++// 1 - SDK 全局配置和创建引擎:
    nertc_sdk_configuration_t sdk_config = { 0 };
    nertc_sdk_configuration_init(&sdk_config);
    sdk_config.app_key = MY_APPKEY;
    sdk_config.device_id = MY_DEVICE_ID;
    sdk_config.force_unsafe_mode = true;
    // License 配置
    sdk_config.licence_cfg.license = MY_LICENSE;
    // 音频配置: 如果采用本地 AEC 方案,需要配置采样率、声道数、帧时长等参数。
    // 如果采用服务端 AEC,请重点关注 OnJoin 回调返回的 recommended_config,并按推荐参数采集和发送音频。
    sdk_config.audio_config.sample_rate = 16000;
    sdk_config.audio_config.frame_duration = OPUS_FRAME_DURATION_MS;
    sdk_config.audio_config.channels = 1;
    sdk_config.audio_config.samples_per_channel = 160;
    sdk_config.audio_config.codec_type = NERTC_SDK_AUDIO_CODEC_TYPE_OPUS;
    // 可选配置:
    sdk_config.optional_config.device_performance_level = NERTC_SDK_DEVICE_LEVEL_NORMAL;
    sdk_config.optional_config.prefer_use_psram = MY_CONFIG_PSRAM_ENABLED ? true : false;
    sdk_config.optional_config.enable_server_aec = MY_DEVICE_AEC_ENABLED ? false : true;
    // 日志配置:
    sdk_config.log_cfg.log_level = NERTC_SDK_LOG_INFO;
    engine_ = nertc_create_engine_with_config(&sdk_config);
    if (!engine_) {
        ESP_LOGE(TAG, "NERtcSDK failed to create engine");
        return;
    }
    
    // 2 - 引擎配置和初始化
    nertc_sdk_engine_config_t engine_config = {};
    nertc_sdk_engine_config_init(&engine_config);
    // 工作模式:
    engine_config.engine_mode = MY_ENABLE_PTT ? NERTC_SDK_ENGINE_MODE_PTT : NERTC_SDK_ENGINE_MODE_AI;
    // 事件回调:
    engine_config.event_handler.on_error = OnError;
    engine_config.event_handler.on_license_expire_warning = OnLicenseExpireWarning;
    engine_config.event_handler.on_channel_status_changed = OnChannelStatusChanged;
    engine_config.event_handler.on_join = OnJoin;
    engine_config.event_handler.on_disconnect = OnDisconnect;
    engine_config.event_handler.on_user_joined = OnUserJoined;
    engine_config.event_handler.on_user_left = OnUserLeft;
    engine_config.event_handler.on_user_audio_start = OnUserAudioStart;
    engine_config.event_handler.on_user_audio_stop = OnUserAudioStop;
    engine_config.event_handler.on_asr_caption_result = OnAsrCaptionResult;
    engine_config.event_handler.on_ai_data = OnAiData;
    engine_config.event_handler.on_audio_encoded_data = OnAudioData;
    // 用户数据(可选):
    engine_config.user_data = this;
    // 初始化引擎
    int ret = nertc_init_engine(engine_, &engine_config);
    if (ret != 0) {
        RTC_LOGE(TAG, "Failed to initialize NERtcSDK, error: %d", ret);
    } else {
        RTC_LOGI(TAG, "Successfully initialized NERtcSDK.");
    }
    

第二步:实现监听函数

您需要根据上一步中设置的回调,实现对应的处理函数。这些函数是 SDK 与您的应用进行异步通信的桥梁。

C++void MyAPPClass::OnJoin(const nertc_sdk_callback_context_t* ctx,
                        uint64_t cid, uint64_t uid,
                        nertc_sdk_error_code_e code,
                        uint64_t elapsed,
                        const nertc_sdk_recommended_config_t* recommended_config) {
    if (code == 0) {
        RTC_LOGI(TAG, "Successfully created AI session %llu with uid %llu.", cid, uid);
    } else {
        RTC_LOGE(TAG, "Failed to create AI session, error: %d", code);
    }
}

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_LOGD(TAG, "Received audio data from user %llu.", uid);
    // 在此处处理接收到的音频数据,例如解码和播放
}

// ... 实现其他必要的监听函数

第三步:建立 AI 对话

初始化成功后,调用 nertc_join 方法建立 AI 对话 session 的底层会话。建立成功后,设备即可进入后续 AI 对话流程。

参数 说明
token 安全认证签名(NERTC Token)。
  • 调试模式 下您可以将 token 设为 null,前提是需要在初始化 SDK 的时候将 nertc_sdk_configuration_t 中的 force_unsafe_mode 设置为 true,这样建立 session 将只会使用 licenseKey 模式鉴权,但可能会有串 session 的风险。
  • 安全模式 下必须传入通过您业务服务器生成的有效 Token。
更多有关两种模式对比的详情,请参考《音视频通话 2.0》基础 Token 鉴权
channel_name 会话名称,长度为 1 ~ 64 字节,目前支持以下 89 个字符:a-z, A-Z, 0-9, space, !#$%&()+-:;≤.,>? @[]^_{| }~"。相同 channel_name 的请求会进入同一个对话 session。
uid 用户的唯一标识 ID,必须在当前 session 内唯一。请在您的业务服务器上自行管理和维护。

成功建立 AI 对话 session 后,SDK 会触发 on_join 回调。

如果音频采用 云端 AEC 方案,您需要特别关注并保存在 on_join 回调中返回的 recommended_config。后续采集和发送音频流时,必须依据该配置进行,以确保 AEC 效果。

C++void MyAPPClass::StartSession() {
    // 使用从业务服务器获取的 Token
    auto ret = nertc_join(engine_, MY_CHANNEL_NAME, MY_TOKEN, MY_UID);
    if (ret != 0) {
        RTC_LOGE(TAG, "Create AI session failed, error: %d", ret);
    } else {
        RTC_LOGI(TAG, "Create AI session success");
    }
}

void MyAPPClass::OnJoin(const nertc_sdk_callback_context_t* ctx,
                        uint64_t cid,
                        uint64_t uid,
                        nertc_sdk_error_code_e code,
                        uint64_t elapsed,
                        const nertc_sdk_recommended_config_t* recommended_config) {
    RTC_LOGI(TAG, "NERtcSDK OnJoin: %llu, %llu, %d, %llu", cid, uid, code, elapsed);

    // 如果是云端 AEC 方案,保存服务器推荐的音频配置
    if (recommended_config) {
        server_sample_rate_ = recommended_config->recommended_audio_config.sample_rate;
        samples_per_channel_ = recommended_config->recommended_audio_config.samples_per_channel;
        server_frame_duration_ = (samples_per_channel_ * 1000) / server_sample_rate_;
    }
}

第四步:开启 AI 对话

AI 对话

当用户通过语音唤醒或手动操作触发对话时,调用 nertc_start_ai_with_config 接口来正式开启 AI 对话。

C++// 开启 AI 对话
nertc_sdk_start_ai_config_t ai_config = { 0 }; // 可按需配置
nertc_sdk_start_ai_config_init(&ai_config);
auto ret = nertc_start_ai_with_config(engine_, &ai_config);
if (ret != 0) {
    RTC_LOGE(TAG, "Start AI failed, error: %d", ret);
} else {
    RTC_LOGI(TAG, "Start AI success");
}

实时字幕

同时,您可以根据业务需求调用 nertc_start_asr_caption 来开启实时字幕功能。

C++// 开启实时字幕
nertc_sdk_asr_caption_config_t asr_config = { 0 }; // 可按需配置
nertc_sdk_asr_caption_config_init(&asr_config);
ret = nertc_start_asr_caption(engine_, &asr_config);
if (ret != 0) {
    RTC_LOGE(TAG, "Start ASR caption failed, error: %d", ret);
} else {
    RTC_LOGI(TAG, "Start ASR caption success");
}

上传语音

网易云信提供了三种 回声消除 AEC 方案云端 AEC 方案本地 AEC 方案无 AEC 方案。此处以云端 AEC 方案为例,介绍如何上传语音数据。您也可以根据自身需求,选择其他 AEC 方案上传语音数据。

C++// 发送麦克风采集的音频流
void MyAPPClass::SendAudioData(const std::vector<uint8_t>& opus_payload) {
    nertc_sdk_audio_encoded_frame_t encoded_frame = { 0 };
    nertc_sdk_audio_encoded_frame_init(&encoded_frame);
    encoded_frame.data = const_cast<unsigned char*>(opus_payload.data());
    encoded_frame.length = opus_payload.size();
    encoded_frame.payload_type = NERTC_SDK_AUDIO_ENCODE_OPUS;

    nertc_sdk_audio_config_t audio_config = { 0 };
    nertc_sdk_audio_config_init(&audio_config);
    audio_config.sample_rate = 16000;
    audio_config.frame_duration = 20;
    audio_config.channels = 1;
    audio_config.samples_per_channel = 320;
    audio_config.codec_type = NERTC_SDK_AUDIO_CODEC_TYPE_OPUS;

    nertc_push_audio_encoded_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO,
                                   audio_config, 100, &encoded_frame);
}

// 接收并处理云端 AI 音频,同时根据实际 AEC 方案回传参考帧
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);
        // 在此处解码并播放云端返回的音频数据
        PlayAudio(payload_vector);

        // 如果您的 AEC 方案需要参考帧,可在播放后按需调用该接口
        nertc_push_audio_reference_frame(engine_, NERTC_SDK_MEDIA_MAIN_AUDIO,
                                         encoded_frame, nullptr);
    }
}

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

状态监听

获取 AI 文本转语音(TTS)状态可以通过监听以下接口实现:

C++void MyAPPClass::OnAiData(const nertc_sdk_callback_context_t* ctx, nertc_sdk_ai_data_result_t* ai_data) {
    std::string type(ai_data->type, ai_data->type_len);
    std::string data(ai_data->data, ai_data->data_len);

    // 可以打印出类似以下的 log:
    // NERtcSDK OnAiData type:event data:audio.agent.speech_started
    // NERtcSDK OnAiData type:event data:audio.agent.speech_stopped
    ESP_LOGI(TAG, "NERtcSDK OnAiData, type=%s data=%s", type.c_str(), data.c_str());
}

第五步:结束 AI 对话

当对话结束时,调用 nertc_leave 方法结束当前 AI 对话 session 并停止数据传输。

如果确认不再需要 NERTC SDK 实例,可以调用 nertc_destroy_engine 方法来释放资源。

C++// 结束 AI 对话 session
nertc_leave(engine_);

// 销毁引擎实例
if (engine_) {
    nertc_destroy_engine(engine_);
    engine_ = nullptr;
}

下一步

参考 配置 AEC 方案 选择 AI 对话过程中的音频回声消除功能。

此文档是否对你有帮助?
有帮助
去反馈
  • 名词解释
  • 准备工作
  • 流程概述
  • 第一步:创建和初始化
  • 第二步:实现监听函数
  • 第三步:建立 AI 对话
  • 第四步:开启 AI 对话
  • AI 对话
  • 实时字幕
  • 上传语音
  • 状态监听
  • 第五步:结束 AI 对话
  • 下一步