帮你快速理解、总结文档立即下载

Android 智能设备

最近更新时间:2026-09-09 16:53:32
我的收藏
本文将介绍在 Android 平台实现设备与 App 之间的双向通话,既包括设备主动呼叫 App,也包括设备响应 App 来电(接听/拒绝),以及通话建立后的音视频采集推流、对端画面渲染与挂断。

前提条件

在接入通话功能前,请您先完成 登录与注册 文档中跑通 login 登录功能。如果您已经完成,可以跳过该操作。
说明:
音视频通话依赖摄像头与麦克风采集,请确保已在 AndroidManifest.xml 中声明 android.permission.CAMERAandroid.permission.RECORD_AUDIO 权限,并在运行时向用户申请授权。
如果您需要呼叫小程序的功能,请您先完成 激活 TWeCall 的接入。

接入步骤

步骤 1:注册通话事件回调

登录成功后,通过 TXIoTCallSession.getInstance().addListener 注册 TXIoTCallSession.Listener,通话相关的回调如下:
回调
说明
onCallRequested
【被叫】收到对端来电时触发,携带 contact(对端信息)与 option(通话媒体类型等)。
onCallAccepted
【主叫】主动呼叫被对端接听时触发,通话建立;此时才开启摄像头/麦克风采集推流。
onCallRejected
【主叫】主动呼叫被对端拒绝时触发,应结束本次呼叫并释放资源。
onCallTimeout
呼叫无人应答超时时触发(主叫拨出无人接听、或被叫来电未及时处理均可能触发)。
onCallHangup
对端挂断通话时触发。
private final TXIoTCallSession.Listener mCallListener = new TXIoTCallSession.Listener() {
@Override
public void onCallRequested(Contact contact, Option option) {
// 被叫:收到来电,根据业务决定接听或拒绝(见步骤 3)
}

@Override
public void onCallAccepted(Contact contact) {
// 主叫:对端接听,通话建立,此时开启采集推流(见步骤 4)
}

@Override
public void onCallRejected(Contact contact) {
// 主叫:对端拒绝,结束本次呼叫并释放资源
}

@Override
public void onCallTimeout(Contact contact) {
// 无人应答超时,释放资源
}

@Override
public void onCallHangup(Contact contact) {
// 对端挂断,停止采集与渲染并释放资源(见步骤 5)
}
};

// 登录成功后注册监听
TXIoTCallSession.getInstance().addListener(mCallListener);

步骤 2:主动发起呼叫(可选)

设备主动呼叫 App 端联系人时,请您先通过 getContacts 拉取联系人列表,从返回的 Contact 中获取目标 userId,再调用 call 发起呼叫。
getContacts 分页拉取联系人:cursornull 拉取首页,后续使用回调返回的 nextCursor 继续拉取,limit 为单页数量。
// 拉取首页联系人(limit 为单页数量)
TXIoTCallSession.getInstance().getContacts(null, 10, new TXIoTValueCallback<ContactsResult>() {
@Override
public void onSuccess(ContactsResult result) {
for (Contact c : result.contacts) {
Log.i(TAG, "contact userId=" + c.userId + " userName=" + c.userName);
}
// 若 result.nextCursor 非空,可再次调用 getContacts(result.nextCursor, 10, ...) 拉取下一页
// 从列表中选定目标联系人后,即可用其 userId 发起呼叫
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "getContacts failed: " + code + " " + desc);
}
});
通话选项 Option 字段说明如下:
字段
说明
mediaContent
通话媒体类型:MediaContent.AUDIO(纯语音)、MediaContent.VIDEO(纯视频)或 MediaContent.AUDIO_VIDEO(音视频)。
customData
透传给对端的自定义数据(可选)。
注意:
call 的回调 onSuccess 仅代表呼叫已发出,通话尚未建立。请勿在此时立即开启采集推流,务必等到 onCallAccepted(对端接听)回调触发后再开启摄像头/麦克风。
String userId = "peer_user_id";
Option option = new Option();
option.mediaContent = MediaContent.AUDIO_VIDEO; // 音视频通话

TXIoTCallSession.getInstance().call(userId, option, new TXIoTCallback() {
@Override
public void onSuccess() {
// 呼叫已发出,等待 onCallAccepted / onCallRejected / onCallTimeout
Log.i(TAG, "calling " + userId + ", waiting for accept ...");
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "call failed: " + code + " " + desc);
}
});

步骤 3:响应来电,接听或拒绝

