旁路推流
NERTC SDK 支持云端音视频混流和 RTMP 旁路推流,可以将实时音视频流转为标准直播流,并将其从网易云信实时音视频云推送到第三方 CDN(Content Delivery Network)或网易云信直播服务,这一过程称为旁路推流。
推流到 CDN 后,基于 CDN 的大规模内容分发,观众可通过 URL 拉流地址使用播放器或 Web 端浏览器直接在线观看直播,也可以加入音视频房间进行实时连麦。当房间中有多个主播时,需要将多个直播流组合成单流,并设置直播流的推流布局,在直播画布中显示主播们的实时画面。
功能描述
在您的项目中实现音视频通话后,可以将频道画面进行旁路推流。网易云信可以将您的媒体流在云端进行混流、转码、合图等操作,生成一路视频流,并将其推送到网易云信直播服务中进行大规模内容分发,以便观众直播观看。
- 房间内首个终端加入自动触发转码,房间内无终端时停止转码。
- 直播流支持 RTMP 协议。
前提条件
- 初始化之前,需要通过 setParameters 开启推流开关。
- 主播端推流之前,需要通过 setChannelProfile 设置房间模式为直播模式。
- 发起推流任务之前,请确认您已实现音视频通话的相关流程。详细信息请参考快速入门。
注意事项
- 同一个音视频房间(即同一个 channelid)可以创建 3 个不同的推流任务。
- 服务端创建推流任务时,需要通过 hostuid 设置主播的 UID;客户端 SDK 添加推流任务时,默认将创建推流任务的用户 UID 设置为主播角色。互动直播场景中,仅支持解析主播端的 SEI 自定义数据。
实现方式
在项目中实现音视频通话后,可以通过 addLiveStreamTask 接口,将频道画面进行旁路推流。您可以通过以下方式增加推流任务:
客户端:
通过客户端接口发起推流任务。
- Android:addLiveStreamTask
- iOS:addLiveStreamTask
- Windows&macOS:addLiveStreamTask
- Web:addTasks
服务端:
通过服务端接口创建推流任务发起推流任务。 客户端网络状况不佳时,为保证直播的稳定性,建议通过服务端实现旁路推流。
注意:以下具体实现方式以 Android API 参数为例,客户端参数名称略有不同,详细信息请查看各端 API 文档。
配置推流任务信息
创建推流任务时,需要配置任务 ID、推流地址等推流参数信息,还可以开启直播视频录制。
录制的视频默认存储在点播服务中,您可以通过点播的相关接口查看并下载视频文件。详细信息请参考点播媒资管理。
推流任务信息相关参数说明:
参数名称 | 描述 |
taskId | 自定义的推流任务ID。请保证此ID唯一。字母数字下划线组成的 64 位以内的字符串。 |
url | 推流地址,例如 rtmp://test.url。 此处的推流地址可设置为网易云信直播产品中服务端 API 创建频道的返回参数pushUrl。 |
serverRecordEnabled | 旁路推流是否需要进行音视频录制。 |
liveMode | 直播推流模式。可设置为:
|
配置推流布局
通过 layout 参数可以设置互动直播的画面布局,即自定义频道画面的各路视频布局方式,例如调整画布(Canvas)大小和颜色、各路视频的图像大小、位置等。
当房间中只有一路视频流时,您也可以设置视频透传,
音视频流配置
config 参数用于配置音视频流编码参数等设置。
通过 singleVideoNoTrans 设置视频透传。
singleVideoNoTrans 参数设置为 true 表示开启视频透传,开启后,如果房间中只有一路视频流输入,则不对输入视频流进行转码,不遵循转码布局,直接推流 CDN。视频透传默认为关闭状态。
通过 audioParam 设置音频编码参数。
目前支持通过 audioParam 中的 bitRate 设置音频推流码率。单位为 kbps,取值范围为 10~192。语音场景建议设置为 64 及以上码率,音乐场景建议设置为 128 及以上码率。
示例代码
Android
// 初始化之前打开推流开关
NERtcParameters mRtcParameters = new NERtcParameters();
mRtcParameters.set(NERtcParameters.KEY_PUBLISH_SELF_STREAM, true);
NERtcEx.getInstance().setParameters(mRtcParameters);
// 加入房间前设置直播模式
// 0 - COMMUNICATION(通信模式), 1 - LIVE_BROADCASTING(直播模式)
NERtcEx.getInstance().setChannelProfile(1);
// 加入房间后添加推流任务。
// 初始化推流任务
NERtcLiveStreamTaskInfo liveTask1 = new NERtcLiveStreamTaskInfo();
//taskID 可选字母、数字,下划线,不超过64位
liveTask1.taskId = String.valueOf(Math.abs(pushUlr.hashCode()));
// 设置推互动直播推流地址,一个推流任务对应一个推流房间
liveTask1.url = pushUlr;
// 设置是否进行互动直播录制,请注意与音视频通话录制区分。
liveTask1.serverRecordEnabled = false;
// 设置推音视频流还是纯音频流
liveTask1.liveMode = enableVideo? NERtcLiveStreamTaskInfo.NERtcLiveStreamMode.kNERtcLsModeVideo : NERtcLiveStreamTaskInfo.NERtcLiveStreamMode.kNERtcLsModeAudio;
//设置整体布局
NERtcLiveStreamLayout layout = new NERtcLiveStreamLayout();
layout.userTranscodingList = new ArrayList<>();
layout.width = 720;//整体布局宽度
layout.height = 1280;//整体布局高度
layout.backgroundColor = Color.parseColor("#3399ff"); // 整体背景色
liveTask1.layout = layout;
// 设置直播成员布局
NERtcLiveStreamUserTranscoding user1 = new NERtcLiveStreamUserTranscoding();
user1.uid = uid1; // 用户id
user1.audioPush = true; // 推流是否发布user1 的音频
user1.videoPush = enableVideo; // 推流是否发布user1的视频
if (user1.videoPush) {// 如果发布视频,需要设置一下视频布局参数
// user1 视频的缩放模式, 详情参考NERtcLiveStreamUserTranscoding 的API 文档
user1.adaption = NERtcLiveStreamUserTranscoding.NERtcLiveStreamVideoScaleMode.kNERtcLsModeVideoScaleCropFill;
user1.x = 10; // user1 的视频布局x偏移,相对整体布局的左上角
user1.y = 10; // user1 的视频布局y偏移,相对整体布局的左上角
user1.width = 180; // user1 的视频布局宽度
user1.height = 320; //user1 的视频布局高度
}
layout.userTranscodingList.add(user1);
...
// 设置第n位直播成员布局
NERtcLiveStreamUserTranscoding usern = new NERtcLiveStreamUserTranscoding();
usern.uid = uidn;
usern.audioPush = true;
usern.videoPush = enableVideo;
if (usern.videoPush) {
usern.adaption = NERtcLiveStreamUserTranscoding.NERtcLiveStreamVideoScaleMode.kNERtcLsModeVideoScaleCropFill;
usern.x = user1.x + user1.width + 10;
usern.y = user1.y + user1.height + 10;
usern.width = 320;
usern.height = 640;
}
layout.userTranscodingList.add(usern);
// 调用 addLiveStreamTask 接口添加推流任务
int ret = NERtcEx.getInstance().addLiveStreamTask(liveTask1, addLiveTaskCallback);
if (ret != 0) {
showToast("调用添加推流任务接口执行失败 , ret : " + ret);
}
// 添加推流任务的异步callback
private AddLiveTaskCallback addLiveTaskCallback = new AddLiveTaskCallback(){
void onAddLiveStreamTask(String taskId, int errCode){
if (code == RtcCode.LiveCode.OK) {
showToast("添加推流任务成功 : taskId " + taskId);
} else {
showToast("添加推流任务失败 : taskId " + taskId + " , code : " + code);
}
}
};
iOS
// 初始化之前打开推流开关
[NERtcEngine.sharedEngine setParameters:@{kNERtcKeyPublishSelfStreamEnabled: @YES}];
// 加入房间前设置直播模式。
// kNERtcChannelProfileCommunication-通信模式
// kNERtcChannelProfileLiveBroadcasting-直播模式
[NERtcEngine.sharedEngine setChannelProfile:kNERtcChannelProfileLiveBroadcasting];
// 加入房间后创建推流任务
//先初始化推流任务
NERtcLiveStreamTaskInfo *info = [[NERtcLiveStreamTaskInfo alloc] init];
//taskID 可选字母、数字,下划线,不超过64位
info.taskID = taskID;
// 设置推互动直播推流地址,一个推流任务对应一个推流房间
info.streamURL = pushUrl;
// 设置是否进行互动直播录制,请注意与音视频通话录制区分。
info.serverRecordEnabled = NO;
// 设置推音视频流还是纯音频流
info.lsMode = enableVideo ? kNERtcLsModeVideo : kNERtcLsModeAudio;
//设置整体布局
NERtcLiveStreamLayout *layout = [[NERtcLiveStreamLayout alloc] init];
layout.width = 720; //整体布局宽度
layout.height = 1280; //整体布局高度
layout.backgroundColor = (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
info.layout = layout;
// 设置直播成员布局
NERtcLiveStreamUserTranscoding *user1 = [[NERtcLiveStreamUserTranscoding alloc] init];
user1.uid = uid1;
user1.audioPush = true; // 推流是否发布user1的音频
user1.videoPush = enableVideo; // 推流是否发布user1的视频
if (user1.videoPush) {
// 如果发布视频,需要设置一下视频布局参数
user1.x = 10; // user1 的视频布局x偏移,相对整体布局的左上角
user1.y = 10; // user1 的视频布局y偏移,相对整体布局的左上角
user1.width = 180; // user1 的视频布局宽度
user1.height = 320; //user1 的视频布局高度
user1.adaption = kNERtcLsModeVideoScaleCropFill;
}
// 设置第n位直播成员布局
NERtcLiveStreamUserTranscoding *usern = [[NERtcLiveStreamUserTranscoding alloc]init];
usern.uid = uidn;
usern.audioPush = true;
usern.videoPush = enableVideo;
if (usern.videoPush) {
usern.adaption = kNERtcLsModeVideoScaleCropFill;
usern.x = user1.x + user1.width + 10;
usern.y = user1.y + user1.height + 10;
usern.width = 320;
usern.height = 640;
}
layout.users = @[user1,...,usern];
//配置背景占位图片,可以不配置。
NERtcLiveStreamImageInfo *imageInfo = [[NERtcLiveStreamImageInfo alloc] init];
imageInfo.url = url;
imageInfo.x = 0;
imageInfo.y = 0;
imageInfo.width = 720;
imageInfo.height = 1280;
layout.bgImage = imageInfo;
//调用 addLiveStreamTask 接口添加推流任务
int ret = [[NERtcEngine sharedEngine] addLiveStreamTask:info
compeltion:^(NSString * _Nonnull taskId, kNERtcLiveStreamError errorCode) {
NSString *toast = !errorCode ? @"添加成功" : [NSString stringWithFormat:@"添加失败 errorcode = %d",errorCode];
MakeToast(toast);
}];
if (ret != 0) {
MakeToast(@"调用添加推流任务失败");
}
Windows
// 初始化前打开推流开关
rtc_engine_->setParameters("{\"publish_self_stream_enabled\":true}");
//加入房间前设置直播模式。
//kNERtcChannelProfileCommunication为通话模式,
// kNERtcChannelProfileLiveBroadcasting为直播模式
NERtcChannelProfileType channelProfileType = kNERtcChannelProfileLiveBroadcasting;
rtc_engine_->setChannelProfile(channelProfileType);
// 加入房间后创建推流任务
// 添加推流任务的异步callback , 更多错误码请参考onAddLiveStreamTask API文档
void onAddLiveStreamTask(const char* task_id, const char* url, int error_code){
if (code == kNERtcNoError) {
showToast("添加推流任务成功 : taskId " + taskId);
} else {
showToast("添加推流任务失败 : taskId " + taskId + " , code : " + code);
}
};
//推流任务数据结构
NERtcLiveStreamTaskInfo ls_task_info_;
//保证一个url 只能设置一个task , 要不然会有问题的,使用上层自己维护的task_id、url
strncpy(ls_task_info_.task_id, task_id.c_str(), kNERtcMaxTaskIDLength);
strncpy(ls_task_info_.stream_url, url.c_str(), kNERtcMaxURILength);
ls_task_info_.ls_mode = kNERtcLsModeVideo;
ls_task_info_.server_record_enabled = false;
//设置整体布局
ls_task_info_.layout.background_color = 0x3399ff; // 整体背景色
ls_task_info_.layout.width = 1280;//整体布局宽度
ls_task_info_.layout.height = 640;//整体布局高度
ls_task_info_.layout.user_count = 2;
ls_task_info_.layout.users = new NERtcLiveStreamUserTranscoding[2];
ls_task_info_.layout.users[0].uid = uid;
ls_task_info_.layout.users[1].uid = remote_uid;
for (int i = 0; i < ls_task_info_.layout.user_count; i++)
{
ls_task_info_.layout.users[i].adaption = kNERtcLsModeVideoScaleFit;
ls_task_info_.layout.users[i].video_push = true;// 推流是否发布user的视频
ls_task_info_.layout.users[i].x = 640 * i; // user 的视频布局x偏移,相对整体布局的左上角
ls_task_info_.layout.users[i].y = 0; // user 的视频布局y偏移,相对整体布局的左上角
ls_task_info_.layout.users[i].width = 640; // user 的视频布局宽度
ls_task_info_.layout.users[i].height = 640; //user1 的视频布局高度
ls_task_info_.layout.users[i].audio_push = true;// 推流是否发布user的音频
}
if (0)
{
ls_task_info_.layout.bg_image = new NERtcLiveStreamImageInfo;
ls_task_info_.layout.bg_image->x = 0;
ls_task_info_.layout.bg_image->y = 0;
ls_task_info_.layout.bg_image->width = 640;
ls_task_info_.layout.bg_image->height = 480;
strncpy(ls_task_info_.layout.bg_image->url, url2.c_str(), kNERtcMaxURILength);
}
else
{
ls_task_info_.layout.bg_image = nullptr;
}
// 如果需要,可以配置多个用户,配置的用户不用已经在房间中,如果用户不在房间中,那么会有相应的视频占位
// 之后用户以配置了的uid进入房间,开启推流开关即可推流
int ret = addLiveStreamTask(ls_task_info_);
if (ret != 0) {
showToast("调用添加推流任务失败 , ret : " + ret);
}
macOS
// 初始化之前打开推流开关。
rtc_engine_->setParameters("{\"publish_self_stream_enabled\":true}");
//加入房间前设置直播模式。
//kNERtcChannelProfileCommunication为通话模式,
// kNERtcChannelProfileLiveBroadcasting为直播模式
NERtcChannelProfileType channelProfileType = kNERtcChannelProfileLiveBroadcasting;
rtc_engine_->setChannelProfile(channelProfileType);
// 加入房间后创建推流任务
// 添加推流任务的异步callback , 更多错误码请参考onAddLiveStreamTask API文档
void onAddLiveStreamTask(const char* task_id, const char* url, int error_code){
if (code == kNERtcNoError) {
showToast("添加推流任务成功 : taskId " + taskId);
} else {
showToast("添加推流任务失败 : taskId " + taskId + " , code : " + code);
}
};
//推流任务数据结构
NERtcLiveStreamTaskInfo ls_task_info_;
//保证一个url 只能设置一个task , 要不然会有问题的,使用上层自己维护的task_id、url
strncpy(ls_task_info_.task_id, task_id.c_str(), kNERtcMaxTaskIDLength);
strncpy(ls_task_info_.stream_url, url.c_str(), kNERtcMaxURILength);
ls_task_info_.ls_mode = kNERtcLsModeVideo;
ls_task_info_.server_record_enabled = false;
//设置整体布局
ls_task_info_.layout.background_color = 0x3399ff; // 整体背景色
ls_task_info_.layout.width = 1280;//整体布局宽度
ls_task_info_.layout.height = 640;//整体布局高度
ls_task_info_.layout.user_count = 2;
ls_task_info_.layout.users = new NERtcLiveStreamUserTranscoding[2];
ls_task_info_.layout.users[0].uid = uid;
ls_task_info_.layout.users[1].uid = remote_uid;
for (int i = 0; i < ls_task_info_.layout.user_count; i++)
{
ls_task_info_.layout.users[i].adaption = kNERtcLsModeVideoScaleFit;
ls_task_info_.layout.users[i].video_push = true;// 推流是否发布user的视频
ls_task_info_.layout.users[i].x = 640 * i; // user 的视频布局x偏移,相对整体布局的左上角
ls_task_info_.layout.users[i].y = 0; // user 的视频布局y偏移,相对整体布局的左上角
ls_task_info_.layout.users[i].width = 640; // user 的视频布局宽度
ls_task_info_.layout.users[i].height = 640; //user1 的视频布局高度
ls_task_info_.layout.users[i].audio_push = true;// 推流是否发布user的音频
}
if (0)
{
ls_task_info_.layout.bg_image = new NERtcLiveStreamImageInfo;
ls_task_info_.layout.bg_image->x = 0;
ls_task_info_.layout.bg_image->y = 0;
ls_task_info_.layout.bg_image->width = 640;
ls_task_info_.layout.bg_image->height = 480;
strncpy(ls_task_info_.layout.bg_image->url, url2.c_str(), kNERtcMaxURILength);
}
else
{
ls_task_info_.layout.bg_image = nullptr;
}
// 如果需要,可以配置多个用户,配置的用户不用已经在房间中,如果用户不在房间中,那么会有相应的视频占位
// 之后用户以配置了的uid进入房间,开启推流开关即可推流
int ret = addLiveStreamTask(ls_task_info_);
if (ret != 0) {
showToast("调用添加推流任务失败 , ret : " + ret);
}
Web
//加入房间前设置直播模式:
//"rtc": 通信场景,"live": 直播场景
rtc.client.setChannelProfile({mode:'live'});
//加入房间并打开推流开关
rtc.client.join({
channelName: '房间名称',
uid: uid,
token: token,
joinChannelLiveConfig:{
liveEnable:true // 设置推流开关
}
}).then((obj) => {
console.info('加入房间成功...')
//初始化本地流,并且发布
initLocalStream() //后面介绍说明
})
// 添加推流任务
// 互动直播的推流任务,可以设置多个推流任务
let rtmpTasks = []
// taskID 可选字母、数字,下划线,不超过64位
let taskId = 'taskId_1'
// 设置推互动直播推流地址,一个推流任务对应一个推流房间
let streamUrl = 'rtmp://xxxxxx-1'
// 设置是否进行互动直播录制,请注意与音视频通话录制区分。
let record = true
let task1 = {
taskId,
streamUrl,
record,
// 整体布局参数
layout: {
canvas: {
//整体布局宽度
width: 1280,
//整体布局高度
height: 720,
//整体布局背景色(转为10进制的数,如:#FFFFFF 16进制转为10进制为 16777215)
color: 16777215
},
users: [{
uid: 100, //用户id
x: 0, // user1 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user1的视频)
y: 0, // user1 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user1的视频)
width: 640, // user1 的视频布局宽度(前提是推流发布user1的视频)
height: 360, //user1 的视频布局高度(前提是推流发布user1的视频)
adaption: 1, //自适应,值默认为1
pushAudio: true, // 推流是否发布user1 的音频
pushVideo: true // 推流是否发布user1的视频
},
{
uid: 200, //用户id
x: 0, // user2 的视频布局x偏移,相对整体布局的左上角(前提是推流发布user2的视频)
y: 0, // user2 的视频布局y偏移,相对整体布局的左上角(前提是推流发布user2的视频)
width: 640, // user2 的视频布局宽度(前提是推流发布user2的视频)
height: 360, //user2 的视频布局高度(前提是推流发布user2的视频)
adaption: 1, //自适应,值默认为1
pushAudio: true, // 推流是否发布user2 的音频
pushVideo: true // 推流是否发布user2 的视频
}],
images: [{
url: "xxxxxx", //设置背景图片
x: 0, // 背景图片x偏移,相对整体布局的左上角
y: 0, // 背景图片y偏移,相对整体布局的左上角
width: 480, // 背景图片宽度
height: 360, //背景图片高度
adaption: 1 //自适应,值默认为1
}]
}
}
rtmpTasks.push(task1)
// 添加推流任务
rtc.client.addTasks({rtmpTasks}).then(()=>{
console.log('添加推流任务接口成功')
}).catch(error=>{
console.warn('添加推流任务接口失败: ', error)
if (error == 'INVALID_PARAMETER') {
console.log('参数错误')
}
})


此文档对你是否有帮助

