本文将介绍腾讯云物联网(IoT)应用端 SDK 的视频通话能力(
TXIoTCallSession):如何在 App 或小程序中呼叫已绑定的设备,建立双向音视频通话,并在通话中控制摄像头、麦克风与音频播放设备。视频通话基于腾讯云实时音视频(TRTC)链路实现,相比传统 IoT 场景常用的 P2P 直连方案,在接通成功率、接通速度、回声消除与弱网表现等方面具备明显优势,详情参见 方案优势。功能概述
TXIoTCallSession 提供的能力包括:发起与挂断呼叫、支持纯音频与音视频两种通话类型、渲染远端画面、开关本地麦克风与摄像头、前后摄像头切换、扬声器与听筒切换、通话双方网络质量监控。视频通话适用于需要「人与人、人与宠物实时对话」的消费类设备场景:

方案优势
TXIoTCallSession 底层复用腾讯云实时音视频(TRTC)的媒体网络与音频处理能力:应用端与设备分别就近接入 TRTC 媒体网络,在同一房间内互相推流与订阅,不依赖手机与设备之间的端到端直连。对通话这类强交互场景,这一架构选择带来的差异尤为明显。与传统 IoT P2P 方案对比

前提条件
在使用视频通话能力前,请确保已完成以下准备工作:
已开通腾讯云物联网相关服务,并在控制台完成实例、应用和设备的准备工作。可参见 开通服务。
已在客户端工程中集成 IoT 应用端 SDK 并完成登录。可参见 Android 集成与登录、iOS 集成与登录。
目标设备已绑定到当前登录账户名下,或已被分享给当前账户。可参见 设备绑定。
目标设备为支持音视频通话的设备,且当前处于在线状态。
已在工程中申请并获取麦克风权限;进行音视频通话时还需获取摄像头权限。
使用建议
调用顺序:获取通话会话对象 →
addListener 注册回调 → callDevice 发起呼叫 → startRemoteView 绑定远端渲染视图 → 收到 onCallBegin 后开启本地麦克风与摄像头 → 结束时 hangup。提前申请权限:在调用
callDevice 之前完成麦克风与摄像头权限申请。若在通话中才发现权限缺失,SDK 会回调错误并结束本次通话。提前绑定远端视图:
startRemoteView 可以在 onCallBegin 之前调用。SDK 会暂存该视图,待设备进入房间后自动绑定,无需等待回调再设置。区分音频与视频的接收方式:远端音频在通话建立后自动播放,无需额外调用;远端视频必须调用
startRemoteView 才会渲染画面。尽早注册监听:在
callDevice 之前完成 addListener(iOS 为 addDelegate),避免错过 onCallBegin、onCallRejected 等关键回调。统一处理呼叫失败:设备拒接、无应答、忙线、离线各有独立回调,不都走
onError,请分别处理并给出差异化提示。详见「呼叫结果与超时机制」。及时释放:退出通话界面时调用
hangup 并移除监听,避免资源泄漏与重复回调。警告:
通话会话对象为单例,同一时刻只允许存在一路通话。若当前已有通话在进行中(已调用
callDevice 且尚未结束),再次调用 callDevice 会通过 onError 回调 ERR_FAILED(iOS 为 TXIoTErrorCodeFailed)并携带「通话已在进行中」的错误信息,原有通话不受影响。切换通话对象前必须先 hangup。视频通话的优先级高于监控拉流。若目标设备当前正被监控拉流,对该设备发起视频通话会抢占设备的推流能力,正在进行的监控会话会被中断,详见 监控拉流。
接入步骤
步骤 1:获取通话会话对象
登录成功后,通过
TXIoTEngine 实例调用 getCallSession() 获取通话会话对象。若返回 null(iOS 为 nil),通常是 SDK 尚未登录或登录已过期。TXIoTCallSession session = TXIoTEngine.getInstance(context).getCallSession();if (session == null) {// SDK 未登录或登录已过期,请先完成登录return;}
TXIoTCallSession *session = [[TXIoTEngine getInstance] getCallSession];if (session == nil) {// SDK 未登录或登录已过期,请先完成登录return;}
步骤 2:注册监听回调
呼叫结果、通话开始与结束、远端流状态、网络质量均通过监听接口返回。请在
callDevice 之前完成注册。TXIoTCallListener callListener = new TXIoTCallListener() {@Overridepublic void onCallBegin(TXIoTCallMediaType mediaType) {// 通话正式开始,切换到通话中界面}@Overridepublic void onCallEnd(TXIoTCallMediaType mediaType, TXIoTCallEndReason reason) {// 通话结束,根据 reason 区分本端挂断、远端挂断、权限不足、网络异常}@Overridepublic void onCallRejected(TXIoTCallUser callUser) {// 设备端用户拒接}@Overridepublic void onCallNoResponse(TXIoTCallUser callUser) {// 呼叫超时,设备端无人接听}@Overridepublic void onCallLineBusy(TXIoTCallUser callUser) {// 设备正忙(已在另一路通话中)}@Overridepublic void onCallUserOffline(TXIoTCallUser callUser) {// 设备离线}@Overridepublic void onCallUserVideoAvailable(TXIoTCallUser callUser, boolean available) {// 远端视频流可用状态变化,可据此显示占位图}@Overridepublic void onCallUserAudioAvailable(TXIoTCallUser callUser, boolean available) {// 远端音频流可用状态变化}@Overridepublic void onCallNetworkQualityChanged(TXIoTQuality localQuality, List<TXIoTQuality> remoteQualityList) {// 网络质量等级变化,可提示“对方网络不佳”}@Overridepublic void onError(TXIoTErrorCode code, String msg) {// 统一处理错误码与用户提示}};session.addListener(callListener);
@interface CallViewController () <TXIoTCallDelegate>@end@implementation CallViewController- (void)onCallBegin:(TXIoTCallMediaType)mediaType {// 通话正式开始,切换到通话中界面}- (void)onCallEnd:(TXIoTCallMediaType)mediaType reason:(TXIoTCallEndReason)reason {// 通话结束,根据 reason 区分本端挂断、远端挂断、权限不足、网络异常}- (void)onCallRejected:(TXIoTCallUser *)callUser {// 设备端用户拒接}- (void)onCallNoResponse:(TXIoTCallUser *)callUser {// 呼叫超时,设备端无人接听}- (void)onCallLineBusy:(TXIoTCallUser *)callUser {// 设备正忙}- (void)onCallUserOffline:(TXIoTCallUser *)callUser {// 设备离线}- (void)onCallUserVideoAvailable:(TXIoTCallUser *)callUser available:(BOOL)available {// 远端视频流可用状态变化}- (void)onCallNetworkQualityChanged:(TXIoTQuality *)localQualityremoteQualityList:(NSArray<TXIoTQuality *> *)remoteQualityList {// 网络质量等级变化}- (void)onError:(TXIoTErrorCode)errorCode errorMessage:(NSString *)errorMessage {// 统一处理错误码与用户提示}@end// 注册代理[session addDelegate:self];// 退出时注销// [session removeDelegate:self];
步骤 3:呼叫设备
调用
callDevice 发起呼叫,callType 指定通话类型:AUDIO 为纯音频通话,VIDEO 为音视频通话。方法 | 说明 |
callDevice | 呼叫目标设备。 deviceId 为设备标识(productId + deviceName),callType 取值见 TXIoTCallMediaType。呼叫结果通过 onCallBegin、onCallRejected、onCallNoResponse、onCallLineBusy、onCallUserOffline 等回调返回。 |
hangup | 挂断当前通话并释放资源,本端挂断后会收到 onCallEnd,结束原因为 LOCAL_HANGUP。 |
TXIoTDeviceId deviceId = new TXIoTDeviceId();deviceId.productId = "已绑定到当前账户的 productId";deviceId.deviceName = "已绑定到当前账户的 deviceName";// 发起音视频通话;纯语音通话传 TXIoTCallMediaType.AUDIOsession.callDevice(deviceId, TXIoTCallMediaType.VIDEO);
TXIoTDeviceId *deviceId = [[TXIoTDeviceId alloc] init];deviceId.productId = @"已绑定到当前账户的 productId";deviceId.deviceName = @"已绑定到当前账户的 deviceName";// 发起音视频通话;纯语音通话传 TXIoTCallMediaTypeAudio[session callDevice:deviceId callType:TXIoTCallMediaTypeVideo];
步骤 4:渲染远端画面
通过
startRemoteView 为远端用户绑定渲染视图。远端用户由 TXIoTCallUser 标识,其中的 deviceId 即被呼叫的设备。注意:
远端音频在通话建立后自动播放,无需调用任何接口;远端视频必须调用
startRemoteView 才会渲染。若只听到声音看不到画面,请首先检查是否调用了 startRemoteView。TXIoTCallUser callUser = new TXIoTCallUser();callUser.deviceId = deviceId;// remoteView 为布局中的 TXCloudVideoView,可在 onCallBegin 之前调用TXCloudVideoView remoteView = findViewById(R.id.remote_view);session.startRemoteView(callUser, remoteView);// 需要关闭远端画面时// session.stopRemoteView(callUser);
TXIoTCallUser *callUser = [[TXIoTCallUser alloc] init];callUser.deviceId = deviceId;// remoteView 为布局中的 UIView,可在 onCallBegin 之前调用[session startRemoteView:callUser view:self.remoteVideoView];// 需要关闭远端画面时// [session stopRemoteView:callUser];
说明:
纯音频通话场景下小程序端同样需要通过
setIoTPlayer 绑定 <iot-player> 组件,远端音频通过该组件播放。步骤 5:开启本地麦克风与摄像头
通话开始后,调用
openMicrophone 上行本地音频;音视频通话还需调用 openCamera 上行本地画面并显示本地预览。// 开启麦克风上行session.openMicrophone();// 开启摄像头上行并在 localView 中预览TXCloudVideoView localView = findViewById(R.id.local_view);session.openCamera(TXIoTCamera.FRONT, localView);// 通话中关闭麦克风(静音自己)// session.closeMicrophone();// 通话中关闭摄像头(转为纯语音)// session.closeCamera();
// 开启麦克风上行[session openMicrophone];// 开启摄像头上行并在 localView 中预览[session openCamera:TXIoTCameraFront view:self.localVideoView];// 通话中关闭麦克风(静音自己)// [session closeMicrophone];// 通话中关闭摄像头(转为纯语音)// [session closeCamera];
步骤 6:挂断通话
结束通话时调用
hangup,随后会收到 onCallEnd 回调;退出界面时同时移除监听。session.hangup();session.removeListener(callListener);
[session hangup];[session removeDelegate:self];
常用能力
切换摄像头与音频播放设备
通话过程中可切换前后摄像头,并在扬声器与听筒之间切换音频播放通路。
说明:
音频播放设备枚举在各端命名略有差异:Android 为
SPEAKER_PHONE / EAR_PIECE,iOS 为 TXIoTAudioPlaybackDeviceSpeakerphone / TXIoTAudioPlaybackDeviceEarpiece,小程序为 SPEAKERPHONE / EARPIECE。// 切换到后置摄像头session.switchCamera(TXIoTCamera.BACK);// 切换到听筒播放(贴耳通话)session.selectAudioPlaybackDevice(TXIoTAudioPlaybackDevice.EAR_PIECE);// 切换回扬声器(免手持)session.selectAudioPlaybackDevice(TXIoTAudioPlaybackDevice.SPEAKER_PHONE);
// 切换到后置摄像头[session switchCamera:TXIoTCameraBack];// 切换到听筒播放(贴耳通话)[session selectAudioPlaybackDevice:TXIoTAudioPlaybackDeviceEarpiece];// 切换回扬声器(免手持)[session selectAudioPlaybackDevice:TXIoTAudioPlaybackDeviceSpeakerphone];
网络质量监控
onCallNetworkQualityChanged 同时回传本端质量与远端质量列表,质量等级取值见 TXIoTNetworkQuality(EXCELLENT、GOOD、POOR、BAD、VERY_BAD、DOWN)。该回调仅在质量等级发生变化时触发,不是周期性回调,可直接用于驱动界面提示,无需业务层再做去重。@Overridepublic void onCallNetworkQualityChanged(TXIoTQuality localQuality, List<TXIoTQuality> remoteQualityList) {if (localQuality.quality == TXIoTNetworkQuality.BAD|| localQuality.quality == TXIoTNetworkQuality.VERY_BAD) {// 提示用户“当前网络不佳”}for (TXIoTQuality remote : remoteQualityList) {// remote.callUser 标识对应设备,可提示“对方网络不佳”}}
- (void)onCallNetworkQualityChanged:(TXIoTQuality *)localQualityremoteQualityList:(NSArray<TXIoTQuality *> *)remoteQualityList {if (localQuality.quality == TXIoTNetworkQualityBad ||localQuality.quality == TXIoTNetworkQualityVeryBad) {// 提示用户“当前网络不佳”}for (TXIoTQuality *remote in remoteQualityList) {// remote.callUser 标识对应设备,可提示“对方网络不佳”}}
呼叫结果与超时机制
呼叫失败的各种情形有独立的回调,不都通过
onError 返回,建议按下表分别处理并给出差异化提示。情形 | SDK 行为 | 建议的界面处理 |
设备端用户拒接 | 回调 onCallRejected,随后回调 onCallEnd。 | 提示「对方已拒接」并退出通话界面。 |
设备端无人接听 | 呼叫发出后约 30 秒无应答,SDK 自动挂断,回调 onCallNoResponse 与 onCallEnd。 | 提示「对方无应答」,无需业务层自行计时。 |
设备忙线 | 回调 onCallLineBusy,随后回调 onCallEnd。不会触发 onError。 | 提示「对方正忙」,引导稍后重试。 |
设备离线 | 回调 onCallUserOffline,随后回调 onCallEnd。不会触发 onError。通话中设备掉线同样回调该方法。 | 提示「设备不在线」,引导检查设备网络。 |
麦克风或摄像头未授权 | 回调 onError(ERR_MIC_NOT_AUTHORIZED / ERR_CAMERA_NOT_AUTHORIZED),并以 CALL_PERMISSION_DENIED 为原因结束通话。 | 引导用户到系统设置开启权限。 |
通话中网络短时中断 | 已接通状态下 SDK 保留约 15 秒的恢复窗口;窗口内恢复则通话继续,超时未恢复则以 NETWORK_ERROR 为原因结束通话。 | 中断期间显示「网络不稳定,正在恢复」,不要立即销毁界面。 |
已有通话进行中再次呼叫 | 回调 onError(ERR_FAILED),原有通话不受影响。 | 先 hangup 再发起新呼叫。 |
状态与回调通知
通话会话通过监听接口(Android:
TXIoTCallListener,iOS:TXIoTCallDelegate)回调状态与错误:回调 | 触发时机 | 关键参数 |
onCallBegin | 设备已接听且已进入房间,通话正式开始。 | mediaType(AUDIO / VIDEO) |
onCallEnd | 通话结束(含正常挂断与异常结束)。 | mediaType, reason(LOCAL_HANGUP / REMOTE_HANGUP / CALL_PERMISSION_DENIED / NETWORK_ERROR / UNKNOWN) |
onCallRejected | 设备端用户拒接本次呼叫。 | callUser |
onCallNoResponse | 呼叫超时,设备端无人接听。 | callUser |
onCallLineBusy | 设备正忙,已在另一路通话中。 | callUser |
onCallUserOffline | 设备离线,或通话过程中设备掉线。 | callUser |
onCallUserAudioAvailable | 远端音频流可用状态变化。 | callUser, available |
onCallUserVideoAvailable | 远端视频流可用状态变化,可据此显示占位图。 | callUser, available |
onCallNetworkQualityChanged | 本端或远端网络质量等级发生变化(小程序端暂不支持)。 | localQuality, remoteQualityList |
onError | 发生错误,例如参数非法、设备权限缺失、重复呼叫。 | errCode, errMsg |
常见问题
呼叫后一直没有接通,如何排查?
按以下顺序排查:
1. 确认监听在
callDevice 之前注册,否则可能错过 onCallRejected、onCallNoResponse 等回调。2. 检查是否收到了
onCallUserOffline(设备离线)或 onCallLineBusy(设备忙线)。这两种情形不会触发 onError,只看 onError 会漏判。3. 确认
productId 与 deviceName 正确,且该设备已绑定或分享给当前登录账户。若无权限,onCallEnd 的结束原因为 CALL_PERMISSION_DENIED。4. 确认设备端已正确实现呼叫接听逻辑,参见 视频通话设备端接入。
5. 若约30秒后收到
onCallNoResponse,说明呼叫已到达但设备端无人接听。通话中只听到声音,看不到对方画面?
远端音频在通话建立后自动播放,远端视频需要业务层显式调用
startRemoteView 绑定渲染视图才会出画面。请确认:已调用 startRemoteView 并传入了正确的 TXIoTCallUser;渲染视图已加载且尺寸不为0;小程序端 setIoTPlayer 传入的 component-id 与 <iot-player> 一致。此外可结合 onCallUserVideoAvailable 判断远端是否确实在推送视频。通话过程中切换 Wi-Fi 与移动网络会断线吗?
不会立即断线。已接通状态下,SDK 对短时网络中断保留约15秒的恢复窗口,窗口内网络恢复则通话继续,不会回调
onCallEnd。建议在此期间显示「网络不稳定,正在恢复」的提示,而不是立即销毁通话界面。若超过恢复窗口仍未恢复,SDK 会以 NETWORK_ERROR 为原因结束通话。可以同时和多台设备通话吗?
不可以。通话会话对象为单例,同一时刻只允许一路通话。若当前通话尚未结束就再次调用
callDevice,SDK 会回调 onError(ERR_FAILED)并保持原有通话不变。切换通话对象前必须先调用 hangup。视频通话会影响设备正在进行的监控拉流吗?
纯音频通话需要申请摄像头权限吗?
不需要。
callType 传 AUDIO 时只上行音频,仅需麦克风权限;此时不要调用 openCamera。若传 VIDEO 并调用 openCamera,则必须同时具备麦克风与摄像头权限,缺失任一权限都会导致 onError 并以 CALL_PERMISSION_DENIED 结束通话。接口参考
视频通话相关接口的完整定义参见: