跳到主要内容

实现音视频通话

CallLib 是在 RTCLib 基础上,额外封装了一套音视频呼叫功能 SDK,包含了单人、多人音视频呼叫的各种场景和功能,通过集成它,您可以自由的实现音视频呼叫场景的各种玩法。

注意

房间人数上限

考虑移动设备的带宽(主要是在多路视频情况下),建议单次通话或房间内,视频不超过 16 人,纯音频不超过 32 人。超过此上限可能影响通话效果。

步骤 1:服务开通

您在融云创建的应用默认不会启用音视频服务。在使用融云提供的任何音视频服务前,您需要前往控制台,为应用开通音视频服务。

具体步骤请参阅 开通音视频服务

注意

服务开通、关闭等设置完成后 30 分钟后生效。如需通过 SDK 判断您的 App 是否已成功开通服务,可使用 CallLib 的 isVoIPEnabled 方法。

步骤 2:SDK 导入

您需要导入融云音视频通话能力库 CallLib,和 RTC 业务所依赖的即时通讯能力库 IMLib。根据您的业务需求,可选择导入美颜扩展库。

具体步骤请参阅 导入 CallLib SDK

步骤 3:代码混淆

!!!include(/common/rtc/android/unified/proguard_rules.md)!!!

步骤 4:权限配置

  1. AndroidManifest.xml 中声明 SDK 需要的所有权限。

    <!-- 音视频需要网络权限 和 监听网络状态权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <!-- 摄像头采集需要 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 音频采集需要 -->
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
  2. 如果您的应用需要支持 Android 6.0(API 级别 23)或更高版本的设备,您还需要在 App 用户使用对应功能时(例如发起呼叫、接听)请求摄像头(CAMERA)、麦克风(RECORD_AUDIO)权限。详见 Android 开发者官方文档运行时权限请求权限的工作流

步骤 5:初始化

音视频 SDK 是基于即时通信 SDK 作为信令通道的,所以要先初始化 IM SDK。如果不换 AppKey,在整个应用生命周期中,初始化一次即可。建议调用位置放在 Application 的 onCreate() 方法内,或在音视频功能模块的加载位置处。

public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
RongIMClient.init(this, "从控制台申请的 AppKey");
}
}

融云即时通信 SDK 采用了后台进程方式来确保稳定性。运行后会发现以下三个进程:

  1. App 进程,进程名为 App 的包名。
  2. 融云 IM 进程,进程名为 ipc。
  3. 融云推送进程,如集成了厂商推送,则不会存在此进程,进程名为 io.rong.push。

步骤 6:通话事件处理

SDK 提供针对来电、通话状态、通话记录的事件处理机制。

监听通话呼入

  1. 调用 RongCallClient 中的 setReceivedCallListener 来监听通话呼入。

    RongCallClient.setReceivedCallListener(new IRongReceivedCallListener() {
    /**
    * 来电回调
    * @param callSession 通话实体
    */
    @Override
    public void onReceivedCall(RongCallSession callSession) {
    }

    /**
    * targetSDKVersion 大于等于 23 时检查权限的回调。当 targetSDKVersion 小于 23 的时候不需要实现。
    * 在这个回调里用户需要使用Android6.0新增的动态权限分配接口通知用户授权,
    * 然后根据用户授权或者不授权分别回调
    * RongCallClient.getInstance().onPermissionGranted()和
    * RongCallClient.getInstance().onPermissionDenied()来通知CallLib。
    *
    * @param callSession 通话实体
    */
    @Override
    public void onCheckPermission(RongCallSession callSession) {
    }
    });
  2. 如果应用需要支持 Android 6.0(API 级别 23)或更高版本的设备,请在 onCheckPermission 回调中通过被叫用户,请求授予摄像头(CAMERA)、麦克风(RECORD_AUDIO)权限,并将结果通知 CallLib。

    • 授权后,通知 CallLib。SDK 会触发来电监听的 onReceivedCall 回调。

      RongCallClient.getInstance().onPermissionGranted();
    • 拒绝授权后,通知 CallLib。此时主叫端会触发通话状态监听的 onCallDisconnected 方法结束呼叫。原因为 REMOTE_REJECT(12) 对方拒绝。

      RongCallClient.getInstance().onPermissionDenied()

