logo
Live Streaming
On this page

Custom Video Rendering

2024-05-21

Feature Overview

Custom video rendering refers to the SDK providing local preview and remote Play stream video frame data to the outside for users to render themselves.

When the following situations occur in your business, it is recommended to use the SDK's custom video rendering feature:

  • The App uses a cross-platform UI framework (e.g., Qt, which requires complex hierarchical interfaces for high-quality interactive experience) or a game engine (e.g., Unity, Unreal Engine, Cocos, etc.).
  • The App needs to obtain video frame data Captured or Played by the SDK for special processing.

Example Source Code Download

Please refer to Download Example Source Code to get the source code.

For related source code, please check the files in the "ZegoExpressExample/Examples/AdvancedVideoProcessing/CustomVideoRender" directory.

Prerequisites

Before implementing custom video rendering, ensure that:

Usage Steps

The usage flow for custom video rendering is as follows: The API call sequence diagram is as follows:

1 Set up custom video rendering configuration

Create a ZegoCustomVideoRenderConfig object and configure parameters.

The bufferType parameter is an enumeration ZegoVideoBufferType, which can specify the custom video rendering video frame data type required by the developer. Currently, only the raw data type ZegoVideoBufferTypeRawData, CVPixelBuffer type ZegoVideoBufferTypeCVPixelBuffer, and ZegoVideoBufferTypeEncodedData are supported. Setting other unsupported enumeration values will not callback video frame data.

The frameFormatSeries parameter is an enumeration ZegoVideoFrameFormatSeries, which can specify the custom video rendering video frame data format required by the developer. This parameter can only specify the RGB or YUV color space category. The specific data format varies between platforms and is subject to the parameters in the callback.

Warning

If the data format in the callback does not meet your expectations, please contact ZEGO technical support for assistance.

  • ZegoVideoFrameFormatSeriesRGB: Returns BGRA.
  • ZegoVideoFrameFormatSeriesYUV: Returns I420.

enableEngineRender indicates whether the SDK internally renders while also performing custom video rendering. When set to "NO", the engine will not Render on the Views set by the preview interface startPreview and the Play stream interface startPlayingStream.

  • Interface prototype

    // Custom video rendering configuration
    @interface ZegoCustomVideoRenderConfig : NSObject
    
    // Custom video rendering video frame data type
    @property (nonatomic, assign) ZegoVideoBufferType bufferType;
    
    // Custom video rendering video frame data format
    @property (nonatomic, assign) ZegoVideoFrameFormatSeries frameFormatSeries;
    
    // Whether the engine also renders while custom video rendering is enabled
    @property (nonatomic, assign) BOOL enableEngineRender;
    
    // Start or stop custom video rendering
    //
    // Must be set before the engine starts, i.e., before calling [startPreview], [startPublishing], [startPlayingStream]; and the configuration can only be modified after the engine stops
    // When developers enable custom rendering, they can call [setCustomVideoRenderHandler] to set up receiving local and remote video frame data for custom rendering
    //
    // @param enable Whether to enable
    // @param config Custom rendering configuration
    - (void)enableCustomVideoRender:(BOOL)enable config:(nullable ZegoCustomVideoRenderConfig *)config;

Call the enableCustomVideoRender interface to enable custom video rendering.

After creating a ZegoCustomVideoRenderConfig object and configuring parameters, call the enableCustomVideoRender interface to enable custom video rendering.

  • Call example

    ZegoCustomVideoRenderConfig *renderConfig = [[ZegoCustomVideoRenderConfig alloc] init];
    // Select CVPixelBuffer type video frame data
    renderConfig.bufferType = ZegoVideoBufferTypeCVPixelBuffer;
    // Select RGB color series data format
    renderConfig.frameFormatSeries = ZegoVideoFrameFormatSeriesRGB;
    // Specify that the engine also renders while custom video rendering is enabled
    renderConfig.enableEngineRender = YES;
    
    [[ZegoExpressEngine sharedEngine] enableCustomVideoRender:YES config:renderConfig];

2 Set up the custom video renderer object and implement callback methods

