> ## Documentation Index
> Fetch the complete documentation index at: https://docs.facebetter.net/llms.txt
> Use this file to discover all available pages before exploring further.

# サードパーティ連携

> TRTC、Agora、LiveKit などの Android 動画パイプラインに Facebetter 美顔を組み込みます

SDK **2.0.0**。Facebetter をサードパーティ SDK の**カスタム動画前処理**コールバックに接続します。その SDK の **OpenGL ES スレッド**上で、外部テクスチャに対して `processImage` を実行し、出力テクスチャを返します。

テクスチャの完全なルール: [美顔の実装 · 外部テクスチャ](/ja/android/implement-beauty#external-texture-opengl-es)。ルーム参加、配信、権限はベンダーのドキュメントに従います。このページは Facebetter の接続のみを扱います。

```
Third-party SDK (GL thread)
  input textureId
       ↓
BeautyEffectEngine (externalContext = true)
  ImageFrame.createWithTexture → processImage
       ↓
  output textureId → write back to vendor frame / callback
```

## 共通の契約

| 項目      | 要件                                                                       |
| ------- | ------------------------------------------------------------------------ |
| エンジン    | `externalContext = true`。**ベンダー GL コールバックスレッド**で遅延作成                     |
| 入力      | `GL_TEXTURE_2D`（RGBA）。`stride` は通常 `width * 4`                           |
| フレームタイプ | `ImageFrame.FrameType.VIDEO`                                             |
| 出力      | SDK が出力テクスチャを所有します。`glDeleteTextures` しないでください。必ず `ImageFrame.release()` |
| ライフサイクル | ベンダーが GL コンテキストを破棄したら `engine.release()` し、新しいコンテキストで再作成                 |

Facebetter は `createWithTexture` に `GL_TEXTURE_EXTERNAL_OES` を**受け付けません**。ベンダーが OES をデフォルトにする場合は、ドキュメントに従い **Texture2D / RGB** に切り替えるか、先に `GL_TEXTURE_2D` へ blit してください。

### 共通ヘルパー

各ベンダーの GL コールバックで再利用します（GL スレッドで実行する必要があります）。

```java theme={null}
import net.pixpark.facebetter.BeautyEffectEngine;
import net.pixpark.facebetter.ImageFrame;

/** @return beauty output GL_TEXTURE_2D; on failure returns srcTextureId (or 0, your choice) */
static int processTexture(BeautyEffectEngine engine, int srcTextureId, int width, int height) {
    if (engine == null || srcTextureId == 0 || width <= 0 || height <= 0) {
        return srcTextureId;
    }
    int stride = width * 4;
    ImageFrame input = ImageFrame.createWithTexture(srcTextureId, width, height, stride);
    if (input == null) {
        return srcTextureId;
    }
    input.type = ImageFrame.FrameType.VIDEO;
    ImageFrame output = engine.processImage(input);
    if (output == null) {
        input.release();
        return srcTextureId;
    }
    int dst = output.getTexture();
    output.release();
    input.release();
    return dst != 0 ? dst : srcTextureId;
}
```

エンジンの作成（こちらも GL スレッド）:

```java theme={null}
BeautyEffectEngine.EngineConfig config = new BeautyEffectEngine.EngineConfig();
config.appId = "your appId";
config.appKey = "your appKey";
// or config.licenseToken = "...";
config.externalContext = true;
BeautyEffectEngine engine = new BeautyEffectEngine(context, config);
engine.setSmoothing(0.5f);
```

認証情報: [サブスクリプション](/ja/intro/enable-service)、[認証とライセンス](/ja/intro/license)。

***

## TRTC（Tencent）

フック: `TRTCCloud.setLocalVideoProcessListener`。ピクセルフォーマットは **Texture\_2D**、バッファタイプは **TEXTURE** です。

`onGLContextCreated` で Facebetter を作成し、`onProcessVideoFrame` で処理して `dstFrame.texture.textureId` を設定し、`onGLContextDestory` で `release()` します。

```java theme={null}
import com.tencent.trtc.TRTCCloud;
import com.tencent.trtc.TRTCCloudDef;
import com.tencent.trtc.TRTCCloudListener;

trtcCloud.setLocalVideoProcessListener(
    TRTCCloudDef.TRTC_VIDEO_PIXEL_FORMAT_Texture_2D,
    TRTCCloudDef.TRTC_VIDEO_BUFFER_TYPE_TEXTURE,
    new TRTCCloudListener.TRTCVideoFrameListener() {
        @Override
        public void onGLContextCreated() {
            // TRTC has bound the GL context — lazy-create BeautyEffectEngine here
            ensureEngine();
        }

        @Override
        public int onProcessVideoFrame(
                TRTCCloudDef.TRTCVideoFrame srcFrame,
                TRTCCloudDef.TRTCVideoFrame dstFrame) {
            int out = processTexture(
                    beautyEngine,
                    srcFrame.texture.textureId,
                    srcFrame.width,
                    srcFrame.height);
            dstFrame.texture.textureId = out;
            return 0;
        }

        @Override
        public void onGLContextDestory() {
            if (beautyEngine != null) {
                beautyEngine.release();
                beautyEngine = null;
            }
        }
    });
```

リスナーを外すかルームを退出したあとにエンジンを解放してください。最新の TRTC 詳細は Tencent のサードパーティ美顔ガイドを参照してください。

Flutter では [Flutter · サードパーティ連携](/ja/flutter/third-party-integration) を使います（`FacebetterPlugin.processTexture`。この Java API ではありません）。

***

## Agora

フック: `RtcEngine.registerVideoFrameObserver`（Video SDK **4.x**）。次が必要です。

1. `getVideoFrameProcessMode()` から `PROCESS_MODE_READ_WRITE` を返す
2. タイプ **RGB（`GL_TEXTURE_2D`）** の **TextureBuffer** を優先する
3. `onCaptureVideoFrame`（または選択した観測位置）で Facebetter を実行し、処理済みテクスチャを `VideoFrame` に**書き戻す**

```java theme={null}
import io.agora.rtc2.RtcEngine;
import io.agora.rtc2.video.IVideoFrameObserver;
import io.agora.base.VideoFrame;

engine.registerVideoFrameObserver(new IVideoFrameObserver() {
    @Override
    public boolean onCaptureVideoFrame(int sourceType, VideoFrame videoFrame) {
        VideoFrame.Buffer buffer = videoFrame.getBuffer();
        if (!(buffer instanceof VideoFrame.TextureBuffer)) {
            return true; // non-texture: pass through or convert, per your app
        }
        VideoFrame.TextureBuffer tex = (VideoFrame.TextureBuffer) buffer;
        if (tex.getType() != VideoFrame.TextureBuffer.Type.RGB) {
            return true; // OES: convert to GL_TEXTURE_2D, or adjust format preference
        }

        ensureEngine(); // must use the current GL / EGL context
        int outId = processTexture(
                beautyEngine,
                tex.getTextureId(),
                tex.getWidth(),
                tex.getHeight());

        // Wrap outId as a TextureBuffer and write it back into videoFrame.
        // Exact APIs (TextureBufferHelper / replaceBuffer, etc.) vary by Agora version —
        // follow Agora’s raw video-frame docs, then return true.
        return true;
    }

    @Override
    public int getVideoFrameProcessMode() {
        return IVideoFrameObserver.PROCESS_MODE_READ_WRITE;
    }

    @Override
    public int getVideoFormatPreference() {
        // Prefer a processable texture; use the constant from your Agora version
        return IVideoFrameObserver.VIDEO_PIXEL_DEFAULT;
    }

    // Implement other callbacks as needed; POSITION_POST_CAPTURER is common
});
```

<Warning>
  新しい `textureId` を Agora の `VideoFrame` に注入する方法は、TextureBuffer ヘルパーに依存し、メジャーバージョン間で異なります。Facebetter が生成するのは新しい `GL_TEXTURE_2D` だけです。書き戻し手順は [Agora の動画フレームオブザーバー](https://docs.agora.io/en/video-calling/develop/product-workflow) / API Reference に従ってください。
</Warning>

`RtcEngine` を破棄する前にオブザーバーを登録解除し、`beautyEngine.release()` してください。

***

## LiveKit

フック: ローカル動画トラック作成時に `org.webrtc.VideoProcessor`（または同等のトラックプロセッサ）を渡します。`onFrameCaptured` でテクスチャを処理し、`sink.onFrame(...)` で配信します。

```kotlin theme={null}
import io.livekit.android.room.track.LocalVideoTrackOptions
import org.webrtc.VideoFrame
import org.webrtc.VideoProcessor
import org.webrtc.VideoSink

class FacebetterVideoProcessor(
    private val appContext: android.content.Context,
) : VideoProcessor {
    private var sink: VideoSink? = null
    private var beautyEngine: BeautyEffectEngine? = null

    override fun setSink(videoSink: VideoSink?) {
        sink = videoSink
    }

    override fun onCapturerStarted(success: Boolean) {}

    override fun onCapturerStopped() {
        beautyEngine?.release()
        beautyEngine = null
    }

    override fun onFrameCaptured(frame: VideoFrame) {
        val buffer = frame.buffer
        if (buffer !is VideoFrame.TextureBuffer ||
            buffer.type != VideoFrame.TextureBuffer.Type.RGB
        ) {
            sink?.onFrame(frame)
            return
        }

        ensureEngine() // LiveKit / WebRTC current EGL context
        val outId = processTexture(
            beautyEngine,
            buffer.textureId,
            buffer.width,
            buffer.height,
        )

        // Build a new TextureBuffer / VideoFrame from outId, then:
        // sink?.onFrame(processedFrame)
        // processedFrame.release()
        //
        // TextureBuffer construction needs WebRTC Handler / YuvConverter, etc. —
        // follow LiveKit Android SDK and WebRTC samples for the wrap.
        sink?.onFrame(frame)
    }
}

// Attach the processor when creating the track (API per your livekit-android version)
val videoTrack = room.localParticipant.createVideoTrack(
    options = LocalVideoTrackOptions(),
    videoProcessor = FacebetterVideoProcessor(context),
)
videoTrack.startCapture()
room.localParticipant.publishVideoTrack(videoTrack)
```

カスタム `VideoCapturer` を実装し、キャプチャ後に `processTexture` を実行してから `capturerObserver.onFrameCaptured` を呼ぶこともできます。公式プロセッサ（バーチャル背景など）は LiveKit の `livekit-android-track-processors` モジュールにあります。

***

## トラブルシューティング

| 症状         | 確認                                                                  |
| ---------- | ------------------------------------------------------------------- |
| 黒画面 / 映像なし | エンジン作成と `processImage` がベンダーの **GL スレッド**上か。失敗フレームを誤って捨てていないか       |
| 美顔なし       | `externalContext == true`。入力が `GL_TEXTURE_2D`                       |
| 断続的なクラッシュ  | GL destroy / capturer stop で `release()`。出力に `glDeleteTextures` しない |
| 歪み         | 幅 / 高さ / 回転 / ミラーがベンダーフレームと一致。OES が 2D に変換されていない                    |

## 関連

* [美顔の実装](/ja/android/implement-beauty)
* [ベストプラクティス](/ja/android/best-practices)
* [エラー処理](/ja/android/error-handling)
* [API リファレンス](/ja/android/api-reference)
