本章节介绍 Android 端互动直播场景核心功能的实现方式。
互动直播功能需要使用互动版 SDK,请您在安装 SDK 时选择正确的版本。
说明
以下是主播端核心功能实现的时序图和参考接入代码。
主播通过 RTC 引擎和推流引擎开始直播推流。
时序图
示例代码
创建 RTC 视频引擎,设置本地预览视图,设置视频编码参数。
// 初始化 RTCVideo 对象 mRTCVideo = RTCVideo.createRTCVideo(Env.getApplicationContext(), mAppId, mRTCVideoEventHandler, null, null); // 设置本地视图 VideoCanvas videoCanvas = new VideoCanvas(); // renderView 为本地用户预览视图,需自行创建、布局并赋值给 videoCanvas videoCanvas.renderView = renderView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; mRTCVideo.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, videoCanvas); // 设置视频编码参数 VideoEncoderConfig config = new VideoEncoderConfig( mConfig.mVideoEncoderWidth, mConfig.mVideoEncoderHeight, mConfig.mVideoEncoderFps, mConfig.mVideoEncoderKBitrate * 1000); mRTCVideo.setVideoEncoderConfig(config);
订阅 RTC 本地音视频数据。
// 订阅本地视频数据 mRTCVideo.setLocalVideoSink(StreamIndex.STREAM_INDEX_MAIN, mVideoFrameListener, IVideoSink.PixelFormat.I420); // 订阅本地音频数据 mRTCVideo.enableAudioFrameCallback(AudioFrameCallbackMethod.AUDIO_FRAME_CALLBACK_RECORD, new AudioFormat(changeSampleRate(mConfig.mAudioCaptureSampleRate), changeChannel(mConfig.mAudioCaptureChannel))); mRTCVideo.registerAudioFrameObserver(mAudioFrameListener);
创建推流引擎,设置推流视频编码参数。
// 创建推流引擎 // 推流配置 VeLivePusherConfiguration config = new VeLivePusherConfiguration(); // 配置上下文 config.setContext(Env.getApplicationContext()); // 失败重连次数 config.setReconnectCount(10); // 创建推流器 mLivePusher = config.build(); // 配置推流参数 // 视频编码配置 VeLivePusherDef.VeLiveVideoEncoderConfiguration videoEncoderCfg = new VeLivePusherDef.VeLiveVideoEncoderConfiguration(); // 设置视频分辨率,内部会根据分辨率设置最佳码率参数 videoEncoderCfg.setResolution(VeLiveVideoResolution720P); // 视频编码初始化码率(仅供参考) videoEncoderCfg.setBitrate(mConfig.mVideoEncoderKBitrate); // 视频编码最大码率(仅供参考) videoEncoderCfg.setMaxBitrate(mConfig.mVideoEncoderKBitrate); // 视频编码最小码率(仅供参考) videoEncoderCfg.setMinBitrate(mConfig.mVideoEncoderKBitrate); // 硬编码 videoEncoderCfg.setEnableAccelerate(mConfig.mIsVideoHardwareEncoder); // 编码帧率 videoEncoderCfg.setFps(mConfig.mVideoEncoderFps); VeLivePusherDef.VeLiveAudioEncoderConfiguration audioEncoderCfg = new VeLivePusherDef.VeLiveAudioEncoderConfiguration(); // 音频编码采样率 if (mConfig.mAudioEncoderSampleRate == 32000) { audioEncoderCfg.setSampleRate(VeLiveAudioSampleRate32000); } else if (mConfig.mAudioEncoderSampleRate == 48000) { audioEncoderCfg.setSampleRate(VeLiveAudioSampleRate48000); } else { audioEncoderCfg.setSampleRate(VeLiveAudioSampleRate44100); } // 音频编码通道数 if (mConfig.mAudioEncoderChannel == 1) { audioEncoderCfg.setChannel(VeLiveAudioChannelMono); } else { audioEncoderCfg.setChannel(VeLiveAudioChannelStereo); } // 音频编码码率 audioEncoderCfg.setBitrate(mConfig.mAudioEncoderKBitrate); // 配置视频编码 mLivePusher.setVideoEncoderConfiguration(videoEncoderCfg); // 配置音频编码 mLivePusher.setAudioEncoderConfiguration(audioEncoderCfg); // 开始视频采集 mLivePusher.startVideoCapture(VeLiveVideoCaptureExternal); // 开始音频采集 mLivePusher.startAudioCapture(VeLiveAudioCaptureExternal);
开启 RTC 音视频采集。
// 开始视频采集 mRTCVideo.startVideoCapture(); // 开始音频采集 mRTCVideo.startAudioCapture();
开启推流引擎推流。
// 开始推流,url 为 RTMP 推流地址 mLivePusher.startPush(url);
RTC 本地音视频回调数据发送给推流引擎。
// 视频采集回调, 发送视频数据给推流引擎 IVideoSink mVideoFrameListener = new IVideoSink() { @Override public void onFrame(com.ss.bytertc.engine.video.VideoFrame frame) { final int width = videoFrame.getWidth(); final int height = videoFrame.getHeight(); final int chromaHeight = (height + 1) / 2; final int chromaWidth = (width + 1) / 2; int bufferSize = width * height + chromaWidth * chromaHeight * 2; final ByteBuffer dstBuffer = ByteBuffer.allocateDirect(bufferSize); YuvHelper.I420Rotate(videoFrame.getPlaneData(0), videoFrame.getPlaneStride(0), videoFrame.getPlaneData(1), videoFrame.getPlaneStride(1), videoFrame.getPlaneData(2), videoFrame.getPlaneStride(2), dstBuffer,width, height,videoFrame.getRotation().value()); dstBuffer.position(0); VeLiveVideoFrame videoFrame1 = new VeLiveVideoFrame(width, height, System.currentTimeMillis() * 1000, dstBuffer); mLivePusher.pushExternalVideoFrame(videoFrame1); videoFrame1.release(); frame.release(); } }; // 音频采集回调, 发送音频数据给推流引擎 mAudioFrameListener = new IAudioFrameObserver() { @Override public void onRecordAudioFrame(IAudioFrame audioFrame) { VeLiveAudioFrame audioFrame = new VeLiveAudioFrame(VeLivePusherDef.VeLiveAudioSampleRate.fromValue(sampleRate, VeLiveAudioSampleRate44100), VeLivePusherDef.VeLiveAudioChannel.fromValue(channels, VeLiveAudioChannelStereo), timestamp, byteBuffer); mLivePusher.pushExternalAudioFrame(audioFrame); } };
主播美颜功能都是通过 RTC 引擎进行对接,使用方式请参考 美颜特效(CV)。
主播停止推流引擎推流,通过 RTC 引擎加入房间连麦,并开启 RTC 服务端合流转推。
时序图
示例代码
停止推流引擎推流。
// 停止推流引擎推流 mLivePusher.stopPush();
创建 RTC 房间,设置用户信息,加入 RTC 房间。参考使用 Token 完成鉴权了解如何通过业务服务器获取鉴权 token。
// 创建 RTC 房间 mRTCRoom = mRTCVideo.createRTCRoom(roomId); mRTCRoom.setRTCRoomEventHandler(mIRtcRoomEventHandler); // 设置用户信息 mUserId = userId; mRoomId = roomId; UserInfo userInfo = new UserInfo(userId, null); RTCRoomConfig roomConfig = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_COMMUNICATION, true, true, true); // 加入房间,token 信息通过业务服务器申请 mRTCRoom.joinRoom(token, userInfo, roomConfig);
收到加入 RTC 房间成功回调,开启 RTC 服务端合流转推。
// 加入房间成功通知 private RtcRoomEventHandlerAdapter mIRtcRoomEventHandler = new RtcRoomEventHandlerAdapter() { @Override public void onRoomStateChanged(String roomId, String uid, int state, String extraInfo) { // 创建 RTC 服务端合流配置 mMixedStreamConfig = MixedStreamConfig.defaultMixedStreamConfig(); mMixedStreamConfig.setRoomID(mRoomId); mMixedStreamConfig.setUserID(mUserId); // 服务端合流 mMixedStreamConfig.setExpectedMixingType(STREAM_MIXING_BY_SERVER); // 设置推流地址, 这里为主播的 RTMP 推流地址 mMixedStreamConfig.setPushURL(mPushUrl); // 设置视频编码参数。该参数需要和推流视频编码参数保持一致 MixedStreamConfig.MixedStreamVideoConfig videoConfig = mMixedStreamConfig.getVideoConfig(); // 分辨率宽 videoConfig.setWidth(mConfig.mVideoEncoderWidth); // 分辨率高 videoConfig.setHeight(mConfig.mVideoEncoderHeight); // fps videoConfig.setFps(mConfig.mVideoEncoderFps); // 比特率 videoConfig.setBitrate(mConfig.mVideoEncoderKBitrate); mMixedStreamConfig.setVideo(videoConfig); // 设置音频编码参数。该参数需要和推流音频编码参数保持一致 MixedStreamConfig.MixedStreamAudioConfig audioConfig = mMixedStreamConfig.getAudioConfig(); // 音频采样率 audioConfig.setSampleRate(mConfig.mAudioEncoderSampleRate); // 通道数 audioConfig.setChannels(mConfig.mAudioEncoderChannel); // 比特率 k audioConfig.setBitrate(mConfig.mAudioEncoderKBitrate); // 配置音频参数 mMixedStreamConfig.setAudioConfig(audioConfig); MixedStreamConfig.MixedStreamLayoutConfig layout = new MixedStreamConfig.MixedStreamLayoutConfig(); // 主播合流布局 MixedStreamConfig.MixedStreamLayoutRegionConfig[] regions = new MixedStreamConfig.MixedStreamLayoutRegionConfig[1]; MixedStreamConfig.MixedStreamLayoutRegionConfig region = new MixedStreamConfig.MixedStreamLayoutRegionConfig(); region.setUserID(mUserId); // 主播uid region.setRoomID(mRoomId); region.setIsLocalUser(true); region.setLocationX(0.0); // 仅供参考 region.setLocationY(0.0); // 仅供参考 region.setWidthProportion(1); // 仅供参考 region.setHeightProportion(1); // 仅供参考 region.setZOrder(0); // 仅供参考 region.setAlpha(1); // 仅供参考 region.setRenderMode(MixedStreamConfig.MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN); regions[0] = region; layout.setRegions(regions); // 设置合流模版 mMixedStreamConfig.setLayout(layout); // 设置合流任务 ID String taskId = ""; // 开始合流转推 mRTCVideo.startPushMixedStreamToCDN(taskId, mMixedStreamConfig, mIMixedStreamObserver); } }
收到房间内连麦用户的音视频流发布通知后,调整用户视图以及合流布局。
private RtcRoomEventHandlerAdapter mIRtcRoomEventHandler = new RtcRoomEventHandlerAdapter() { @Override public void onUserPublishStream(String uid, MediaStreamType type) { if (type == RTC_MEDIA_STREAM_TYPE_VIDEO || type == RTC_MEDIA_STREAM_TYPE_BOTH) { // 添加连麦用户视图 TextureView renderView = new TextureView(Env.getApplicationContext()); VideoCanvas canvas = new VideoCanvas(); canvas.renderView = renderView; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas); } MixedStreamConfig.MixedStreamLayoutRegionConfig[] regions = new MixedStreamConfig.MixedStreamLayoutRegionConfig[2]; // 主播合流布局 MixedStreamConfig.MixedStreamLayoutRegionConfig region = new MixedStreamConfig.MixedStreamLayoutRegionConfig(); region.setUserID(mUserId); // 主播uid region.setRoomID(mRoomId); region.setIsLocalUser(true); region.setLocationX(0.0); //仅供参考 region.setLocationY(0.0); //仅供参考 region.setWidthProportion(0.5); //仅供参考 region.setHeightProportion(0.5); //仅供参考 region.setAlpha(1.0); //仅供参考 region.setZOrder(0); //仅供参考 region.setRenderMode(MixedStreamConfig.MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN); regions[0] = region; // 连麦用户合流布局 MixedStreamConfig.MixedStreamLayoutRegionConfig regionRemote = new MixedStreamConfig.MixedStreamLayoutRegionConfig(); regionRemote.setUserID(uid); // 连麦uid regionRemote.setRoomID(mRoomId); regionRemote.setIsLocalUser(false); regionRemote.setLocationX(0.0); //仅供参考 regionRemote.setLocationY(0.0); //仅供参考 regionRemote.setWidthProportion(0.5); //仅供参考 regionRemote.setHeightProportion(0.5); //仅供参考 regionRemote.setAlpha(1.0); //仅供参考 regionRemote.setZOrder(0); //仅供参考 regionRemote.setRenderMode(MixedStreamConfig.MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN); regions[1] = regionRemote; // 设置合流模版 MixedStreamConfig.MixedStreamLayoutConfig layout = new MixedStreamConfig.MixedStreamLayoutConfig(); layout.setRegions(regions); // 设置合流任务ID,ID 为上一步设置的合流任务 ID String taskId = ""; mMixedStreamConfig.setLayout(layout); // 更新合流 mRTCVideo.updatePushMixedStreamToCDN(taskId, mMixedStreamConfig); } @Override public void onUserUnpublishStream(String uid, MediaStreamType type, StreamRemoveReason reason) { if (type == RTC_MEDIA_STREAM_TYPE_VIDEO || type == RTC_MEDIA_STREAM_TYPE_BOTH) { // 移除连麦用户视图 VideoCanvas canvas = new VideoCanvas(); canvas.renderView = null; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas); } MixedStreamConfig.MixedStreamLayoutConfig layout = new MixedStreamConfig.MixedStreamLayoutConfig(); // 主播合流布局 MixedStreamConfig.MixedStreamLayoutRegionConfig[] regions = new MixedStreamConfig.MixedStreamLayoutRegionConfig[1]; MixedStreamConfig.MixedStreamLayoutRegionConfig region = new MixedStreamConfig.MixedStreamLayoutRegionConfig(); region.setUserID(mUserId); // 主播uid region.setRoomID(mRoomId); region.setIsLocalUser(true); region.setLocationX(0.0); // 仅供参考 region.setLocationY(0.0); // 仅供参考 region.setWidthProportion(1); // 仅供参考 region.setHeightProportion(1); // 仅供参考 region.setZOrder(0); // 仅供参考 region.setAlpha(1); // 仅供参考 region.setRenderMode(MixedStreamConfig.MixedStreamRenderMode.MIXED_STREAM_RENDER_MODE_HIDDEN); regions[0] = region; layout.setRegions(regions); // 设置合流模版 mMixedStreamConfig.setLayout(layout); // 设置合流任务ID,ID 为上一步设置的合流任务 ID String taskId = ""; // 更新合流 mRTCVideo.updatePushMixedStreamToCDN(taskId, mLiveTranscoding); } };
主播停止 RTC 服务端合流转推,离开 RTC 房间,开启推流引擎推流。
时序图
示例代码
停止 RTC 服务端合流,离开房间,移除连麦用户视图。
// 停止 RTC 服务端合流转推 mRTCVideo.stopPushStreamToCDN(taskId); // 离开 RTC 房间 mRTCRoom.leaveRoom(); // 移除连麦用户视图 VideoCanvas canvas = new VideoCanvas(); canvas.renderView = null; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas);
开启推流引擎推流。
// 开始推流引擎推流 mLivePusher.startPush(url);
主播停止直播,销毁 RTC 引擎和推流引擎。
时序图
示例代码
停止推流引擎推流,销毁 RTC 引擎和推流引擎。
// 停止推流引擎推流 mLivePusher.stopPush(); // 销毁推流引擎 mLivePusher.release(); mLivePusher = null;
停止 RTC 音视频采集,移除本地预览视图。
// 停止 RTC 视频采集 mRTCVideo.stopVideoCapture(); // 停止 RTC 音频采集 mRTCVideo.stopAudioCapture(); // 移除 RTC 本地预览视图 VideoCanvas videoCanvas = new VideoCanvas(); videoCanvas.renderView = null; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; mRTCVideo.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, videoCanvas);
销毁 RTC 房间,销毁 RTC 视频引擎。
// 销毁 RTC 房间 mRTCRoom.destroy(); mRTCRoom = null; // 销毁 RTC 视频引擎 RTCVideo.destroyRTCVideo(); mRTCVideo = null;
以下是观众端核心功能实现的时序图和参考接入代码。
观众端通过播放器拉流观看直播。
时序图
示例代码
创建和设置播放器。
// 创建播放器 VeLivePlayer mLivePlayer = new VideoLiveManager(Env.getApplicationContext()); // 设置播放器回调 mLivePlayer.setObserver(mLivePlayerObserver); // 播放器参数设置 VeLivePlayerConfiguration config = new VeLivePlayerConfiguration(); config.enableStatisticsCallback = true; config.enableLiveDNS = true; mLivePlayer.setConfig(config);
设置播放器视图,设置直播地址,开启拉流播放。
// 设置播放器视图 mLivePlayer.setSurfaceHolder(holder); // 设置播放地址 mLivePlayer.setPlayUrl(mPullUrl); // 开始播放 mLivePlayer.play();
连麦观众停止播放器拉流播放,通过 RTC 引擎进行连麦。
时序图
示例代码
停止播放,移除播放器视图。
// 停止播放 mLivePlayer.stop(); // 移除播放器视图 mLocalVideoContainer.removeView(mPlayerView);
创建 RTC 视频引擎,设置本地预览视图,设置视频编码参数。
// 初始化 ByteRTCVideo 对象 mRTCVideo = RTCVideo.createRTCVideo(Env.getApplicationContext(), mAppId, mRTCVideoEventHandler, null, null); // 设置本地视图 VideoCanvas videoCanvas = new VideoCanvas(); // renderView 为本地用户预览视图,需自行创建、布局并赋值给 VideoCanvas videoCanvas.renderView = renderView; videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN; mRTCVideo.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, videoCanvas); // 设置视频编码参数 VideoEncoderConfig config = new VideoEncoderConfig( mConfig.mVideoEncoderWidth, mConfig.mVideoEncoderHeight, mConfig.mVideoEncoderFps, mConfig.mVideoEncoderKBitrate * 1000); mRTCVideo.setVideoEncoderConfig(config);
开启 RTC 音视频采集。
// 开始视频采集 mRTCVideo.startVideoCapture(); // 开始音频采集 mRTCVideo.startAudioCapture();
创建 RTC 房间,设置用户信息,加入 RTC 房间。
// 创建 RTC 房间 mRTCRoom = mRTCVideo.createRTCRoom(roomId); mRTCRoom.setRTCRoomEventHandler(mIRtcRoomEventHandler); // 设置用户信息 UserInfo userInfo = new UserInfo(userId, null); RTCRoomConfig roomConfig = new RTCRoomConfig(ChannelProfile.CHANNEL_PROFILE_COMMUNICATION, true, true, true); // 加入房间,token 信息通过业务服务器申请 mRTCRoom.joinRoom(token, userInfo, roomConfig);
收到房间内连麦用户的音视频流发布通知后,调整用户视图。
private RtcRoomEventHandlerAdapter mIRtcRoomEventHandler = new RtcRoomEventHandlerAdapter() { @Override public void onUserPublishStream(String uid, MediaStreamType type) { if (type == RTC_MEDIA_STREAM_TYPE_VIDEO || type == RTC_MEDIA_STREAM_TYPE_BOTH) { // 添加连麦用户视图 TextureView renderView = new TextureView(Env.getApplicationContext()); VideoCanvas canvas = new VideoCanvas(); canvas.renderView = renderView; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas); } } @Override public void onUserUnpublishStream(String uid, MediaStreamType type, StreamRemoveReason reason) { if (type == RTC_MEDIA_STREAM_TYPE_VIDEO || type == RTC_MEDIA_STREAM_TYPE_BOTH) { // 移除连麦用户视图 VideoCanvas canvas = new VideoCanvas(); canvas.renderView = null; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas); } } };
观众连麦美时颜功能都是通过 RTC 引擎进行对接,使用方式请参考 美颜特效(CV)。
观众停止 RTC 引擎连麦,恢复拉流观看直播。
时序图
示例代码
离开 RTC 房间,停止 RTC 音视频采集,移除 RTC 本地和远端视图。
// 离开 RTC 房间 mRTCRoom.leaveRoom(); // 停止视频采集 mRTCVideo.stopVideoCapture(); // 停止音频采集 mRTCVideo.stopAudioCapture(); // 移除本地用户视图 VideoCanvas canvas = new VideoCanvas(null, RENDER_MODE_HIDDEN, mRoomId, mUserId, false); mRTCVideo.setLocalVideoCanvas(mUserId, StreamIndex.STREAM_INDEX_MAIN, canvas); // 移除连麦用户视图 VideoCanvas canvas = new VideoCanvas(); canvas.renderView = null; canvas.renderMode = RENDER_MODE_HIDDEN; RemoteStreamKey key = new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN); mRTCVideo.setRemoteVideoCanvas(key, canvas);
设置播放器视图,启动拉流播放。
// 设置播放器视图 mLivePlayer.setSurfaceHolder(holder); // 开始播放 mLivePlayer.play();
观众停止观看直播,销毁播放器。
时序图
示例代码
停止播放,移除播放器视图,销毁播放器。
// 停止播放 mLivePlayer.stop(); // 移除播放器视图 mLocalVideoContainer.removeView(mPlayerView); // 销毁播放器 mLivePlayer.destroy(); mLivePlayer = null;
销毁 RTC 房间,销毁 RTC 引擎。
// 销毁 RTC 房间 mRTCRoom.destroy(); mRTCRoom = null; // 销毁 RTC 引擎 RTCVideo.destroyRTCVideo(); mRTCVideo = null;