跳到主要内容

实现低延迟直播

融云开发者账户是使用融云 SDK 产品的必要条件。在开始之前,请先前往 融云官网注册开发者账户。注册后,控制台将自动为您创建一个应用,默认为开发环境应用,使用国内数据中心。请获取该应用的 App Key,在本教程中使用。

首次使用融云音视频的用户,建议参考文档 运行示例项目,以完成开发者账号注册、音视频服务开通等工作。

步骤 1:服务开通

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

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

提示

服务开通、关闭等设置完成后 30 分钟后生效。

步骤 2:SDK 导入

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

具体步骤请参阅 导入 SDK

步骤 3:代码混淆

若开发者发布的 App 启用代码混淆,请务必在 app/proguard-rules.pro 文件添加如下配置:

-keepattributes Exceptions,InnerClasses

-keepattributes Signature
#RongRTCLib
-keep public class cn.rongcloud.** {*;}

#RongIMLib
-keep class io.rong.** {*;}
-keep class cn.rongcloud.** {*;}
-keep class * implements io.rong.imlib.model.MessageContent {*;}
-dontwarn io.rong.push.**
-dontnote com.xiaomi.**
-dontnote com.google.android.gms.gcm.**
-dontnote io.rong.**

-ignorewarnings

步骤 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:使用 App Key 初始化

RTCLib 依赖融云即时通讯客户端(IM)SDK 提供信令通道。要在您的应用程序中集成和运行,您需要先对 IM SDK 进行初始化。在成功建立 IM 连接后再初始化 RTCLib SDK。

融云即时通讯客户端 SDK 核心类为 RongCoreClientRongIMClient。在 Application 的 onCreate() 方法中,调用 RongIMClient 的初始化方法,传入生产开发环境的 App Key。如果不换 AppKey,在整个应用生命周期中,初始化一次即可。

如果 IM SDK 版本 ≧ 5.4.2,请使用以下初始化方法。

String appKey = "Your_AppKey"; // example: bos9p5rlcm2ba
InitOption initOption = new InitOption.Builder().build();

RongIMClient.init(getApplicationContext(), appKey, initOption);

初始化配置(InitOption)中封装了区域码(AreaCode),导航服务地址(naviServer)、文件服务地址(fileServer)、数据统计服务地址(statisticServer)配置,以及是否开启推送的开关(enablePush)和主进程开关(isMainProcess)。不传入任何配置表示全部使用默认配置。SDK 默认连接北京数据中心。

如果 App Key 不属于中国(北京)数据中心,则必须传入有效的初始化配置。

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

关于 IM SDK 初始化的更多配置请参见初始化

步骤 6:连接融云服务器

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

IM 连接成功建立后可以初始化 RTCLib SDK。部分 RTC 引擎必须在初始化时提供,详见引擎配置