设备作为被叫,收到 onCallRequested 后,根据业务逻辑调用 accept 接听,或调用 reject 拒绝:
@Override
public void onCallRequested(Contact contact, Option option) {
// 示例:直接接听,实际业务可结合按键、界面弹窗等方式决定接听或拒绝
TXIoTCallSession.getInstance().accept(contact.userId, new TXIoTCallback() {
@Override
public void onSuccess() {
// 接听成功,通话建立,开启采集推流(见步骤 4)
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "accept failed: " + code + " " + desc);
}
});

// 拒绝来电:
// TXIoTCallSession.getInstance().reject(contact.userId, callback);
}

步骤 4:开启采集推流与渲染对端画面

通话建立后(被叫方 accept 成功,或主叫方收到 onCallAccepted),根据通话的 mediaContent 开启本地采集推流,直接调用如下接口即可:
接口
说明
openCamera(frontCamera, view)
开启摄像头采集并推流,frontCamera 为是否使用前置摄像头,view 为本地画面预览的 TXCloudVideoView(可为 null),视频通话时调用。
switchCamera(frontCamera)
切换前/后置摄像头。
openMicrophone()
开启麦克风采集并推流,音频/视频通话均需调用。
startRemoteView(userId, view)
拉取并渲染对端 userId 的视频画面到指定 TXCloudVideoView
muteRemoteAudio(userId, mute)
静音/取消静音对端 userId 的音频。
上述接口返回 int 错误码,返回 TXIoTError.SUCCESS0)代表调用成功。
// mLocalView / mRemoteView 为布局中的 TXCloudVideoView,分别用于本地预览与对端画面
String userId = "peer_user_id";
if (media == MediaContent.VIDEO || media == MediaContent.AUDIO_VIDEO) {
TXIoTCallSession.getInstance().openCamera(true, mLocalView); // 使用前置摄像头,本地预览到 mLocalView
TXIoTCallSession.getInstance().startRemoteView(userId, mRemoteView);// 渲染对端画面
}

if (media == MediaContent.AUDIO || media == MediaContent.AUDIO_VIDEO) {
TXIoTCallSession.getInstance().openMicrophone();
}

步骤 5:挂断通话

通话中主动挂断,调用 hangup;对端挂断时会触发 onCallHangup,此时应关闭本地采集、停止对端画面渲染:
// 主动挂断
TXIoTCallSession.getInstance().hangup(userId, new TXIoTCallback() {
@Override
public void onSuccess() {
// 停止采集与渲染,释放资源(onCallHangup / onCallTimeout / onCallRejected 均调用)
TXIoTCallSession.getInstance().stopRemoteView(userId);
TXIoTCallSession.getInstance().closeCamera();
TXIoTCallSession.getInstance().closeMicrophone();
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "hangup failed: " + code + " " + desc);
}
});


说明:
己方来电未及时处理时会触发 onCallTimeout,处理方式与 onCallHangup 一致:关闭采集、停止渲染并释放资源。

完整示例代码

以下 CallActivity 示例整合了通话事件响应、发起语音/视频呼叫、接听来电、开启采集推流与渲染对端画面、以及挂断的完整流程,实际接入时,请将布局中的 TXCloudVideoView 与呼叫入口替换为您的业务界面。
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import com.tencent.liteav.iot.TXIoTCallback;
import com.tencent.liteav.iot.TXIoTCallSession;
import com.tencent.liteav.iot.TXIoTCallSession.Contact;
import com.tencent.liteav.iot.TXIoTCallSession.MediaContent;
import com.tencent.liteav.iot.TXIoTCallSession.Option;
import com.tencent.liteav.iot.TXIoTError;
import com.tencent.rtmp.ui.TXCloudVideoView;

public class CallActivity extends AppCompatActivity {
private static final String TAG = "CallActivity";

private TXCloudVideoView mLocalView;
private TXCloudVideoView mRemoteView;

// 记住当前通话的媒体类型与对端账号,供接听/挂断复用
private MediaContent mCurrentMedia = MediaContent.AUDIO_VIDEO;
private String mPeerUserId;

private final TXIoTCallSession.Listener mCallListener = new TXIoTCallSession.Listener() {
@Override
public void onCallRequested(Contact contact, Option option) {
Log.i(TAG, "onCallRequested userId=" + contact.userId + " media=" + option.mediaContent);
mPeerUserId = contact.userId;
mCurrentMedia = option.mediaContent;
// 示例:自动接听。实际可弹出接听/拒绝界面
acceptCall(contact.userId);
}

@Override
public void onCallAccepted(Contact contact) {
Log.i(TAG, "onCallAccepted userId=" + contact.userId);
mPeerUserId = contact.userId;
// 主叫:对端接听,通话建立,开启采集推流(复用发起时的媒体类型)
startCallStreams(contact, mCurrentMedia);
}

@Override
public void onCallRejected(Contact contact) {
Log.i(TAG, "onCallRejected userId=" + contact.userId);
stopCallStreams(contact.userId);
}

@Override
public void onCallTimeout(Contact contact) {
Log.i(TAG, "onCallTimeout userId=" + contact.userId);
stopCallStreams(contact.userId);
}

@Override
public void onCallHangup(Contact contact) {
Log.i(TAG, "onCallHangup userId=" + contact.userId);
stopCallStreams(contact.userId);
}
};

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_call);
// mLocalView = findViewById(R.id.local_view);
// mRemoteView = findViewById(R.id.remote_view);

