すべてのプロダクト
Search
ドキュメントセンター

ApsaraVideo Live:カスタムビデオレンダリング

最終更新日:Apr 10, 2026

このトピックでは、カスタムビデオレンダリングを実装する方法について説明します。

機能説明

Alibaba Real-Time Communication (ARTC) には、市場で実績のあるビデオレンダリングモジュールが含まれています。組み込みモジュールは、安定した効率的なビデオ再生体験を提供します。

成熟した自社開発のレンダリングモジュールがある場合や、色精度やフレームレート制御に特定の要件がある場合、ARTC SDK は柔軟なインターフェイスを提供します。これらのインターフェイスを使用すると、カスタムビデオレンダリングモジュールを統合して、ビジネスニーズを満たすことができます。

前提条件

ビデオ設定を構成する前に、次の前提条件が満たされていることを確認してください。

  • 有効な Alibaba Cloud アカウントで ApsaraVideo Real-time Communication アプリケーションを作成済みであること。詳細については、「アプリケーションの作成」をご参照ください。また、アプリケーション管理コンソールから App ID と App Key を取得する必要があります。

  • ARTC SDK をプロジェクトに統合し、基本的な音声・映像コミュニケーション機能を実装済みであること。SDK の統合に関する詳細については、「SDK のダウンロードと統合」をご参照ください。音声・映像通話の実装方法については、「音声・映像通話の実装」をご参照ください。

仕組み

リモートビデオレンダリング

バッファーベースのローカルビデオレンダリング

テクスチャ ID ベースのローカルビデオレンダリング

実装

Android の実装

1. オブザーバーの登録

まず、AliRtcEngineregisterVideoSampleObserver メソッドを呼び出して、カスタム処理オブザーバーを登録します。次に、AliRtcEngine.AliRtcVideoObserver 抽象クラスのメソッドを実装します。これにより、AliRtcEngine 処理パイプライン全体でレタッチを適用できます。

AliRtcVideoObserver の登録例:

mAliRtcEngine.registerVideoSampleObserver(mAliRtcVideoObserver);

必要に応じて抽象インターフェイスを実装します。ARTC は、ローカルビデオキャプチャ後、ローカルエンコーディング前、リモートビデオデコーディング後の3つの段階でカスタム処理をサポートしています。

public enum AliRtcVideoObserPosition{
        /*! キャプチャされたビデオデータ。onLocalVideoSample コールバックに対応します。*/
        AliRtcPositionPostCapture(1),
        /*! レンダリングされたビデオデータ。onRemoteVideoSample コールバックに対応します。*/
        AliRtcPositionPreRender(2),
        /*! エンコーディング前のビデオデータ。onPreEncodeVideoSample コールバックに対応します。*/
        AliRtcPositionPreEncoder(4);
    }
public static abstract class AliRtcVideoObserver {
    // サブスクライブしたローカルビデオキャプチャデータのコールバック。
    public boolean onLocalVideoSample(AliRtcVideoSourceType sourceType, AliRtcVideoSample videoSample){
        // TODO: ローカルビデオキャプチャ中にレタッチやエフェクトが必要な場合は、ここで処理します。
    }

    // サブスクライブしたリモートビデオデータのコールバック。
    public boolean onRemoteVideoSample(String callId,AliRtcVideoSourceType sourceType, AliRtcVideoSample videoSample){
        // TODO: リモートエンドからプルしたフレームを表示する前にレタッチやエフェクトが必要な場合は、ここで処理します。
    }

    // エンコーディング前のローカルビデオデータのコールバック。
    public boolean onPreEncodeVideoSample(AliRtcVideoSourceType sourceType, AliRtcVideoSample videoRawData){
        // TODO: ローカルビデオフレームをエンコーディングする前にレタッチやエフェクトが必要な場合は、ここで処理します。
    }

    ...

    public int onGetObservedFramePosition(){
        // TODO: ビジネスニーズに基づいてコールバックのタイミングを指定します。上記で定義された AliRtcVideoObserPosition の値を使用します。
        // 例えば、キャプチャ前とレンダリング前にカスタム処理を実行するには、次の値を定義します。
        // return AliRtcVideoObserPosition.AliRtcPositionPostCapture.getValue() | AliRtcVideoObserPosition.AliRtcPositionPreRender.getValue();
    }
}
説明

