IPC厂家接入SDK常见问题
本页面汇总 IPC 厂家在接入 DeviceSDK(IoTSDK + StreamSDK)过程中反馈较多的问题,覆盖设备绑定、在线状态、音视频同步、直播回放、对讲、物模型、透传通道、投屏、OTA 与云录像等核心功能,按"现象 → 排查思路 → 常见错误码"组织,便于快速定位。本文内容以 DeviceSDK 1.7.0 版本接口为基准,具体接口定义以随包头文件为准。
各章节日志样板中的超长 JSON/SDP 已做截断,保留时间戳、方法名和关键字段;实际日志以设备输出为准。
反馈问题前,建议先准备以下信息,可以显著缩短定位时间:
- 设备信息:productKey、deviceName、uuid、固件版本、DeviceSDK 版本(xs_iot_get_version)
- 日志:通过 xs_iot_setLogLevel 打开 DEBUG 级别,并在 xs_logCallback、xs_streamLogCallback 中落盘;必要时通过物模型服务 TriggerFileCapture 远程拉取日志
- 时间:设备当前 UTC 时间,以及是否已收到 xs_systimeCallback 并完成系统时间同步
- 网络:设备网络类型(WiFi/有线/4G)、DNS/代理/端口白名单情况、上下行带宽
- 复现信息:APP 操作步骤、操作时间点、设备端日志对应时间点、问题是否必现
设备无法绑定/上线
现象:APP 配网或扫码后提示绑定失败,或长时间停留在绑定中;设备端没有收到 DEVICE_STATUS_BIND 回调;控制台显示设备未绑定。
排查思路:
- 先确认使用的是哪种绑定方式,不同方式的必要条件不同:
- 快速绑定:APP 扫描控制台生成的测试序列号,设备无需上线即可绑定成功,仅用于对接调试阶段(每款设备可申请的测试序列号数量以控制台为准)。
- 扫码激活绑定(4G 设备或有线设备):设备必须先通过 xs_iot_open、xs_iot_connect 登录成功,APP 再发起扫码激活绑定。
- WiFi 配网绑定:APP 生成 bindToken,通过二维码(或蓝牙、局域网等方式)传给设备,设备解析后调用 xs_iot_setBindInfo;海外设备还需传入 regionId,没有则填 -1。
- 平台间绑定(目前大部分接入厂商在使用的):设备配网过程由客户自行处理, APP 调用客户平台接口,客户平台再调用 AIRTC 绑定接口,设备端不使用 bindToken,只需保持在线并处理绑定状态回调。此方式可以避免客户平台与 AIRTC 平台的绑定数据不一致。
- 确认设备已登录成功:xs_connectStatusCallback 返回 1,且没有出现登录鉴权类错误(见下表)。设备未上线时,扫码激活绑定无法完成。
- 检查三元组与 UUID:productKey/deviceName/deviceSecret 必须与控制台签发的一致且不能错位;一个三元组只能与一个 UUID 关联,换板、重复烧录、恢复出厂时清掉 UUID 都会导致登录被拒绝(XSIOT_EC_LOGIN_UUID_NOT_MATCH,-5412)。
- 确认设备未被其他账号绑定,且未超过账号的绑定数量限制;已绑定过的设备需要先在 APP 或控制台解绑。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XSIOT_EC_DEVICE_NOT_BIND(-5010) | 设备未绑定,绑定完成前不要调用上报类接口 |
| XSIOT_EC_INVALID_UUID(-5022) | UUID 为空或长度超过 64 字节 |
| XSIOT_EC_LOGIN_DEVICE_NAME_ERROR(-5410) | deviceName 错误,核对三元组中的 deviceName(服务器返回 31007) |
| XSIOT_EC_LOGIN_DEVICE_SECRET_ERROR(-5411) | deviceSecret 错误,检查是否烧录错误或被截断(服务器返回 31506) |
| XSIOT_EC_LOGIN_UUID_NOT_MATCH(-5412) | 三元组与 UUID 不匹配,检查是否换板、三元组重复(服务器返回 36115) |
| XSIOT_EC_LOGIN_REGION_ERROR(-5414) | regionType/regionId 与设备实际区域不一致(服务器返回 31511) |
日志样板:
分配信令服务器地址(关键字:thing.sig.assign):
[2026-10-23 10:45:16 836][iotsdk][tid:33282]:[im_client.c][INFO][im_client_send_message:1144] [wan] send msg | cmdId:21 |msgLen:271 | msg: {"params":{"groupList":["sig","stun","log"],"supportProto":{"sig":["TCP-SSL","KCP-SSL"],"log":["TCP-SSL"]}},"id":"d2588e56fa0d45e6b321690f79d874ae","version":"1.0","productKey":"xLeWc***************","deviceName":"demoWang003","method":"thing.sig.assign","sys":{"ack":1}} | sendTimes:0
[2026-10-23 10:45:17 217][iotsdk][tid:33282]:[im_client.c][INFO][parse_server_address_result:438] [wan] parse server address:{"id":"d2588e56fa0d45e6b321690f79d874ae","code":200,"message":"success","version":"1.0","method":"thing.sig.assign","productKey":"xLeWc***************","deviceName":"demoWang003","data":{"serviceList":[{"name":"sig","type":0,"address":"[{\"ip\":\"118.178.32.57\",\"port\":9822,\"proto\":\"TCP-SSL\"},{\"ip\":\"2408:4005:3ad:6501:f96:3a22:4dd7:2128\",\"port\":9822,\"proto\":\"TCP-SSL\"},{\"ip\":\"118.178.32.57\",\"port\":9826,\"proto\":\"KCP-SSL\"},{\"ip\":\"2408:4005:3ad:6501:f96:3a22:4dd7:2128\",\"port\":9826,\"proto\":\"KCP-SSL\"}]"},{"name":"log","address":"[{\"ip\":\"118.178.32.57\",\"port\":9822,\"proto\":\"TCP-SSL\"},{\"ip\":\"2408:4005:3ad:6501:f96:3a22:4dd7:2128\",\"port\":9822,\"proto\":\"TCP-SSL\"}]"},{"name":"stun","domain":"stun.iotrtc.cn:3478"}],"timestamp":1792723517143,"timestampStr":"1792723517143"}}
登录信令服务器(关键字:thing.auth.authenticate):
[2026-10-23 10:45:17 217][iotsdk][tid:33282]:[im_client.c][INFO][im_client_send_message:1144] [wan] send msg | cmdId:1 |msgLen:346 | msg: {"id":"d567f31f9d5b43c6800e610f8cb3e61b","version":"1.0","productKey":"xLeWc***************","deviceName":"demoWang003","method":"thing.auth.authenticate","sys":{"ack":1},"params":{"heartbeat":30,"timestamp":"1792723517217","uuid":"demoW******","signmethod":"hmacsha256","sign":"73cbd***********************************************************"}} | sendTimes:0
登录成功:
[2026-10-23 10:45:17 505][iotsdk][tid:33282]:[im_client.c][INFO][recv_msg_cb:997] [wan] recv msg, cmd id = 2, | route id:0, src:, src session: |body:{"code":200,"data":{"bindStatus":1,"bindTime":"1792659095","code":200,"heartbeat":30,"liveTime":1800},"deviceName":"demoWang003","id":"d567f31f9d5b43c6800e610f8cb3e61b","message":"success","method":"thing.auth.authenticate","productKey":"xLeWc***************","version":"1.0"}
登录失败(uuid不匹配):
[2026-10-23 13:38:15 782][iotsdk][tid:36664]:[im_client.c][INFO][recv_msg_cb:1000] [wan] recv msg, cmd id = 2, | route id:0, src:, src session: |body:{"code":36115,"data":{"code":200},"deviceName":"TIOZf4kvSo573pNwKzNi","id":"7735eb2f82e84e1083d42c12785869bd","message":"","method":"thing.auth.authenticate","productKey":"dwOAu***","version":"1.0"}
参考:IoTSDK接入 - 设备绑定、IoTSDK接入 - 设备上线
设备离线
现象:控制台或 APP 显示设备离线;设备端 xs_connectStatusCallback 返回 0;直播、回放、物模型操作超时。
三元组与 UUID 重点检查:
很多"设备离线"并非网络问题,而是三元组或 UUID 校验失败导致登录被云端拒绝,SDK 持续重连。优先核对以下内容:
- 三元组必须一台设备一套:
productKey、deviceName、deviceSecret必须与控制台签发信息完全一致,注意大小写、空格、换行和截断,且deviceName与deviceSecret不能写反。 - UUID 用于防止三元组重复(串号):
uuid必填、长度不超过 64 字节,主设备和子设备都要填写,可使用设备唯一 SN、MAC 或首次启动生成的 UUID。设备首次登录成功后,云端会记录三元组与 UUID 的绑定关系;后续同一三元组携带其他 UUID 登录会被拒绝(XSIOT_EC_LOGIN_UUID_NOT_MATCH,-5412,服务器返回 36115)。 - UUID 必须固化且全局唯一:如果使用首次启动生成的 UUID,必须写入 flash 或配置文件并在重启后沿用,不能每次启动重新生成;恢复出厂设置也不能清除 UUID;产线不能给多台设备写入同一个 UUID;换板、换主控后 UUID 发生变化,原三元组也会无法登录。设备生产时要保证不同设备的 UUID 不重复。
- 不要靠修改 UUID 绕过三元组重复:三元组被多台设备复用时,应重新分配三元组或修正产线烧录,而不是反复改 UUID 重试,否则设备会长期鉴权失败、反复重连,看起来就是"设备离线"。
排查思路:
- 先确认是否登录鉴权失败(三元组 / UUID):如果 xs_exceptionCallback 反复返回 -5410、-5411、-5412、-5414,说明网络已经连通但登录被云端拒绝,SDK 会持续重连,不能按普通断网处理。逐项核对三元组、UUID、regionType/regionId,详见 三元组和UUID重要说明。
- 再区分"真离线"和"未绑定":设备联网但尚未绑定时,同样会回调在线状态,APP 侧可能显示为不可用,需要结合绑定状态一起判断。
- 检查信令连接错误:关注 xs_exceptionCallback 回调的 err_code。DNS 解析失败(-5403)、TCP 连接失败(-5401)、SSL 握手失败(-5402)通常与网络、端口白名单、设备系统时间有关;私有化专属版本必须在 initParam.serverHostName 中填写 host:port。
- 检查心跳配置:heartbeatInterval 小于 15 会按 15 秒处理,最大不超过 180 秒。如果通过 XS_IOT_CONTROL_ENABLE_EXT_HEARTBEAT 启用了外部心跳管理,SDK 内部将停止发送心跳,必须由外部按不小于 10 秒的间隔调用 XS_IOT_CONTROL_SEND_HEARTBEAT。
- 低功耗设备检查休眠链路:
- 主控休眠前调用 xs_stream_sleep(阻塞接口),结束后再进入休眠;
- 信媒分离方案下,主控休眠后 wifi 侧必须调用 xs_iot_notifyHostStatus(XS_IOT_HOST_SLEEP);
- 保活方案需保存 xs_keepliveCallback 下发的保活信息,wifi 模组按参数连接保活服务器并定时发送心跳;
- 唤醒后通过 xs_stream_wakeup 告知 SDK 唤醒原因,加快出图。
- 网关子设备状态:子设备上线、下线必须分别调用 xs_iot_connect、xs_iot_disconnect 同步给云端;子设备已断开但未调用 xs_iot_disconnect,云端仍会认为子设备在线,造成状态不同步。彻底移除子设备时才调用 xs_iot_close。
- 网络切换后立即重连:WiFi 切有线等场景,调用 xs_iot_control(XS_IOT_CONTROL_RECONNECT_IMMEDIATELY, devId, &force) 让 SDK 立即重连,不要自行反复 xs_iot_close + xs_iot_open 模拟重连。
- 重置后离线:启用 XS_IOT_CONTROL_ENABLE_RESET_OFFLINE 后,设备重置(调用 xs_iot_factory_reset 或清除 SDK 配置文件)会显示离线,必须重新绑定成功才会恢复在线。
- 区分休眠与断电:仅通过物模型 powerstate 关闭设备时,SDK 仍维持信令连接,但需要销毁流、停止推流和事件上报;整机断电、网络完全中断或主动调用 xs_iot_disconnect 才会真正离线。
排查手段:
- 打开 DEBUG 日志,确认断开原因和重连过程;远程拉取日志参考 远程拉取日志
- 核对实际烧录值:打印设备侧实际使用的 productKey、deviceName、uuid,与控制台逐字比对;确认 deviceName 与 deviceSecret 没有写反,UUID 没有被截断、清空或恢复出厂时重置。
- 排查串号:控制台按 UUID 查询设备,确认同一三元组是否被多台设备复用、同一 UUID 是否被复制到多台设备;设备反复上下线时优先怀疑产线烧录了相同三元组。
- 对照服务器返回码:-5410/31007 对应 deviceName 错误,-5411/31506 对应 deviceSecret 错误,-5412/36115 对应三元组与 UUID 不匹配,-5414/31511 对应 region 配置错误。
- 首次部署或更换网络环境时,可调用 PMTU 探测命令确认链路可用,参考 探测路径MTU
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XSIOT_EC_INVALID_UUID(-5022) | UUID 为空或长度超过 64 字节,检查 xs_deviceInfo_st 中的 uuid 赋值 |
| XSIOT_EC_LOGIN_DEVICE_NAME_ERROR(-5410) | deviceName 错误,核对三元组中的 deviceName(服务器返回 31007) |
| XSIOT_EC_LOGIN_DEVICE_SECRET_ERROR(-5411) | deviceSecret 错误,检查是否烧录错误或被截断(服务器返回 31506) |
| XSIOT_EC_LOGIN_UUID_NOT_MATCH(-5412) | 三元组与 UUID 不匹配,检查是否换板、UUID 被清除或三元组重复(服务器返回 36115) |
| XSIOT_EC_LOGIN_REGION_ERROR(-5414) | regionType/regionId 与设备实际区域不一致(服务器返回 31511) |
| XSIOT_EC_QUERY_SIG_SERVER_FAILED(-5201) | 获取信令服务器地址失败,检查网络与服务器配置 |
| XSIOT_EC_IM_SERVER_DISCONNECT(-5202) | 信令连接断开,SDK 内部会自动重连 |
| XSIOT_EC_WAIT_HEARTBEAT_RESPONSE_TIMEOUT(-5212) | 心跳响应超时,检查链路质量与心跳间隔 |
| XSIOT_EC_WAIT_STREAM_WAKEUP_TIMEOUT(-5213) | 等待 StreamSDK 唤醒超时,检查跨芯片消息转发与唤醒流程 |
| XSIOT_EC_STATUS_NOT_READY(-5208) | 设备不在线或未绑定,业务接口返回此错误时先确认在线状态 |
| XSIOT_EC_TCP_CONNECT_FAILED(-5401) | TCP 连接失败,检查端口是否放通 |
| XSIOT_EC_SSL_HANDSHAKE_FAILED(-5402) | SSL 握手失败,常见于设备时间错误、证书校验失败 |
| XSIOT_EC_DNS_RESOLVE_FAILED(-5403) | 域名解析失败,检查 DNS 或改用可解析的服务器地址 |
日志样板:
正常上线对照(关键字:thing.auth.authenticate / connectStatusCb:online):
[2026-10-23 11:33:18 571][iotsdk][tid:34100]:[net_client.c][INFO][net_client_set_reconnect_interval:1229] [wan] set reconnect interval:3s
[2026-10-23 11:33:18 622][iotsdk][tid:34112]:[net_client.c][INFO][net_read_proc:772] [wan] connect success, server:dev-sig.iotrtc.cn:9822, protocol:TCP-SSL
[2026-10-23 11:33:19 144][iotsdk][tid:34112]:[im_msg.c][INFO][handle_login_response_msg:396] [wan] publish login status:1, devId:0, bindStatus:1
[2026-10-23 11:33:19 144][iotsdk][tid:34112]:[iot_device.c][INFO][iot_dev_set_online_status:554] devId:0 | connect with server
[2026-10-23 11:33:19 144][iotsdk][tid:34112]:[iot_device.c][INFO][iot_dev_online_status_notify:586] devId:0 | connectStatusCb:online
# 掉线时按关键字过滤:connectStatusCb:offline
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[im_client.c][ERROR][check_heartbeat_timeout:652] [wan] im client heartbeat timeout [sendHeartBeatTime:97703622, recvMsgTime:97673617, delta:30005], will reconnect with server
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[net_client.c][INFO][net_client_reconnect:1140] [wan] try to reconnect with server later
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[net_client.c][ERROR][net_read_proc:825] [wan] active reconnect requested
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[xs_hal_tls_mbedtls.c][INFO][xs_hal_ssl_destroy:256] [im]destroy ssl connection
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[xs_hal_tcp_linux.c][INFO][xs_hal_tcp_destroy:278] [im]fd-7 is closed
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[im_client.c][INFO][connect_cb:892] [wan] server disconnected [isSignalServer:1]
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[im_client.c][INFO][check_reset_server_address:87] [wan] loginFailedTimes:0 | connectFailedTimes:1
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[iot_device.c][INFO][iot_dev_set_online_status:574] devId:0 | disconnect with server
[2026-10-23 13:34:44 874][iotsdk][tid:36644]:[iot_device.c][INFO][iot_dev_online_status_notify:606] devId:0 | connectStatusCb:offline
参考:IoTSDK接入 - 三元组和UUID重要说明、IoTSDK接入 - 设备上线
音视频卡顿、花屏、丢帧
现象:直播或回放时声音与画面不同步、口型对不上,且偏移可能越来越大;声音断断续续、变速、爆音;画面卡顿、马赛克、绿屏或大面积花屏;帧率明显下降、动作不连贯;seek 后音画错位或短时间花屏。
通用排查思路:
- 音视频 pts 必须同源、单调递增:上行音视频时间戳要来自同一时钟,音视频误差尽量控制在 150ms 以内。视频按实际帧率增长,丢帧、变帧率、动态码率场景下应使用采集或编码时刻的真实时间戳;音频必须严格按采样率与每帧采样点数递增,详见下文"音视频卡顿"。
- 编码参数必须与实际数据一致:xs_stream_createStream 传入的 videoParam、audioParam 要与实际送来的码流一致,包括 H264/H265、G711A/G711U/AAC/OPUS、采样率(8K/16K)、声道数、位宽。采样率或声道数声明错误会导致播放速度异常,并持续累积成音画偏移,G711 的 8K 与 16K 不能混用。
- 控制塞流节奏:直播按实际帧率塞流,本地回放按 1 倍速逐帧推送;不要一次性灌入大量数据,否则 SDK 内部平滑与 APP 端抖动缓冲会导致音画漂移。
- 关键帧与 seek 标记:直播和回放的第一帧必须是关键帧且包含 SPS/PPS;seek 之后按回调参数回填 seekId,否则 APP 会出现花屏或重新对齐失败。
- 回放的 utcms 计算方式:回放时视频和音频的 utcms 应填写"回放起始时间 + pts",不能填当前系统时间;pts 本身可以相对文件或片段从 0 递增。按时间回放时 seekTime 是 UTC 绝对时间,按文件回放时是相对文件起始位置的偏移量。
- 倍速回放的时间戳处理:按原始编码 pts 塞入数据,由 SDK 内部按倍速换算,不要自行缩放 pts;非 1 倍速时 SDK 会丢弃音频流,属于正常现象。
- 多目设备的音频与关键帧:双目/多目只需创建一条流,音频只塞一份,通过 lensId 区分镜头;各镜头视频关键帧尽量对齐,否则 APP 端画面切换会出现不同步。
- 排除网络因素:弱网丢包会表现为卡顿、花屏和丢帧,先通过 QoS 统计确认丢包率,并检查路径 MTU;具体优化见下文"丢帧"。
- 排除设备时基问题:如果音频采集时钟与视频时钟不同源(不同晶振),长时间直播会出现缓慢漂移,建议在设备侧统一时基,使用同一个系统时钟生成 pts 和 utcms。
音视频卡顿:重点检查音频时间戳:
- 音频 pts 严格按采样率均匀递增:每帧增量 = 本帧采样点数 ÷ 采样率 × 1000 ms。例如 8kHz、20ms/帧对应 160 个采样点,每帧固定 +20ms;16kHz、20ms/帧对应 320 个采样点,同样是 +20ms。不要直接用系统时间代替pts
- 丢帧后不能压缩时间轴:如果上层丢弃了若干音频帧,后续帧的 pts 仍要按实际采样点累计,保留被丢帧占用的时长;否则播放器会认为音频突然加速或减速,表现为声音卡顿、爆音或音画逐渐漂移。
- 编码格式与帧长要匹配:G711A/G711U、AAC、OPUS 的采样率、声道数、位宽必须与 audioParam 一致;G711 的 8K/16K 不能混用。AAC/OPUS 要按真实帧长计算 pts,不能统一按 20ms 处理。
- 单个音频帧控制在 1KB 以内:编码后的音频帧超过 1KB 会被拆成多个 RTP 包发送,任一包丢失都会导致整帧丢弃或等待重传,也更容易受弱网抖动影响,播放端表现为卡顿、断续。AAC/OPUS 可通过缩短帧长(如 20ms)或降低码率控制单帧大小;G711 在 8K/20ms 下约 160 字节、16K/20ms 下约 320 字节,本身满足要求。
- 排查手段:连续打印 audio pts、相邻差值、payload_size、payload_duration,确认差值稳定在理论帧长(毫秒取整允许 ±1ms),没有回退、跳变或长期漂移;再对比 video pts,确认音视频时间轴没有持续分离。
花屏:优先排查塞入的视频数据,再排查网络:
- 关键帧必须带 SPS/PPS:每个 IDR 都要包含完整的 SPS/PPS;推流开始、seek 后、编码参数切换后、断线重连后的第一帧必须是 IDR。I 帧缺少参数集、P 帧被标成 I 帧,都会导致 APP 绿屏或花屏,直到下一个 IDR。
- GOP 内不能缺参考帧:H264/H265 的 P 帧依赖前面的参考帧,丢掉一个 P 帧后,同一 GOP 内后续 P 帧都会解码错误。弱网时 xs_stream_inputVideo 可能因 SDK 内部发送队列积压而耗时变长,如果业务线程为了追帧随意丢帧,很容易丢掉关键帧或参考帧,表现为花屏、马赛克。
- 丢帧要丢整个 GOP:确实需要丢帧时,不要只丢中间的 P 帧后继续发送后续 P 帧;应丢弃整个非关键 GOP,并尽快产生新的 IDR。
- 检查帧数据本身:确认 payload 没有被截断、没有在调用返回前被复用或释放;NAL 起始码和类型正确,frame_type 与实际 NAL 类型一致;分辨率、H264/H265 编码格式与 xs_stream_createStream 声明一致,不能混塞。
- 检查缓存与关键帧策略:streamBufferSize=0 时不要主动塞流,按 xs_startPushLiveStream、xs_stopPushLiveStream 回调和 xs_forceIFrame 请求控制;streamBufferSize>0 时建议不小于 1MB。缓存主要用于新观众首帧和事件预录,不能修复已经损坏的帧数据。
- 快速区分数据问题和网络问题:把设备实际塞入的同一段码流保存下来,用本地播放器或 ffmpeg 解码。本地也花屏,优先查编码器、SPS/PPS、GOP 丢帧和内存复用;本地正常而 APP 花屏,优先查上下行丢包、RTP 重传、MTU 和 APP 解码。
丢帧:优先排查网络,其次排查音视频时间戳:
- 网络带宽与丢包:上行带宽不足、丢包、抖动会导致 SDK 发送队列积压、RTP 重传不及时,APP 端表现为卡顿、丢帧、花屏。打开 QoS 丢包统计和带宽评估,观察 lost、targetBitrate、bufferMilliseconds,按建议调整编码码率或分辨率。
- 时间戳严重不同步:音视频 pts 长期偏差超过 150ms,或任一方向出现回退、大跳变,播放器可能主动丢帧维持同步,表现为"网络正常但一直丢帧"。重点检查音频是否按采样率均匀递增、视频是否使用真实采集/编码时间戳,以及丢帧后是否压缩了时间轴。
- 塞流缓存满:xs_stream_inputVideo、xs_stream_inputAudio 返回 XS_STREAM_EC_BUFFER_FULL(-10055)时,说明 SDK 内部缓存已满,继续高频塞流只会加剧积压。应做背压或限速,必要时丢弃非关键帧并等待队列恢复,不要忽略返回值。
- 关键帧间隔与 GOP:GOP 过长时,一次丢包会影响较长时间,直到下一个 IDR 才恢复;弱网下可适当缩短关键帧间隔,但会增加码率,需要结合带宽权衡。
- 重传与 FEC:videoParam.bpsMax 建议设置为编码码率的 2 倍,设置过小会影响弱网下的 RTP 重传缓存;FEC 默认关闭,弱网可评估开启,但会占用带宽,建议配合带宽评估使用。路径 MTU 低于 1100 字节会导致传输异常,建议两端均不低于 1300 字节。
- 多路流争抢带宽:同时推主码流、辅码流、多目多流或云录像时,总上行可能超过链路带宽;优先保证当前观看和录像码流,降低非关键码流的码率。
排查手段:
- outputFrame 的耗时:每条
send key frame末尾的cost:xms是这一帧从塞入到发出的实际耗时,正常为几十毫秒以内;如果持续增大到几百毫秒,说明发送队列在积压,会反过来拖慢塞流线程,表现为卡顿、丢帧,弱网时尤其明显。 - outputFrame 的时间戳:重点看
utcms(应为采集/编码时刻,回放时为“回放起始时间 + pts”)、pts:[origin:x, adjust:y](origin 是原始 pts,adjust 是 SDK 调整后的 pts,两者应同步增长,长时间直播时差值不应持续扩大)、lastAudioPts(长时间不变化说明音频断流,音画会逐渐漂移)、seekId(必须与回调参数一致)、pFramesSinceLastGop(一直增大且不归零说明长时间没有产生关键帧)。 - 发送统计(printfStatistics):约每 10 秒输出一次,用
outVideoFrames/outAudioFrames与duration换算出实际帧率,用outVideoBytes/outAudioBytes与duration换算出实际码率,与编码参数对比,确认没有长时间不出帧或码率异常。 - 发送质量统计(reportStatistic):
rtc push stats中的framesDiscardedOnSend、packetsDiscardedOnSend不为 0,说明发送侧在丢数据(带宽不足或队列满);nackCount、retransmittedPacketsSent增长说明接收侧丢包、正在重传;firCount、pliCount持续增长说明 APP 在反复请求关键帧,通常伴随花屏或解码失败;qualityLimitationReason非 0 表示编码受限(带宽/CPU)。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_UTC_TIMESTAMP_ERROR(-10022) | 音视频 utc 时间戳错误,检查设备时间是否同步、utcms 是否合法 |
| XS_STREAM_EC_BUFFER_FULL(-10055) | 塞流缓存已满,检查是否塞入过快或网络拥塞,必要时背压或丢弃非关键帧 |
| XS_STREAM_EC_PARAMS_INVALID(-10058) | 参数错误,检查编码参数、帧长、采样率是否与创建流时一致 |
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线,塞流前先确认设备状态 |
日志样板:
创建流(关键字:createStream,编码/采样率必须与实际塞流一致):
[2026-10-23 11:33:18 574][streamsdk][tid:34100]:[INFO][streamSource.cpp][createStream:715] [stream:0-0-0] | stream name: LiveStream-1 created success| service id:1 |transPrivateData:0 |videoCode:H265 |audioCodec:OPUS, sample rate:16000
输入统计(关键字:inputVideo,观察帧率、码率、GOP 和当前 I 帧):
[2026-10-23 11:33:41 734][streamsdk][tid:34124]:[INFO][streamSource.cpp][inputVideo:66] streamIndex:[0-0-0] |name:LiveStream-1 |gop count:4| duration:20463(ms) | video: codec:h265, count:174,size:507207, framerate:8.5(fps), bitrate:198.3(kbps)| audio: cnt:293, size:94865, frame rate:14.3(fps), bit rate:37.1(kbps) |current I: size:25571, pts:59324510, utcms:1792726421734
关键帧发送(关键字:outputFrame / send key frame,重点看 pts、utcms、lastAudioPts、seekId、pFramesSinceLastGop):
[2026-10-23 11:33:21 272][streamsdk][tid:34115]:[INFO][rtcPushStream.cpp][outputFrame:1178] [cms:demoWang003_0_0][lensId:0][stream:0]: send key frame,[type=1] cost:1ms, size:25571, utcms:1792726401271[2026-10-23 11:33:21 271], pts:[origin:59324510, adjust:59324510], lastAudioPts:0, speed:1.0, record flag:0, seekId:0, pFramesSinceLastGop:0
发送统计(关键字:printfStatistics):
[2026-10-23 11:33:31 291][streamsdk][tid:34115]:[INFO][rtcPushStream.cpp][printfStatistics:995] [streamid:demoWang003_0_0] | lensId:0 | mode:cms | type: live_preview| stats: gopCount:4, outVideoFrames:150, outAudioFrames:254, outVideoBytes:425125, outAudioBytes:81043, duration:9953ms
发送质量统计(关键字:reportStatistic / outBoundRtp;重点看 framesDiscardedOnSend、packetsDiscardedOnSend、nackCount、retransmittedPacketsSent、targetBitrate、qualityLimitationReason):
[2026-10-23 11:34:10 020][streamsdk][tid:34109]:[INFO][rtcPushStream.cpp][reportStatistic:1447] rtc push stats:{"channelName":"demoWang003_0_0","channelType":"cms","pushStat":{"audioBytes":115073,"audioCount":349,"gop":5,"pushSeconds":13,"videoBytes":593877,"videoCount":208},"rtcStat":{"localCandidate":{...},"outBoundRtp":{"bytesDiscardedOnSend":0,"bytesSent":1510395,"firCount":0,"framesDiscardedOnSend":0,"framesEncoded":519,"framesPerSecond":"14.9","framesSent":519,"hugeFramesSent":0,"keyFramesEncoded":13,"kind":"video","nackCount":0,"packetsDiscardedOnSend":0,"packetsSent":1547,"pliCount":17,"qualityLimitationReason":0,"retransmittedBytesSent":0,"retransmittedPacketsSent":0,"sliCount":0,"targetBitrate":0},"remoteCandidate":{...}}}
参考:StreamSDK接入 - 实时推流、StreamSDK接入 - 本地回放、StreamSDK接入 - FEC功能
直播回放失败
直播失败:
- 确认基础链路:设备已在线且已绑定 → xs_stream_createStream 返回大于 0 的 serviceId → xs_stream_start 调用成功。videoParam 和 audioParam 按照正确参数填写。
- 确认回调处理:收到 xs_startPushLiveStream 后,使用回调参数中的 serviceId 塞流,不能用 devId 代替;收到 xs_stopPushLiveStream 后停止塞流。若采用持续推流模式,可忽略这两个回调,直接使用 xs_stream_createStream 返回的 serviceId。
- 确认缓存策略:streamBufferSize 为 0 时不要主动塞流,等待 SDK 回调;同时 SDK 会在需要关键帧时通过 xs_forceIFrame 回调请求,设备需要立即产生 I 帧。
- 确认码流与声明一致:塞入的数据编码格式、分辨率、音频参数必须与创建流时声明的一致,关键帧要同时包含 SPS/PPS。需要动态切换 H264/H265 时,创建流必须声明 supportChangeVideoEncode,并通过 XS_STREAM_COMMAND_SET_VIDEO_ENCODE_TYPE 通知 SDK。
- 确认设备未被关闭:powerstate 关闭后需要销毁流;设备关闭状态下发起直播会返回 XS_STREAM_EC_DEVICE_IS_TURN_OFF,流已销毁后再塞流会返回 XS_STREAM_EC_NOT_EXIST,重新打开设备后需要重新创建流。
- 弱网优化:开启 QoS 统计观察丢包和收发速率,按需开启带宽自适应和 FEC;MTU 低于 1100 字节会导致播放异常,需要先解决链路 MTU 问题。
- P2P 建链是否成功:日志中出现 executeFailedIceAgentState、rtc channel disconnect,或 Candidate gathering time 超过 10 秒,说明 ICE 打洞失败或 STUN 不可达,APP 侧表现为一直加载或黑屏。
- 多目设备:某一路镜头无视频时,用 XS_VIDEO_FRAME_MISSING 通知 SDK;双目只需创建一条流,通过 lensId 区分镜头。
本地回放失败:
- 回调是否实现完整:录像查询(xs_queryRecordTimeSection、xs_queryRecordFileList、xs_queryMonthRecord)、开始回放(xs_startPlaybackByTime、xs_startPlaybackByFileName)、控制(xs_seekPlaybackByTime、xs_playbackCtrl、xs_pausePlayback、xs_stopPlayback)、能力查询(xs_getPlaybackProperty)。
- 查询规则是否正确:从回调参数 endTime 往前查询,优先返回最新片段;每次最多返回 pageSize 条(时间段列表和事件列表不超过 500 条,文件列表不超过 100 条),hasMore 必须正确设置;section 由设备分配内存,回调结束后由 SDK 释放;时间参数均为 UTC 毫秒。
- 开始回放是否正确:从回调的 startTime 或 fileName 开始,创建独立线程按 1 倍速逐帧推送,第一帧为关键帧,并保持音视频同步;serviceId 使用回调参数中的值,不要填错。
- seek 与暂停是否正确:暂停时停止塞流但不要退出推流线程;继续播放通过 seek 指令实现;seek 后的数据必须回填 seekId,首个关键帧 new_data_flag 置为 TRUE。
- 结束与异常是否正确通知:指定时间段播放完毕或文件结束时,发送 frame_type = XS_FRAME_PLAYBACK_END 的结束包(payload_size 填 0,seekId 必填);无数据可播、读盘异常等场景通过 xs_stream_sendCmd 发送 XS_STREAM_COMMAND_LOCALSTORAGE_NO_DATA 告知 APP。
- 并发路数是否超限:不实现 xs_getPlaybackProperty 时默认回放并发为 1 路,超过限制会返回 XS_STREAM_EC_PLAYBACK_STREAM_COUNT_REACH_TO_MAX。
- 网关跨子设备回放:必须为主设备创建音频流和视频流,且所有子设备录像的编码格式与主设备流一致;塞流时必须在 vinfo.deviceName 中填写当前子设备的 deviceName,seek 切换子设备后更新 deviceName 并强制关键帧,否则 APP 无法识别画面归属。
- 时间同步是否完成:设备系统时间未同步会导致回放时间轴错位,塞流时可能返回 XS_STREAM_EC_UTC_TIMESTAMP_ERROR。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_NOT_EXIST(-10000) | 音视频流不存在,检查是否已创建流、serviceId 是否正确 |
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线 |
| XS_STREAM_EC_CREATE_STREAM_SOURCE_FAILED(-10010) | 创建流失败,检查参数与内存是否充足 |
| XS_STREAM_EC_UTC_TIMESTAMP_ERROR(-10022) | utc 时间戳错误,检查设备时间同步与 utcms 处理 |
| XS_STREAM_EC_PLAYBACK_STREAM_COUNT_REACH_TO_MAX(-10025) | 回放并发路数达到上限 |
| XS_STREAM_EC_DEVICE_IS_TURN_OFF(-10033) | 设备处于关闭状态,需要先打开设备并重建流 |
| XS_STREAM_EC_FILE_NOT_EXIST(-10035) | 回放文件不存在,检查按文件名回放传入的 fileName |
| XS_STREAM_EC_INVALID_SERVICE_ID(-10037) | serviceId 无效,确认使用的是回调参数中的 serviceId |
| XS_STREAM_EC_BUFFER_FULL(-10055) | 缓冲区已满,检查塞流速度是否过快 |
日志样板:
收到直播请求(关键字:thing.media.push):
[2026-10-23 11:33:21 112][iotsdk][tid:34112]:[im_client.c][INFO][recv_msg_cb:993] [wan] recv msg, cmd id = 163, | route id:0, src:mas-go, src session: |body:{"deviceName":"demoWang003","id":"179272640128300003","lensId":0,"method":"thing.media.push","params":{"protocol":"rtc","app":"xLeWcoelZbyOePbJludl","stream":"demoWang003_0_0","lensId":0,"channel":"0","sdp":"","sdpZip":"","url":""},"productKey":"xLeWcoelZb...
创建直播流通道(直播通道不存在的情况下才会创建,创建后无人播放后,通道默认会保持5分钟):
[2026-10-23 11:33:21 112][streamsdk][tid:34109]:[INFO][rtcChannelMgr.cpp][startCmsLivePlay:408] appname:xLeWcoelZbyOePbJludl, streamId:demoWang003_0_0
[2026-10-23 11:33:21 114][streamsdk][tid:34109]:[INFO][rtcPushStream.cpp][CreateRtcChannel:409] [streamid:demoWang003_0_0]create rtc channel success, protocol:UDP | channel mode:CMS | play type:live_preview | video codec:H265 | audio codec:OPUS| needTransPrivateData:0 |videoBpsMax:0 |dataChannel:disable |twcc:enable |gccbwe:enable |fec:disable |audioTrackCount:1 |videoTrackCount:1
通道建立成功:
[2026-10-23 11:33:21 271][streamsdk][tid:34114]:[INFO][rtcPushStream.cpp][OnStatus:599] [streamid:demoWang003_0_0] rtc channel connect, remote ip[121.43.252.249:8000], public ip
通道建立失败:
[2026-10-23 14:24:27 769][streamsdk][tid:36915]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:iceAgentGatherCandidateTimerCallback(): [Candidate gathering time] Time taken: 10004 ms
[2026-10-23 14:24:27 788][streamsdk][tid:36915]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:fromCheckConnectionIceAgentState(): operation returned status code: 0x5a00000d
[2026-10-23 14:24:27 788][streamsdk][tid:36915]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:executeFailedIceAgentState(): IceAgent failed with 0x5a00000d
[2026-10-23 14:24:27 788][streamsdk][tid:36915]:[INFO][rtcPushStream.cpp][OnStatus:610] [streamid:demoWang003_0_0] rtc channel disconnect
SDK回调上层开始推流:
[2026-10-23 11:33:21 271][streamsdk][tid:34114]:[INFO][streamSource.cpp][operator():672] [stream:0-0-0][service id:1]: trigger push live stream callback | preRecordDuration:0
发送关键帧(每个关键帧都会打印):
[2026-10-23 11:33:21 272][streamsdk][tid:34115]:[INFO][rtcPushStream.cpp][outputFrame:1178] [cms:demoWang003_0_0][lensId:0][stream:0]: send key frame,[type=1] cost:1ms, size:25571, utcms:1792726401271[2026-10-23 11:33:21 271], pts:[origin:59324510, adjust:59324510], lastAudioPts:0, speed:1.0, record flag:0, seekId:0, pFramesSinceLastGop:0
回放开始(关键字:StartVodByTime / startPlaybackByTime / startPlaybackCb):
[2026-10-23 11:33:45 980][streamsdk][tid:34109]:[INFO][streamExtMsg.cpp][processServiceMsg:1300] serviceId:StartVodByTime
[2026-10-23 11:33:45 980][streamsdk][tid:34109]:[INFO][rtcChannelMgr.cpp][startPlaybackByTime:463] [demoWang003_0_l0_sdplayback_1792726426177_chan_cms]playback by time |srcId:54| start time:1792724493927[2026-10-23 11:01:33 927] | isSeek:0 |seekId:2
[2026-10-23 11:33:45 980][streamsdk][tid:34109]:[INFO][streamSource.cpp][createStream:715] [stream:0-0-0] | stream name: PlaybackStream-4 created success| service id:4 |transPrivateData:0 |videoCode:H265 |audioCodec:OPUS, sample rate:16000
[2026-10-23 11:33:46 144][streamsdk][tid:34126]:[INFO][rtcChannel.cpp][CheckStartPlayback:285] call startPlaybackCb, serviceId:4, beginTime:1792724493927, seekId:2
[2026-10-23 11:33:46 146][streamsdk][tid:34127]:[INFO][rtcPushStream.cpp][outputFrame:1178] [cms:demoWang003_0_l0_sdplayback_1792726426177_chan_cms][lensId:0][stream:0]: send key frame,[type=1] cost:2ms, size:71880, utcms:1792724493993[2026-10-23 11:01:33 993], pts:[origin:70227487, adjust:70227487], lastAudioPts:0, speed:1.0, record flag:0, seekId:2, pFramesSinceLastGop:0
seek(关键字:StartVodByTime / isSeek:1 / seekId):
[2026-10-23 11:33:48 181][streamsdk][tid:34109]:[INFO][rtcChannelMgr.cpp][startPlaybackByTime:498] rtc channel exist, this is seek request
[2026-10-23 11:33:48 226][streamsdk][tid:34127]:[INFO][rtcPushStream.cpp][outputFrame:1178] [cms:demoWang003_0_l0_sdplayback_1792726426177_chan_cms][lensId:0][stream:0]: send key frame,[type=1] cost:1ms, size:22611, utcms:1792725230422[2026-10-23 11:13:50 422], pts:[origin:51607145, adjust:70229468], lastAudioPts:552603953, speed:1.0, record flag:0, seekId:3, pFramesSinceLastGop:30
直播结束(关键字:thing.media.nonereader / StopCmsLivePreview / trigger stop push live stream callback,无人观看或 APP 关流都会走到这里):
[2026-10-23 11:34:10 020][iotsdk][tid:34112]:[im_client.c][INFO][recv_msg_cb:997] [wan] recv msg, cmd id = 167, | route id:0, src:mas-go, src session: |body:{"deviceName":"demoWang003","id":"b24f3009-9b57-443b-b1c3-459a59d04679","lensId":0,"method":"thing.media.nonereader","params":{"app":"xLeWcoelZbyOePbJludl","stream":"demoWang003_0_0"},"productKey":"xLeWc***************","sys":{"ack":0},"version":"1.0.0"}
[2026-10-23 11:34:10 020][streamsdk][tid:34109]:[INFO][rtcChannelMgr.cpp][stopPlayback:1438] stop play| streamId:demoWang003_0_0
[2026-10-23 11:34:10 020][streamsdk][tid:34105]:[INFO][streamSource.cpp][operator():682] [service id:1]: trigger stop push live stream callback
回放结束与销毁(关键字:StopVod / destroyStream / trigger stopPlayback,用于区分"回放没有数据"和"播放已经结束"):
[2026-10-23 11:33:37 470][streamsdk][tid:34109]:[INFO][streamExtMsg.cpp][processServiceMsg:1300] serviceId:StopVod
[2026-10-23 11:33:37 470][streamsdk][tid:34109]:[INFO][streamSource.cpp][destroyStream:727] stream[3] destroy begin
[2026-10-23 11:33:37 470][streamsdk][tid:34109]:[INFO][streamSource.cpp][destroyStream:750] [service id:3]: trigger stopPlayback
[2026-10-23 11:33:37 505][streamsdk][tid:34109]:[INFO][streamSource.cpp][destroyStream:761] stream[0x3] destroy end
P2P 链路建立成功对照(关键字:enable gcc bwe / ICE Hole Punching Time / rtc channel connect):
[2026-10-23 11:33:21 112][streamsdk][tid:34109]:[INFO][rtcPushStream.cpp][CreateRtcChannel:361] [streamid:demoWang003_0_0] enable gcc bwe, initial:1000000, min:300000, max:4000000
[2026-10-23 11:33:21 271][streamsdk][tid:34114]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:changePeerConnectionState(): [ICE Hole Punching Time] Time taken: 75 ms
[2026-10-23 11:33:21 271][streamsdk][tid:34114]:[INFO][rtcPushStream.cpp][OnStatus:599] [streamid:demoWang003_0_0] rtc channel connect, remote ip[121.43.252.249:8000], public ip
P2P 链路建立失败(关键字:IceAgent failed / rtc channel disconnect / Candidate gathering time):
注意:日志里可能经常会有P2P通道(streamid中带有P2P字样的)建立失败的日志如下,因为默认是走转发模式播起来之后,后台尝试建立P2P直连,建立失败会有如下打印,但是不影响直播和回放功能。
[2026-10-23 11:33:34 127][streamsdk][tid:34116]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:executeFailedIceAgentState(): IceAgent failed with 0x5a00000d
[2026-10-23 11:33:34 127][streamsdk][tid:34116]:[INFO][rtcPushStream.cpp][OnStatus:610] [streamid:demoWang003_0_l0_live_1792726401146_chan_P2P] rtc channel disconnect
[2026-10-23 11:33:34 144][streamsdk][tid:34116]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:iceAgentGatherCandidateTimerCallback(): [Candidate gathering time] Time taken: 10046 ms
[2026-10-23 11:33:35 212][streamsdk][tid:34108]:[INFO][rtcPushStream.cpp][Stop:499] [streamid:demoWang003_0_l0_live_1792726401146_chan_P2P] stop end
参考:StreamSDK接入 - 实时推流、StreamSDK接入 - 本地回放、网关设备接入 - 跨镜头回放
对讲失败
现象:APP 点击对讲后设备端没有回调;对讲无声、断续或几秒后自动结束;带屏设备没有双向视频。
排查思路:
- 检查回调是否注册并正确返回:xs_talkCallback_st 中的 talkStartCb、talkAudioCb、talkVideoCb(带屏设备)、talkStopCb、queryTalkParams。talkStartCb 返回非 0 会直接导致本次对讲失败。
- 检查音频格式协商:未实现 xs_queryTalkParams 时,APP 默认下发与主码流一致的音频格式(即 xs_stream_createStream 中指定的 audioParam);如果设备只能接收固定格式,必须实现 xs_queryTalkParams。APP 下行支持 G711A/G711U/OPUS,G711 需区分 8K/16K,设备侧按回调中的 encode 和实际帧长解码播放;日志中 create rtc pull channel 后的 audio codec/sample 就是协商结果,应与实际送数一致。
- 检查播放链路:talkAudioCb 回调的是编码后的音频数据,需要按 encode 解码后送入扬声器;回调内不要阻塞写声卡,建议入队后由独立播放线程处理,否则会出现卡顿、丢帧。
- 确认对讲链路已建立:日志顺序应为
thing.service.StartVoiceIntercom→create rtc pull channel→rtc pull channel connected→startTalk→send rtc channel status | status:0。只有 StartVoiceIntercom 而没有 startTalk,说明 RTC 通道没有建起来,重点检查设备网络、STUN 与端口;通道走 tcp 还是 udp 由 SDK 自动选择,设备侧不需要干预。 - 用 inbound audio statistic 判断 APP 上行是否正常:正常情况下约每秒输出一条,8kHz/G711A、20ms/帧时 total count 每秒增加约 50、bytes 每秒增加约 8000、cur audio pts 每秒增加 1000、size 固定 160 字节。count 长时间不增长说明 APP 没在推流或通道已断开;size 与协商格式不符说明 encode 或帧长不对;pts 跳变说明 APP 侧送数异常。对讲结束时 rtc pull stats 中的 audioCount/audioBytes 可用于核对实际对讲时长,videoCount 为 0 表示本次只有单向音频。
- 双向视频对讲:带屏设备需要实现 talkVideoCb,并在 xs_queryTalkParams 中返回可接收的 videoCodec,否则只有单向音频;StartVoiceIntercom 中的 MediaType 与结束时 rtc pull stats 的 videoCount,可用于确认本次是否真的带视频。
- 检查 busy 状态:上一次对讲没有正常停止就再次发起,会出现对讲 busy(XS_STREAM_EC_AUDIO_TALK_BUSY);需要在 talkStopCb 中释放资源、停止播放,保证下一次可以重新开始。
- 检查设备状态:设备离线、已关闭、流已销毁时无法对讲;低功耗设备在业务期间不能进入休眠,需要结合 xs_forbidSleepCallback 与 WakeupHeartbeat 心跳控制休眠时机。
- 检查回声与无声问题:确认扬声器通路、采样率、声道数、音量增益配置正确;外放设备建议开启 AEC 并保证参考信号,否则容易产生回声甚至啸叫。同时确认 APP 侧已获得麦克风权限。
- 投屏场景:Alexa / Google Home 投屏的双向语音复用对讲链路,设备侧已实现对讲回调即可,回调会照常触发。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_PARAM_NOT_ENOUGH(-10001) | 缺少参数,检查创建流时的音频参数 |
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线 |
| XS_STREAM_EC_AUDIO_TALK_BUSY(-10023) | 对讲通道被占用,确认上一次对讲是否已停止 |
| XS_STREAM_EC_DEVICE_WILL_SLEEP(-10047) | 设备即将休眠,客户端会收到此错误码;对讲期间需要禁止休眠 |
| XS_STREAM_EC_NOT_INITIALIZED(-10050) | StreamSDK 未初始化 |
日志样板:
对讲开始(关键字:thing.service.StartVoiceIntercom / create rtc pull channel / startTalk / ChannelStatus):
[2026-10-23 14:27:52 352][iotsdk][tid:36943]:[im_client.c][INFO][recv_msg_cb:997] [wan] recv msg, cmd id = 143, | route id:3, src:54, src session:28a4e1595bca@signaling-1 |body:{"deviceName":"demoWang003","id":"179273687235700141","lensId":0,"method":"thing.service.StartVoiceIntercom","params":{"App":"xLeWcoelZbyOePbJludl","Stream":"ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms","Protocol":"rtc","ChannelType":"cms","MediaType":"audio"},...}
[2026-10-23 14:27:52 354][streamsdk][tid:36940]:[INFO][rtcPullStream.cpp][CreateRtcChannel:168] [streamid:ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms]create rtc pull channel, ret=0, protocol:tcp, audio codec:2 sample:8000
[2026-10-23 14:27:52 445][streamsdk][tid:36940]:[INFO][rtcChannel.cpp][SetServerSdp:492] [streamid:ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms] set server sdp[length:3158], m_gotServerSDP:0, m_getLocalSdp:1
[2026-10-23 14:27:52 544][streamsdk][tid:36951]:[INFO][rtcPullStream.cpp][OnStatus:55] [streamid:ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms] rtc pull channel connected,
[2026-10-23 14:27:52 544][streamsdk][tid:36951]:[INFO][rtcChannel.cpp][startTalk:547] devId:0, start talk, talkServiceId:1
[2026-10-23 14:27:52 544][streamsdk][tid:36951]:[INFO][streamMsg.cpp][sendRtcChannelStatusToPeer:764] send rtc channel status | Stream:ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms | status:0
音频接收统计(关键字:inbound audio statistic,约每秒一条,重点看 count、bytes、pts 与 size):
[2026-10-23 14:27:53 659][streamsdk][tid:36951]:[INFO][rtcChannel.cpp][OnFrame:629] [ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms]inbound audio statistic| codec:G711A| total count:50 ,bytes:8000| cur audio pts:980, size:160
[2026-10-23 14:28:03 658][streamsdk][tid:36951]:[INFO][rtcChannel.cpp][OnFrame:629] [ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms]inbound audio statistic| codec:G711A| total count:550 ,bytes:88000| cur audio pts:10980, size:160
对讲结束(关键字:thing.service.StopVoiceIntercom / stopTalk / rtc pull stats):
[2026-10-23 14:28:07 797][iotsdk][tid:36943]:[im_client.c][INFO][recv_msg_cb:997] [wan] recv msg, cmd id = 143, | route id:3, src:54, src session:28a4e1595bca@signaling-1 |body:{"deviceName":"demoWang003","id":"179273688782000145","lensId":0,"method":"thing.service.StopVoiceIntercom","params":{"App":"xLeWcoelZbyOePbJludl","Stream":"ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms"},...}
[2026-10-23 14:28:07 797][streamsdk][tid:36940]:[INFO][rtcChannelMgr.cpp][stopTalk:1714] [ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms]stop talk
[2026-10-23 14:28:07 798][streamsdk][tid:36940]:[INFO][rtcPullStream.cpp][reportStatistic:216] rtc pull stats:{"channelName":"ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms","channelType":"cms","pullStat":{"audioBytes":121120,"audioCount":757,"videoBytes":0,"videoCount":0}}
[2026-10-23 14:28:07 866][streamsdk][tid:36940]:[INFO][rtcPullStream.cpp][Stop:235] [streamid:ANDRC_60bf08aeab413f3a94f076a4a173c043_push_1792736872271_chan_cms] stop end
# 观察点:count 每秒约 +50、bytes 每秒约 +8000、pts 每秒约 +1000、size 固定 160,说明 8kHz/G711A、20ms/帧。
# count 不增长表示 APP 未推流或通道已断开;videoCount 为 0 表示本次只有单向音频。
对讲busy(关键字:CheckTalkBusy,SDK会返回-10023错误码给APP端)
[2026-10-23 14:32:35 267][streamsdk][tid:36940]:[INFO][rtcChannelMgr.cpp][CheckTalkBusy:1757] devId:0, talk busy, cms pull stream ongoing
[2026-10-23 14:32:35 268][iotsdk][tid:36932]:[im_client.c][INFO][im_client_send_message:1144] [wan] send msg | cmdId:144 |msgLen:197 | msg: {"code":-10023,"deviceName":"demoWang003","id":"179273715525100161","lensId":0,"message":"talk busy","method":"thing.service.StartVoiceIntercom","productKey":"xLeWc***************","version":"1.0"} | sendTimes:0
参考:StreamSDK接入 - 音视频对讲、低功耗SDK接入
物模型相关问题
现象:APP 设置属性后一直 loading 或提示失败;属性变化后 APP 不刷新;事件上报成功但 APP 查不到;调用服务无响应;参数类型不匹配导致异常。
排查思路:
- 严格遵循控制台的物模型定义:属性、服务、事件的标识符、数据类型、取值范围必须与 TSL 完全一致,大小写敏感;物模型不一致会解析失败甚至崩溃(-5100/-5101/-5102)。
- 自定义物模型不要与 SDK 内部名称冲突:SDK 内部使用 CloudResource、Reconnect、TriggerPicCapture、StartVoiceIntercom、QueryRecordTimeList、StartVod、PreConnect 等服务,以及 CloudRecordPlan、DeviceOffPlan、deviceSdkAbility、LensCount、VideoCodec、AudioCodec 等属性,自定义项不要复用这些名称。
- 属性设置要回传 sessionId:收到 xs_setPropertyCallback(格式如
{"VoiceDetectionSensitivity":{"value":2}})后修改本地参数,再用 xs_iot_report_v2 上报修改后的值,并把回调中的 sessionId 原样带回,否则本次设置不会得到正确确认。参数之间存在互斥关系时,设备自行处理并一并上报。 - 属性上报的前提与限制:必须在线且已绑定(否则 -5010 / -5208);payload 不超过 4096 字节(-5013);上报是异步的,结果通过 xs_reportMsgCallback 返回;普通消息最多重传一次,重要消息会一直重传;队列满返回 XSIOT_EC_MSG_QUEUE_FULL(-5214);10 秒内超过 300 条会被限流(XSIOT_EC_REPORT_RATE_LIMIT,-5215)。
- 属性未变化不会上报:SDK 只上传有变化的值,未变化时返回 XSIOT_EC_PROPERTY_NOT_CHANGED(-5023)。CellularSignal、ElectricQuantity、StorageRemainCapacity、WiFiRSSI 等属性 SDK 内部还有额外节流,APP 需要实时值时建议通过物模型服务主动查询。
- 事件上报:eventId 必须是物模型定义的事件标识符,否则云端会过滤;jsonData 不超过 4096 字节;带图片的事件必须在 xs_iot_init 的初始化参数中设置 eventPicBufSize,否则图片会被丢弃;isPassthroughMessage = 1 表示仅透传给客户端,不保存到云端。
- 服务调用:按 serviceid 区分服务,request 按物模型 inputData 解析;response 按 outputData 拼装,内存由 SDK 在回调结束后 free,不要自己释放,也不要返回栈内存。
- 海外设备时区:TimezoneOffset 属性由设备侧处理,修改后调用 xs_iot_set_timezone;SDK 不会修改设备时区,只处理夏令时切换并通过 xs_timezoneCallback 下发。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XSIOT_EC_DEVICE_NOT_BIND(-5010) | 设备未绑定,先完成绑定再上报 |
| XSIOT_EC_MSG_TOO_LONG(-5013) | 消息过长,payload 不能超过 4096 字节 |
| XSIOT_EC_PROPERTY_NOT_CHANGED(-5023) | 属性未变化,SDK 未执行上报 |
| XSIOT_EC_PARSE_JSON_FAILED(-5100) | JSON 解析失败,检查上报内容格式 |
| XSIOT_EC_JSON_KEY_NOT_FOUND(-5101) | JSON 中缺少物模型定义的字段,核对标识符 |
| XSIOT_EC_PROPETY_PARSE_FAILED(-5102) | 物模型属性解析错误,核对类型与取值范围 |
| XSIOT_EC_STATUS_NOT_READY(-5208) | 设备不在线或未绑定 |
| XSIOT_EC_MSG_QUEUE_FULL(-5214) | 发送消息缓存队列已满,降低上报频率或调整队列大小 |
| XSIOT_EC_REPORT_RATE_LIMIT(-5215) | 上报频率超限(10 秒内超过 300 条) |
日志样板:
属性上报(关键字:thing.event.property.post):
[2026-10-23 11:34:20 034][iotsdk][tid:34100]:[xs_iot_api.c][INFO][xs_iot_report:56] devId:0 | msgType:1 |payload:{"StorageStatus":1}
[2026-10-23 11:34:20 035][iotsdk][tid:34101]:[im_client.c][INFO][im_client_send_message:1144] [wan] send msg | cmdId:101 |msgLen:202 | msg: {"id":"0a075e5c1bc54ece8579088be250afa9","version":"1.0","productKey":"xLeWc***************","deviceName":"demoWang003","method":"thing.event.property.post","sys":{"ack":1},"params":{"StorageStatus":1}} | sendTimes:1
[2026-10-23 11:34:20 073][iotsdk][tid:34112]:[iot_ext_msg.c][INFO][set_msg_resp_code_in_queue:2700] reportMsgCb | devId:0 | serviceId:4 |result:0 | errStr:
属性未变化(关键字:no property change):
[2026-10-23 11:33:20 180][iotsdk][tid:34101]:[iot_ext_msg.c][INFO][handle_tsl_report_msg:1015] no property change, do not report
服务调用(关键字:thing.service.PTZActionControl / service callback):
[2026-10-23 11:33:57 708][iotsdk][tid:34112]:[iot_device.c][INFO][iot_dev_tsl_service_cb:384] service callback |serviceId:PTZActionControl | devId:0 |param:{"ActionType":1,"Step":10} | ret:0
DataChannel 透传通道异常
现象:设备收不到通道打开回调;发送自定义数据返回失败或超时;大文件传输中途通道被关闭;APP 与设备对 cmdId 的理解不一致。
排查思路:
- 确认通道已建立:DataChannel 由 APP 发起,协商成功后设备才会收到 xs_DataChannelOpenCb;在收到 open 回调之前不要调用 xs_stream_sendUserData,也不要自行假设 channelId,否则可能返回通道未就绪或通道不存在(-10052/-10053)。
- 检查回调注册与处理:初始化时注册 onDataChannelOpen、onDataChannelMsg、onDataChannelClose;收到数据回调后先拷贝数据再异步处理,不要在回调中做耗时或阻塞操作,否则会拖慢整个通道。
- cmdId 必须在约定范围内:0~1000 由业务自定义,1000 以上为相速内部使用;越界或与内部命令冲突会返回 XS_STREAM_EC_INVALID_COMMOND_ID(-10042),APP 与设备必须使用同一套 cmdId 定义。
- 单次发送不超过 128KB:xs_stream_sendUserData 单次长度上限为 128KB,超出需要自行分片,并自定义序号、校验与重传;超长会发送失败(XS_STREAM_EC_MSG_TOO_LONG,-10060)。
- 发送超时与通道关闭:timeoutMs 填 0 时使用 SDK 默认的 30 秒;接口返回成功只表示数据已塞入底层 socket 缓冲区,不代表对端已收到。发送失败或超时(XS_STREAM_EC_SEND_DATA_TIMEOUT,-10061)后 SDK 会认为通道不可用并触发 xs_DataChannelCloseCb,此时不能继续使用该 channelId,需要等待 APP 重新建立通道。
- 控制发送速率:默认上限约 2MB/s,可通过 XS_STREAM_COMMAND_SET_DATACHANNEL_RATE_LIMIT 调整(64~10240 KB/s,0 表示恢复默认);瞬间灌入大量数据容易触发超时甚至断链。
- 关注链路 MTU:路径 MTU 过小时大包分片会失败,可通过 PMTU 探测命令(XS_STREAM_COMMAND_PROBE_PMTU)让 SDK 自动调整 DataChannel 分片长度;设备侧最小 MTU 不低于 1100 字节。
- 区分信令与透传数据:物模型属性和服务走 IoTSDK 信令通道,DataChannel 只用于设备与 APP 之间的自定义二进制数据(图片、录像、文件等),不要混用。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线 |
| XS_STREAM_EC_INVALID_COMMOND_ID(-10042) | cmdId 无效,检查是否超出 0~1000 |
| XS_STREAM_EC_CHANNEL_NOT_READY(-10052) | 通道未就绪,等待 open 回调后再发送 |
| XS_STREAM_EC_CHANNEL_NOT_EXIST(-10053) | 通道未创建,检查 APP 是否已发起建链 |
| XS_STREAM_EC_MSG_TOO_LONG(-10060) | 单次发送超过 128KB,需要分片 |
| XS_STREAM_EC_SEND_DATA_TIMEOUT(-10061) | 发送超时,通道随后会被关闭 |
日志样板:
可以在日志中过滤datachannel关键字查看所有和datachannel相关的信息:
APP 发起建链(关键字:CreateDataChannel / createDataChannel):
[2026-10-23 11:33:38 012][streamsdk][tid:34109]:[INFO][streamExtMsg.cpp][processServiceMsg:1300] serviceId:CreateDataChannel
[2026-10-23 11:33:38 012][streamsdk][tid:34109]:[INFO][streamExtMsg.cpp][createDataChannel:2404] [stream-demoWang003_data_1792726418124_android_cms]createDataChannel channelName:binary protocol:kcp(useKcp:1)
[2026-10-23 11:33:38 012][streamsdk][tid:34109]:[INFO][rtcPushStream.cpp][CreateRtcChannel:409] [streamid:demoWang003_data_1792726418124_android_cms]create rtc channel success, protocol:UDP | channel mode:CMS | play type:data_channel | video codec:H265 | audio codec:OPUS| needTransPrivateData:0 |videoBpsMax:0 |dataChannel:enable |twcc:disable |gccbwe:disable |fec:disable |audioTrackCount:0 |videoTrackCount:0
[2026-10-23 11:33:38 012][streamsdk][tid:34109]:[INFO][rtcChannelMgr.cpp][createDataChannel:2127] [devId:0]create data channel demoWang003_data_1792726418124_android_cms[binary] protocol:KCP success
通道打开(关键字:OnDataChannelStatus / onDataChannelOpen):
[2026-10-23 11:33:38 603][streamsdk][tid:34122]:[INFO][rtcPushStream.cpp][OnDataChannelStatus:1550] [demoWang003_data_1792726418124_android_cms]data channel [kcp-ch-0] is connected
[2026-10-23 11:33:38 604][streamsdk][tid:34122]:[INFO][dataChannelMsg.cpp][start:500] onDataChannelOpen, chanel id:1, devId:0
收发数据(关键字:cmdId / handleBufferData / onRecvMessage / sendAckMsg;1001 为相速内部命令,业务自定义请用 0~1000):
[2026-10-23 11:33:38 604][streamsdk][tid:34122]:[INFO][dataChannelProto.cpp][handleBufferData:135] [DC Proto] handleBufferData: parsed msg cmdId=1001 headLen=283 jsonLen=274 binaryLen=0 totalLen=283
[2026-10-23 11:33:38 604][streamsdk][tid:34122]:[INFO][dataChannelMsg.cpp][onRecvMessage:297] [DC Msg] onRecvMessage cmdId=1001 jsonLen=274 binaryLen=0 json={"deviceName":"demoWang003","id":"179272641812000023","lensId":0,"method":"thing.service.GetPic","params":{"PicIds":["1792726357980"]},...}
[2026-10-23 11:33:38 604][streamsdk][tid:34123]:[INFO][dataChannelMsg.cpp][sendAckMsg:219] [demoWang003_data_1792726418124_android_cms]output msg, json len:209, binaryLen:40133, total len:40351
通道断开 (关键字:onDataChannelClose)
[2026-10-23 14:46:19 948][streamsdk][tid:37145]:[INFO][dataChannelMsg.cpp][stop:516] onDataChannelClose, chanel id:1, devId:0
[2026-10-23 14:46:19 949][streamsdk][tid:37179]:[INFO][dataChannelMsg.cpp][ThreadProcess:124] [demoWang003_data_1792737954435_android_cms] data channel thread end
[2026-10-23 14:46:19 949][streamsdk][tid:37145]:[INFO][dataChannelMsg.cpp][~DataChannelMsg:75] [demoWang003_data_1792737954435_android_cms]~Destroyed
投屏(Alexa / Google Home)异常
现象:音箱投屏黑屏、一直加载或被拒绝;投屏建立后短时间正常,随后画面突然中断、需要重新发起;连续多次投屏只有个别成功;投屏没有声音或双向语音无效;投屏结束后直播画面花屏、编码没有恢复。
投屏链路的四个关键特征:
- 独立 P2P 通道 + TURN 中继:SDK 收到
thing.service.ScreenCast(内部 serviceId 为ScreenCast)后,独立创建 P2P 通道推流,不复用 APP 的直播通道;投屏只使用实时流,不支持回放,固定lensId=0。 - 投屏的 streamId 不是码流名:请求中的 streamId 是服务器生成的通道名(形如
设备名_随机串),不是device_0_0形式的码流名;实际使用的码流由 channel 字段或设备通过 XS_STREAM_COMMAND_SET_SCREENCAST_CHANNEL 指定,不要用 streamId 去推导码流。 - 没有会话级的开始/断开通知:投屏是否成功、何时断开都在 SDK 与 RTC 内部判定,设备侧只能通过
xs_startPushLiveStream/xs_stopPushLiveStream感知推流状态,日志中不会出现对应的业务错误回调。 - 断开由设备侧超时判定:当前没有定义"停止投屏"的会话级信令,对端停止回程数据约 20 秒后,由设备侧 ICE 判定通道失效并自行断开(
0x5a000025)。"推流几十秒到几分钟后自己断了"多数属于该超时,不是 SDK 主动掐断,也不是设备侧崩溃;SDK 不会自动重连,对端需要重新发起投屏。
投屏链路的 4 个关键阶段(先按日志确认卡在哪一步):
| 阶段 | 日志关键字 | 正常表现 |
|---|---|---|
| 1. 收到投屏请求 | processServiceMsg 后紧跟 serviceId:ScreenCast | 随后打印 recv screenCast request, streamId:..., channel:..., source:... |
| 2. 回复 SDP answer | screenCast SDP answer ready, length:xxxx → screenCast success | 收到请求后约 1 秒内返回,answer 长度约 2925~2933 字节 |
| 3. ICE 建链成功 | rtc channel connect + trigger push live stream callback | answer 后约 4Candidate gathering time 约 2.5 |
| 4. 推出首帧 | send first key frame | 建链后 100ms 左右出现,之后是周期性的 send key frame |
投屏失败/中断的两种典型模式(用于区分"从未连上"和"连上后又断开"):
| 模式 | 日志序列 | 含义 | 排查方向 |
|---|---|---|---|
A 0x5a00000d | fromCheckConnectionIceAgentState → executeFailedIceAgentState → rtc channel disconnect → Candidate gathering time: ~10000 ms | 从未建链成功,候选收集约 10 秒超时,期间没有 iceAgentReadyStateSetup / Selected pair | 对端没有发起 ICE 连通性检查(对端未真正拉流、出口限制 UDP、TURN 不可用),详见排查思路第 10 条 |
B 0x5a000025 | fromReadyIceAgentState → executeFailedIceAgentState → rtc channel disconnect(没有候选收集超时日志) | 建链并推流一段时间后断开,约 20 秒收不到对端回程数据 | 对端停止拉流、退出投屏或网络中断,详见排查思路第 11 条 |
排查思路:
- 确认前提:设备已绑定到对应的技能账号,参考 Alexa / Google Home 接入指南;投屏只使用实时流,不支持回放,采用单镜头(lensId=0)模式。
- 确认编码能力:投屏使用 H264 与音箱协商。如果投屏使用的码流是 H265,创建流时必须将 videoParam.supportChangeVideoEncode 设为 TRUE,并实现 xs_changeVideoEncode 回调;投屏开始阶段为严格模式,回调未实现或返回失败会直接拒绝本次投屏。
- 编码切换回调要快速返回:不要在 xs_changeVideoEncode 中同步切换编码器或阻塞等待,只记录目标编码并通知推流线程;切换完成后使用回调参数中的 serviceId 塞入 H264 数据。投屏结束后 SDK 会延迟约 30 秒回调恢复原编码,该阶段为尽力而为,失败不影响本次业务。
- 码流选择是否正确:服务器不知道哪条码流是 H264,可通过 XS_STREAM_COMMAND_SET_SCREENCAST_CHANNEL 指定投屏码流(devId + streamId,取值 0~streams_per_lens-1,0 表示主码流、1 表示辅码流,lensId 忽略);未指定时 SDK 默认按 1(辅码流)处理;超出每镜头码流数会返回 XS_STREAM_EC_NOT_EXIST(-10000),日志为
[stream-x-x-x not exist]。日志中screenCast: use user configured channel:4 (devId:0), sdp channel:1表示设备指定了 4 号码流、信令下发的是 1 号码流,二者不一致时以此条日志为准。 - 推流回调必须正常响应:投屏所选码流没有消费者时,SDK 仍会触发 xs_startPushLiveStream 通知设备开始推流;设备不能因为没有 APP 在观看就拒绝推流或停止塞流。
- 声音与双向语音:先确认音箱支持收音、设备支持音频播放(满足条件时 SDK 默认开启双向对讲);投屏的双向语音复用对讲链路,设备需要正常实现对讲回调,只有单向声音时重点检查 xs_queryTalkParams 返回的音频格式与音频输出通路。Google Home 来源默认不开启对讲(日志
screenCast from google home, skip startTalk)。 - 编码切换期间 APP 会重新拉流,这是 SDK 内部行为,设备不需要额外处理,但必须保证切换后塞入的码流编码与回调要求一致。
- 弱网场景:投屏多使用辅码流,画面卡顿先看带宽和丢包,可开启 QoS 丢包统计(XS_STREAM_COMMAND_ENABLE_ADAPTIVE_BITRATE)确认,必要时调整辅码流的分辨率与码率。
- SDP answer 是否超时(阶段 2 缺失):投屏模式下 SDK 最多等待 5 秒生成 SDP answer(内部等待本地 relay 候选约 3 秒),超时打印
screenCast SDP answer timeout并直接判本次投屏失败。Alexa 来源不等待本地 relay 候选,Google Home 及其他来源需要等待 relay 候选就绪,因此 TURN/STUN 不可达、relay 候选收集慢会表现为"收到投屏请求但一直没有回复";候选过多时可用 XS_STREAM_COMMAND_SET_ICE_INTERFACE_FILTER 过滤 docker0/veth 等虚拟网卡。 - answer 已回复但通道连不上(模式 A):先区分设备侧与对端。设备侧 TURN 分配、候选收集、SDP answer 都正常,只是没有
Selected pair时,说明对端根本没有向设备的 relay 候选发起连通性检查。此时应重点查对端网络出口是否限制 UDP、对端应用是否真正开始拉流,而不是继续排查设备。 - 建链后一段时间断开(模式 B):
0x5a000025表示 ICE 曾经 ready、之后失败,且没有候选收集超时日志。设备侧保活依赖对端回程数据,实测对端停止回程后 20.5~21.3 秒才判定断开,因此这类"自动断开"要以抓包中对端回程帧(Binding Request / ChannelData)的最后一帧时间为基准,确认是对端退出了投屏还是对端网络中断;断开后 SDK 会延迟 1 秒释放该通道(日志####delay task to release live stream)。 - 并发投屏与停流时机:同一账号短时间连续发起多次投屏时,可能同时存在多条投屏通道。同一路流已经在推流时,SDK 不会重复触发 xs_startPushLiveStream(日志表现为没有
trigger push live stream callback,但仍会出现start push stream与send first key frame),此时设备按之前收到的回调继续塞流即可;只要该路流还有其他会话在消费,就不会回调 xs_stopPushLiveStream,因此不要按"某条投屏通道断开"自行停流,一切以 xs_startPushLiveStream / xs_stopPushLiveStream 回调为准。P2P 通道数超过 p2pChannelCount 时新的投屏会被拒绝,日志为p2p count reach to max, reject screenCast request,返回 XS_STREAM_EC_P2P_CHANNEL_REACH_TO_MAX(-10029)。并发投屏还会抢占对讲链路,日志为talk busy, screenCast skip startTalk。 - 投屏结束后编码未恢复:投屏通道断开(对端超时断开或收到 StopScreenCast)都会在 30 秒后触发编码恢复,期间如果重新开始投屏会取消恢复(日志
cancel screenCast restore encode task)。如果 30 秒后仍未恢复 H265,检查 xs_changeVideoEncode 回调是否实现、返回是否成功,以及恢复时该流是否被新的投屏占用(日志screenCast is active, skip restore encode)。 - 低功耗场景:设备即将休眠时 SDK 会忽略投屏请求并返回 XS_STREAM_EC_DEVICE_WILL_SLEEP(-10047),日志为
device will sleep, ignore screenCast request;投屏开始后 SDK 会调用 DEV_CTRL_TYPE_FORBID_SLEEP 禁止休眠,设备侧不要自行进入休眠。 - 双向语音中途没声音:对讲开始后如果超过 10 秒收不到对端的音视频数据,SDK 会自动停止对讲并通知对端(日志
no talk data for 10 seconds/notify no talk data),表现为"投屏画面正常但语音几秒后断开"。此时应确认对端是否停止了上行音频,而不是设备侧音频通路问题。
排查手段:
- 按 4 个关键阶段对时间线:卡在第 1 步查绑定与信令,卡在第 2 步查 SDP 与 TURN 候选,卡在第 3 步查 ICE 与对端,第 4 步之后查塞流与编码。
- 按关键字过滤:投屏链路日志相对分散,建议按下方"日志关键字"逐条 grep,重点确认
screenCast success、rtc channel connect、send first key frame三个节点是否齐全。 - 打开 SDP 明文:
[screenCast:offer]/[screenCast:answer]由编译宏 PRINT_SDP_INFO 控制,现场版本默认不打印;需要核对 H264、candidate、ufrag 时再编译打开。 - 用对端现象交叉验证:模式 A 时同步查看对端(Google Home 应用 / Chrome 的 chrome://webrtc-internals)的 ICE agent,确认对端是否在发送 Binding Request;设备侧 TURN 分配全部成功、失败会话对端回程为 0,即可判定问题在对端网络出口或对端应用。
- 确认实际使用的码流:核对创建流时的 streams_per_lens、是否调用过 XS_STREAM_COMMAND_SET_SCREENCAST_CHANNEL,以及
use user configured channel与recv screenCast request, channel的差值。 - 双向语音按对讲章节排查:用
inbound audio statistic的 count/bytes/pts/size 判断对端上行是否正常,并区分talk busy与no talk data两类日志。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_NOT_EXIST(-10000) | 投屏指定的码流不存在,检查 streams_per_lens 与 SET_SCREENCAST_CHANNEL 取值 |
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线 |
| XS_STREAM_EC_CREATE_RTC_CHANNEL_FAILED(-10015) | 创建投屏 P2P 通道失败,检查内存与 P2P 通道数 |
| XS_STREAM_EC_STREAM_PARAM_NOT_ENOUGH(-10024) | 投屏信令缺少 stream 或 sdpOffer |
| XS_STREAM_EC_P2P_CHANNEL_REACH_TO_MAX(-10029) | P2P 通道数达到上限(p2pChannelCount),新的投屏请求被拒绝 |
| XS_STREAM_EC_INVALID_SERVICE_ID(-10037) | serviceId 无效 |
| XS_STREAM_EC_DEVICE_WILL_SLEEP(-10047) | 设备即将休眠,投屏请求被忽略 |
| XS_STREAM_EC_PARAMS_INVALID(-10058) | 参数错误,例如投屏码流 streamId 超出范围 |
日志关键字(投屏链路日志较少,现场按下列关键字过滤):
# 请求与 SDP:serviceId:ScreenCast / recv screenCast request / screenCast: use user configured channel
# / [ScreenCast] waiting for relay candidate / relay candidate ready
# / screenCast SDP answer ready / screenCast success / screenCast SDP answer timeout
# 建链与推流:Selected pair / rtc channel connect / trigger push live stream callback / start push stream / send first key frame
# 失败与断开:0x5a00000d(约10秒候选收集超时,从未连上)/ 0x5a000025(推流中约20秒无对端回程后自断)/ rtc channel disconnect
# 编码切换:switchVideoEncode / screenCast end, restore encode / cancel screenCast restore encode task / skip restore encode
# 双向语音:talk busy, screenCast skip startTalk / start talk / no talk data for 10 seconds / notify no talk data
日志样板:
收到投屏请求与生成 SDP answer(关键字:serviceId:ScreenCast / SDP answer ready / screenCast success):
[2026-10-12 15:20:14 803][streamsdk][tid:1886]:[INFO][streamExtMsg.cpp][processServiceMsg:1282] serviceId:ScreenCast
[2026-10-12 15:20:15 142][streamsdk][tid:1886]:[INFO][rtcChannelMgr.cpp][startScreenCast:881] recv screenCast request, streamId:Rh8K2hY****_cb4d956c22, channel:4
[2026-10-12 15:20:15 174][streamsdk][tid:1886]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:SetRemoteOffer(): [ScreenCast] remote does not support trickle ICE, waiting for relay candidate...
[2026-10-12 15:20:15 732][streamsdk][tid:836]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:OnIceCandidateHandler(): [ScreenCast] relay candidate detected, notifying SetRemoteOffer
[2026-10-12 15:20:15 737][streamsdk][tid:1886]:[INFO][rtcChannel.cpp][OnLocalAnswerReady:475] [streamid:Rh8K2hY****_cb4d956c22] screenCast SDP answer captured
[2026-10-12 15:20:15 738][streamsdk][tid:1886]:[INFO][rtcChannelMgr.cpp][startScreenCast:975] [streamid:Rh8K2hY****_cb4d956c22] screenCast SDP answer ready, length:2925
[2026-10-12 15:20:16 772][streamsdk][tid:1886]:[INFO][streamExtMsg.cpp][processScreenCast:2642] screenCast success, streamId:Rh8K2hY****_cb4d956c22, channel:4, sdpAnswer length:2925
# 若等待超过 5 秒仍无 answer,会打印 screenCast SDP answer timeout,本次投屏失败:
# [rtcChannelMgr.cpp][startScreenCast] [streamid:...] screenCast SDP answer timeout
ICE 建链与推流回调(关键字:Selected pair / Candidate gathering time / rtc channel connect / trigger push live stream callback):
[2026-10-12 15:20:18 117][streamsdk][tid:836]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:iceAgentReadyStateSetup(): Selected pair TRISgmIi_vn0awYVO, local candidate type: relay. remote candidate type: relay. Round trip time 0 ms
[2026-10-12 15:20:18 150][streamsdk][tid:836]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:iceAgentGatherCandidateTimerCallback(): [Candidate gathering time] Time taken: 2978 ms
[2026-10-12 15:20:20 368][streamsdk][tid:837]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:changePeerConnectionState(): [ICE Hole Punching Time] Time taken: 5194 ms
[2026-10-12 15:20:20 369][streamsdk][tid:837]:[INFO][rtcPushStream.cpp][OnStatus:562] [streamid:Rh8K2hY****_cb4d956c22] rtc channel connect, remote ip[174.138.***.***:50402], public ip
[2026-10-12 15:20:20 369][streamsdk][tid:837]:[INFO][streamSource.cpp][operator():666] [stream:0-0-4][service id:5]: trigger push live stream callback | preRecordDuration:0
推出首帧(关键字:start push stream / send first key frame):
[2026-10-12 15:20:20 402][streamsdk][tid:837]:[INFO][rtcPushStream.cpp][AttachMediaSink:708] [streamid:Rh8K2hY****_cb4d956c22] start push stream | videoTrack:0
[2026-10-12 15:20:20 478][streamsdk][tid:1884]:[INFO][rtcPushStream.cpp][outputFrame:1054] [p2p:Rh8K2hY****_cb4d956c22][lensId:0], send first key frame, size:32213, utcms:1791789620472, record flag:0
模式 A:候选收集约 10 秒超时,从未建链(关键字:0x5a00000d / Candidate gathering time ~10000ms):
[2026-10-12 15:21:29 604][streamsdk][tid:1041]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:fromCheckConnectionIceAgentState(): operation returned status code: 0x5a00000d
[2026-10-12 15:21:29 607][streamsdk][tid:1041]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:executeFailedIceAgentState(): IceAgent failed with 0x5a00000d
[2026-10-12 15:21:29 607][streamsdk][tid:1041]:[INFO][rtcPushStream.cpp][OnStatus:573] [streamid:Rh8K2hY****_54789ef2b2] rtc channel disconnect
[2026-10-12 15:21:29 636][streamsdk][tid:1041]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:iceAgentGatherCandidateTimerCallback(): [Candidate gathering time] Time taken: 10035 ms
# 该模式下没有 Selected pair、没有 rtc channel connect、没有 trigger push live stream callback。
模式 B:推流一段时间后设备侧超时自断(关键字:0x5a000025 / fromReadyIceAgentState):
[2026-10-12 15:20:51 160][streamsdk][tid:836]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:fromReadyIceAgentState(): operation returned status code: 0x5a000025
[2026-10-12 15:20:51 161][streamsdk][tid:836]:[INFO][rtcChannelMgr.cpp][onRtcLogCallback:59] [rtclog]:executeFailedIceAgentState(): IceAgent failed with 0x5a000025
[2026-10-12 15:20:51 161][streamsdk][tid:836]:[INFO][rtcPushStream.cpp][OnStatus:573] [streamid:Rh8K2hY****_cb4d956c22] rtc channel disconnect
[2026-10-12 15:20:52 175][streamsdk][tid:1885]:[INFO][rtcChannelMgr.cpp][operator():1469] ####delay task to release live stream:Rh8K2hY****_cb4d956c22
[2026-10-12 15:20:53 349][streamsdk][tid:1869]:[INFO][streamSource.cpp][operator():676] [service id:5]: trigger stop push live stream callback
# 本会话 15:20:20 推出首帧、15:20:51 断开,推流约 31 秒;无 Candidate gathering 超时日志。
并发投屏抢占对讲(关键字:talk busy / screenCast skip startTalk):
[2026-10-12 15:27:45 185][streamsdk][tid:1886]:[WARN][rtcChannelMgr.cpp][startScreenCast:990] [streamid:Rh8K2hY****_b8d5abbdce] device:0 talk busy, screenCast skip startTalk, audio/video receive from remote may not work
双向语音超时结束(关键字:no talk data for 10 seconds / notify no talk data):
[2026-10-12 15:36:17 347][streamsdk][tid:1885]:[INFO][rtcChannel.cpp][operator():556] devId:0, no talk data for 10 seconds, _latestPktRecvTime:1791790567262, stop talk
[2026-10-12 15:36:17 353][streamsdk][tid:1885]:[INFO][streamMsg.cpp][notifyNoTalkData:944] notify no talk data, Stream:Rh8K2hY****_5760321c4e
投屏结束后的编码恢复(关键字:screenCast end, restore encode / cancel screenCast restore encode task):
# 投屏结束时先调度延迟任务:
# [rtcChannelMgr.cpp][onScreenCastEnd] [stream-x-0-4] screenCast end, restore encode from H264 to H265 after 30000ms
# 30 秒后实际恢复:
# [rtcChannelMgr.cpp][restoreStreamEncode] [stream-x-0-4] restore video encode from H264 to H265 after screenCast end
# 期间重新投屏则取消:
# [rtcChannelMgr.cpp][cancelScreenCastRestore] [serviceId:x] cancel screenCast restore encode task
参考:投屏和对讲、低功耗SDK接入、Amazon Alexa技能接入指南、Google Home技能接入指南
OTA 升级异常
现象:控制台检测不到新版本;APP 不弹升级提示;升级包下载失败、校验失败或升级卡住;升级完成后版本号不变。
排查思路:
- 版本号上报:通过 xs_iot_report_v2 加 XS_IOT_MSG_OTA_INFORM 上报
{"version":"1.1.0","module":"default"};module 必须与控制台升级包所属模块一致(默认 default),存在多个模块时要分别上报,否则控制台无法检测到升级包。 - 区分版本号用途:OTA_INFORM 中的 version 仅用于检测升级包,APP 展示的固件版本通常取物模型中的 IpcVersion,两者不要混淆。
- 确认升级包与网络:控制台已上传升级包并配置测试设备;设备能访问 fileUrl(OSS/HTTPS),系统时间正确,否则可能因证书校验失败或 URL 过期导致下载失败。
- 解析升级回调:upgradeCb 中的 files 是数组,可能包含多个升级包,需要逐个下载、校验 signMethod/fileSign 后烧写,不能只处理第一个。
- 按阶段上报进度:每完成一个阶段调用 xs_iot_report_v2(XS_IOT_MSG_OTA_PROGRESS)上报 step;失败时上报负数并在 desc 中写明原因,desc 不超过 128 个字符。
- 升级后再次上报版本:烧写完成并重启后重新上报 OTA_INFORM,否则平台仍认为设备是旧版本,可能重复推送升级。
- 空间与完整性:下载前确认剩余空间足够、fileSign 校验通过;校验失败不要继续烧写,避免设备变砖。
进度与状态值:
| step | 含义 |
|---|---|
| 3 | 文件下载成功 |
| 4 | 升级中 |
| 5 | 升级成功 |
| -1 | 升级失败 |
| -2 | 下载失败 |
| -3 | 校验失败 |
| -4 | 烧写失败 |
日志关键字(OTA 链路日志较少,现场按下列关键字过滤):
# OTA 链路按关键字过滤:
# 版本上报:XS_IOT_MSG_OTA_INFORM / thing.event.property.post(version)
# 升级通知:upgradeCb / files / fileUrl / fileSign
# 进度上报:XS_IOT_MSG_OTA_PROGRESS / step
参考:OTA 固件在线升级
云录像与智能告警异常
现象:设备有报警但 APP 没有事件记录或没有云录像;云录像时长与预期不符;报警图片不显示;断网期间的录像没有补录。
排查思路:
- 区分智能告警和自定义事件:只有 xs_stream_postIntelligentAlarm 上报的智能告警会在套餐支持时触发云录像;xs_iot_report_event_v2、xs_stream_report_event 上报的自定义事件不会触发云录像。
- 上报时机与频率:收到 serviceReadyCb 后再上报;报警持续发生时可以持续调用,但间隔不小于 5 秒,否则返回 XS_STREAM_EC_EVENT_TOO_FREQUENT(-10040);图片过大或缓存已满会分别返回 -10020/-10021;extra 字符串不超过 2KB(超出会被截断)。
- 事件时长由策略决定:云端套餐决定定长或不定长录制(定长默认 15 秒,不定长单个事件最长 10 分钟),设备也可通过 XS_STREAM_COMMAND_SET_ALARM_RECORD_POLICY 覆盖;如果设备自定义了录像策略,先确认没有被外部策略覆盖。
- 云存套餐与计划:通过 cloudResourceCb 获取套餐类型与有效天数;CloudRecordPlan 时间段外不会触发云录像(-10031);低功耗与长电模式的云录像规则不同,切换功耗模式后通过 XS_STREAM_COMMAND_DEVICE_POWER_MODE 通知 SDK。
- 主码流与音频:SDK 默认使用 streamId=0 作为云录像码流,创建流时的音频参数决定录像是否有声;云录像音频默认开启,可用 XS_STREAM_COMMAND_DISABLE_AUDIO_CLOUD_RECORD 关闭(关闭后录像用静音包替换,直播音频不受影响),该功能目前仅支持 OPUS 20ms/40ms 且需要填写 payload_duration。
- 图片补传:TF 卡挂载/拔出时调用 XS_STREAM_COMMAND_LOCALSTORAGE_PLUG_IN / PLUG_OUT,SDK 会缓存上传失败的图片并重传;如果设备自己实现了图片补传,则不要设置,否则会重复上传。
- 上报失败不会自动重传:xs_stream_postIntelligentAlarm 是异步接口,事件或图片上传失败后不会重传,结果通过 xs_asyncResultCallback 返回;需要可靠补传时,可启用 TF 卡图片缓存(仅图片)或自行记录失败事件后重试。
- 断网补录:根据 connectStatusCb 记录断网时间段,网络恢复后按套餐与有效天数调用 xs_stream_createAppendStream 创建补录流,使用返回的 serviceId 推流(不能复用直播流的 serviceId);推流必须连续,出现空洞需要销毁后重新创建;结束后 xs_stream_destroyAppendStream 会返回实际补录起止时间,再通过 xs_stream_postAppendIntelligentAlarm 补事件和图片。
- 时间与状态:设备未同步时间会导致事件时间和录像时间轴错误;设备离线或未绑定时上报会失败,先看 -10002。
常见错误码:
| 错误码 | 说明与处理 |
|---|---|
| XS_STREAM_EC_STATUS_NOT_READY(-10002) | 设备未绑定或不在线 |
| XS_STREAM_EC_PICTURE_SIZE_TOO_BIG(-10020) | 图片太大,超过 imgMaxSize |
| XS_STREAM_EC_PICTURE_BUF_FULL(-10021) | 图片缓存已满 |
| XS_STREAM_EC_NOT_IN_CLOUD_RECORD_PLAN(-10031) | 不在云录像计划时间段内 |
| XS_STREAM_EC_EVENT_TOO_FREQUENT(-10040) | 事件上报频率过快 |
| XS_STREAM_EC_CAPTURE_PIC_TOO_FREQUENT(-10041) | 抓图频率过快(小于 3 秒) |
| XS_STREAM_EC_OSS_UPLOAD_FAILED(-10045) | OSS 上传失败,检查网络与对象存储配置 |
日志样板:
云存服务就绪(关键字:serviceReadyCb):
[2026-10-23 11:33:19 144][streamsdk][tid:34109]:[INFO][deviceCtrl.cpp][ioCtrl:102] serviceReadyCb |devId:0 | service ready ready
云存套餐下发(关键字:CloudResource / handleCloudResourceMsg):
[2026-10-23 11:33:19 145][streamsdk][tid:34109]:[INFO][streamExtMsg.cpp][handleCloudResourceMsg:1574] devId:0 | serviceDays:1, service type:3 |recordPicture:1 |eventInterval:60 |duration:15
报警图片缓存目录异常(关键字:alarmPicDelayUpload):
[2026-10-23 11:33:28 635][streamsdk][tid:34105]:[ERROR][alarmPicDelayUpload.cpp][getFilesWithPrefix:45] can not open dir:/home/ubuntu/sdcard/rtcx_data/xLeWcoelZbyOePbJludl_demoWang003/alarmPic/
抓图上传(关键字:TriggerPicCapture / postCapturePicture / uploadCapturePictureToOss):
[2026-10-23 11:34:05 370][streamsdk][tid:34104]:[INFO][streamExtMsg.cpp][processServiceMsg:1300] serviceId:TriggerPicCapture
[2026-10-23 11:34:05 375][streamsdk][tid:34104]:[INFO][streamExtMsg.cpp][postCapturePicture:2276] post capture picture| serviceId:1 | picture len:40133
[2026-10-23 11:34:05 497][streamsdk][tid:34104]:[INFO][streamExtMsg.cpp][uploadCapturePictureToOss:2260] [serviceId:1] upload picture success, size:40133, ret:0
# 智能告警与云录像按关键字过滤:xs_stream_postIntelligentAlarm / xs_asyncResultCallback / cloudRecordMgr / createAppendStream / postAppendIntelligentAlarm