实现低延迟直播
本教程将指导您快速实现低延迟直播功能。通过本教程,您将学会如何搭建主播推流和观众观看的完整直播系统。
开始前的准备:
- 注册融云开发者账户:前往融云官网注册开发者账户
- 获取 App Key:注册后系统将自动创建应用,请获取应用的 App Key 用于本教程
- 首次使用建议:如果您是首次使用融云音视频,建议先参考运行示例项目完成基础配置,以完成开发者账号注册、音视频服务开通等工作。
系统默认创建开发环境应用,使用国内数据中心。如需其他配置,请在控制台进行调整。
步骤 1:开通音视频服务
在开始开发前,您需要为应用开通音视频直播服务。新创建的应用默认未开启音视频功能。
开通方法: 登录融云控制台,选择您的应用,开通音视频服务和音视频直播服务。详细步骤请参考:开通音视频服务
服务开通、关闭等设置完成后需要 15 分钟生效,请耐心等待。
步骤 2:导入 SDK
实现直播功能需要导入以下 SDK 库:
必须导入:
- RTCLib:音视频核心能力库
- IMLib:即时通讯能力库(提供信令通道)
可选导入:
- 美颜扩展库:提供美颜滤镜功能
- CDN 扩展库:支持 CDN 推流功能
详细导入步骤请参考:导入 SDK
步骤 3:配置代码混淆
如果您的应用启用了代码混淆,需要添加相应的混淆规则以确保 SDK 正常工作。
配置方法:
在 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
未正确配置混淆规则可能导致 SDK 功能异常,请务必添加上述配置。
步骤 4:配置应用权限
直播功能需要网络、摄像头和麦克风等权限,您需要在应用中正确配置这些权限。
声明权限
在 AndroidManifest.xml
中添加以下权限声明:
<!-- 网络相关权限 -->
<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" />
运行时权限
对于 Android 6.0(API 级别 23)及以上版本,您需要在使用相关功能时动态请求以下敏感权限:
- CAMERA:摄像头权限
- RECORD_AUDIO:麦克风权限
详见 Android 开发者官方文档: 运行时权限概览, 请求权限的工作流程。
步骤 5:初始化 SDK
RTCLib 基于即时通讯 SDK 提供信令通道,因此需要先初始化 IM SDK,再初始化 RTCLib SDK。
初始化顺序: 初始化 IM SDK → 建立连接 → 初始化 RTCLib SDK
初始化 IM SDK
在 Application 的 onCreate()
方法中初始化 IM SDK,整个应用生命周期内只需初始化一次。
示例代码:(适用于 IM SDK 版本 ≥ 5.4.2)
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
// 使用您从控制台获取的 App Key
String appKey = "Your_AppKey"; // 示例:bos9p5rlcm2ba
InitOption initOption = new InitOption.Builder().build();
// 初始化 IM SDK
RongIMClient.init(getApplicationContext(), appKey, initOption);
}
}
配置说明:
InitOption
包含区域码、服务地址等配置选项- 不传入配置表示使用默认设置(连接北京数据中心)
- 如果您的 App Key 属于其他数据中心,需要传入对应的配置
关于 IM SDK 初始化的详细配置请参考:初始化文档
步骤 6:连接融云服务器并初始化 RTCLib
建立 IM 连接
音视频用户之间的信令传输依赖于融云的即时通信(IM)服务,因此需要先调用 connect
与 IM 服务建立好 TCP 长连接。建议在功
能模块的加载位置处调用,之后再进行音视频直播业务。当模块退出后调用 disconnect
或 logout
断开该连接。
IM 连接成功建立后可以初始化 RTCLib SDK。 示例代码:
// 使用从您服务器获取的 Token 建立连接
RongIMClient.connect("从您服务器端获取的 Token", new RongIMClient.ConnectCallback() {
@Override
public void onSuccess(String userId) {
// IM 连接成功,初始化 RTCLib SDK
RCRTCConfig.Builder config = RCRTCConfig.Builder.create();
RCRTCEngine.getInstance().init(context, config.build());
}
@Override
public void onError(RongIMClient.ConnectionErrorCode code) {
// 连接失败,处理错误
Log.e("RTC", "IM 连接失败: " + code);
}
@Override
public void onDatabaseOpened(RongIMClient.DatabaseOpenStatus status) {
// 数据库状态回调
}
});
更多 RTC 引擎配置选项请参考:引擎配置文档
主播端实现
实现直播功能需要主播和观众加入同一个直播房间。根据直播类型,可以选择不同的房间类型:
房间类型:
- LIVE_AUDIO_VIDEO:音视频直播间
- LIVE_AUDIO:纯音频直播间
用户角色:
- BROADCASTER:主播(推流)
- AUDIENCE:观众(拉流)
步骤 6.1:主播加入直播房间
1. 构建 RoomConfig,指定房间类型和主播身份
RCRTCRoomConfig roomConfig = RCRTCRoomConfig.Builder.create()
// 选择房间类型:音视频直播或纯音频直播
.setRoomType(RCRTCRoomType.LIVE_AUDIO_VIDEO)
// 设置为主播角色
.setLiveRole(RCRTCLiveRole.BROADCASTER)
.build();
2. 调用 RCRTCEngine
下的 joinRoom
方法创建并加入一个直播房间
String roomId = "your_live_room_id"; // 直播间 ID
RCRTCEngine.getInstance().joinRoom(roomId, roomConfig, new IRCRTCResultDataCallback<RCRTCRoom>() {
@Override
public void onSuccess(RCRTCRoom room) {
// 加入房间成功,保存房间对象
rtcRoom = room;
Log.i("RTC", "主播加入房间成功");
}
@Override
public void onFailed(RTCErrorCode code) {
// 加入房间失败
Log.e("RTC", "主播加入房间失败: " + code);
}
});
- 客户端通过 joinRoom() 传入的直播间 ID 来加入不同房间
- 房间无需预先创建,融云服务会自动创建不存在的房间
- 当所有主播离开房间超过 24 小时后,融云服务会自动销毁房间
- 直播间 ID 由您的业务系统生成和管理
步骤 6.2:发布音视频流
-
加入房间后,开启摄像头采集并发布音视频流。
JavaRCRTCEngine.getInstance().getDefaultVideoStream().startCamera(null);
rtcRoom.getLocalUser().publishDefaultLiveStreams(new IRCRTCResultDataCallback<RCRTCLiveInfo>() {
@Override
public void onSuccess(RCRTCLiveInfo liveInfo) {
}
@Override
public void onFailed(RTCErrorCode code) {
}
}); -
调用
RCRTCLocalUser
下的getStreams
方法获取主播推送的本地流,获得的视频流可用于本地的预览。Javapublic 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);
}
}
} -
创建用于显示视频的
RCRTCVideoView
,调用RCRTCVideoOutputStream
下的setVideoView
方法设置本地流的显示view
。Java/**
* 当远端或本端视频流发生变化时全量更新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:主播订阅房间内其他主播的资源
-
调用
RCRTCLocalUser
下的subscribeStreams
方法订阅房间内其他主播的资源,在主播连麦的场景下会用到该方法,当远端主播发布资源时,会通过onRemoteUserPublishResource()
回调通知,需要订阅音视频流并显示视图。Java/**
* 订阅远端用户资源
*/
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
public void onSuccess(List<RCRTCInputStream> data) {
}
@Override
// 如果 SDK ≧ 5.3.4,您可以使用 IRCRTCResultDataCallback,onFailed 方法 会返回订阅失败的流列表和错误码。
// 如果 SDK < 5.3.4,仅支持使用 IRCRTCResultCallback,onFailed 方法仅返回错误码。
public void onFailed(List<RCRTCInputStream> failedStreams, RTCErrorCode errorCode) {
}
});
} -
调用
RCRTCRoom
下的getRemoteUsers
方法获取房间内其他主播推送的流,获得的视频流可用于本地的预览。Java/**
* 获得当前视频流
*/
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);
}
}
} -
创建用于显示视频的
RCRTCVideoView
,调用RCRTCVideoInputStream
下的setVideoView
方法设置远端流的显示view
。Java/**
* 当远端或本端视频流发生变化时全量更新 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:加入房间
-
调用
RCRTCEngine.getInstance().joinRoom()
以观众身份加入房间。通过onSuccess
和onFailed
回调判断是否加入房间成功JavaRCRTCRoomConfig 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:观众观看直播
-
观众获取直播合流方式有两种: 一是在加入直播间的成功回调里,遍历
RCRTCRoom#getLiveStreams
得到房间内已经存在主播发布的合流,二是在观众先加房间,主播后发布资源的情况下,通过IRCRTCRoomEventsListener#onRemoteUserPublishResource
回调方法获得主播发布的合流资源. -
调用
RCRTCLocalUser#subscribeStreams
观看直播。Javafinal List<RCRTCInputStream> inputStreams = this.room.getLiveStreams();
room.getLocalUser().subscribeStreams(inputStreams, new IRCRTCResultDataCallback<List<RCRTCInputStream>>() {
@Override
public void onSuccess() {
}
@Override
public void onSuccess(List<RCRTCInputStream> data) {
}
@Override
// 如果 SDK ≧ 5.3.4,您可以使用 IRCRTCResultDataCallback,onFailed 方法会返回订阅失败的流列表和错误码。
// 如果 SDK < 5.3.4,仅支持使用 IRCRTCResultCallback,onFailed 方法仅返回错误码。
public void onFailed(List<RCRTCInputStream> failedStreams, RTCErrorCode errorCode) {
}
}); -
创建用于显示视频的
RCRTCVideoView
,调用RCRTCVideoInputStream
下的setVideoView()
方法设置房间直播流的显示view
。Java/**
* 当远端或本端视频流发生变化时全量更新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);
}