You need to enable JavaScript to run this app.
导航
客户端发起转推直播
最近更新时间:2025.11.10 15:11:15首次发布时间:2021.07.18 15:06:10
复制全文
我的收藏
有用
有用
无用
无用

对于一个音视频通话,你可以将其中的多路音视频流合为一路,并将合并得到的音视频流推送到指定的推流地址(通常是 CDN 地址)。你可以在应用服务端和应用客户端启动合流转推,本文介绍如何通过调用客户端 API,在 RTC 服务端发起和完成合流转推任务。

alt

关于如何调用 Open API,在服务端完成合流转推,参见 通过 OpenAPI 使用合流转推功能

前提条件

你已经集成 RTC SDK,实现了基本的音视频通话

调用时序

api

1. 创建引擎类

创建和初始化一个音视频引擎类。

参考 构建 RTC 应用 获取详细步骤。

// 首先定义:
    // IRTCEngineEventHandler rtcEngineEventHandler = new IRTCEngineEventHandler() {
    //    @Override public void onWarning(int warn) { ... }
    //    @Override public void onError(int err) { ... }
    // }
    
    // 创建引擎
    EngineConfig config = new EngineConfig();
    config.appID = appId;
    config.context = applicationContext;
    rtcEngine = RTCEngine.createRTCEngine(config, rtcEngineEventHandler);
    // 开启音视频采集
    rtcEngine.startVideoCapture();
    rtcEngine.startAudioCapture();

2. 进房

创建房间实例后,你可以加入房间发布和订阅音视频流。建议设置房间回调接口,以便监听房间和音视频流的状态回调。

private void joinRoom(String roomId) {
    // 创建房间
    rtcRoom = rtcEngine.createRTCRoom(roomId);
    rtcRoom.setRTCRoomEventHandler(roomEventHandler);
    String token = requestRoomToken(roomId);
    // 用户信息
    UserInfo userInfo = new UserInfo(localUid, "");
    // 房间配置
    boolean isAutoPublishAudio = true;
    boolean isAutoPublishVideo = true;
    boolean isAutoSubscribeAudio = true;
    boolean isAutoSubscribeVideo = true;
    RTCRoomConfig roomConfig = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM, isAutoPublishAudio, isAutoPublishVideo, isAutoSubscribeAudio, isAutoSubscribeVideo);
    // 加入房间
    rtcRoom.joinRoom(token, userInfo, true, roomConfig);
}

3. 开启任务

  1. 发起合流转推任务,在收到 onRoomStateChanged 回调,进入 RTC 房间成功后调用 startPushMixedStream
private void startPushCDNStream() {
      MixedStreamPushTargetConfig targetConfig = new MixedStreamPushTargetConfig();
      targetConfig.pushTargetType = MixedStreamPushTargetType.PUSH_TO_CDN;
      targetConfig.pushCDNURL = "CDN_URL";
  
      MixedStreamConfig mixedStreamConfig = MixedStreamConfig.defaultMixedStreamConfig();
      mixedStreamConfig.userID = 发起任务的用户ID;
      mixedStreamConfig.roomID = 房间ID;
      //视频相关参数
      mixedStreamConfig.videoConfig.width = 360;
      mixedStreamConfig.videoConfig.height = 640;
      mixedStreamConfig.videoConfig.fps = 15;
      mixedStreamConfig.videoConfig.bitrate = 500000;
      //音频相关参数
      mixedStreamConfig.audioConfig.channels = 2;
      mixedStreamConfig.audioConfig.sampleRate = 48000;
      mixedStreamConfig.audioConfig.bitrate = 64;
      //布局相关参数
      mixedStreamConfig.regions = getLayoutRegions();
      rtcEngine.startPushMixedStream(taskID, targetConfig, mixedStreamConfig);
    }
    // 监听任务回调
    IRTCEngineEventHandler engineEventHandler = new IRTCEngineEventHandler() {
        @Override
        public void onMixedStreamEvent(MixedStreamTaskInfo info, MixedStreamTaskEvent event, MixedStreamTaskErrorCode error) {
            super.onMixedStreamEvent(info, event, error);
            String msg = String.format("onMixedStreamEvent,taskId:%s, error:%s, event:%s", info.getTaskId(), error.toString(), event.toString());
            Log.d(TAG, msg);
        }
    };
};
  1. 合流视频的布局设置。分别以 1x4 和 2x2 两种布局模式为例。
