当你使用 RTC 实现实时音视频通信时,RTC 默认使用内部的渲染模块进行音视频渲染。然而在一些场景下,你可能会发现内部渲染模块无法满足需求,比如:
你已经集成了 3.25 及以上版本的 RTC SDK,实现了基本的音视频通话。
将 RTC SDK 在本地采集的视频图像或远端用户的视频图像通过自定义的渲染模块进行渲染。
说明:不同平台的实现步骤相同,但接口名称、参数名称可能略有差异。以下指南以 Android RTC SDK 为例,参考对应平台的 API 文档获取更多信息。

注意:由于硬件差异,通过 IVideoSink.onFrame 收到的视频帧可能会呈一定角度。你可以调用 RTCVideoFrame.getRotation 获取该角度,并在自定义渲染器中进行相应处理。
/**通过创建 ByteRTCVideoSinkDelegate 实例,添加自定义渲染逻辑,构建自定义渲染器。*/
@interface CustomVideoRenderView : UIView <ByteRTCVideoSinkDelegate>
@end
@implementation CustomVideoRenderView
/**
* 视频帧回调
* @param pixelBuffer 视频的 PixelBuffer
* @param rotation 视频旋转角度,参看 ByteRTCVideoRotation
* @param contentType 视频内部类型 参看 ByteRTCVideoContentType
* @param extendedData 视频解码后获得的附加数据
*/
- (void)renderPixelBuffer:(CVPixelBufferRef _Nonnull)pixelBuffer
rotation:(ByteRTCVideoRotation)rotation
contentType:(ByteRTCVideoContentType)contentType
extendedData:(NSData * _Nullable)extendedData {
// 在此进行自定义视频渲染
CFRetain(pixelBuffer);
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = [UIImage imageWithCIImage:[CIImage imageWithCVImageBuffer:pixelBuffer]];
imageView.contentMode = UIViewContentModeScaleAspectFill;
switch (rotation) {
case VideoRotation_0:
imageView.transform = CGAffineTransformMakeRotation(0);
break;
case VideoRotation_90:
imageView.transform = CGAffineTransformMakeRotation(M_PI_2);
break;
case VideoRotation_180:
imageView.transform = CGAffineTransformMakeRotation(M_PI);
break;
case VideoRotation_270:
imageView.transform = CGAffineTransformMakeRotation(M_PI_2 + M_PI);
break;
default:
break;
}
CFRelease(pixelBuffer);
});
}
@endobjectivec
/**通过实现 IVideoSink,添加自定义渲染逻辑,构建自定义渲染器*/
public class CustomRenderView implements IVideoSink {
/**
* 视频帧回调
* @param videoFrame 视频帧结构类,参看 VideoFrame
*/
@Override
public void onFrame(VideoFrame videoFrame) {
// 在此进行自定义视频渲染
// 获取视频帧时间戳,单位:微秒
long timeStampUs = videoFrame.getTimeStampUs();
// 获取视频帧宽度,单位:px
int width = videoFrame.getWidth();
// 获取视频帧高度,单位:px
int height = videoFrame.getHeight();
// 获取视频帧旋转角度
VideoRotation rotation = videoFrame.getRotation();
}
}java获取到视频流信息以后,将视频流绑定到自定义渲染器。通过传入参数 requiredFormat 指定视频数据的像素格式。
调用 setLocalVideoSink 为本地媒体流指定自定义渲染器。
在开始采集之后,视频帧将通过 renderPixelBuffer/IVideoSink.onFrame 传递到自渲染模块。
[self.rtcVideo startVideoCapture];
CustomVideoRenderView *renderView = [[CustomVideoRenderView alloc] init];
[self.rtcVideo setLocalVideoSink:ByteRTCStreamIndexMain withSink:renderView withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];objectivec
mRTCVideo.startVideoCapture();
IVideoSink videoSink = new CustomRenderView(this);
mRTCVideo.setLocalVideoSink(StreamIndex.STREAM_INDEX_MAIN, videoSink, IVideoSink.PixelFormat.I420);java调用 setRemoteVideoSink 为远端媒体流指定自定义渲染器。
绑定自定义渲染器所需的媒体流参数可以从 onUserPublishStream 回调获取。
用户进入房间,并订阅了远端视频流,视频帧到达后将通过 renderPixelBuffer/IVideoSink.onFrame 传递到自渲染模块。
- (void)rtcRoom:(ByteRTCRoom *)rtcRoom onUserPublishStream:(NSString *)userId type:(ByteRTCMediaStreamType)type {
if (type == ByteRTCMediaStreamTypeVideo || type == ByteRTCMediaStreamTypeBoth) {
ByteRTCRemoteStreamKey *streamKey = [[ByteRTCRemoteStreamKey alloc] init];
streamKey.userId = userId;
streamKey.streamIndex = ByteRTCStreamIndexMain;
streamKey.roomId = self.roomID;
CustomVideoRenderView *renderView = [[CustomVideoRenderView alloc] init];
[self.rtcVideo setRemoteVideoSink:streamKey withSink:renderView withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];
}
}objectivec
private IRTCRoomEventHandler mItcRoomEventHandler = new IRTCRoomEventHandler() {
@Override
public void onUserPublishStream(String uid, MediaStreamType mediaStreamType) {
if (type != MediaStreamType.RTC_MEDIA_STREAM_TYPE_AUDIO) {
runOnUiThread(() -> setRemoteView(new RemoteStreamKey(mRoomId, uid, StreamIndex.STREAM_INDEX_MAIN)));
}
}
};
private void setRemoteView(RemoteStreamKey remoteStreamKey) {
IVideoSink videoSink = new CustomRenderView(this);
mRTCVideo.setRemoteVideoSink(remoteStreamKey, videoSink, IVideoSink.PixelFormat.I420);
}java调用 setPublicStreamVideoSink 为公共流指定自定义渲染器。
用户订阅了公共流,视频帧到达后,将通过 renderPixelBuffer/IVideoSink.onFrame 传递到自渲染模块。
更多关于公共流的信息详见发布和订阅公共流。
/**
* 设置公共流视频渲染
* @param streamId 公共流ID
*/
- (void)setPublicStreamCustomVideoRender:(NSString *)streamId {
CustomVideoRenderView *renderView = [[CustomVideoRenderView alloc] init];
[self.rtcVideo setPublicStreamVideoSink:streamId withSink:renderView withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];
}objectivec
/**
* 设置公共流视频渲染
* @param streamId 公共流ID
*/
private void setPublicStreamCustomVideoRender(String streamId) {
IVideoSink videoSink = new CustomRenderView(this);
mRTCVideo.setPublicStreamVideoSink(streamId, videoSink, IVideoSink.PixelFormat.I420);
}java调用步骤2 中提到的相应接口,将 videoSink 设置为 null。解绑之后,视频帧将通过 SDK 内部渲染器进行渲染。
/**
* 解绑本地自定义渲染
*/
- (void)unBindLocalCustomVideoRender {
[self.rtcVideo setLocalVideoSink:ByteRTCStreamIndexMain withSink:nil withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];
}
/**
* 解绑远端视频自定义渲染
* @param streamKey 远端视频信息
*/
- (void)unBindRemoteCustomVideoRender:(ByteRTCRemoteStreamKey *)streamKey {
[self.rtcVideo setRemoteVideoSink:streamKey withSink:nil withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];
}
/**
* 解绑公共流视频自定义渲染
* @param streamId 公共流ID
*/
- (void)unBindPublicStreamCustomVideoRender:(NSString *)streamId {
[self.rtcVideo setPublicStreamVideoSink:streamId withSink:nil withPixelFormat:ByteRTCVideoSinkPixelFormatNV12];
}objectivec
/**
* 解绑本地自定义渲染
*/
private void unBindLocalCustomVideoRender() {
mRTCVideo.setLocalVideoSink(StreamIndex.STREAM_INDEX_MAIN, null, IVideoSink.PixelFormat.I420);
}
/**
* 解绑远端视频自定义渲染
* @param streamKey 远端视频信息
*/
private void unBindRemoteCustomVideoRender(RemoteStreamKey streamKey) {
mRTCVideo.setRemoteVideoSink(streamKey, null, IVideoSink.PixelFormat.I420);
}
/**
* 解绑公共流视频自定义渲染
* @param streamId 公共流ID
*/
private void unBindPublicStreamCustomVideoRender(String streamId) {
mRTCVideo.setPublicStreamVideoSink(streamId, null, IVideoSink.PixelFormat.I420);
}java