> ## 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.

# 第三方对接

> 在 iOS 上将 Facebetter 美颜接入 TRTC、声网 Agora、LiveKit 等第三方视频管线

SDK **2.0.0**。把 Facebetter 挂在第三方 SDK 的**自定义视频前处理**回调里：在对方提供的 **OpenGL ES 线程**（或像素缓冲回调）上处理帧，再交回对方。

完整纹理约定见 [实现美颜 · 外部纹理](/zh/ios/implement-beauty#外部纹理)。对方 SDK 的进房、推流、权限以其官方文档为准；本页只写与 Facebetter 的接线。

```
第三方 SDK
  textureId 或 CVPixelBuffer
       ↓
FBBeautyEffectEngine
  createWithTexture / createWithNV12|BGRA → processImage:
       ↓
  输出纹理或像素 → 写回第三方帧
```

## 共性约定

| 项      | 要求                                                                                            |
| ------ | --------------------------------------------------------------------------------------------- |
| 纹理路径   | `externalContext = YES`，在**对方 GL 回调线程**惰性 `createEngineWithConfig:`                           |
| 输入纹理   | `GL_TEXTURE_2D`（RGBA）；`stride` 一般为 `width * 4`                                                |
| 像素缓冲路径 | 可用 `createWithNV12:` / `createWithBGRA:` 等；此时通常 **`externalContext = NO`**，由 Facebetter 自管 GL |
| 帧类型    | `FBFrameTypeVideo`                                                                            |
| 输出     | 输出纹理由 SDK 持有，不要 `glDeleteTextures`；引擎用 strong 引用，会话结束置 `nil`                                  |
| 生命周期   | 对方销毁 GL 上下文时先将引擎置 `nil`，再在新上下文重建                                                              |

Apple 平台上 TRTC 自定义前处理常用 **OpenGL 纹理**；声网 / LiveKit 更常见 **`CVPixelBuffer`**。两种路径都可，不要混用同一台引擎的两种模式。

### 共用：处理纹理

```objc theme={null}
#import <Facebetter/Facebetter.h>

static GLuint FBProcessTexture(FBBeautyEffectEngine *engine,
                               GLuint srcTextureId,
                               int width,
                               int height) {
  if (!engine || srcTextureId == 0 || width <= 0 || height <= 0) {
    return srcTextureId;
  }
  FBImageFrame *input = [FBImageFrame createWithTexture:srcTextureId
                                                  width:width
                                                 height:height
                                                 stride:width * 4];
  if (!input) {
    return srcTextureId;
  }
  input.type = FBFrameTypeVideo;
  FBImageFrame *output = [engine processImage:input];
  if (!output) {
    return srcTextureId;
  }
  GLuint dst = [output texture];
  return dst != 0 ? dst : srcTextureId;
}
```

创建引擎（GL 线程）：

```objc theme={null}
FBEngineConfig *config = [[FBEngineConfig alloc] init];
config.appId = @"your appId";
config.appKey = @"your appKey";
// 或 config.licenseToken = @"...";
config.externalContext = YES;
self.beautyEngine = [FBBeautyEffectEngine createEngineWithConfig:config];
[self.beautyEngine setSmoothing:0.5f];
```

### 共用：处理 CVPixelBuffer（NV12 示意）

声网 / LiveKit 回调里常见 NV12。平面指针与 stride 取自 `CVPixelBuffer`；写回对方帧的方式随厂商版本变化。

```objc theme={null}
static FBImageFrame *FBFrameFromNV12PixelBuffer(CVPixelBufferRef pb) {
  OSType fmt = pb ? CVPixelBufferGetPixelFormatType(pb) : 0;
  if (!pb || (fmt != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
              && fmt != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)) {
    return nil;
  }
  CVPixelBufferLockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
  int width = (int)CVPixelBufferGetWidth(pb);
  int height = (int)CVPixelBufferGetHeight(pb);
  uint8_t *y = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pb, 0);
  uint8_t *uv = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pb, 1);
  int strideY = (int)CVPixelBufferGetBytesPerRowOfPlane(pb, 0);
  int strideUV = (int)CVPixelBufferGetBytesPerRowOfPlane(pb, 1);
  FBImageFrame *frame = [FBImageFrame createWithNV12:width
                                              height:height
                                               dataY:y
                                             strideY:strideY
                                              dataUV:uv
                                            strideUV:strideUV];
  CVPixelBufferUnlockBaseAddress(pb, kCVPixelBufferLock_ReadOnly);
  return frame;
}
```

像素缓冲路径下创建引擎时设 `externalContext = NO`。凭证：[订阅服务](/zh/intro/enable-service)、[授权与许可](/zh/intro/license)。

***

## TRTC（腾讯云）

挂钩 API：`setLocalVideoProcessDelegete:pixelFormat:bufferType:`（官方拼写为 **Delegete**）。像素格式 **`TRTCVideoPixelFormat_Texture_2D`**，缓冲类型 **`TRTCVideoBufferType_Texture`**。

实现 `TRTCVideoFrameDelegate`：在 `onGLContextCreated` 创建引擎；在 `onProcessVideoFrame:dstFrame:` 写回 `dstFrame.textureId`；在 `onGLContextDestory` 将引擎置 `nil`。

```objc theme={null}
#import <TXLiteAVSDK_TRTC/TRTCCloud.h>
#import <Facebetter/Facebetter.h>

@interface BeautyProcessDelegate : NSObject <TRTCVideoFrameDelegate>
@property (nonatomic, strong) FBBeautyEffectEngine *beautyEngine;
@end

@implementation BeautyProcessDelegate

- (void)onGLContextCreated {
  FBEngineConfig *config = [[FBEngineConfig alloc] init];
  config.appId = @"your appId";
  config.appKey = @"your appKey";
  config.externalContext = YES;
  self.beautyEngine = [FBBeautyEffectEngine createEngineWithConfig:config];
  [self.beautyEngine setSmoothing:0.5f];
}

- (uint32_t)onProcessVideoFrame:(TRTCVideoFrame *)srcFrame
                       dstFrame:(TRTCVideoFrame *)dstFrame {
  GLuint out = FBProcessTexture(self.beautyEngine,
                                srcFrame.textureId,
                                srcFrame.width,
                                srcFrame.height);
  dstFrame.textureId = out;
  return 0;
}

- (void)onGLContextDestory {
  self.beautyEngine = nil;
}

@end

// 注册
BeautyProcessDelegate *delegate = [[BeautyProcessDelegate alloc] init];
[[TRTCCloud sharedInstance] setLocalVideoProcessDelegete:delegate
                                             pixelFormat:TRTCVideoPixelFormat_Texture_2D
                                              bufferType:TRTCVideoBufferType_Texture];
```

取消委托或退房后释放引擎。详见腾讯云 [TRTC 第三方美颜](https://cloud.tencent.com/document/product/647/32270)。

Flutter 请用 [Flutter · 第三方对接](/zh/flutter/third-party-integration)（`FacebetterPlugin.processTexture`）。

***

## 声网 Agora

挂钩 API：`AgoraRtcEngineKit.setVideoFrameDelegate:`（Video SDK **4.x**）。iOS 上采集帧多为 **`CVPixelBuffer`**（`textureBuf` / format `12`），而不是 Android 那种 `GL_TEXTURE_2D` id。

需要：

1. `getVideoFrameProcessMode` 返回 **ReadWrite**
2. 在 `onCapture:sourceType:`（或旧名 `onCaptureVideoFrame`）里取出 `CVPixelBuffer`
3. 用 `createWithNV12:` / `createWithBGRA:` 等喂给 Facebetter，再把结果**写回** `AgoraOutputVideoFrame`

```objc theme={null}
#import <AgoraRtcKit/AgoraRtcKit.h>

@interface BeautyFrameDelegate : NSObject <AgoraVideoFrameDelegate>
@property (nonatomic, strong) FBBeautyEffectEngine *beautyEngine;
@end

@implementation BeautyFrameDelegate

- (AgoraVideoFrameProcessMode)getVideoFrameProcessMode {
  return AgoraVideoFrameProcessModeReadWrite;
}

- (BOOL)onCapture:(AgoraOutputVideoFrame *)videoFrame
       sourceType:(AgoraVideoSourceType)sourceType {
  CVPixelBufferRef pb = videoFrame.pixelBuffer; // 部分版本为 textureBuf，以你集成的头文件为准
  if (!pb) {
    return YES;
  }

  if (!self.beautyEngine) {
    FBEngineConfig *config = [[FBEngineConfig alloc] init];
    config.appId = @"your appId";
    config.appKey = @"your appKey";
    config.externalContext = NO; // 像素缓冲路径
    self.beautyEngine = [FBBeautyEffectEngine createEngineWithConfig:config];
  }

  FBImageFrame *input = FBFrameFromNV12PixelBuffer(pb);
  if (!input) {
    return YES; // 非 NV12：改用 BGRA 工厂方法或先转换
  }
  input.type = FBFrameTypeVideo;
  FBImageFrame *output = [self.beautyEngine processImage:input];
  if (!output) {
    return YES;
  }

  // 将 output 的 NV12/BGRA 平面写回 videoFrame 的 CVPixelBuffer，
  // 或按声网文档替换 pixelBuffer / textureBuf。细节随 SDK 版本变化。
  return YES;
}

@end

[agoraKit setVideoFrameDelegate:delegate];
```

<Warning>
  声网 iOS 侧写回 `CVPixelBuffer` 的方式随大版本变化。Facebetter 保证 `processImage:` 输出与输入同格式；如何塞回 `AgoraOutputVideoFrame` 以 [Agora 原始视频处理](https://docs.agora.io/en/video-calling/develop/product-workflow) 为准。
</Warning>

销毁引擎前 `setVideoFrameDelegate:nil`，并将 `beautyEngine` 置 `nil`。

***

## LiveKit

挂钩方式：实现 Swift `VideoProcessor`，在 `process(frame:)` 里处理，再返回新帧（或 `nil` 丢帧）。创建相机轨时传入 `processor:`，或之后设置 `track.processor`。

```swift theme={null}
import LiveKit
import Facebetter // 以你的模块名为准

final class FacebetterVideoProcessor: VideoProcessor {
  private var beautyEngine: FBBeautyEffectEngine?

  func process(frame: VideoFrame) -> VideoFrame? {
    // 从 LiveKit VideoFrame 取出 CVPixelBuffer（属性名随 SDK 版本可能为
    // buffer / pixelBuffer / rtcFrame 等，请对照当前 LiveKit Swift 头文件）
    guard let pb = frame.buffer as CVPixelBuffer? else {
      return frame
    }

    if beautyEngine == nil {
      let config = FBEngineConfig()
      config.appId = "your appId"
      config.appKey = "your appKey"
      config.externalContext = false
      beautyEngine = FBBeautyEffectEngine.createEngine(with: config)
      beautyEngine?.setSmoothing(0.5)
    }

    // ObjC 辅助：FBFrameFromNV12PixelBuffer → processImage:
    // 再用输出构造 LiveKit VideoFrame 并 return
    // 包装步骤见 LiveKit「Video Processors」文档
    return frame
  }
}

let processor = FacebetterVideoProcessor()
let track = LocalVideoTrack.createCameraTrack(processor: processor)
// 或：track.processor = processor
try await room.localParticipant.publish(track: track)
```

官方示例处理器（如背景模糊）见 LiveKit Swift 的 `BackgroundBlurVideoProcessor`。自研 capturer 时，也可在送入编码器前先跑 Facebetter。

***

## 排错要点

| 现象       | 排查                                                                        |
| -------- | ------------------------------------------------------------------------- |
| 黑屏 / 无画面 | 纹理路径是否在对方 **GL 线程**创建引擎；像素路径是否锁/解锁 `CVPixelBuffer`                        |
| 美颜不生效    | `externalContext` 是否与路径匹配（纹理 YES / 像素常 NO）；格式是否 NV12/BGRA/`GL_TEXTURE_2D` |
| 偶发闪退     | `onGLContextDestory` 是否置 `nil`；是否误删输出纹理                                   |
| 花屏 / 拉伸  | 宽高、旋转、镜像是否与对方帧一致；stride 是否用 `BytesPerRow`                                 |

## 相关文档

* [实现美颜](/zh/ios/implement-beauty)
* [最佳实践](/zh/ios/best-practices)
* [错误处理](/zh/ios/error-handling)
* [API 参考](/zh/ios/api-reference)
* [macOS · 第三方对接](/zh/macos/third-party-integration)