Call the setCustomVideoRenderHandler interface to set up custom video rendering callbacks.

  • Interface prototype

    // Set custom video rendering callback
    //
    // Custom video rendering requires developers to Render video data onto UI components themselves. This feature is generally used by developers who use third-party beauty features or third-party rendering frameworks.
    // When developers use the advanced features of the SDK's custom video rendering, they need to call this interface to set up a callback object for throwing video data to developers.
    // When developers call start preview [startPreview], start Publish stream [startPublishingStream], start Play stream [startPlayingStream], the callback method for throwing video data to developers will be triggered.
    // Developers can Render video frames based on the SDK's video data callback.
    // Custom video rendering can be used simultaneously with custom video Capture.
    //
    // @param handler Custom video rendering callback object
    - (void)setCustomVideoRenderHandler:(nullable id<ZegoCustomVideoRenderHandler>)handler;

    The custom video rendering callback protocol ZegoCustomVideoRenderHandler is defined as follows:

    @protocol ZegoCustomVideoRenderHandler <NSObject>
    
    @optional
    
    // Local preview video frame raw data callback
    //
    // @param data Raw data of the video frame (e.g., for RGBA, only consider data[0]; for I420, consider data[0,1,2])
    // @param dataLength Length of the data (e.g., for RGBA, only consider dataLength[0]; for I420, consider dataLength[0,1,2])
    // @param param Video frame parameters
    // @param flipMode Video frame flip mode
    // @param channel Publish stream channel
    - (void)onCapturedVideoFrameRawData:(unsigned char * _Nonnull * _Nonnull)data dataLength:(unsigned int *)dataLength param:(ZegoVideoFrameParam *)param flipMode:(ZegoVideoFlipMode)flipMode channel:(ZegoPublishChannel)channel;
    
    // Remote Play stream video frame raw data callback, distinguishes different streams by streamID
    //
    // @param data Raw data of the video frame (e.g., for RGBA, only consider data[0]; for I420, consider data[0,1,2])
    // @param dataLength Length of the data (e.g., for RGBA, only consider dataLength[0]; for I420, consider dataLength[0,1,2])
    // @param param Video frame parameters
    // @param streamID Stream ID of the Play stream
    - (void)onRemoteVideoFrameRawData:(unsigned char * _Nonnull * _Nonnull)data dataLength:(unsigned int *)dataLength param:(ZegoVideoFrameParam *)param streamID:(NSString *)streamID;
    
    // Local preview video frame CVPixelBuffer data callback, distinguishes different streams by streamID
    //
    // @param buffer Video frame data encapsulated as CVPixelBuffer
    // @param param Video frame parameters
    // @param flipMode Video frame flip mode
    // @param channel Publish stream channel
    - (void)onCapturedVideoFrameCVPixelBuffer:(CVPixelBufferRef)buffer param:(ZegoVideoFrameParam *)param flipMode:(ZegoVideoFlipMode)flipMode channel:(ZegoPublishChannel)channel;
    
    // Remote Play stream video frame CVPixelBuffer data callback, distinguishes different streams by streamID
    //
    // @param buffer Video frame data encapsulated as CVPixelBuffer
    // @param param Video frame parameters
    // @param streamID Stream ID of the Play stream
    - (void)onRemoteVideoFrameCVPixelBuffer:(CVPixelBufferRef)buffer param:(ZegoVideoFrameParam *)param streamID:(NSString *)streamID;
    
    @end
  • Call example

    @interface ViewController () <ZegoEventHandler, ZegoCustomVideoRenderHandler>
    
    ......
    
    // Set custom video rendering callback
    
    // Set self as the custom video rendering callback object
    [[ZegoExpressEngine sharedEngine] setCustomVideoRenderHandler:self];
    
    // Implement local preview Capture video frame callback method
    // When `ZegoCustomVideoRenderConfig.bufferType` is set to raw data `ZegoVideoBufferTypeRawData`, the raw data of the local preview Captured video frames will be called back from this method
    - (void)onCapturedVideoFrameRawData:(unsigned char * _Nonnull *)data dataLength:(unsigned int *)dataLength param:(ZegoVideoFrameParam *)param flipMode:(ZegoVideoFlipMode)flipMode {
        NSLog(@"raw data video frame callback. format:%d, width:%f, height:%f, isNeedFlip:%d", (int)param.format, param.size.width, param.size.height, (int)flipMode);
    }
    
    // When `ZegoCustomVideoRenderConfig.bufferType` is set to `ZegoVideoBufferTypeCVPixelBuffer`, the CVPixelBuffer video frames Captured from local preview will be called back from this method
    - (void)onCapturedVideoFrameCVPixelBuffer:(CVPixelBufferRef)buffer param:(ZegoVideoFrameParam *)param flipMode:(ZegoVideoFlipMode)flipMode {
        NSLog(@"pixel buffer video frame callback. format:%d, width:%f, height:%f, isNeedFlip:%d", (int)param.format, param.size.width, param.size.height, (int)flipMode);
    }
    
    // Implement remote Play stream video frame callback method
    // When `ZegoCustomVideoRenderConfig.bufferType` is set to raw data `ZegoVideoBufferTypeRawData`, the raw data of remote Play stream video frames will be called back from this method, identified by streamID which stream's data is being called back
    - (void)onRemoteVideoFrameRawData:(unsigned char * _Nonnull * _Nonnull)data dataLength:(unsigned int *)dataLength param:(ZegoVideoFrameParam *)param streamID:(NSString *)streamID {
        NSLog(@"raw data video frame callback. format:%d, width:%f, height:%f", (int)param.format, param.size.width, param.size.height);
    }
    
    // When `ZegoCustomVideoRenderConfig.bufferType` is set to `ZegoVideoBufferTypeCVPixelBuffer`, the CVPixelBuffer video frames from remote Play stream will be called back from this method, identified by streamID which stream's data is being called back
    - (void)onRemoteVideoFrameCVPixelBuffer:(CVPixelBufferRef)buffer param:(ZegoVideoFrameParam *)param streamID:(NSString *)streamID {
        NSLog(@"pixel buffer video frame callback. format:%d, width:%f, height:%f", (int)param.format, param.size.width, param.size.height);
    }
