This topic explains how to use Alibaba Cloud's proprietary Queen SDK with ApsaraVideo Real-Time Communication (ARTC) to implement retouching and special effects.
Queen SDK
Queen SDK provides facial retouching, filters, stickers, and a variety of personalized services, such as gesture recognition and AR interaction. It offers excellent cross-platform compatibility, ensuring a consistent development experience across multiple devices. The SDK is suitable for live streaming, video conferencing, and short video production, enhancing the user experience.
For more details on specific features, see Queen SDK overview.
Integration
ARTC provides a comprehensive suite of APIs to support third-party video processing. The Queen SDK and the ARTC SDK are developed and iterated independently, allowing them to be upgraded separately for quick and flexible integration.
The integration process involves two steps:
Configure the custom video processing interface of ARTC.
Call the functions for retouching and special effects.
This process lets you efficiently integrate the retouching features.
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;
}
}