Custom video rendering
Integrate a custom video rendering module with the ARTC SDK to meet specific requirements for color accuracy and frame rate control.
Feature description
Alibaba Real-Time Communication (ARTC) includes a built-in, market-proven video rendering module that provides a stable and efficient video playback experience.
For scenarios that require a self-developed rendering module or specific color accuracy and frame rate control, the ARTC SDK provides flexible interfaces to integrate a custom video rendering module.
Prerequisites
Before you configure video settings, make sure the following prerequisites are met:
-
You have created an ApsaraVideo Real-time Communication application with a valid Alibaba Cloud account. For more information, see Create an application. You must also obtain an App ID and an App Key from the Application Management console.
-
You have integrated the ARTC SDK into your project and implemented basic audio and video communication features. For more information about SDK integration, see Download and integrate the SDK. To learn how to implement audio and video calls, see Implement an audio and video call.
Technical principles
Remote video rendering
Buffer-based local video rendering
Texture ID-based local video rendering
Implementation
Android implementation
1. Register a observer
First, call the registerVideoSampleObserver method of AliRtcEngine to register a custom processing observer. Then, implement the methods of the AliRtcEngine.AliRtcVideoObserver abstract class. This lets you apply retouching throughout the AliRtcEngine processing pipeline.
Example of registering the AliRtcVideoObserver:
mAliRtcEngine.registerVideoSampleObserver(mAliRtcVideoObserver);Implement the abstract interface as needed. ARTC supports custom processing at three stages: after local video capture, before local encoding, and after remote video decoding.
public enum AliRtcVideoObserPosition{
/*! Captured video data. Corresponds to the onLocalVideoSample callback. */
AliRtcPositionPostCapture(1),
/*! Rendered video data. Corresponds to the onRemoteVideoSample callback. */
AliRtcPositionPreRender(2),
/*! Video data before encoding. Corresponds to the onPreEncodeVideoSample callback. */
AliRtcPositionPreEncoder(4);
}public static abstract class AliRtcVideoObserver {
// Callback for subscribed local video capture data.
public boolean onLocalVideoSample(AliRtcVideoSourceType sourceType, AliRtcVideoSample videoSample){
// TODO: If retouching and effects are needed during local video capture, process them here.
}
// Callback for subscribed remote video data.
public boolean onRemoteVideoSample(String callId,AliRtcVideoSourceType sourceType, AliRtcVideoSample videoSample){
// TODO: If retouching and effects are needed before displaying the frame pulled from the remote end, process them here.
}
// Callback for local video data before encoding.
public boolean onPreEncodeVideoSample(AliRtcVideoSourceType sourceType, AliRtcVideoSample videoRawData){
// TODO: If retouching and effects are needed before encoding the local video frame, process them here.
}
...
public int onGetObservedFramePosition(){
// TODO: Specify the timing for the callback based on your business needs. Use the AliRtcVideoObserPosition values defined above.
// For example, to perform custom processing before capture and pre-rendering, define the following value.
// return AliRtcVideoObserPosition.AliRtcPositionPostCapture.getValue() | AliRtcVideoObserPosition.AliRtcPositionPreRender.getValue();
}
}To use the Queen SDK, you must process the video stream in the corresponding interface.
Unregister the listener
When custom processing is no longer needed, unregister the listener to reduce calls from the SDK layer, improve processing efficiency, and prevent memory leaks.
// TODO: Perform resource release tasks.
mAliRtcEngine.unRegisterVideoSampleObserver()2. Implement custom processing
The preceding callback interfaces can be implemented by calling the handleBeautyProcess method.
private boolean handleBeautyProcess(AliRtcEngine.AliRtcVideoSample videoSample) {
if (!isAdvanceBeautifyOn) { // Specifies whether to enable retouching.
return false;
}
if (mQueenBeautyImp == null) {
mQueenBeautyImp = new QueenBeautyImp(getContext(), videoSample.glContex);
}
return mQueenBeautyImp.onBeautyProcess(videoSample);
}QueenBeautyImp is a simple wrapper class for QueenBeautyEffector.
Create a retouching processor
Create the processor as follows:
// Add a synchronization lock to prevent multiple creations.
private synchronized void ensureQueenEngine(Context context, long glShareContext) {
if (mQueenBeautyEffector == null) {
try {
QueenConfig queenConfig = new QueenConfig();
bool isNeedCreateNewThread = glShareContext != 0; // Specifies whether to create a separate thread. We recommend that you set this to true for texture mode and false for buffer mode.
bool isNeedCreateNewGLContext = true; // Specifies whether to create a GL context. For texture mode, keep this value consistent with isNeedCreateNewThread. For buffer mode, we recommend that you set this to true.
queenConfig.withNewGlThread = isNeedCreateNewThread;
queenConfig.withContext = isNeedCreateNewGLContext;
queenConfig.shareGlContext = glShareContext;
// queenConfig.enableDebugLog = true; // Debugging feature: Enable logs.
mQueenBeautyEffector = new QueenBeautyEffector(context, queenConfig);
// Advanced retouching debugging features.
// mQueenBeautyEffector.getEngine().enableFacePointDebug(true); // Enable facial landmark debugging.
// mQueenBeautyEffector.getEngine().enableFaceDetectGPUMode(false); // Disable the GPU mode for facial recognition.
} catch (Exception ex) {
ex.printStackTrace();
}
}
}Set retouching parameters
private void updateQueenEngineParams() {
mQueenBeautyEffector.onUpdateParams(() -> {
QueenEngine queenEngine = mQueenBeautyEffector.getEngine();
// Skin smoothing and sharpening share one switch.
queenEngine.enableBeautyType(BeautyFilterType.kSkinBuffing, true); // Skin smoothing switch.
queenEngine.setBeautyParam(com.aliyun.android.libqueen.models.BeautyParams.kBPSkinBuffing, 0.85f); // Skin smoothing. Valid values: [0,1].
queenEngine.setBeautyParam(com.aliyun.android.libqueen.models.BeautyParams.kBPSkinSharpen, 0.2f); // Sharpening. Valid values: [0,1].
// Skin whitening and rosy cheeks share one switch.
queenEngine.enableBeautyType(BeautyFilterType.kSkinWhiting, true); // Skin whitening switch.
queenEngine.setBeautyParam(BeautyParams.kBPSkinWhitening, 0.5f); // Skin whitening. Valid values: [0,1].
// Eye enlarging and face slimming.
queenEngine.enableBeautyType(BeautyFilterType.kFaceShape, true);
queenEngine.updateFaceShape(FaceShapeType.typeBigEye,1.0f);
queenEngine.updateFaceShape(FaceShapeType.typeCutFace,1.0f);
});
}Process frames
Process the data as a texture or a buffer based on the callback data type.
// Add a synchronization lock to prevent mQueenBeautyEffector from being destroyed in a multi-threaded environment.
public synchronized boolean onBeautyProcess(AliRtcEngine.AliRtcVideoSample videoSample) {
// Update retouching parameters.
updateQueenEngineParams();
boolean result = false;
if (videoSample.glContex != 0 && videoSample.textureid > 0) {
// Texture mode.
result = onProcessBeautyTexture(videoSample);
} else {
// Buffer mode.
result = onProcessBeautyBuffer(videoSample);
}
return result;
}Process texture callbacks:
private boolean onProcessBeautyTexture(AliRtcEngine.AliRtcVideoSample videoSample) {
boolean result = false;
boolean isOesTexture = videoSample.format == AliRtcEngine.AliRtcVideoFormat.AliRtcVideoFormatTextureOES;
// The texture captured by an Android camera is by default a landscape image rotated by 270 degrees. The Queen SDK automatically swaps the width and height.
// However, the width and height in the videoSample callback are already corrected by the ARTC SDK. Therefore, you must manually swap the width and height here.
int w = isOesTexture ? videoSample.height : videoSample.width;
int h = isOesTexture ? videoSample.width : videoSample.height;
int newTextId = mQueenBeautyEffector.onProcessTexture((int)videoSample.textureid, isOesTexture, videoSample.matrix, w, h, 270, 0, 0);
if (newTextId != videoSample.textureid) { // 0 indicates QueenResult.QUEEN_OK.
// Modify the texture ID.
videoSample.textureid = newTextId;
videoSample.format = AliRtcEngine.AliRtcVideoFormat.AliRtcVideoFormatTexture2D;
result = true;
}
return result;
}Process buffer callbacks:
private boolean onProcessBeautyBuffer(AliRtcEngine.AliRtcVideoSample videoSample) {
boolean result = false;
int queenResult = mQueenBeautyEffector.onProcessDataBuf(videoSample.data, videoSample.data, ImageFormat.I420, videoSample.width, videoSample.height, 0, 0, 0, 0);
if (queenResult == 0) {
result = true;
}
return result;
}3. Exit and destroy resources
When you leave a meeting or exit the video call interface, destroy the custom processing engine promptly.
// Add a synchronization lock to prevent mQueenBeautyEffector from being destroyed in a multi-threaded environment.
public synchronized void release() {
if (mQueenBeautyEffector != null) {
mQueenBeautyEffector.onReleaseEngine();
mQueenBeautyEffector = null;
}
}