// 请确保已完成 login 登录,并已申请摄像头/麦克风运行时权限
TXIoTCallSession.getInstance().addListener(mCallListener);
}

// 主叫:主动发起一路音视频呼叫。对端接听后在 onCallAccepted 中才开始推流
private void startOutgoingCall(String peerUserId) {
mPeerUserId = peerUserId;
Option option = new Option();
option.mediaContent = MediaContent.AUDIO_VIDEO;
mCurrentMedia = option.mediaContent;

TXIoTCallSession.getInstance().call(peerUserId, option, new TXIoTCallback() {
@Override
public void onSuccess() {
Log.i(TAG, "calling " + peerUserId + ", waiting for accept ...");
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "call failed: " + code + " " + desc);
}
});
}

// 被叫:接听来电
private void acceptCall(String userId) {
TXIoTCallSession.getInstance().accept(userId, new TXIoTCallback() {
@Override
public void onSuccess() {
Contact contact = new Contact();
contact.userId = userId;
startCallStreams(contact, mCurrentMedia);
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "accept failed: " + code + " " + desc);
}
});
}

// 通话建立后:根据媒体类型开启采集推流并渲染对端画面
private void startCallStreams(Contact contact, MediaContent media) {
TXIoTCallSession session = TXIoTCallSession.getInstance();
if (media == MediaContent.VIDEO || media == MediaContent.AUDIO_VIDEO) {
int rc = session.openCamera(true, mLocalView);
if (rc != TXIoTError.SUCCESS) {
Log.e(TAG, "openCamera failed: " + rc);
}
session.startRemoteView(contact.userId, mRemoteView);
}
if (media == MediaContent.AUDIO || media == MediaContent.AUDIO_VIDEO) {
int rc = session.openMicrophone();
if (rc != TXIoTError.SUCCESS) {
Log.e(TAG, "openMicrophone failed: " + rc);
}
}
}

// 主动挂断
private void hangupCall(String userId) {
TXIoTCallSession.getInstance().hangup(userId, new TXIoTCallback() {
@Override
public void onSuccess() {
stopCallStreams(userId);
}

@Override
public void onError(int code, String desc) {
Log.e(TAG, "hangup failed: " + code + " " + desc);
}
});
}

// 停止采集与渲染,释放资源
private void stopCallStreams(String userId) {
TXIoTCallSession session = TXIoTCallSession.getInstance();
if (userId != null) {
session.stopRemoteView(userId);
}
session.closeCamera();
session.closeMicrophone();
}

@Override
protected void onDestroy() {
super.onDestroy();
// 退出通话界面时,务必挂断并释放资源,移除监听
if (mPeerUserId != null) {
hangupCall(mPeerUserId);
} else {
stopCallStreams(null);
}
TXIoTCallSession.getInstance().removeListener(mCallListener);
}
}

常见问题

现象
排查建议
APP 发起呼叫后,设备端未收到 onCallRequested
确认设备已成功登录;确认已调用 addListener 注册监听且未被提前 removeListener
accept 回调 onError 返回失败
确认设备已成功登录;确认 userId 有效;确认当前没有其他通话正在进行。
调用 call 后对端 App 未收到呼叫
确认设备已成功登录;确认 userId 为有效的 App 端账号且对端在线;确认当前没有其他通话正在进行。
主叫已接通(onCallAccepted 已触发),但对端看不到画面或听不到声音
确认在 onCallAccepted(而非 call 回调 onSuccess)后才开启 openCamera / openMicrophone;确认沿用了发起呼叫时的 mediaContent
本地或对端画面不显示
确认已声明并授予 CAMERARECORD_AUDIO 权限;确认 openCamera 传入的本地 TXCloudVideoViewstartRemoteView 传入的对端 TXCloudVideoView 均已正确加入布局且可见。
多次通话后设备资源占用持续增长
检查 onCallHangup / onCallTimeout / onCallRejected 是否都正确调用了 closeCamera / closeMicrophone / stopRemoteView,并在退出界面时 removeListener,避免资源泄漏。

联系我们

如果您在接入或使用过程中有任何疑问或者建议,欢迎 联系我们 提交反馈。