Queen SDK を使用するには、対応するインターフェイスでビデオストリームを処理する必要があります。

リスナーの登録解除

カスタム処理が不要になった場合は、リスナーの登録を解除して SDK レイヤーからの呼び出しを減らし、処理効率を向上させ、メモリリークを防ぎます。

// TODO: リソース解放タスクを実行します。
mAliRtcEngine.unRegisterVideoSampleObserver()

2. カスタム処理の実装

前述のコールバックインターフェイスは、handleBeautyProcess メソッドを呼び出すことで実装できます。

private boolean handleBeautyProcess(AliRtcEngine.AliRtcVideoSample videoSample) {
    if (!isAdvanceBeautifyOn) {     // レタッチを有効にするかどうかを指定します。
        return false;
    }
    if (mQueenBeautyImp == null) {
        mQueenBeautyImp = new QueenBeautyImp(getContext(), videoSample.glContex);
    }

    return mQueenBeautyImp.onBeautyProcess(videoSample);
}
説明

QueenBeautyImp は QueenBeautyEffector のシンプルなラッパークラスです。

レタッチプロセッサの作成

プロセッサは次のように作成します。

// 複数作成を防ぐために同期ロックを追加します。
private synchronized void ensureQueenEngine(Context context, long glShareContext) {
        if (mQueenBeautyEffector == null) {
            try {
                QueenConfig queenConfig = new QueenConfig();
                bool isNeedCreateNewThread = glShareContext != 0; // 別のスレッドを作成するかどうかを指定します。テクスチャモードでは true、バッファーモードでは false に設定することを推奨します。
                bool isNeedCreateNewGLContext = true; // GL コンテキストを作成するかどうかを指定します。テクスチャモードでは、この値を isNeedCreateNewThread と一致させてください。バッファーモードでは、true に設定することを推奨します。
                queenConfig.withNewGlThread = isNeedCreateNewThread; 
                queenConfig.withContext = isNeedCreateNewGLContext;
                queenConfig.shareGlContext = glShareContext;
//                queenConfig.enableDebugLog = true;   // デバッグ機能:ログを有効にします。
                mQueenBeautyEffector = new QueenBeautyEffector(context, queenConfig);
                // 高度なレタッチデバッグ機能。
//                mQueenBeautyEffector.getEngine().enableFacePointDebug(true);    // 顔のランドマークデバッグを有効にします。
//                mQueenBeautyEffector.getEngine().enableFaceDetectGPUMode(false);  // 顔認識の GPU モードを無効にします。
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
レタッチパラメーターの設定
private void updateQueenEngineParams() {
    mQueenBeautyEffector.onUpdateParams(() -> {
        QueenEngine queenEngine = mQueenBeautyEffector.getEngine();
        // 肌の平滑化とシャープ化は1つのスイッチを共有します。
        queenEngine.enableBeautyType(BeautyFilterType.kSkinBuffing, true); // 肌の平滑化スイッチ。
        queenEngine.setBeautyParam(com.aliyun.android.libqueen.models.BeautyParams.kBPSkinBuffing, 0.85f);  // 肌の平滑化。有効な値:[0,1]。
        queenEngine.setBeautyParam(com.aliyun.android.libqueen.models.BeautyParams.kBPSkinSharpen, 0.2f);  // シャープ化。有効な値:[0,1]。
        // 美白と血色は1つのスイッチを共有します。
        queenEngine.enableBeautyType(BeautyFilterType.kSkinWhiting, true); // 美白スイッチ。
        queenEngine.setBeautyParam(BeautyParams.kBPSkinWhitening, 0.5f);  // 美白。有効な値:[0,1]。

        // 目の拡大と顔痩せ。
        queenEngine.enableBeautyType(BeautyFilterType.kFaceShape, true);
        queenEngine.updateFaceShape(FaceShapeType.typeBigEye,1.0f);
        queenEngine.updateFaceShape(FaceShapeType.typeCutFace,1.0f);
    });
}
フレームの処理

コールバックデータの型に基づいて、データをテクスチャまたはバッファーとして処理します。

// マルチスレッド環境で mQueenBeautyEffector が破棄されるのを防ぐために同期ロックを追加します。
public synchronized boolean onBeautyProcess(AliRtcEngine.AliRtcVideoSample videoSample) {
    // レタッチパラメーターを更新します。
    updateQueenEngineParams();

    boolean result = false;
    if (videoSample.glContex != 0 && videoSample.textureid > 0) {
        // テクスチャモード。
        result = onProcessBeautyTexture(videoSample);
    } else {
        // バッファーモード。
        result = onProcessBeautyBuffer(videoSample);
    }
    return result;
}

テクスチャコールバックの処理:

private boolean onProcessBeautyTexture(AliRtcEngine.AliRtcVideoSample videoSample) {
    boolean result = false;
    boolean isOesTexture = videoSample.format == AliRtcEngine.AliRtcVideoFormat.AliRtcVideoFormatTextureOES;
    // Android カメラでキャプチャされたテクスチャは、デフォルトで270度回転した横長の画像です。Queen SDK は幅と高さを自動的に入れ替えます。
    // ただし、videoSample コールバックの幅と高さは ARTC SDK によってすでに修正されています。そのため、ここで手動で幅と高さを入れ替える必要があります。
    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 は QueenResult.QUEEN_OK を示します。
        // テクスチャ ID を変更します。
        videoSample.textureid = newTextId;
        videoSample.format = AliRtcEngine.AliRtcVideoFormat.AliRtcVideoFormatTexture2D;

        result = true;
    }
    return result;
}

バッファーコールバックの処理:

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. 終了とリソースの破棄

会議から退出するか、ビデオ通話インターフェイスを終了するときは、カスタム処理エンジンを速やかに破棄してください。

// マルチスレッド環境で mQueenBeautyEffector が破棄されるのを防ぐために同期ロックを追加します。
public synchronized void release() { 
    if (mQueenBeautyEffector != null) {
        mQueenBeautyEffector.onReleaseEngine();
        mQueenBeautyEffector = null;
    }
}

iOS の実装

バッファー処理の統合

1. リスナーの登録

まず、AliRtcEngineDelegate デリゲートインターフェイスを実装し、初期化時に AliRtcEngine インスタンスに渡します。

次に、[self.engine registerVideoSampleObserver]; を呼び出して、ビデオ処理オブザーバーを登録します。

さらに、以下の主要なインターフェイスを実装して、リスナーイベントを正確に制御できます。

// ビデオデータが出力される位置。デフォルトでは、すべての位置が返されます。
- (NSInteger)onGetVideoObservedFramePosition {
    // 以下の値の1つまたは組み合わせを返します。
    // AliRtcPositionPostCapture:ローカルでキャプチャされたビデオデータ。onCaptureVideoSample コールバックに対応します。
    // AliRtcPositionPreRender:リモートでレンダリングされたビデオデータ。onRemoteVideoSample コールバックに対応します。
    // AliRtcPositionPreEncoder:エンコーディング前のビデオデータ。onPreEncodeVideoSample コールバックに対応します。
    return AliRtcPositionPreRender|AliRtcPositionPostCapture;
}

// ビデオデータの出力フォーマットを指定します。これはオプションです。
- (AliRtcVideoFormat)onGetVideoFormatPreference {
    return AliRtcVideoFormat_cvPixelBuffer;
}
2. カスタム処理の実装
// ローカルでキャプチャされたビデオデータのコールバック。
- (BOOL)onCaptureVideoSample:(AliRtcVideoSource)videoSource videoSample:(AliRtcVideoDataSample *)videoSample {
       return [self processVideoBeauty:videoSample];
}

// サブスクライブしたリモートビデオデータのコールバック。
- (BOOL)onRemoteVideoSample:(NSString *)uid videoSource:(AliRtcVideoSource)videoSource videoSample:(AliRtcVideoDataSample *)videoSample {
       return [self processVideoBeauty:videoSample];
}
レタッチプロセッサの作成

前述のコールバックインターフェイスは、handleBeautyProcess メソッドを呼び出すことで実装できます。

- (bool)processVideoBeauty:(AliRtcVideoDataSample *)videoSample {
    
    if(videoSample) {
        if(videoSample.type == AliRtcBufferType_CVPixelBuffer) {
            if(videoSample.pixelBuffer) {
                return [self handleBeautyProcessBuffer:videoSample.pixelBuffer];
            }
            return false ;
        }
    }
    
    return false ;
}
- (bool)handleBeautyProcessBuffer:(CVPixelBufferRef)pixelBufferRef {
    if (!self.beautyEngine) {
        [self initBeautyEngine:YES];
    }
    if (self.beautyEngine && pixelBufferRef)
    {
        QEPixelBufferData *bufferData = [QEPixelBufferData new];
        bufferData.bufferIn = pixelBufferRef;
        bufferData.bufferOut = pixelBufferRef;
        // pixelBuffer で画像編集を行い、処理後のバッファーを出力します。
        kQueenResultCode resultCode = [self.beautyEngine processPixelBuffer:bufferData]; // このメソッドを実行するスレッドは常に同じである必要があります。
        if (resultCode == kQueenResultCodeOK && bufferData.bufferOut)
        {
            return YES;
        }
    }
    return NO;
}

initBeautyEngine メソッドは、レタッチ engine を作成します。

レタッチパラメーターの設定
#pragma mark レタッチ関連の処理
- (void)initBeautyEngine:(BOOL)processBuffer
{
    if (self.beautyEngine != nil) {
        return;
    }
    // エンジン構成情報オブジェクトを初期化します。
    QueenEngineConfigInfo *configInfo = [QueenEngineConfigInfo new];

    // 画像の回転角度を自動的に設定するかどうかを指定します。デバイスの画面がロックされており、画像がデフォルトでカメラからキャプチャされる場合は、画像の回転角度を自動的に設定できます。
    configInfo.autoSettingImgAngle = YES;
    configInfo.runOnCustomThread = processBuffer ? NO : YES; // テクスチャ呼び出しの場合は YES、バッファー呼び出しの場合は NO に設定します。NO は、呼び出しが現在の呼び出し元スレッドで実行されず、内部で新しいスレッドが作成されることを示します。
    configInfo.withContext = processBuffer ? YES : NO;        // テクスチャ呼び出しの場合は NO、バッファー呼び出しの場合は YES に設定します。YES は、内部で glContext を作成する必要があることを示します。

    // デバッグインターフェイス:Queen SDK のロギング機能を有効にします。
    // configInfo.enableDebugLog = YES;
    
    // エンジンを初期化します。
    self.beautyEngine = [[QueenEngine alloc] initWithConfigInfo:configInfo];
    
    // 基本的なレタッチ。
    // 肌の平滑化。
    [_beautyEngine setQueenBeautyType:kQueenBeautyTypeSkinBuffing enable:YES mode:kQueenBeautyFilterModeSkinBuffing_Natural];
    // 肌の平滑化係数を設定します。
    [self.beautyEngine setQueenBeautyParams:kQueenBeautyParamsSkinBuffing value:0.5f];
    // シャープ化係数を設定します。
    [self.beautyEngine setQueenBeautyParams:kQueenBeautyParamsSharpen value:0.5f];
    
    // 美白。
    [_beautyEngine setQueenBeautyType:kQueenBeautyTypeSkinWhiting enable:YES];
    // 美白係数を設定します。
    [_beautyEngine setQueenBeautyParams:kQueenBeautyParamsWhitening value:0.5f];
    // 高度なレタッチ。
    [_beautyEngine setQueenBeautyType:kQueenBeautyTypeFaceBuffing enable:YES];
    // しわ取り。
    [_beautyEngine setQueenBeautyParams:kQueenBeautyParamsWrinkles value:0.5f];
    // クマ取り係数を設定します。
    [_beautyEngine setQueenBeautyParams:kQueenBeautyParamsPouch value:0.5f];
    // ほうれい線除去係数を設定します。
    [_beautyEngine setQueenBeautyParams:kQueenBeautyParamsNasolabialFolds value:0.5f];
    // 歯のホワイトニング係数を設定します。
    [_beautyEngine setQueenBeautyParams:kQueenBeautyParamsWhiteTeeth value:0.5f];
    
    // 顔のシェイピング機能を有効にします。
    [_beautyEngine setQueenBeautyType:kQueenBeautyTypeFaceShape enable:YES mode:kQueenBeautyFilterModeFaceShape_Main];
    // 顔痩せ。
    [_beautyEngine setFaceShape:kQueenBeautyFaceShapeTypeCutFace value:0.5f];
    // 目の拡大。
    [_beautyEngine setFaceShape:kQueenBeautyFaceShapeTypeBigEye value:0.9f];
    
    // デバッグインターフェイス:顔認識の特徴点を表示します。
    //[self.beautyEngine showFaceDetectPoint:YES];
    
}
フレームの処理

バッファーコールバックの処理:

- (bool)handleBeautyProcessBuffer:(CVPixelBufferRef)pixelBufferRef {
    if (!self.beautyEngine) {
        [self initBeautyEngine];
    }
    if (self.beautyEngine && pixelBufferRef)
    {
        QEPixelBufferData *bufferData = [QEPixelBufferData new];
        bufferData.bufferIn = pixelBufferRef;
        bufferData.bufferOut = pixelBufferRef;
        // pixelBuffer で画像編集を行い、処理後のバッファーを出力します。
        kQueenResultCode resultCode = [self.beautyEngine processPixelBuffer:bufferData]; // このメソッドを実行するスレッドは常に同じである必要があります。
        if (resultCode == kQueenResultCodeOK && bufferData.bufferOut)
        {
            return YES;
        }
    }
    return NO;
}

テクスチャコールバックの処理:

- (bool)handleBeautyProcessTexture:(int)textureID withWidth:(int)width withHeight:(int)height {
    // 純粋なテクスチャ処理モードについては、以下のセクションをご参照ください。
}
3. 終了とリソースの破棄

イベントリスナーの登録を解除します。

/* I420/cvPixelBuffer */
unregisterVideoSampleObserver

beautyEngine を破棄します。

    // バッファーを処理する場合、内部に非同期スレッドが存在します。そのため、RTCEngine を破棄する際にエンジンを破棄できます。
    // 破棄の順序に注意してください。RTCEngine が beautyEngine を再度呼び出さないようにしてください。最後に解放することができます。
- (void)dealloc {   
    ...
    if (nil != self.beautyEngine) {
         [self.beautyEngine destroyEngine];
         self.beautyEngine = nil;
    }
}

純粋なテクスチャの統合

1. リスナーの登録

まず、Delegate インターフェイス AliRtcEngineDelegate を実装し、初期化時に AliRtcEngine インスタンスに渡します。

次に、[self.engine registerLocalVideoTexture]; を呼び出して、ビデオ処理オブザーバーを登録します。

[_engine registerLocalVideoTexture];	// キャプチャ段階のテクスチャコールバックを有効にします。
2. カスタム処理の実装

Delegate インターフェイス AliRtcEngineDelegate の以下の3つのメソッドを実装できます。

- (void)onTextureCreate:(void *)context {
    // 何もしません。ここでレタッチエンジンを初期化することもできます。この例では、handleBeautyProcessTexture 内で初期化されます。
}

- (int)onTextureUpdate:(int)textureId width:(int)width height:(int)height videoSample:(AliRtcVideoDataSample *_Nonnull)videoSample{
    
    if (self.settingModel.openRaceBeauty) {
        return [self handleBeautyProcessTexture:textureId withWidth:videoSample.width withHeight:videoSample.height];
    }
    return textureId;
}

- (void)onTextureDestory
{
    // テクスチャ呼び出しの場合、エンジンが作成されたのと同じスレッドで呼び出されるように、ここでエンジンを破棄する必要があります。
    if (nil != self.beautyEngine) {
         [self.beautyEngine destroyEngine];
         self.beautyEngine = nil;
    }
}
3. レタッチプロセッサの作成
- (void)initBeautyEngine:(BOOL)processBuffer
{
    if (self.beautyEngine != nil) {
        return;
    }
    // エンジン構成オブジェクトを初期化します。
    QueenEngineConfigInfo *configInfo = [QueenEngineConfigInfo new];

    // イメージの回転角度を自動的に設定するかどうかを指定します。 デバイスの画面がロックされていて、デフォルトのイメージがカメラからキャプチャされる場合は、YES に設定します。
    configInfo.autoSettingImgAngle = YES;
    
    configInfo.runOnCustomThread = processBuffer ? NO : YES; // テクスチャの呼び出しには YES を、バッファーの呼び出しには NO を設定します。 NO に設定した場合、プロセスは現在の呼び出し元スレッドでは実行されません。 内部で新しいスレッドが作成されます。
    configInfo.withContext = processBuffer ? YES : NO;        // テクスチャの呼び出しには NO を、バッファーの呼び出しには YES を設定します。 YES に設定した場合、内部で glContext が作成されます。
    
    // レタッチ SDK のログ機能を有効にするためのデバッグ API です。
    // configInfo.enableDebugLog = YES;

    // エンジンを初期化します。
    self.beautyEngine = [[QueenEngine alloc] initWithConfigInfo:configInfo];
    
    ...
    // 残りのコードは変更ありません。
}
4. フレームの処理
- (int)handleBeautyProcessTexture:(int)textureID withWidth:(int)width withHeight:(int)height {
    if (!self.beautyEngine) {
        [self initBeautyEngine:NO];
    }

    // 高度なレタッチ機能には顔検出アルゴリズムが必要なため、bufData を渡す必要があります。
    // 基本的なレタッチのみを有効にする場合は、updateInputDataAndRunAlg メソッドを呼び出さないでください。
    uint8_t* bufData = [self extractI420DataFromPixelBuffer:videoSample.pixelBuffer];
    [self.beautyEngine updateInputDataAndRunAlg:bufData
                                            withImgFormat:kQueenImageFormatNV12
                                            withWidth:width
                                            withHeight:height
                                             withStride:0
                                         withInputAngle:0
                                        withOutputAngle:0
                                           withFlipAxis:0];
    free(bufData);
    
    QETextureData* textureData = [[QETextureData alloc] init];
    textureData.inputTextureID = textureID;
    textureData.width = width;
    textureData.height = height;
    kQueenResultCode result = [self.beautyEngine processTexture:textureData];
    if (result != kQueenResultCodeOK)
    {
        return textureID;
    }
    return textureData.outputTextureID;
}

テクスチャモードで処理する場合、アプリケーション層はバッファーを抽出し、それをレタッチ SDK に渡して API を呼び出す必要があります。このメソッドは、テクスチャからのバッファーデータの再抽出を回避することで、全体のパフォーマンスを向上させます。

次のコードは、バッファーを抽出する方法を示しています:

-(uint8_t *)extractI420DataFromPixelBuffer:(CVPixelBufferRef) pixelBuffer {
    uint8_t *bufData = nil;
    // フォーマット情報を取得します。
    OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer);
    size_t bufWidth = CVPixelBufferGetWidth(pixelBuffer);
    size_t bufHeight = CVPixelBufferGetHeight(pixelBuffer);
        
    // バッファーをロックします。
    CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
    
    if (CVPixelBufferIsPlanar(pixelBuffer)) {
        if (pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
            uint8_t *yPlane = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
            uint8_t *uvPlane = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);

            size_t yStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
            size_t uvStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);

            size_t width = CVPixelBufferGetWidth(pixelBuffer);
            size_t height = CVPixelBufferGetHeight(pixelBuffer);
            // 標準の NV12 サイズを割り当てます (パディングなし)。
            size_t bufDataSize = width * height * 3 / 2;
            bufData = (uint8_t *)malloc(bufDataSize);
            // Y プレーンを行ごとにコピーします (ストライドのパディングを削除)。
            for (size_t i = 0; i < height; i++) {
                memcpy(bufData + i * width, yPlane + i * yStride, width);
            }
            // UV プレーンを行ごとにコピーします。
            for (size_t i = 0; i < height / 2; i++) {
                memcpy(bufData + width * height + i * width,
                       uvPlane + i * uvStride,
                       width);
            }
        }
        
    } else {
        // BGRA などのパック形式。
        void *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
        size_t dataSize = CVPixelBufferGetDataSize(pixelBuffer);
        // baseAddress を処理します。
        bufData = (uint8_t *)malloc(dataSize);
        memcpy(bufData, baseAddress, dataSize);
    }
    CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);

    return bufData;
}
5. 終了と破棄

イベントリスナーの登録を解除します。

/* textureID */
unregisterLocalVideoTexture

呼び出されたテクスチャは onTextureDestroy() 内で解放および破棄する必要があります。

- (void)onTextureDestory
{
    // テクスチャを使用する場合、ここでエンジンを破棄します。これにより、エンジンが作成されたスレッドと同じスレッドで破棄されることが保証されます。
    if (nil != self.beautyEngine) {
         [self.beautyEngine destroyEngine];
         self.beautyEngine = nil;
    }
}