监听通话状态变化

调用 RongCallClient 中的 setVoIPCallListener 来监听通话状态的变化。

RongCallClient.getInstance().setVoIPCallListener(new IRongCallListener() {
/**
* 电话已拨出。
* 主叫端拨出电话后,通过回调 onCallOutgoing 通知当前 call 的详细信息。
*
* @param callSession 通话实体。
* @param localVideo 本地 camera 信息。
*/
@Override
public void onCallOutgoing(RongCallSession callSession, SurfaceView localVideo) {
}

/**
* 已建立通话。
* 通话接通时,通过回调 onCallConnected 通知当前 call 的详细信息。
*
* @param callSession 通话实体。
* @param localVideo 本地 camera 信息。
*/
@Override
public void onCallConnected(RongCallSession callSession, SurfaceView localVideo) {
}

/**
* 通话结束。
* 通话中,对方挂断,己方挂断,或者通话过程网络异常造成的通话中断,都会回调 onCallDisconnected。
*
* @param callSession 通话实体。
* @param reason 通话中断原因。
*/
@Override
public void onCallDisconnected(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) {
}

/**
* 被叫端加入通话。
* 主叫端拨出电话,被叫端收到请求后,加入通话,回调 onRemoteUserJoined。
*
* @param userId 加入用户的 id。<br />
* @param mediaType 加入用户的媒体类型,audio or video。<br />
* @param userType 加入用户的类型,1:正常用户,2:观察者。<br />
* @param remoteVideo 加入用户者的 camera 信息。如果 userType为2,remoteVideo对象为空;<br />
* 如果对端调用{@link RongCallClient#startCall(int, boolean, Conversation.ConversationType, String, List, List, RongCallCommon.CallMediaType, String, StartCameraCallback)} 或
* {@link RongCallClient#acceptCall(String, int, boolean, StartCameraCallback)}开始的音视频通话,则可以使用如下设置改变对端视频流的镜像显示:<br />
* <pre class="prettyprint">
* public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
* if (null != remoteVideo) {
* ((RongRTCVideoView) remoteVideo).setMirror( boolean);//观看对方视频流是否镜像处理
* }
* }
* </pre>
*/
@Override
public void onRemoteUserJoined(String userId, RongCallCommon.CallMediaType mediaType, int userType, SurfaceView remoteVideo) {
}

/**
* 通话中的远端参与者离开。
* 回调 onRemoteUserLeft 通知状态更新。
*
* @param userId 远端参与者的 id。
* @param reason 远端参与者离开原因。
*/
@Override
public void onRemoteUserLeft(String userId, RongCallCommon.CallDisconnectedReason reason) {
}
});

监听漏接电话

调用 RongCallClient 中的 setMissedCallListener 来监听漏接的通话,一般用于记录通话记录。

RongCallClient.setMissedCallListener(new RongCallMissedListener() {
/**
* 漏接通话回调
* @param callSession 通话实体
* @param reason 远端参与者离开原因。
*/
@Override
public void onRongCallMissed(RongCallSession callSession, RongCallCommon.CallDisconnectedReason reason) {
}
});

通话计时

CallLib SDK 无法直接获取通话时长,您可以在 IRongCallListener 的通话建立成功、音频首帧回调、或视频首帧回调方法中,使用当前时间减去通话起始时间,获取通话时长。

回调方法:

    /**
* 已建立通话。 通话接通时,通过回调 onCallConnected 通知当前 call 的详细信息。
*
* @param callSession 通话实体。
* @param localVideo 本地 camera 信息。
*/
void onCallConnected(RongCallSession callSession, SurfaceView localVideo);

/**
* 收到某个用户的第一帧视频数据
*
* @param userId
* @param height
* @param width
*/
void onFirstRemoteVideoFrame(String userId, int height, int width);

