接入 RTC 互动能力
更新时间: 2026/05/28 18:41:39
本文介绍 IoT 设备基于 NERTC SDK 接入 RTC 互动能力时,应用层需要关注的接口调用顺序、回调处理和音频处理要求。
本文不展开 SDK 初始化、鉴权、License、device_id 绑定等通用接入步骤。开始前,请先完成 基础实现流程 中的 SDK 初始化和进房鉴权配置。
示例工程
公开 Demo 可作为参考实现,不作为最小 RTC 接入流程:
重点结论
- RTC 互通不要调用
nertc_start_ai或nertc_start_ai_with_config。RTC 对端是真实 RTC 用户,不是 AI 服务。 - 两端必须加入同一个房间名,并使用不同
uid。 - 当前 IoT RTC 接入实现按
1 对 1通话设计,只处理一路有效远端音频下行。房间内可以有观众,但不能有多个需要 IoT 播放或混音的发音频用户。 - IoT 与手机端或 PC 端互通时,建议按兼容要求使用
20 ms帧长。IoT 双端互通可以按双方能力使用20 ms或60 ms,但参数必须匹配。 nertc_push_audio_encoded_frame第三个参数传audio_config,不是&audio_config;第四个参数是audio_rms_level,不是包时长。- 当前文档不把服务端 AEC 作为 RTC 互通可依赖能力。RTC 上行音频应优先由板端硬件 AEC 或软件 AEC 处理好后再送入 SDK。
on_user_audio_start/on_user_audio_stop是远端音频发布状态通知,不是应用层主动调用接口,也不能替代on_audio_encoded_data判断实际收到音频包。
适用场景
RTC 互动模式用于两个真实 RTC 端之间的实时音频通话,例如 IoT 设备与另一个 IoT 设备、手机端或 PC 端互通。
当前 IoT 侧 RTC 互动按 1 对 1 通话设计。 房间内可以有不发音频或不需要 IoT 处理下行的观众,但 IoT 端只处理一路远端下行音频,不支持同时处理多个 RTC 对端,也不支持在 RTC 互通时同时叠加 AI 下行。这个限制是当前 IoT SDK / Demo 接入实现和音频播放 / AEC 链路的能力边界,不代表 NERTC native SDK 或云端房间只能容纳两个用户。
它和 AI 聊天模式的核心差异是:
| 项目 | AI 聊天模式 | RTC 互动模式 |
|---|---|---|
| 对端 | 云端 AI 服务 | 另一个 RTC 用户 |
| 进入房间 | nertc_join |
nertc_join |
| 启动对话 | nertc_start_ai / ASR / LLM 等 |
不启动 AI |
| 音频下行 | AI TTS 或 RTC 下行 | 远端 RTC 用户音频 |
| 房间要求 | 单端设备可独立工作 | 两端必须加入同一个房间 |
uid 要求 |
通常只关注本端 uid |
两端必须使用不同 uid |
| AEC | 可按 AI 链路选择 SDK/服务端/本地方案 | 板端自行处理硬件 AEC 或软件 AEC |
RTC 接入前提
RTC 互动必须满足两个条件:
- 两个端加入相同的房间名。房间名由业务侧分配,并作为
nertc_join的 channel name / cname 参数传入。 - 两个端使用不同的
uid。uid需要在同一房间内唯一,并作为nertc_join的uid参数传入。
如果两端使用不同房间名,双方不会互通。如果两端使用相同 uid,可能导致互通异常或被服务端按同一用户处理。
能力边界:
- IoT 端只支持一路有效远端音频下行,推荐业务上约束为一个 IoT 端只和一个 RTC 对端通话。
- 房间内允许存在观众类用户,但观众不应产生需要 IoT 端混音或播放的多路下行。
- 不支持多个 RTC 对端同时向 IoT 端发音频。
- 不支持 RTC 对端音频和 AI 音频同时作为 IoT 端下行输入;需要在业务状态机上保证 RTC 模式和 AI 对话模式互斥。
RTC 互动模式的对端是真实 RTC 用户,不是云端 AI 服务。因此,纯 RTC 互动流程中不要调用 nertc_start_ai 或 nertc_start_ai_with_config。
前提条件
根据本文操作前,请确保您已经完成以下配置:
- 已完成 SDK 创建、初始化和事件回调注册。
- 已获取可用于进房的 App Key、License、房间名、
uid和 Token。 - 两个 RTC 端使用相同房间名,并使用不同
uid。 - 已确定设备的音频采集、编码、播放和 AEC 处理方案。
- 如果需要和手机端或 PC 端互通,建议确认双方音频帧长、编码格式和采样率配置。
接口调用主流程
纯 RTC 接入可以直接围绕 SDK 接口和回调组织状态机,不需要复用 Demo 中为兼容 AI 对话和打电话流程而写的状态逻辑。
sequenceDiagram
participant App as 应用层
participant SDK as NERTC SDK
participant Peer as 远端 RTC 用户
App->>SDK: nertc_join(room_name, token, local_uid)
SDK-->>App: on_join
Peer->>SDK: 加入相同 room_name,使用不同 `uid`
SDK-->>App: on_user_joined
loop 通话中
App->>SDK: nertc_push_audio_encoded_frame
SDK-->>App: on_audio_encoded_data
end
Peer->>SDK: 离开房间
SDK-->>App: on_user_left
App->>SDK: nertc_leave
1. 注册 RTC 相关回调
初始化引擎时需要注册 RTC 流程中用到的回调。具体初始化字段以当前使用的 SDK 版本为准。最小通话链路应处理 on_join、on_user_joined、on_user_left、on_audio_encoded_data、on_disconnect 和 on_error;on_user_audio_start / on_user_audio_stop 可作为状态观察,但不能作为收到音频包的唯一依据。
| 回调 | 作用 |
|---|---|
on_join |
本端进房结果;成功后保存房间信息、uid 和推荐音频参数 |
on_user_joined |
远端用户加入;可切换到可通话或等待音频状态 |
on_user_left |
远端用户离开;可关闭播放、停止采集或回到等待态 |
on_user_audio_start |
远端发布音频流的状态通知;用于 UI 状态和问题定位,不代表已经收到可播放音频包 |
on_user_audio_stop |
远端停止发布音频流的状态通知;用于 UI 状态和问题定位 |
on_audio_encoded_data |
收到远端编码音频;交给播放链路处理 |
on_disconnect |
本端断开;清理本地通话状态并触发重连或退出 |
on_channel_status_changed |
房间连接状态变化;用于定位网络和房间状态 |
on_error |
SDK 错误;用于定位接口调用或服务端异常 |
说明:当前 IoT SDK 里 on_user_audio_start / on_user_audio_stop 是事件回调,不是应用层需要调用的接口。历史日志中可看到 OnNewProducer 后触发 OnUserAudioStart,它表示远端音频 producer 已发布;AI 链路的 audio.agent.speech_started 则是 on_ai_data 事件,二者不是同一个概念。
2. 加入 RTC 房间
应用层调用 nertc_join 加入 RTC 房间:
C++nertc_join(engine, room_name, token, local_uid);
接入侧需要保证:
room_name与远端一致。local_uid与远端不同。- Token / 鉴权参数按当前 SDK 版本要求传入。
- 等待
on_join成功后再认为本端已进入房间。
on_join 成功后,建议保存 SDK 回调中的推荐音频参数,例如采样率、帧时长、每通道采样点数、下行采样率等。后续上行音频应按 SDK 要求组织编码帧。
3. 等待或感知远端用户
RTC 模式的对端是真实 RTC 用户,不需要调用 nertc_start_ai 或 nertc_start_ai_with_config。应用层可以按产品形态选择:
- 本端进房成功后立即开始采集和发送音频。
- 等到
on_user_joined后再开始采集和发送音频。 - 超过业务等待时间未收到
on_user_joined时提示用户、重试或退出房间。
如果产品需要响铃、等待接听、取消呼叫等体验,这些状态应由应用层业务状态机处理,不属于 SDK RTC 基础流程的必需部分。
4. 发送上行音频
IoT RTC 互动通常发送编码音频帧。以 encoded frame 流程为例:
C++constexpr int kAudioPacketDurationMs = 60;
// 如果需要和手机端或 PC 端互通,建议改为 20 ms,以避免端侧兼容性问题。
audio_config.frame_duration = kAudioPacketDurationMs;
audio_config.samples_per_channel = audio_config.sample_rate * kAudioPacketDurationMs / 1000;
uint8_t audio_rms_level = 100; // 音量标记,取值 [0,100];不是音频包时长。
nertc_push_audio_encoded_frame(
engine,
NERTC_SDK_MEDIA_MAIN_AUDIO,
audio_config,
audio_rms_level,
&encoded_frame);
注意:头文件签名中第三个参数是 nertc_sdk_audio_config_t audio_config,按值传入 audio_config,不是传 &audio_config。
接入侧需要保证:
encoded_frame.data和encoded_frame.length指向有效的编码音频数据。audio_config与 SDK 协商或推荐的采样率、声道数、帧采样点数匹配。- 音频送入 SDK 前已经完成必要的降噪、增益和回声消除处理。
帧时长建议:
- IoT 设备之间互通:可根据双方能力选择
20 ms、60 ms等帧时长,但两端和 SDK 侧参数需要匹配。 - IoT 设备与手机端或 PC 端 RTC 互通:建议按兼容要求使用
20 ms。当前文档不把它描述成 SDK C API 的强制参数校验,但客户联调、问题排查和交付验证都应优先按20 ms验证;60 ms等较长帧长可能遇到端侧兼容性、播放平滑性或延迟处理问题。 nertc_push_audio_encoded_frame的第 4 个参数是audio_rms_level,用于标记音量,默认可填100;音频包时长应通过audio_config.frame_duration和编码器输出帧控制。
5. 接收远端音频
SDK 通过 on_audio_encoded_data 回调下发远端编码音频。应用层收到后需要:
- 根据回调中的编码数据、时间戳和音频参数送入播放链路。
- 如果设备同时开麦和外放,将播放音频送入本地 AEC 参考链路。
- 根据
on_user_audio_start/on_user_audio_stop更新 UI 或业务状态,但实际是否收到远端音频,应以on_audio_encoded_data和播放链路日志为准。
6. 远端离开和本端退出
收到 on_user_left 后,应用层可以按业务关闭音频通道、停止采集播放、回到等待态,或退出房间。
本端结束 RTC 互动时,调用:
C++nertc_leave(engine);
engine 销毁、资源释放等生命周期管理按 SDK 通用接入流程处理。
AEC 处理要求
RTC 互动和 AI 聊天的 AEC 处理方式不同,这是接入时最容易误判的地方。
RTC 互动中,两个端都是实际 RTC 用户。RTC 基础流程不会启动 nertc_start_ai / nertc_start_ai_with_config,也就没有 AI 服务链路上的 SDK AEC 或服务端 AEC 能力可依赖。本文覆盖当前仓库和公开 Demo 所对应的 IoT RTC 流程,不把服务端 AEC 作为 RTC 互通可依赖能力。此时模块侧需要自行保证上行音频已经完成回声消除:
- 板子有硬件 AEC:采集链路应使用硬件 AEC 后的近端音频。
- 板子有可用软件 AEC:播放下行音频应作为参考音,采集后先经过软件 AEC,再送入 SDK。
- 没有 AEC:只适合耳机、半双工、低外放音量、或其它不会形成明显回声的场景。
不要把 AI 场景里的服务端 AEC、SDK AEC 或 Demo 中的 AEC 参考帧逻辑当成 RTC 互动模式的默认回声消除方案。RTC 通话上行给 SDK 的音频,应当已经是板端处理后的音频。
版本边界:如果某个后续 IoT SDK 版本明确声明支持 RTC 互通场景的服务端 AEC,应以该版本的 release note、API 文档和联调验证结果为准,并在接入文档中单独补充版本号、配置项和验证方法。本文不对未声明版本做能力承诺。
推荐应用层伪代码
C++// 初始化 engine,并注册 on_join / on_user_joined / on_audio_encoded_data 等回调。
const char* room_name = "rtc_room_1001";
uint64_t local_uid = 1001;
int ret = nertc_join(engine, room_name, token, local_uid);
if (ret != 0) {
// 进房请求失败:检查 engine、网络、room_name、uid、token/鉴权参数。
return;
}
// 等待 on_join 成功。
// RTC 模式下不要调用 nertc_start_ai 或 nertc_start_ai_with_config。
// 可等待 on_user_joined 后再开始采集,也可进房成功后立即开始采集。
constexpr int kAudioPacketDurationMs = 60;
// 如果需要和手机端或 PC 端互通,建议改为 20 ms。
audio_config.frame_duration = kAudioPacketDurationMs;
audio_config.samples_per_channel = audio_config.sample_rate * kAudioPacketDurationMs / 1000;
while (capturing) {
auto encoded_frame = audio_capture.GetEncodedFrameAfterAec(kAudioPacketDurationMs);
uint8_t audio_rms_level = 100; // 音量标记,不是包时长。
nertc_push_audio_encoded_frame(
engine,
NERTC_SDK_MEDIA_MAIN_AUDIO,
audio_config,
audio_rms_level,
&encoded_frame);
}
// on_audio_encoded_data 回调中播放远端音频。
// 如果设备外放,播放数据同时进入本地 AEC 参考链路。
nertc_leave(engine);
上面的伪代码只描述 RTC 功能调用顺序,不代表完整初始化和资源释放逻辑。
Demo 参考实现说明
公开 Demo 中的 NeRtcProtocol 不是最小 RTC 接入样例。它同时兼容 AI 对话、AI function call 触发 RTC 呼叫、响铃/表情/状态切换等业务流程,因此代码里会看到一些并非 RTC 基础接入必需的逻辑。
使用 Demo 验证 RTC 时,需要把 Demo 内部的 rtc_call 开关打开,设置为 true。否则 OpenAudioChannel() 会走 AI 对话路径,调用 nertc_start_ai,而不是进入 RTC P2P 流程。具体开关位置和配置方式以所使用的 Demo 版本为准,本文不把 Demo 的配置文件结构作为 SDK 接入要求。
当前参考实现中,读取逻辑在 main/protocols/nertc_protocol.cc 的 LoadLocalConfig():它会读取 Demo 配置里的 custom_config.rtc_call、custom_config.cname 和 custom_config.uid。配置模板路径通常是 create_local_config/config.json.s3 或 create_local_config/config.json.c3;公开 Demo 的模板不一定默认展示 rtc_call 字段,需要按所使用的 Demo 版本手动补充。这个配置只服务于 Demo 验证,正式接入仍应按前文 SDK 参数流程传入房间名和 uid。
Demo 中关键方法和 SDK 基础接口的关系如下:
| Demo 方法或回调 | 对应含义 |
|---|---|
NeRtcProtocol::Start() |
读取 Demo 自己的房间和 uid 配置后调用 nertc_join |
NeRtcProtocol::OpenAudioChannel() |
AI 模式启动 AI;rtc_call=true 时不启动 AI,而是等待远端用户加入 |
NeRtcProtocol::SendAudio() |
封装 nertc_push_audio_encoded_frame 发送上行编码音频 |
NeRtcProtocol::OnAudioData() |
处理 on_audio_encoded_data,转交应用播放链路 |
NeRtcProtocol::OnUserJoined() |
远端用户加入;Demo 中会唤醒等待并切换 P2P 状态 |
NeRtcProtocol::OnUserLeft() |
远端用户离开;Demo 中会按业务状态回到 idle 或关闭音频 |
NeRtcProtocol::OnAiData() 中的 rtc_call |
从 AI function call 触发 Demo 的 RTC 呼叫状态机;纯 RTC 接入不必复用 |
Demo 的 P2P 状态大致为:
stateDiagram-v2
[*] --> idle
idle --> pre_connecting: AI function call rtc_call
pre_connecting --> connecting: 业务推进或再次触发
connecting --> connected: on_user_joined
connected --> idle: on_user_left
pre_connecting --> idle: 取消或关闭
connecting --> idle: 取消或关闭
如果只做纯 RTC 双端互通,可以直接按本文前面的 SDK 接口流程实现,不需要照搬 Demo 的 AI function call 和 P2P 状态机。
常见问题
两端进房后为什么听不到对方?
优先检查:
- 两端房间名是否完全一致。
- 两端
uid是否不同。 - IoT 端是否只面对一个有效发音频的 RTC 对端。
- 两端是否都收到
on_join成功回调。 - 是否收到对方的
on_user_joined。 - 远端是否真正发布并发送音频;只收到
on_user_joined不代表远端已经发音频。 - 播放链路是否收到
on_audio_encoded_data并正常解码播放。 - 是否持续调用音频发送接口,且 encoded frame 参数匹配。
- 如果对端是手机或 PC,IoT 侧是否使用
20 ms帧长。
RTC 模式为什么不启动 AI?
这是预期行为。RTC 互动的对端是真实 RTC 用户,不是云端 AI。纯 RTC 流程只需要进房、发送上行音频、接收远端音频和处理用户进出房间回调。RTC 模式不要调用 nertc_start_ai 或 nertc_start_ai_with_config。
RTC 模式能否使用服务端 AEC?
当前仓库和公开 Demo 对应的 IoT RTC 流程不要按服务端 AEC 方案设计。RTC 模式没有 AI 服务端 AEC 链路兜底,板端需要提供硬件 AEC 或软件 AEC。接入侧应保证送入 SDK 的上行音频已经完成必要的回声消除。
nertc_push_audio_encoded_frame 第三个参数传值还是传指针?
传 audio_config,不是 &audio_config。头文件签名是:
C++int nertc_push_audio_encoded_frame(
nertc_sdk_engine_t engine,
nertc_sdk_media_stream_e stream_type,
nertc_sdk_audio_config_t audio_config,
uint8_t audio_rms_level,
nertc_sdk_audio_encoded_frame_t* audio_encoded_frame);
其中第四个参数 audio_rms_level 是音量标记,默认可填 100,不是音频包时长。
IoT 和手机端或 PC 端互通时 20 ms 是强制还是推荐?
本文按“兼容性要求”推荐 20 ms。当前文档不描述为 C API 层面的强制校验,但和手机端或 PC 端互通时,客户联调和交付验证应按 20 ms 优先实现和排查。继续使用 60 ms 可能导致兼容性、播放平滑性或延迟问题。
只收到 on_user_joined,为什么仍然没有声音?
on_user_joined 只表示远端用户进房,不表示远端已经发布或发送音频。继续检查 on_user_audio_start 是否触发、远端是否开麦、IoT 侧是否收到 on_audio_encoded_data,以及播放链路是否正常。
on_user_audio_start 能否作为收到音频的判断依据?
不能。它是远端音频发布状态通知,当前 IoT SDK 没有对应的应用层主动调用接口。实际是否收到可播放音频,应以 on_audio_encoded_data 和播放链路日志为准。AI 链路中的 audio.agent.speech_started 是 on_ai_data 事件,不能和 RTC 的 on_user_audio_start 混用。
房间里有多个发音频用户会怎样?
当前 IoT RTC 接入实现只处理一路有效远端下行。多个 RTC 对端同时发音频时,IoT 侧没有多路混音/多对端播放能力,业务上应限制为一个有效发音频对端。观众可以存在,但不应产生需要 IoT 播放的下行音频。
Demo 里 rtc_call=true 应该配在哪里?
当前参考 Demo 的读取逻辑在 main/protocols/nertc_protocol.cc 的 LoadLocalConfig(),字段是 custom_config.rtc_call。常见模板路径是 create_local_config/config.json.s3 或 create_local_config/config.json.c3。公开 Demo 模板可能默认不展示该字段,验证 RTC 时需要按 Demo 版本手动补充,并同时配置同房间名和不同 uid。该字段是 Demo 的业务开关,不是 SDK C API 的通用接入参数。
是否必须使用 Demo 中 AI function call 的 rtc_call?
不必须。Demo 里 rtc_call 用于从 AI 对话中触发 RTC 呼叫状态切换。纯 RTC 产品可以直接由按键、屏幕 UI、远程指令或业务协议触发房间加入和通话状态变化。
调试日志建议
联调 RTC 互动时,建议重点观察以下日志或回调:
- 实际加入的房间名和
uid。 on_join结果,确认进房成功。on_user_joined,确认对端进入同一房间。on_user_left,确认对端离开。on_user_audio_start/on_user_audio_stop,确认远端音频发送状态。on_audio_encoded_data对应的播放链路日志,确认下行音频到达应用层。on_disconnect/on_channel_status_changed/on_error,定位网络、房间和 SDK 异常。




