超低延时高清直播
1. 简介
视界提供的超低延时直播是一种实时通信(延时<500毫秒)直播模式,能够给用户更好的实时体验,主播可以与用户实时互动。
超低延迟视频直播场景的典型使用案例是主播和观众以一对多进行视频直播。
典型应用场景如下:
- 秀场直播:主播低延时直播给普通观众,并可与多个观众实时互动。
- 互动游戏:游戏主播与多个玩家实时互动,同时以低延时直播方式将画面或声音共享给观众。
- 电子竞技:队长主播,普通观众希望第一时间知道结果。
- 体育赛事直播:在这种场景下,观众更愿意及时知道赛事实时状况,增强了观众体验。
超低延时直播相比于传统直播优势:
- 传统直播模式主播采用RTMP流协议推流,而视界音视频采用UDP协议的RTP流, 创新性地给观众带来百毫秒级延时直播体验;
- 视界音视频引擎采用服务器Mesh网络技术,单节点最大支持10万个用户同时低延时观看直播,同时可通过扩展服务器节点提升用户容量;
- 通过RTP协议以及核心音视频算法,实现更好的抗丢包性能,能在30%丢包情况下保证用户可接受的视频质量,70%~80%丢包情况下保证音频可懂度;
- 观众观看直播过程中,随时可以无缝切换为嘉宾参与连麦,实现随时与主播开启实时互动;
- 主播端与观众端都将拥有更好的网络适应能力,兼顾高网络质量用户与低网络质量用户,做到高网络质量高体验,低网络质量可用体验;
对于不支持RTP协议流的用户(如某些浏览器上的网页端),视界音视频引擎提供将视频流转推到RTMP CDN(如:网宿CDN)模式,做到兼容传统方案以及非Native方案。
2. 数据流结构图
如上图所示具体流程如下:
- 主播端加入房间:主播端以主播(
CLIENT_ROLE_COHOST
)角色,通过调用JoinRoom
加入房间。加入房间后SDK将推流到媒体服务器,媒体服务器负责维护房间和转发媒体流; - 主播端开启转播(可选):主播端通过调用SDK API
StartPublisher
,通知服务器启动转码推流到CDN。注:用户如不需要,可以跳过此步; - 低延时直播客户端:观众端作为观看者(
CLIENT_ROLE_VIEWER
)角色,调用JoinRoom
加入房间,加入房间后SDK将自动通过RTP协议获取媒体流; - 网页端(可选):通过RTMP从传统CDN拉流。注:用户如不需要,可以跳过此步;
注:此流程中,主播和观众加入房间的逻辑,除角色不同外,基本与实时通话场景类似。在直播模式下,不同点在于主播将收不到观众加入的通知事件,而实时通话场景中,任何人都能收到其它用户加入或退出的消息或事件。
为了便于开发者更快理解 Demo 中的逻辑,下述内容将功能核心源码片段挑出来并加以讲解。开发者亦可直接阅读Demo源码,两者是一致的。Demo源码下载地址
3. 低延迟直播示例代码
1. 初始化引擎(主播端、观众端)
这部分逻辑将初始化系统,整个程序生命周期只需要执行一次
// 初始化系统,并提供注册时生成的AppId(整数格式)
SystemUtil::Init(self.getApplicationContext(), XXXX/*<your-app-id>*/);
//DeviceManager设备管理类初始化
DeviceManager deviceManager = new DeviceManager(self.getApplicationContext());
//RenderManager渲染管理类初始化
RenderManager renderManager = new RenderManager(self.getApplicationContext());
2. 初始化房间(主播端、观众端)
x//RoomCallback初始化(房间事件监听)
RoomCallback roomCallback = new RoomCallback() {
...
};
//Room 管理类初始化
Room room = new Room(self.getApplicationContext(), deviceManager.getNativeInstance());
room.attachCallback(roomCallback);
3. 主播加入房间、开启设备(主播端)
xxxxxxxxxx
final String roomId = "123";
final String hostUserName = "host_user_id";
Room.Profile profile = new Room.Profile();
profile.joinWithoutVideo = false; // 主播加入时开启视频
profile.clientRole = ClientRole.CLIENT_ROLE_COHOST; // 重要:以主播角色加入
profile.width = 720; //竖屏模式
profile.height = 1280;
profile.enableAdaptiveResolution = true; //开启系统性能不够时,降低分辨率
room.joinRoom(
roomId,
hostUserName,
profile,
"your_app_token");
// 启动音视频设备
deviceManager.startAudioDevice();
deviceManager.setSpeaker(true);
deviceManager.startCamera();
更多用户角色说明如下:
用户角色 | 描述 |
---|---|
CLIENT_ROLE_COHOST | 直播场景;cohost(主播或嘉宾)角色 |
CLIENT_ROLE_VIEWER | 直播场景;观众角色 |
CLIENT_ROLE_ATTENDEE | 实时通话场景;参会者,普通音视频通话场景,参会者只会能看到参会者,不能看到主播和嘉宾 |
Cohost:Cohost之间可以互相实时看到对方画面,Cohost看不到观众画面。一个房间可以同时存在多个Cohost,产品需求中的嘉宾可以通过Cohost角色加入实现。同时,最后一个Cohost退出房间时,房间将被销毁,同一房间的观众将会被动断开;
观众:观众可以看到多个Cohost(主播和嘉宾)画面,观众之间互相不可见;
参会者:参会者角色CLIENT_ROLE_ATTENDEE
主要用于实时通话场景,此角色与其它直播角色(包括:CLIENT_ROLE_COHOST
、CLIENT_ROLE_VIEWER
)在系统内部是互相隔离的。开发者可以认为他们加入的是不同的类型的房间,因此不能互相看见对方;
4. 观众端加入房间(观众端)
xxxxxxxxxx
final String roomId = "123";
final String localUserName = "viewer_user_id";
Room.Profile profile = new Room.Profile();
profile.clientRole = ClientRole.CLIENT_ROLE_VIEWER; // 重要:以观众角色加入
profile.enableAdaptiveResolution = true; //开启系统性能不够时,降低分辨率
room.joinRoom(
roomId,
localUserName,
profile,
"your_app_token");
// 开启speaker设备
deviceManager.setSpeaker(true);
deviceManager.startAudioPlayoutDevice(); // 只开启播放设备,观众无需开启录音设备
5. 观众端处理视频流到达事件(观众端)
xxxxxxxxxx
// 远收到远端第一帧数据 bindRender,渲染显示,该方法实现要在UI线程
public void onFirstVideoFrameReceived(String uid) {
Log.e(TAG, "onVideoStreamCreated user name is " + uid);
final String id = uid;
// 创建视频显示View
VideoView render = renderManager.createRender(size);
RelativeLayout render_view = (RelativeLayout)layout.findViewById(R.id.video_view);
render_view.addView(
render,
new LayoutParams(layout.getWidth(), layout.getHeight()));
render.setZOrderMediaOverlay(true);
renderManager.bindRenderWithStream(render, id);
}
6. 观众端主播端流移除事件处理(观众端)
此时,如果离开者是主播或嘉宾,服务器会广播离开的消息,其它观众会收到相应的流移除事件 onRemoteVideoStreamRemoved。
xxxxxxxxxx
// 远端退出房间,该方法实现要在UI线程
public void onRemoteVideoStreamRemoved(String uid, String streamId) {
// 主播退出处理逻辑,如果主播是因为网络原因退出,可以等待主播重新加入房间
// 也可以销毁相关surface view,并退出房间
// 此处假设主播主动退出,销毁视频View
final String id = uid;
VideoView render = renderManager.getRender(id);
renderManager.unbindRenderWithStream(render);
renderManager.destroyRender(render);
}
7. 释放资源(主播端、观众端)
xxxxxxxxxx
room.destroy();
deviceManager.destroy();
renderManager.destroy();
4. 观众加入连麦
当观众主动想连麦时,观众端需要开启录音及摄像头设备,函数调用如下:
xxxxxxxxxx
deviceManager.startAudioDevice(); //开启音频设备
deviceManager.startCamera(); //开启摄像头
room.switchClientRole(CLIENT_ROLE_COHOST); //切换本地为嘉宾角色
结束连麦时:
room.switchClientRole(CLIENT_ROLE_VIEWER); //切换回观众角色
deviceManager.stopAudioDevice(); // 关闭音频设备
deviceManager.stopCamera(); // 关闭摄像头
如果嘉宾是以视频方式加入,主播端需要显示嘉宾画面,此时主播需要处理嘉宾加入事件:
// 加入房间后 开启microphone,开启 speaker,开启carmera
public void onConfrenceJoin(String s) {
deviceManager.startAudioDevice();
deviceManager.setSpeaker(true);
deviceManager.startCamera();
}
// 远端来流后 bindRender,渲染显示
public void onRemoteVidStreamCreated(String uid) {
final String id = uid;
RelativeLayout layout = CreateLayout();
Point size = new Point(layout.getWidth(), layout.getHeight());
//创建视频Surface View
VideoView render = renderManager.createRender(size);
RelativeLayout render_view = (RelativeLayout)layout.
findViewById(R.id.video_view);
render_view.addView(
render,
new LayoutParams(layout.getWidth(), layout.getHeight()));
render.setZOrderMediaOverlay(true);
// 将创建的视频View与视频流关联
renderManager.bindRenderWithStream(render, id);
}
5. 退出房间
当主播、嘉宾或者观众想离开时,调用:
room.leaveRoom()
6. 启动混流转播服务
如需要开启此项功能,需要先联系视界客服或商务开启此项服务。
以上是主播推流与观众拉流以及连麦流程介绍,设备相关API与回调函数详细用法详细请见实时通话文档部分。