Note

The flipMode parameter in the local preview Capture video frame callback method is related to mirroring. It notifies developers whether they need to flip the video frame themselves to make the picture conform to the description of the ZegoVideoMirrorMode enumeration value set in setVideoMirrorMode.

The param parameter (ZegoVideoFrameParam object) in the above callback methods describes some parameters of the video frame, defined as follows:

// Video frame parameter object
//
// Including video frame format, width, height, etc.
@interface ZegoVideoFrameParam : NSObject

// Format of the video frame
@property (nonatomic, assign) ZegoVideoFrameFormat format;

// Bytes per row for each plane (this parameter is an int array with a length of 4; for RGBA, only consider strides[0]; for I420, consider strides[0,1,2])
@property (nonatomic, assign) int *strides;

// Image size of the video frame
@property (nonatomic, assign) CGSize size;

@end

Among them, "format" identifies the specific data format of the video frame, "strides" is an array describing the number of bytes per row for each plane, and "size" describes the image size of the video frame. The relationship between "strides" and the image is shown in the figure:

3 Custom video rendering video frame data callback

Publish stream preview rendering

The publisher needs to call the start preview interface first to receive custom video rendering video frame data callbacks. If the "enableEngineRender" parameter of ZegoCustomVideoRenderConfig custom video rendering configuration is "NO", the canvas parameter for starting preview can be empty. After starting preview, you can start Publish stream.

// If you need to render internally while custom video rendering, set the `enableEngineRender` parameter of `ZegoCustomVideoRenderConfig` to `YES`, then pass in the View for internal rendering during preview
ZegoCanvas *previewCanvas = [ZegoCanvas canvasWithView:self.previewView];
[[ZegoExpressEngine sharedEngine] startPreview:previewCanvas];

// If you only need custom video rendering, set the `enableEngineRender` parameter of `ZegoCustomVideoRenderConfig` to `NO`, and the `canvas` parameter can be empty, but you must still call this interface, otherwise custom video rendering will not callback preview video frame data
[[ZegoExpressEngine sharedEngine] startPreview:nil];

// After starting preview, custom video rendering preview video frame data callbacks will be received at this time

// Start Publish stream
[[ZegoExpressEngine sharedEngine] startPublishing:self.streamID];

Play stream rendering

After successful Publish stream, you can call enableEngineRender in ZegoCustomVideoRenderConfig to set Play stream rendering parameters. Then call startPlayingStream to Play stream.

At this point, the App successfully obtains the video frame data callbacked by the SDK for actual rendering operations or deep processing operations.

// If you need to render internally while custom video rendering, set the `enableEngineRender` parameter of `ZegoCustomVideoRenderConfig` to `YES`, then pass in the View for internal rendering when Playing stream
ZegoCanvas *playCanvas = [ZegoCanvas canvasWithView:self.playView];
[[ZegoExpressEngine sharedEngine] startPlayingStream:self.streamID canvas:playCanvas];