/**
* 收到某个用户的第一帧音频数据
*
* @param userId
*/
void onFirstRemoteAudioFrame(String userId);

定时计算通话时长:

@Override
public void onCallConnected(final RongCallSession callSession, SurfaceView localVideo) {

// TODO

handler.postDelayed(new Runnable() {
@Override
public void run() {
RongCallClient instance = RongCallClient.getInstance();
if (instance == null) {
return;
}
RongCallSession callSession = instance.getCallSession();
if (callSession == null) {
return;
}
// 获取通话建立成功时的时间戳
long ConnectedTime = callSession.getActiveTime();
// 获取当前本地系统时间戳
long currentTime = System.currentTimeMillis();
// 计算通话时长
long callDuration = currentTime - ConnectedTime;
// 格式化输出
String formatTime = String.format("%02d:%02d:%02d", callDuration / 3600000, (callDuration % 3600000) / 60000, callDuration % 60000);
// 弹出提示
Toast.makeText(this, "通话时长:" + formatTime, Toast.LENGTH_SHORT).show();
handler.postDelayed(this,1000);//循环获取通话时长
}
}, 1000); // 延迟 1 秒后执行一次
};

提示

如需在应用程序的服务端进行通话计时,建议使用融云提供的服务端回调音视频房间状态同步。通过实时回调事件 event 11(成员加入音视频房间)和 event 12(成员加入音视频房间)记录计费的开始和结束时间。

步骤 7:连接 IM 服务

音视频用户之间的信令传输依赖于融云的即时通信(IM)服务,因此需要先调用 connect 与 IM 服务建立好 TCP 长连接。建议在功能模块的加载位置处调用,之后再进行音视频呼叫业务。当模块退出后调用 disconnectlogout 断开该连接。

RongIMClient.connect("从您服务器端获取的 Token", new RongIMClient.ConnectCallback() {
@Override
public void onSuccess(String userId) {
// 连接成功
}

@Override
public void onError(RongIMClient.ConnectionErrorCode code) {
// 连接失败
}

@Override
public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus databaseOpenStatus) {
// 数据库打开失败
}
});

注意

  • 如调用此接口时,遇到网络不好导致连接失败,SDK 会自动启动重连机制进行最多 10 次重连,重连时间间隔分别为 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 秒。在这之后如果仍没有连接成功,还会在检测到设备网络状态变化,比如网络恢复或切换网络时再次尝试重连。
  • 如 App 在被杀死后,接收到了推送通知,点击通知拉起应用时,需要再次调用 connect 方法进行连接。

步骤 8:发起呼叫

连接 IM 服务成功后,可调用 RongCallClient 中的 startCall 来方法发起通话。

发起单人呼叫

// 被叫用户 Id
String targetId = "UserB";
List<String> userIds = new ArrayList<>();
userIds.add(targetId);
RongCallClient.getInstance().startCall(Conversation.ConversationType.PRIVATE, targetId, userIds, null, RongCallCommon.CallMediaType.VIDEO, null);

发起多人呼叫

// 被叫用户所在共同群组 Id
String targetId = "GroupX";
// 群组内的被叫用户 id 集合
List<String> userIds = new ArrayList<>();
userIds.add("UserB");
userIds.add("UserC");
RongCallClient.getInstance().startCall(Conversation.ConversationType.GROUP, targetId, userIds, null, RongCallCommon.CallMediaType.VIDEO, null);

步骤 9:呼叫接听

在收到 onReceivedCall(RongCallSession rongCallSession) 回调之后,调用如下方法接听通话。

RongCallClient.getInstance().acceptCall(RongCallClient.getInstance().getCallSession().getCallId());

步骤 10:离线推送通知

集成离线推送后,即使 App 已经被系统回收,也可以收到呼叫的推送通知。详细请参考 Android 推送

注意

集成 FCM 推送时您可能需要自行实现音视频信令消息(呼叫邀请、挂断等)的通知弹出逻辑。