private MixedStreamConfig.MixedStreamLayoutRegionConfig[] getLayoutRegions() {
    int width = 360;
    int height = 640;
    int userNum = userNameList.size();
    MixedStreamConfig.MixedStreamLayoutRegionConfig[] regions = new MixedStreamConfig.MixedStreamLayoutRegionConfig[userNum];
    int index = 0;
    //是否是 1x4 布局
    String mode = layoutMode.getSelectedItem().toString();
    if ("1x4".equals(mode)) {
        for (String uid : userNameList) {
            MixedStreamConfig.MixedStreamLayoutRegionConfig region = new MixedStreamConfig.MixedStreamLayoutRegionConfig();
            region.setRoomID(roomID);
            region.setUserID(uid);
            region.isLocalUser = (uid.equals(localUid));
            region.setLocationX(index * width / userNum);
            // 留出部分背景区域
            region.setLocationY(50);
            region.setWidth(width / userNum);
            region.setHeight(height);
            region.setAlpha(1);
            region.setZOrder(0);
            region.setRenderMode(MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN);
            region.setStreamType(MixedStreamVideoType.MIXED_STREAM_VIDEO_TYPE_MAIN);
            region.setMediaType(MixedStreamMediaType.MIXED_STREAM_MEDIA_TYPE_AUDIO_AND_VIDEO);
            regions[index] = region;
            index ++;
        }
    } else if ("2x2".equals(mode)) {
        for (String uid : userNameList) {
            MixedStreamConfig.MixedStreamLayoutRegionConfig region = new MixedStreamConfig.MixedStreamLayoutRegionConfig();
            region.setRoomID(roomID);
            region.setUserID(uid);
            region.isLocalUser = (uid.equals(localUid));
            region.setLocationX((index % 2) * width / userNum);
            region.setLocationY((index / 2) * height / 2 + 50);
            region.setWidth(width / 2);
            // 为展示部分背景
            region.setHeight(height / 2);
            region.setAlpha(1);
            region.setZOrder(0);
            region.setRenderMode(MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN);
            region.setStreamType(MixedStreamLayoutRegionConfig.MixedStreamVideoType.MIXED_STREAM_VIDEO_TYPE_MAIN);
            region.setMediaType(MixedStreamMediaType.MIXED_STREAM_MEDIA_TYPE_AUDIO_AND_VIDEO);
            regions[index] = region;
            index ++;
        }
    }
    return regions;
}

4. 更新任务

在收到远端用户视频流后,才可以更新合流布局。

private void updateCDNStreamConfig() {
    String cdnAddr = cdnAddressInput.getText().toString();
        if (cdnAddr.isEmpty()) {
            ToastUtil.showAlert(this, "cdn address is null");
            return;
        }
        targetConfig.pushCDNURL = cdnAddr;

        mixedStreamConfig.backgroundColor = layoutColorInput.getText().toString();
        mixedStreamConfig.regions = getLayoutRegions();
        rtcEngine.updatePushMixedStream(CDN_TASK_ID, targetConfig, mixedStreamConfig);
}

5. 结束任务

在音视频互动中,你可以随时启动或停止合流转推。

private void stopPushCDNStream() {
    rtcEngine.stopPushMixedStream(CDN_TASK_ID, MixedStreamPushTargetType.PUSH_TO_CDN);
  }

6. 离房

private void leaveRoom() {
    if (rtcRoom != null) {
        rtcRoom.leaveRoom();
        rtcRoom.destroy();
        rtcRoom = null;
    }
}

7. 销毁引擎

RTCEngine.destroyRTCEngine();

示例项目

常见问题

1. 如何在合流转推流中使用 SEI

SEI 是视频编码格式中的补充增强信息,和视频编码帧一起打包发送,因此可以达到和视频帧发送和解析同步的效果。转推任务发起成功后,画面布局和背景等信息作为 SEI 透传到 RTMP 流中。拉流端需要自行提取和解析 SEI,例如,更新画面布局。

  • 合流接口中传递到直播流中的信息,会在合流 I 帧前重复发送。例如,合流布局不变更,重复发送相同 SEI 数据,当合流布局变更,触发一个最新的 SEI。

  • 在开启/更新合流时,可以通过设置 layoutConfig.userConfigExtraInfo 来设置自定义 SEI 信息。详见 发送和接收媒体补充增强信息。比如在直播答题场景中,在 SEI 中打包题目信息,每个人听到主播讲题时,同时看到对应的题目,不会因为不同延时导致题目出现的时间与讲解不匹配。

合流的 SEI 结构示例如下,其中,自定义消息为 app_data 的值。

{
    "app_data": "自定义消息",
    "canvas": {
        "bgnd": "#000000",
        "h": 640,
        "w": 360
    },
    "regions": [
        {
            "alpha": 1.0,
            "contentControl": 0,
            "height": 640,
            "locationX": 0,
            "locationY": 50,
            "renderMode": 1,
            "uid": "user_343",
            "width": 360,
            "zorder": 0
        }
    ],
    "ts": 1705994199709
}

2. 如何设置 task_id

当 APP 需要开启多个视频合流时,可以通过 task_id 来区分多个合流 ID。如果同时只有一个合流视频数据可以使用空字符串代替。
startPushMixedStreamstopPushMixedStreamtask_id 需要成对出现。 如果 task_id 不同,会导致合流不会关闭。

3. 如何处理发起端意外掉线后重新登录

在开启转推任务后,如果因为进行刷新页面等操作,造成应用端进程异常终止,则转推任务会在空闲时间超过设定值后自动停止,默认空闲超时时间为180s
重启客户端重新登录后,需先调用 stopPushMixedStream 结束上一个任务,再开启新的转推任务,以免造成多个任务同时操作一个推流地址,导致新的转推任务失败。

4. 错误码

合流转推过程中返回的错误码详见各端 API 文档。

AndroidiOSMacWindowsElectronFlutterWeb
MixedStreamTaskErrorCodeByteRTCMixedStreamTaskErrorCodeByteRTCMixedStreamTaskErrorCodeMixedStreamTaskErrorCodeStreamMixingErrorCodeStreamMixingErrorCodeStreamMixingEventErrorCode

5. 调用开启转推直播任务接口传入的用户离开房间后,转推直播任务是否会停止?

  • 若未设置 TaskId
    该用户离开房间后,该任务会自动停止。
  • 若设置 TaskId
    该用户离开房间任务不会停止。直至房间内无用户后,该任务会自动停止。