文档反馈
文档反馈

旁路推流

重要通知

网易云信新版文档中心现已正式上线!

音视频通话2.0互动直播2.0多人语音聊天室PK 直播等产品和场景方案已迁移至新版文档中心维护,欢迎体验!

NERTC SDK 支持云端音视频混流和 RTMP 旁路推流,可以将实时音视频流转为标准直播流,并将其从网易云信实时音视频云推送到第三方 CDN(Content Delivery Network)或网易云信直播服务,这一过程称为旁路推流。

推流到 CDN 后,基于 CDN 的大规模内容分发,观众可通过 URL 拉流地址使用播放器或 Web 端浏览器直接在线观看直播,也可以加入音视频房间进行实时连麦。当房间中有多个主播时,需要将多个直播流组合成单流,并设置直播流的推流布局,在直播画布中显示主播们的实时画面。

功能描述

在您的项目中实现音视频通话后,可以将频道画面进行旁路推流。网易云信可以将您的媒体流在云端进行混流、转码、合图等操作,生成一路视频流,并将其推送到网易云信直播服务中进行大规模内容分发,以便观众直播观看。

前提条件

注意事项

实现方式

在项目中实现音视频通话后,可以通过 addLiveStreamTask 接口,将频道画面进行旁路推流。您可以通过以下方式增加推流任务:

注意:以下具体实现方式以 Android API 参数为例,客户端参数名称略有不同,详细信息请查看各端 API 文档。

配置推流任务信息

创建推流任务时,需要配置任务 ID、推流地址等推流参数信息,还可以开启直播视频录制。

录制的视频默认存储在点播服务中,您可以通过点播的相关接口查看并下载视频文件。详细信息请参考点播媒资管理

推流任务信息相关参数说明:

参数名称 描述
taskId 自定义的推流任务ID。请保证此ID唯一。字母数字下划线组成的 64 位以内的字符串。
url 推流地址,例如 rtmp://test.url。
此处的推流地址可设置为网易云信直播产品中服务端 API 创建频道的返回参数pushUrl。
serverRecordEnabled 旁路推流是否需要进行音视频录制。
liveMode 直播推流模式。可设置为:
  • kNERtcLsModeAudio:推流纯音频。
  • kNERtcLsModeVideo:推流音视频。

配置推流布局

通过 layout 参数可以设置互动直播的画面布局,即自定义频道画面的各路视频布局方式,例如调整画布(Canvas)大小和颜色、各路视频的图像大小、位置等。

当房间中只有一路视频流时,您也可以设置视频透传,

音视频流配置

config 参数用于配置音视频流编码参数等设置。

示例代码

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('参数错误')
    }
  })
×

反馈成功

非常感谢您的反馈,我们会继续努力做得更好。