RongIMClient.connect("从您服务器端获取的 Token", new RongIMClient.ConnectCallback() {
@Override
public void onSuccess(String userId) {
// 连接成功
// RTCLib 初始化
RCRTCConfig.Builder config = RCRTCConfig.Builder.create();
RCRTCEngine.getInstance().init(context, config.build());
}

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

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

主播端

多人之间想要发起音视频通话,需要加入同一个音视频房间。对于直播需求来讲,房间类型需选择 LIVE_AUDIO_VIDEOLIVE_AUDIO,即音视频直播间或纯音频直播间。加入房间的角色也分为主播(BROADCASTER)和观众(AUDIENCE),下面就这两种身份,分别进行说明。

步骤 6.1:加入房间

  1. 构建 RoomConfig,指定房间类型和主播身份:

    RCRTCRoomConfig roomConfig = RCRTCRoomConfig.Builder.create()
    // 根据实际场景,选择音视频直播:LIVE_AUDIO_VIDEO 或音频直播:LIVE_AUDIO
    .setRoomType(RCRTCRoomType.LIVE_AUDIO_VIDEO)
    .setLiveRole(BROADCASTER)
    .build();
  2. 调用 RCRTCEngine 下的 joinRoom 方法创建并加入一个直播房间:

    RCRTCEngine.getInstance().joinRoom("直播间 ID", roomConfig, new IRCRTCResultDataCallback<RCRTCRoom>() {
    @Override
    public void onSuccess(final RCRTCRoom room) {
    // 保存房间对象
    this.rtcRoom = room;
    }

    @Override
    public void onFailed(RTCErrorCode code) {
    }
    });
提示

客户端通过 joinRoom() 传入的直播间 ID 来加入不同房间。房间不需要客户端创建或销毁,融云服务若发现该房间不存在时会自动创建。当所有主播(只有观众不算)离开持续 24 小时后,服务会自动销毁该房间。

步骤 6.2:发布音视频流

  1. 加入房间后,开始摄像头采集并发布音视频流。

    RCRTCEngine.getInstance().getDefaultVideoStream().startCamera(null);
    rtcRoom.getLocalUser().publishDefaultLiveStreams(new IRCRTCResultDataCallback<RCRTCLiveInfo>() {
    @Override
    public void onSuccess(RCRTCLiveInfo liveInfo) {
    }

    @Override
    public void onFailed(RTCErrorCode code) {
    }
    });
  2. 调用 RCRTCLocalUser 下的 getStreams 方法获取主播推送的本地流,获得的视频流可用于本地的预览。

    public void getVideoStream(List<RCRTCVideoOutputStream> outputStreams, List<RCRTCVideoInputStream> inputStreams) {
    for (final RCRTCRemoteUser remoteUser : mRtcRoom.getRemoteUsers()) {
    if (remoteUser.getStreams().size() == 0) {
    continue;
    }
    List<RCRTCInputStream> userStreams = remoteUser.getStreams();
    for (RCRTCInputStream i : userStreams) {
    if (i.getMediaType() == RCRTCMediaType.VIDEO) {
    inputStreams.add((RCRTCVideoInputStream) i);
    }
    }
    }

    for (RCRTCOutputStream o : mRtcRoom.getLocalUser().getStreams()) {
    if (o.getMediaType() == RCRTCMediaType.VIDEO) {
    outputStreams.add((RCRTCVideoOutputStream) o);
    }
    }
    }
  3. 创建用于显示视频的 RCRTCVideoView,调用 RCRTCVideoOutputStream 下的 setVideoView 方法设置本地流的显示 view。

    /**
    * 当远端或本端视频流发生变化时全量更新ui
    */
    void updateVideoView(List<RCRTCVideoOutputStream> outputStreams, List<RCRTCVideoInputStream> inputStreams) {
    ArrayList<RCRTCVideoView> list = new ArrayList<>();
    if (null != outputStreams) {
    for (RCRTCVideoOutputStream o : outputStreams) {
    RCRTCVideoView rongRTCVideoView = new RCRTCVideoView(LiveActivity.this);
    o.setVideoView(rongRTCVideoView);
    list.add(rongRTCVideoView);
    }
    }

    if (null != inputStreams) {
    for (RCRTCVideoInputStream i : inputStreams) {
    RCRTCVideoView rongRTCVideoView = new RCRTCVideoView(LiveActivity.this);
    i.setVideoView(rongRTCVideoView);
    list.add(rongRTCVideoView);
    }
    }
    videoViewManager.update(list);
    }

步骤 6.3:主播订阅房间内其他主播的资源

  1. 调用 RCRTCLocalUser 下的 subscribeStreams 方法订阅房间内其他主播的资源,在主播连麦的场景下会用到该方法,当远端主播发布资源时,会通过 onRemoteUserPublishResource() 回调通知,需要订阅音视频流并显示视图。

    /**
    * 订阅远端用户资源
    */
    public void subscribeAVStream() {
    if (mRtcRoom == null || mRtcRoom.getRemoteUsers() == null) {
    return;
    }
    List<RCRTCInputStream> subscribeInputStreams = new ArrayList<>();
    for (final RCRTCRemoteUser remoteUser : mRtcRoom.getRemoteUsers()) {
    if (remoteUser.getStreams().size() == 0) {
    continue;
    }
    List<RCRTCInputStream> userStreams = remoteUser.getStreams();
    subscribeInputStreams.addAll(userStreams);
    }

    if (subscribeInputStreams.size() == 0) {
    return;
    }
    mRtcRoom.getLocalUser().subscribeStreams(subscribeInputStreams, new IRCRTCResultDataCallback<List<RCRTCInputStream>>() {
    @Override
    public void onSuccess() {

    }

    @Override
    // 如果 SDK ≧ 5.3.4,您可以使用 IRCRTCResultDataCallback,onFailed 方法会返回订阅失败的流列表和错误码。
    // 如果 SDK < 5.3.4,仅支持使用 IRCRTCResultCallback,onFailed 方法仅返回错误码。
    public void onFailed(List<RCRTCInputStream> failedStreams, RTCErrorCode errorCode) {

    }
    });
    }
  2. 调用 RCRTCRoom 下的 getRemoteUsers 方法获取房间内其他主播推送的流,获得的视频流可用于本地的预览。

    /**
    * 获得当前视频流
    */
    public void getVideoStream(List<RCRTCVideoOutputStream> outputStreams, List<RCRTCVideoInputStream> inputStreams) {
    for (final RCRTCRemoteUser remoteUser : mRtcRoom.getRemoteUsers()) {
    if (remoteUser.getStreams().size() == 0) {
    continue;
    }
    List<RCRTCInputStream> userStreams = remoteUser.getStreams();
    for (RCRTCInputStream i : userStreams) {
    if (i.getMediaType() == RCRTCMediaType.VIDEO) {
    inputStreams.add((RCRTCVideoInputStream) i);
    }
    }
    }

    for (RCRTCOutputStream o : mRtcRoom.getLocalUser().getStreams()) {
    if (o.getMediaType() == RCRTCMediaType.VIDEO) {
    outputStreams.add((RCRTCVideoOutputStream) o);
    }
    }
    }
  3. 创建用于显示视频的 RCRTCVideoView,调用 RCRTCVideoInputStream 下的 setVideoView 方法设置远端流的显示 view。

    /**
    * 当远端或本端视频流发生变化时全量更新 ui
    */
    void updateVideoView(List<RCRTCVideoOutputStream> outputStreams, List<RCRTCVideoInputStream> inputStreams) {
    ArrayList<RCRTCVideoView> list = new ArrayList<>();
    if (null != outputStreams) {
    for (RCRTCVideoOutputStream o : outputStreams) {
    RCRTCVideoView rongRTCVideoView = new RCRTCVideoView(LiveActivity.this);
    o.setVideoView(rongRTCVideoView);
    list.add(rongRTCVideoView);
    }
    }

    if (null != inputStreams) {
    for (RCRTCVideoInputStream i : inputStreams) {
    RCRTCVideoView rongRTCVideoView = new RCRTCVideoView(LiveActivity.this);
    i.setVideoView(rongRTCVideoView);
    list.add(rongRTCVideoView);
    }
    }
    videoViewManager.update(list);
    }

观众端

步骤 7.1:加入房间

  1. 调用 RCRTCEngine.getInstance().joinRoom() 以观众身份加入房间。通过 onSuccessonFailed 回调判断是否加入房间成功

    RCRTCRoomConfig roomConfig = RCRTCRoomConfig.Builder.create()
    // 根据实际场景,选择音视频直播:LIVE_AUDIO_VIDEO 或音频直播:LIVE_AUDIO
    .setRoomType(RCRTCRoomType.LIVE_AUDIO_VIDEO)
    .setLiveRole(RCRTCLiveRole.AUDIENCE)
    .build();
    RCRTCEngine.getInstance().joinRoom(roomId, roomConfig, new IRCRTCResultDataCallback<RCRTCRoom>() {
    @Override
    public void onSuccess(final RCRTCRoom room) {
    // 保存房间对象
    this.rtcRoom = room;
    ...
    }

    @Override
    public void onFailed(RTCErrorCode code) {
    }
    });

步骤 7.2:观众观看直播

  1. 观众获取直播合流方式有两种: 一是在加入直播间的成功回调里,遍历 RCRTCRoom#getLiveStreams 得到房间内已经存在主播发布的合流,二是在观众先加房间,主播后发布资源的情况下,通过 IRCRTCRoomEventsListener#onRemoteUserPublishResource 回调方法获得主播发布的合流资源.

  2. 调用 RCRTCLocalUser#subscribeStreams 观看直播。

    final List<RCRTCInputStream> inputStreams = this.room.getLiveStreams();
    room.getLocalUser().subscribeStreams(inputStreams, new IRCRTCResultDataCallback<List<RCRTCInputStream>>() {
    @Override
    public void onSuccess() {

    }

    @Override
    // 如果 SDK ≧ 5.3.4,您可以使用 IRCRTCResultDataCallback,onFailed 方法会返回订阅失败的流列表和错误码。
    // 如果 SDK < 5.3.4,仅支持使用 IRCRTCResultCallback,onFailed 方法仅返回错误码。
    public void onFailed(List<RCRTCInputStream> failedStreams, RTCErrorCode errorCode) {

    }
    });
  3. 创建用于显示视频的 RCRTCVideoView,调用 RCRTCVideoInputStream 下的 setVideoView() 方法设置房间直播流的显示 view。

    /**
    * 当远端或本端视频流发生变化时全量更新ui
    */
    void updateVideoView(List<RCRTCVideoInputStream> inputStreams) {
    ArrayList<RCRTCVideoView> list = new ArrayList<>();
    if (null != inputStreams) {
    for (RCRTCVideoInputStream i : inputStreams) {
    RCRTCVideoView rongRTCVideoView = new RCRTCVideoView(LiveActivity.this);
    i.setVideoView(rongRTCVideoView);
    list.add(rongRTCVideoView);
    }
    }
    videoViewManager.update(list);
    }