// If you only need custom video rendering, set the `enableEngineRender` parameter of `ZegoCustomVideoRenderConfig` to `NO`, and the `canvas` parameter can be empty
[[ZegoExpressEngine sharedEngine] startPlayingStream:self.streamID canvas:nil];

// After starting Play stream, custom video rendering video frame data callbacks for this Played stream will be received at this time

FAQ

  1. Custom video rendering is divided into local Capture preview custom video rendering and remote Play stream custom video rendering. What are their respective functions? In what scenarios are they used?

    Local Capture preview custom video rendering allows the host to see additional rendering effects, adding effects and other features on top of the original video picture; remote Play stream custom video rendering allows the audience to see different rendered pictures, adding effects on top of the Play stream picture according to audience preferences.

  2. If custom video rendering is configured with ZegoCustomVideoRenderConfig and the enableEngineRender parameter is "NO", what should be filled in the "canvas" parameter of the preview interface startPreview and the Play stream interface startPlayingStream?

    When enableEngineRender is "NO", the engine does not Render, so the canvas parameter of the preview and Play stream interfaces can be set to empty "nil".

  3. When using custom video rendering during Publish stream, will the processed preview video data buffer only be displayed locally? Will it be included in the Publish stream?

    It will only be displayed locally and will not affect the video data that is Published out.

  4. What is the width and height of the video frames for local Capture preview custom video rendering?

    The width and height of the video frames for local Capture preview custom video rendering will be returned in the param parameter in the callback. Their values are the same as the resolution set by setVideoConfig.

  5. What is the video frame data format for custom video rendering? Is YUV supported?

    It depends on the selected rendering data format series ZegoVideoFrameFormatSeries and the data returned directly after soft decoding/hardware decoding, i.e., the format (ZegoVideoFrameFormat) parameter in the param parameter of the video frame callback method.

    Description of video frame data format ZegoVideoFrameFormat:

Video frame data format enumeration valueDescription
ZegoVideoFrameFormatI420YUV420P, one set of YUV 12 bits, Y, U, V three planes, four Y share one set of UV.
ZegoVideoFrameFormatNV12YUV420SP, one set of YUV 12 bits, Y, UV two planes, UV plane data is arranged in U first then V order, four Y share one set of UV.
ZegoVideoFrameFormatNV21YUV420SP, one set of YUV 12 bits, Y, UV two planes, UV plane data is arranged in V first then U order, four Y share one set of UV.
ZegoVideoFrameFormatBGRA32BGRA32.
ZegoVideoFrameFormatRGBA32RGBA32.
ZegoVideoFrameFormatARGB32ARGB32.
ZegoVideoFrameFormatABGR32ABGR32.
ZegoVideoFrameFormatI422YUV422P, one set of YUV 16 bits, two Y share one set of UV.
ZegoVideoFrameFormatBGR24BGR24.
ZegoVideoFrameFormatRGB24RGB24.
  1. What is the callback frequency per second for custom video rendering? What should be noted?

    The callback frequency for local Capture preview custom video rendering is generally the same as the frame rate set during Publish stream. However, if flow control is enabled and the control attributes include frame rate, the callback frequency for local Capture preview custom video rendering will change accordingly. The callback frequency for remote Play stream custom video rendering will also change with the received video data frame rate. For example, if the Publish stream side enables flow control causing frame rate changes, Play stream side network lag, or Play stream side network recovery causing the SDK to start catching up frames, all will affect the callback frequency of Play stream custom video rendering.

  2. How does custom video rendering get the first frame data?

    The data returned for the first time in the custom video rendering callback ZegoCustomVideoRenderHandler is the first frame data.

  3. Is it supported to do local Capture preview rendering ourselves and let ZEGO SDK do Play stream rendering?

    Yes, when custom video rendering is configured with ZegoCustomVideoRenderConfig, setting the enableEngineRender parameter to "YES" will not only callback video frame data from the custom video renderer, but also the SDK will internally Render on the Views in the canvas of the preview interface startPreview and the Play stream interface startPlayingStream.

  4. When rendering the preview view, why can't I receive video data callbacks?

    If you need to render the preview view, you need to start preview startPreview before Publish stream, otherwise you will not receive video data callbacks.

  5. For local preview custom video rendering, why is the video frame data from the phone's front camera not horizontally mirrored and flipped by default?

    The flipping of the custom video rendering video frame picture needs to be implemented by the developer themselves. You can know whether the frame needs to be flipped through the flipMode in the video frame data callback interface.

Previous

Custom Video Capture

Next

Custom Video Preprocessing