# 开发视频插件 (/zh-CN/realtime-media/marketplace/build/create-extensions/video-filter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
本文展示如何使用声网 SDK 提供的 API 开发一个视频插件。开发视频插件需要使用以下接口：

- `IExtensionVideoFilter`：实现视频数据的处理能力，包括接收、处理和返回处理完的视频帧。
- `IExtensionProvider`：将视频数据的处理能力封装为插件。

## 前提条件 [#前提条件]

开发前，请确保你的开发环境满足以下要求：

- Android Studio 3.0 或以上版本。
- Android API 级别 16 或以上。

## 准备开发环境 [#准备开发环境]

参考如下步骤将声网云市场的 API 添加到你的项目中：

1. 下载[视频 SDK v4.x](/zh-CN/realtime-media/rtc/reference/downloads)，然后解压下载的 SDK 包。
2. 将 SDK 包中的 `/rtc/sdk/low_level_api/include` 路径下的头文件拷贝至你的项目文件夹中。

## 开发视频插件 [#开发视频插件]

### 实现插件 [#实现插件]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode)
- [`start`](#start)
- [`stop`](#stop)
- [`getVideoFormatWanted`](#getvideoformatwanted)
- [`adaptVideoFrame`](#adaptvideoframe)
- [`pendVideoFrame`](#pendvideoframe)
- [`deliverVideoFrame`](#delivervideoframe)
- [`setProperty`](#setproperty)
- [`getProperty`](#getproperty)

#### getProcessMode [#getprocessmode]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-0-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-1-1) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-0-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-2-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-1-1"></a>

**video-filter-android-ios-macos-windows-html-0-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 `deliverVideoFrame` 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-2-1"></a>

**video-filter-android-ios-macos-windows-html-0-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以根据 YUV 运算的复杂度和是否使用 OpenGL 设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
- 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

<a id="delivervideoframe"></a>

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-1-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-1-2-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-1-2-1"></a>

**video-filter-android-ios-macos-windows-html-1-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty]

设置视频插件属性。App 开发者调用 `setExtensionProperty` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之间的序列化和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "ExtensionVideoFilter.h"
#include "../logutils.h"
#include <sstream>

namespace agora {
    namespace extension {

        ExtensionVideoFilter::ExtensionVideoFilter(agora_refptr < ByteDanceProcessor > byteDanceProcessor): threadPool_(1) {
            byteDanceProcessor_ = byteDanceProcessor;
        }

        ExtensionVideoFilter::~ExtensionVideoFilter() {
            byteDanceProcessor_ -> releaseOpenGL();
        }

        // 设置 SDK 与视频插件的交互模式。
        void ExtensionVideoFilter::getProcessMode(ProcessMode & mode, bool & independent_thread) {
            mode = ProcessMode::kSync;
            independent_thread = false;
            mode_ = mode;
        }

        // 设置待处理视频数据的类型和格式。
        void ExtensionVideoFilter::getVideoFormatWanted(rtc::VideoFrameData::Type & type,
            rtc::RawPixelBuffer::Format & format) {
            type = rtc::VideoFrameData::Type::kRawPixels;
            format = rtc::RawPixelBuffer::Format::kI420;
        }

        // 在 start 回调中保留 Control 对象并初始化 OpenGL
        int ExtensionVideoFilter::start(agora::agora_refptr < Control > control) {
            PRINTF_INFO("ExtensionVideoFilter::start");
            if (!byteDanceProcessor_) {
                return -1;
            }
            if (control) {
                control_ = control;
                byteDanceProcessor_ -> setExtensionControl(control);
            }
            if (mode_ == ProcessMode::kAsync) {
                invoker_id = threadPool_.RegisterInvoker("thread_videofilter");
                auto res = threadPool_.PostTaskWithRes(invoker_id, [byteDanceProcessor = byteDanceProcessor_] {
                    return byteDanceProcessor -> initOpenGL();
                });
                isInitOpenGL = res.get();
            } else {
                isInitOpenGL = byteDanceProcessor_ -> initOpenGL();
            }
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int ExtensionVideoFilter::stop() {
            PRINTF_INFO("ExtensionVideoFilter::stop");
            if (byteDanceProcessor_) {
                byteDanceProcessor_ -> releaseOpenGL();
                isInitOpenGL = false;
            }
            return 0;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::pendVideoFrame(agora::agora_refptr < rtc::IVideoFrame > frame) {
            if (!frame || !isInitOpenGL) {
                return kBypass;
            }

            bool isAsyncMode = (mode_ == ProcessMode::kAsync);
            if (isAsyncMode && byteDanceProcessor_ && control_ && invoker_id >= 0) {
                threadPool_.PostTask(invoker_id, [videoFrame = frame, byteDanceProcessor = byteDanceProcessor_, control = control_] {
                    rtc::VideoFrameData srcData;
                    videoFrame -> getVideoFrameData(srcData);
                    byteDanceProcessor -> processFrame(srcData);
                    control -> deliverVideoFrame(videoFrame);
                });
                return kSuccess;
            }
            return kBypass;
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::adaptVideoFrame(agora::agora_refptr < rtc::IVideoFrame > src,
            agora::agora_refptr < rtc::IVideoFrame > & dst) {
            if (!isInitOpenGL) {
                return kBypass;
            }
            bool isSyncMode = (mode_ == ProcessMode::kSync);
            if (isSyncMode && byteDanceProcessor_) {
                rtc::VideoFrameData srcData;
                src -> getVideoFrameData(srcData);
                byteDanceProcessor_ -> processFrame(srcData);
                dst = src;
                return kSuccess;
            }
            return kBypass;
        }

        // 设置视频插件属性。
        int ExtensionVideoFilter::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            PRINTF_INFO("setProperty  %s  %s", key, buf);
            std::string stringParameter((char * ) buf);
            byteDanceProcessor_ -> setParameters(stringParameter);
            return 0;
        }

        // 获取视频插件属性
        int ExtensionVideoFilter::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }
    }
}
```

### 封装插件 [#封装插件]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions)
- [`createVideoFilter`](#createvideofilter)

`IExtensionProvider` 是插件动态库的提供者，由全局唯一的名字标识。每一个插件动态库可以提供一个或多个有不同功能的插件，如视频前处理插件库可以提供不同功能的视频插件（Video filter）。每个插件是一个具体的 Extension 对象。

#### enumerateExtensions [#enumerateextensions]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数 | 描述 |
| --- | --- |
| `extension_list` | [video-filter-android-ios-macos-windows-html-2-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-2-1-1) |
| `extension_count` | 支持封装的插件的总数量。 |

<a id="table-slot-video-filter-android-ios-macos-windows-html-2-1-1"></a>

**video-filter-android-ios-macos-windows-html-2-1-1 details**

插件的信息，包括插件类型和插件名称。支持的视频插件类型有：

- `VIDEO_PRE_PROCESSING_FILTER`（上行）
- `VIDEO_POST_PROCESSING_FILTER`（下行）

插件名由插件服务商指定，需要在 `IExtensionProvider` 对象内唯一。插件名为 String 型，支持如下字符：

- 26 个小写字母 `a`~`z`
- 数字和下划线（不能是第一个或最后一个）
如果插件名不符合规则，会无法通过编译。

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 已废弃，请改用 AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER 或 AUDIO_POST_PROCESSING_FILTER
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 用于处理本地采集（如耳返采集）的，用于本地播放的音频数据
    AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER = 10000,
    // 经过 3A 算法后的本地音频数据后处理
    AUDIO_POST_PROCESSING_FILTER = 10001,
    // 用于对远端用户的音频数据进行处理
    AUDIO_REMOTE_USER_PLAYBACK_FILTER = 10002,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-1]

参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#include "ExtensionProvider.h"
#include "../logutils.h"
#include "VideoProcessor.h"
#include "plugin_source_code/JniHelper.h"

namespace agora {
    namespace extension {
        ExtensionProvider::ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider create");
            byteDanceProcessor_ = new agora::RefCountedObject < ByteDanceProcessor > ();
            audioProcessor_ = new agora::RefCountedObject < AdjustVolumeAudioProcessor > ();
        }

        ExtensionProvider::~ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider destroy");
            byteDanceProcessor_.reset();
            audioProcessor_.reset();
        }

        // 提供所有支持封装的插件的信息。
        void ExtensionProvider::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "YourExtensionName";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < agora::rtc::IExtensionVideoFilter > ExtensionProvider::createVideoFilter(const char * name) {
            PRINTF_INFO("ExtensionProvider::createVideoFilter %s", name);
            auto videoFilter = new agora::RefCountedObject < agora::extension::ExtensionVideoFilter > (byteDanceProcessor_);
            return videoFilter;
        }

        void ExtensionProvider::setExtensionControl(rtc::IExtensionControl * control) {}
    }
}
```

## 打包视频插件 [#打包视频插件]

完成插件开发后，你需要对其进行注册、打包，并将最终的 `.aar` 或 `.so` 文件，连同一个包含了插件名称、服务商名称和 Filter 名称的文件提交给声网进行验证。

### 注册插件 [#注册插件]

插件通过宏 `REGISTER_AGORA_EXTENSION_PROVIDER` 进行注册，该宏位于 `AgoraExtensionProviderEntry.h` 文件中。

你需要在插件的入口使用这个宏。SDK 在加载插件时，该宏会自动向 SDK 注册你的插件。注意填入 `PROVIDER_NAME` 时不要填写标点符号。示例：

```cpp
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::extension::ExtensionProvider);
```

在 `CMakeLists.txt` 文件中，按照下表指定 SDK 包中 `libagora-rtc-sdk-jni.so` 文件的存储路径：

| 依赖文件 | 存储路径 |
| ---------------- | ---------------- |
| 64 位的 `libagora-rtc-sdk-jni.so`      | `AgoraWithByteDanceAndroid/agora-bytedance/src/main/agoraLibs/arm64-v8a`     |
| 32 位的 `libagora-rtc-sdk-jni.so` | `AgoraWithByteDanceAndroid/agora-bytedance/src/main/agoraLibs/arm64-v7a` |

### 提供插件信息 [#提供插件信息]

新建一个 `.java` 或 `.md` 文件，并在其中填入如下信息：

- `EXTENSION_NAME`：插件包的名称，如 `libagora-bytedance.so` 文件的 `EXTENSION_NAME` 就是 `agora-bytedance`。
- `EXTENSION_VENDOR_NAME`：服务商名称，即插件的 `Provider` 对象名称，用于在 `.cpp` 文件中进行注册。
- `EXTENSION_FILTER_NAME`：`ExtensionProvider.h` 文件中定义的 Filter 名称。

## 示例项目 [#示例项目]

声网提供一个 Android 插件开发的示例项目 [agora-simple-filter](https://github.com/AgoraIO/API-Examples/tree/main/Android/APIExample/agora-simple-filter) 供你参考。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
本文展示如何使用声网 SDK 提供的 API 开发一个视频插件。

开发视频插件需要使用以下接口：

- `IExtensionVideoFilter`：实现视频数据的处理能力，包括接收、处理和返回处理完的视频帧。
- `IExtensionProvider`：将视频数据的处理能力封装为插件。

## 前提条件 [#前提条件-1]

开发前，请确保你的开发环境满足以下要求：

- Xcode 9.0 或以上版本。
- iOS 9.0 或以上版本的设备。

## 准备开发环境 [#准备开发环境-1]

参考如下步骤将声网云市场的 API 添加到你的项目中：

1. 下载[视频 SDK v4.x](/zh-CN/realtime-media/rtc/reference/downloads)，然后解压下载的 SDK 包。
2. 打开 **Xcode**，进入 **TARGETS** > **Project Name** > **General** > **Frameworks, Libraries, and Embedded Content** 菜单，点击 **+** > **Add Other...** > **Add Files**，将安装包中的 `/libs/AgoraRtcKit.framework` 文件添加至你的项目中。确保 **Embed** 属性设置为 **Do Not Embed**。

## 开发视频插件 [#开发视频插件-1]

### 实现插件 [#实现插件-1]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode-1)
- [`start`](#start-1)
- [`stop`](#stop-1)
- [`getVideoFormatWanted`](#getvideoformatwanted-1)
- [`adaptVideoFrame`](#adaptvideoframe-1)
- [`pendVideoFrame`](#pendvideoframe-1)
- [`deliverVideoFrame`（见 Control 定义）](#start-1)
- [`setProperty`](#setproperty-1)
- [`getProperty`](#getproperty-1)

#### getProcessMode [#getprocessmode-1]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-0-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-1-1-1) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-0-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-2-1-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-1-1-1"></a>

**video-filter-android-ios-macos-windows-html-0-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 `deliverVideoFrame` 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-2-1-1"></a>

**video-filter-android-ios-macos-windows-html-0-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以根据 YUV 运算的复杂度和是否使用 OpenGL 设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
- 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start-1]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop-1]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted-1]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-1-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-1-2-1-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-1-2-1-1"></a>

**video-filter-android-ios-macos-windows-html-1-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe-1]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe-1]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty-1]

设置视频插件属性。App 开发者调用 `setExtensionPropertyWithVendor` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之间的序列化和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty-1]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码-2]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "ExtensionVideoFilter.h"
#include "../logutils.h"
#include <sstream>

namespace agora {
    namespace extension {

        ExtensionVideoFilter::ExtensionVideoFilter(agora_refptr < ByteDanceProcessor > byteDanceProcessor): threadPool_(1) {
            byteDanceProcessor_ = byteDanceProcessor;
        }

        ExtensionVideoFilter::~ExtensionVideoFilter() {
            byteDanceProcessor_ -> releaseOpenGL();
        }

        // 设置 SDK 与视频插件的交互模式。
        void ExtensionVideoFilter::getProcessMode(ProcessMode & mode, bool & independent_thread) {
            mode = ProcessMode::kSync;
            independent_thread = false;
            mode_ = mode;
        }

        // 设置待处理视频数据的类型和格式。
        void ExtensionVideoFilter::getVideoFormatWanted(rtc::VideoFrameData::Type & type,
            rtc::RawPixelBuffer::Format & format) {
            type = rtc::VideoFrameData::Type::kRawPixels;
            format = rtc::RawPixelBuffer::Format::kI420;
        }

        // 在 start 回调中保留 Control 对象并初始化 OpenGL
        int ExtensionVideoFilter::start(agora::agora_refptr < Control > control) {
            PRINTF_INFO("ExtensionVideoFilter::start");
            if (!byteDanceProcessor_) {
                return -1;
            }
            if (control) {
                control_ = control;
                byteDanceProcessor_ -> setExtensionControl(control);
            }
            if (mode_ == ProcessMode::kAsync) {
                invoker_id = threadPool_.RegisterInvoker("thread_videofilter");
                auto res = threadPool_.PostTaskWithRes(invoker_id, [byteDanceProcessor = byteDanceProcessor_] {
                    return byteDanceProcessor -> initOpenGL();
                });
                isInitOpenGL = res.get();
            } else {
                isInitOpenGL = byteDanceProcessor_ -> initOpenGL();
            }
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int ExtensionVideoFilter::stop() {
            PRINTF_INFO("ExtensionVideoFilter::stop");
            if (byteDanceProcessor_) {
                byteDanceProcessor_ -> releaseOpenGL();
                isInitOpenGL = false;
            }
            return 0;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::pendVideoFrame(agora::agora_refptr < rtc::IVideoFrame > frame) {
            if (!frame || !isInitOpenGL) {
                return kBypass;
            }

            bool isAsyncMode = (mode_ == ProcessMode::kAsync);
            if (isAsyncMode && byteDanceProcessor_ && control_ && invoker_id >= 0) {
                threadPool_.PostTask(invoker_id, [videoFrame = frame, byteDanceProcessor = byteDanceProcessor_, control = control_] {
                    rtc::VideoFrameData srcData;
                    videoFrame -> getVideoFrameData(srcData);
                    byteDanceProcessor -> processFrame(srcData);
                    control -> deliverVideoFrame(videoFrame);
                });
                return kSuccess;
            }
            return kBypass;
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::adaptVideoFrame(agora::agora_refptr < rtc::IVideoFrame > src,
            agora::agora_refptr < rtc::IVideoFrame > & dst) {
            if (!isInitOpenGL) {
                return kBypass;
            }
            bool isSyncMode = (mode_ == ProcessMode::kSync);
            if (isSyncMode && byteDanceProcessor_) {
                rtc::VideoFrameData srcData;
                src -> getVideoFrameData(srcData);
                byteDanceProcessor_ -> processFrame(srcData);
                dst = src;
                return kSuccess;
            }
            return kBypass;
        }

        // 设置视频插件属性。
        int ExtensionVideoFilter::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            PRINTF_INFO("setProperty  %s  %s", key, buf);
            std::string stringParameter((char * ) buf);
            byteDanceProcessor_ -> setParameters(stringParameter);
            return 0;
        }

        // 获取视频插件属性
        int ExtensionVideoFilter::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }
    }
}
```

### 封装插件 [#封装插件-1]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions-1)
- [`createVideoFilter`](#createvideofilter-1)

`IExtensionProvider` 是插件动态库的提供者，由全局唯一的名字标识。每一个插件动态库可以提供一个或多个有不同功能的插件，如视频前处理插件库可以提供不同功能的视频插件（Video filter）。每个插件是一个具体的 Extension 对象。

#### enumerateExtensions [#enumerateextensions-1]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数 | 描述 |
| --- | --- |
| `extension_list` | [video-filter-android-ios-macos-windows-html-2-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-2-1-1-1) |
| `extension_count` | 支持封装的插件的总数量。 |

<a id="table-slot-video-filter-android-ios-macos-windows-html-2-1-1-1"></a>

**video-filter-android-ios-macos-windows-html-2-1-1 details**

插件的信息，包括插件类型和插件名称。支持的视频插件类型有：

- `VIDEO_PRE_PROCESSING_FILTER`（上行）
- `VIDEO_POST_PROCESSING_FILTER`（下行）

插件名由插件服务商指定，需要在 `IExtensionProvider` 对象内唯一。插件名为 String 型，支持如下字符：

- 26 个小写字母 `a`~`z`
- 数字和下划线（不能是第一个或最后一个）
如果插件名不符合规则，会无法通过编译。

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 已废弃，请改用 AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER 或 AUDIO_POST_PROCESSING_FILTER
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 用于处理本地采集（如耳返采集）的，用于本地播放的音频数据
    AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER = 10000,
    // 经过 3A 算法后的本地音频数据后处理
    AUDIO_POST_PROCESSING_FILTER = 10001,
    // 用于对远端用户的音频数据进行处理
    AUDIO_REMOTE_USER_PLAYBACK_FILTER = 10002,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter-1]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-3]

参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#include "ExtensionProvider.h"
#include "../logutils.h"
#include "VideoProcessor.h"
#include "plugin_source_code/JniHelper.h"

namespace agora {
    namespace extension {
        ExtensionProvider::ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider create");
            byteDanceProcessor_ = new agora::RefCountedObject < ByteDanceProcessor > ();
            audioProcessor_ = new agora::RefCountedObject < AdjustVolumeAudioProcessor > ();
        }

        ExtensionProvider::~ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider destroy");
            byteDanceProcessor_.reset();
            audioProcessor_.reset();
        }

        // 提供所有支持封装的插件的信息。
        void ExtensionProvider::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "YourExtensionName";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < agora::rtc::IExtensionVideoFilter > ExtensionProvider::createVideoFilter(const char * name) {
            PRINTF_INFO("ExtensionProvider::createVideoFilter %s", name);
            auto videoFilter = new agora::RefCountedObject < agora::extension::ExtensionVideoFilter > (byteDanceProcessor_);
            return videoFilter;
        }

        void ExtensionProvider::setExtensionControl(rtc::IExtensionControl * control) {}
    }
}
```

### 实现插件 [#实现插件-2]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode-2)
- [`start`](#start-2)
- [`stop`](#stop-2)
- [`getVideoFormatWanted`](#getvideoformatwanted-2)
- [`adaptVideoFrame`](#adaptvideoframe-2)
- [`pendVideoFrame`](#pendvideoframe-2)
- [`deliverVideoFrame`（见 Control 定义）](#start-2)
- [`setProperty`](#setproperty-2)
- [`getProperty`](#getproperty-2)

#### getProcessMode [#getprocessmode-2]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-3-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-3-1-1) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-3-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-3-2-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-3-1-1"></a>

**video-filter-android-ios-macos-windows-html-3-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 deliverVideoFrame 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-3-2-1"></a>

**video-filter-android-ios-macos-windows-html-3-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以参考以下标准设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度和是否使用 OpenGL 设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
  - 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start-2]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop-2]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted-2]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-4-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-4-2-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-4-2-1"></a>

**video-filter-android-ios-macos-windows-html-4-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe-2]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe-2]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty-2]

设置视频插件属性。App 开发者调用 `setExtensionPropertyWithVendor` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之前的序列和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty-2]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码-4]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "BDVideoFilterCpp.h"
#include "BDVideoFilterCpp.h"

namespace agora {
    namespace rtc {
        BDVideoFilterCpp::BDVideoFilterCpp(): mode_(ProcessMode::kSync), opengl_released_(false) {

        }

        BDVideoFilterCpp::~BDVideoFilterCpp() {
            releaseOpenGL();
            opengl_released_ = true;
            delete processor_;
            processor_ = nullptr;
        }

        // 设置 SDK 与视频插件的交互模式。
        void BDVideoFilterCpp::getProcessMode(ProcessMode & mode, bool & isolated) {
            mode = ProcessMode::kSync;
            isolated = false;
            mode_ = mode;
        }

        // 在 start 回调中传递 Control 对象和初始化 OpenGL
        int BDVideoFilterCpp::start(agora::agora_refptr < Control > control) {
            if (control) {
                control_ = control;
            }

            processor_ = new ByteDance::Extension::BDProcessorCpp(control_);
            initOpenGL();
            makeCurrent();
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int BDVideoFilterCpp::stop() {
            releaseOpenGL();
            opengl_released_ = true;
            return 0;
        }

        // 设置待处理视频数据的类型和格式。
        void BDVideoFilterCpp::getVideoFormatWanted(VideoFrameData::Type & type,
            RawPixelBuffer::Format & format) {
            type = VideoFrameData::Type::kRawPixels;
            format = RawPixelBuffer::Format::kI420;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        IExtensionVideoFilter::ProcessResult BDVideoFilterCpp::pendVideoFrame(agora::agora_refptr < IVideoFrame > frame) {
            @autoreleasepool {
                if (!frame) {
                    return kBypass;
                }

                bool isAsyncMode = (mode_ == ProcessMode::kAsync);
                if (isAsyncMode) {
                    if (processor_ && control_) {
                        VideoFrameData data;
                        frame -> getVideoFrameData(data);
                        processor_ -> processFrame(data);

                        auto dst = control_ -> getMemoryPool() -> createVideoFrame(data);
                        control_ -> deliverVideoFrame(dst);
                        return kSuccess;
                    }
                    return kBypass;
                }
                return kBypass;
            }
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        IExtensionVideoFilter::ProcessResult BDVideoFilterCpp::adaptVideoFrame(agora::agora_refptr < IVideoFrame > src,
            agora::agora_refptr < IVideoFrame > & dst) {
            @autoreleasepool {
                bool isSyncMode = (mode_ == ProcessMode::kSync);
                if (isSyncMode) {
                    if (processor_ && control_) {
                        VideoFrameData srcData;
                        src -> getVideoFrameData(srcData);
                        processor_ -> processFrame(srcData);
                        dst = control_ -> getMemoryPool() -> createVideoFrame(srcData);
                        return kSuccess;
                    }

                    return kBypass;
                }
                return kBypass;
            }
        }

        // 设置视频插件属性。
        int BDVideoFilterCpp::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            @autoreleasepool {
                if (!buf || !key || * key == 0) {
                    return -1;
                }
                if (!processor_) {
                    return -1;
                }
                std::string param((char * ) buf);
                processor_ -> setParameters(param);
                return 0;
            }
        }

        // 获取视频插件属性
        int BDVideoFilterCpp::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }

        void BDVideoFilterCpp::initOpenGL() {
            if (_context == nil) {
                _context = [
                    [EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2
                ];
            }
            [EAGLContext setCurrentContext: _context];
        }

        void BDVideoFilterCpp::releaseOpenGL() {
            [EAGLContext setCurrentContext: nil];
            _context = nil;
        }

        void BDVideoFilterCpp::makeCurrent() {
            EAGLContext * prev = [EAGLContext currentContext];
            if (prev != _context) {
                [EAGLContext setCurrentContext: _context];
            }
        }

    }
}
```

### 封装插件 [#封装插件-2]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions-2)
- [ `createVideoFilter`](#createvideofilter-2)

#### enumerateExtensions [#enumerateextensions-2]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数             | 描述                                 |
| :--------------- | :----------------------------------- |
| `extension_list` | 插件的信息，包括插件类型和插件名称。 |
| `extension_count`  | 支持封装的插件的总数量。             |

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 音频处理插件
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 预留参数
    UNKNOWN,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter-2]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-5]
参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#import "BDVideoFilterProviderCpp.h"
#include "BDVideoFilterCpp.h"
#include <AgoraRtcKit/AgoraRefCountedObject.h>
#include <AgoraRtcKit/NGIAgoraExtensionControl.h>
#include <AgoraRtcKit/AgoraExtensionProviderEntry.h>

// 注册视频插件
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::rtc::BDVideoFilterProviderCpp);

namespace agora {
    namespace rtc {
        BDVideoFilterProviderCpp::BDVideoFilterProviderCpp(): control_(nullptr) {

        }

        BDVideoFilterProviderCpp::~BDVideoFilterProviderCpp() {
            control_ = nullptr;
        }

        // 提供所有支持封装的插件的信息。
        void BDVideoFilterProviderCpp::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "Beauty";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < IExtensionVideoFilter > BDVideoFilterProviderCpp::createVideoFilter(const char * name) {
            return new RefCountedObject < BDVideoFilterCpp > ();
        }

        int BDVideoFilterProviderCpp::log(int level,
            const char * message) {
            if (!control_) {
                return -1;
            }

            return control_ -> log(static_cast < commons::LOG_LEVEL > (level), message);
        }

        int BDVideoFilterProviderCpp::fireEvent(const char * provider_name,
            const char * ext_name,
                const char * event_key,
                    const char * event_json_str) {
            if (!control_) {
                return -1;
            }
            return control_ -> fireEvent(provider_name, ext_name, event_key, event_json_str);
        }

    }
}
```

## 打包视频插件 [#打包视频插件-1]

完成插件开发后，你需要对其进行注册、打包，并将最终的 `.framework` 或 `.xcframework` 文件，连同一个包含了插件名称、服务商名称和 Filter 名称的文件提交给声网进行验证。

插件通过宏 `REGISTER_AGORA_EXTENSION_PROVIDER` 进行注册，该宏位于 `AgoraExtensionProviderEntry.h` 文件中。

你需要在插件的入口使用这个宏。SDK 在加载插件时，该宏会自动向 SDK 注册你的插件。注意填入 `PROVIDER_NAME` 时不要填写标点符号。示例：

```cpp
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::extension::ExtensionProvider);
```

## 示例项目 [#示例项目-1]

声网提供一个 iOS 插件开发的示例项目 [SimpleFilter](https://github.com/AgoraIO/API-Examples/tree/4.0.0-GA/iOS/SimpleFilter) 供你参考。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
本文展示如何使用声网 SDK 提供的 API 开发一个视频插件。

开发视频插件需要使用以下接口：

- `IExtensionVideoFilter`：实现视频数据的处理能力，包括接收、处理和返回处理完的视频帧。
- `IExtensionProvider`：将视频数据的处理能力封装为插件。

## 前提条件 [#前提条件-2]

开发前，请确保你的开发环境满足以下要求：

- Xcode 9.0 或以上版本。
- macOS 10.10 或以上版本的设备。

## 准备开发环境 [#准备开发环境-2]

参考如下步骤将声网云市场的 API 添加到你的项目中：

1. 下载[视频 SDK v4.x](/zh-CN/realtime-media/rtc/reference/downloads)，然后解压下载的 SDK 包。
2. 打开 **Xcode**，进入 **TARGETS** > **Project Name** > **General** > **Frameworks, Libraries, and Embedded Content** 菜单，点击 **+** > **Add Other...** > **Add Files**，将安装包中的 `/libs/AgoraRtcKit.framework` 文件添加至你的项目中。确保 **Embed** 属性设置为 **Do Not Embed**。

## 开发视频插件 [#开发视频插件-2]

### 实现插件 [#实现插件-3]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode-3)
- [`start`](#start-3)
- [`stop`](#stop-3)
- [`getVideoFormatWanted`](#getvideoformatwanted-3)
- [`adaptVideoFrame`](#adaptvideoframe-3)
- [`pendVideoFrame`](#pendvideoframe-3)
- [`deliverVideoFrame`（见 Control 定义）](#start-3)
- [`setProperty`](#setproperty-3)
- [`getProperty`](#getproperty-3)

#### getProcessMode [#getprocessmode-3]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-0-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-1-1-2) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-0-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-2-1-2) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-1-1-2"></a>

**video-filter-android-ios-macos-windows-html-0-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 `deliverVideoFrame` 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-2-1-2"></a>

**video-filter-android-ios-macos-windows-html-0-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以根据 YUV 运算的复杂度和是否使用 OpenGL 设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
- 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start-3]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop-3]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted-3]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-1-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-1-2-1-2) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-1-2-1-2"></a>

**video-filter-android-ios-macos-windows-html-1-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe-3]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe-3]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty-3]

设置视频插件属性。App 开发者调用 `setExtensionPropertyWithVendor` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之间的序列化和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty-3]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码-6]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "ExtensionVideoFilter.h"
#include "../logutils.h"
#include <sstream>

namespace agora {
    namespace extension {

        ExtensionVideoFilter::ExtensionVideoFilter(agora_refptr < ByteDanceProcessor > byteDanceProcessor): threadPool_(1) {
            byteDanceProcessor_ = byteDanceProcessor;
        }

        ExtensionVideoFilter::~ExtensionVideoFilter() {
            byteDanceProcessor_ -> releaseOpenGL();
        }

        // 设置 SDK 与视频插件的交互模式。
        void ExtensionVideoFilter::getProcessMode(ProcessMode & mode, bool & independent_thread) {
            mode = ProcessMode::kSync;
            independent_thread = false;
            mode_ = mode;
        }

        // 设置待处理视频数据的类型和格式。
        void ExtensionVideoFilter::getVideoFormatWanted(rtc::VideoFrameData::Type & type,
            rtc::RawPixelBuffer::Format & format) {
            type = rtc::VideoFrameData::Type::kRawPixels;
            format = rtc::RawPixelBuffer::Format::kI420;
        }

        // 在 start 回调中保留 Control 对象并初始化 OpenGL
        int ExtensionVideoFilter::start(agora::agora_refptr < Control > control) {
            PRINTF_INFO("ExtensionVideoFilter::start");
            if (!byteDanceProcessor_) {
                return -1;
            }
            if (control) {
                control_ = control;
                byteDanceProcessor_ -> setExtensionControl(control);
            }
            if (mode_ == ProcessMode::kAsync) {
                invoker_id = threadPool_.RegisterInvoker("thread_videofilter");
                auto res = threadPool_.PostTaskWithRes(invoker_id, [byteDanceProcessor = byteDanceProcessor_] {
                    return byteDanceProcessor -> initOpenGL();
                });
                isInitOpenGL = res.get();
            } else {
                isInitOpenGL = byteDanceProcessor_ -> initOpenGL();
            }
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int ExtensionVideoFilter::stop() {
            PRINTF_INFO("ExtensionVideoFilter::stop");
            if (byteDanceProcessor_) {
                byteDanceProcessor_ -> releaseOpenGL();
                isInitOpenGL = false;
            }
            return 0;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::pendVideoFrame(agora::agora_refptr < rtc::IVideoFrame > frame) {
            if (!frame || !isInitOpenGL) {
                return kBypass;
            }

            bool isAsyncMode = (mode_ == ProcessMode::kAsync);
            if (isAsyncMode && byteDanceProcessor_ && control_ && invoker_id >= 0) {
                threadPool_.PostTask(invoker_id, [videoFrame = frame, byteDanceProcessor = byteDanceProcessor_, control = control_] {
                    rtc::VideoFrameData srcData;
                    videoFrame -> getVideoFrameData(srcData);
                    byteDanceProcessor -> processFrame(srcData);
                    control -> deliverVideoFrame(videoFrame);
                });
                return kSuccess;
            }
            return kBypass;
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::adaptVideoFrame(agora::agora_refptr < rtc::IVideoFrame > src,
            agora::agora_refptr < rtc::IVideoFrame > & dst) {
            if (!isInitOpenGL) {
                return kBypass;
            }
            bool isSyncMode = (mode_ == ProcessMode::kSync);
            if (isSyncMode && byteDanceProcessor_) {
                rtc::VideoFrameData srcData;
                src -> getVideoFrameData(srcData);
                byteDanceProcessor_ -> processFrame(srcData);
                dst = src;
                return kSuccess;
            }
            return kBypass;
        }

        // 设置视频插件属性。
        int ExtensionVideoFilter::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            PRINTF_INFO("setProperty  %s  %s", key, buf);
            std::string stringParameter((char * ) buf);
            byteDanceProcessor_ -> setParameters(stringParameter);
            return 0;
        }

        // 获取视频插件属性
        int ExtensionVideoFilter::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }
    }
}
```

### 封装插件 [#封装插件-3]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions-3)
- [`createVideoFilter`](#createvideofilter-3)

`IExtensionProvider` 是插件动态库的提供者，由全局唯一的名字标识。每一个插件动态库可以提供一个或多个有不同功能的插件，如视频前处理插件库可以提供不同功能的视频插件（Video filter）。每个插件是一个具体的 Extension 对象。

#### enumerateExtensions [#enumerateextensions-3]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数 | 描述 |
| --- | --- |
| `extension_list` | [video-filter-android-ios-macos-windows-html-2-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-2-1-1-2) |
| `extension_count` | 支持封装的插件的总数量。 |

<a id="table-slot-video-filter-android-ios-macos-windows-html-2-1-1-2"></a>

**video-filter-android-ios-macos-windows-html-2-1-1 details**

插件的信息，包括插件类型和插件名称。支持的视频插件类型有：

- `VIDEO_PRE_PROCESSING_FILTER`（上行）
- `VIDEO_POST_PROCESSING_FILTER`（下行）

插件名由插件服务商指定，需要在 `IExtensionProvider` 对象内唯一。插件名为 String 型，支持如下字符：

- 26 个小写字母 `a`~`z`
- 数字和下划线（不能是第一个或最后一个）
如果插件名不符合规则，会无法通过编译。

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 已废弃，请改用 AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER 或 AUDIO_POST_PROCESSING_FILTER
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 用于处理本地采集（如耳返采集）的，用于本地播放的音频数据
    AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER = 10000,
    // 经过 3A 算法后的本地音频数据后处理
    AUDIO_POST_PROCESSING_FILTER = 10001,
    // 用于对远端用户的音频数据进行处理
    AUDIO_REMOTE_USER_PLAYBACK_FILTER = 10002,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter-3]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-7]

参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#include "ExtensionProvider.h"
#include "../logutils.h"
#include "VideoProcessor.h"
#include "plugin_source_code/JniHelper.h"

namespace agora {
    namespace extension {
        ExtensionProvider::ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider create");
            byteDanceProcessor_ = new agora::RefCountedObject < ByteDanceProcessor > ();
            audioProcessor_ = new agora::RefCountedObject < AdjustVolumeAudioProcessor > ();
        }

        ExtensionProvider::~ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider destroy");
            byteDanceProcessor_.reset();
            audioProcessor_.reset();
        }

        // 提供所有支持封装的插件的信息。
        void ExtensionProvider::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "YourExtensionName";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < agora::rtc::IExtensionVideoFilter > ExtensionProvider::createVideoFilter(const char * name) {
            PRINTF_INFO("ExtensionProvider::createVideoFilter %s", name);
            auto videoFilter = new agora::RefCountedObject < agora::extension::ExtensionVideoFilter > (byteDanceProcessor_);
            return videoFilter;
        }

        void ExtensionProvider::setExtensionControl(rtc::IExtensionControl * control) {}
    }
}
```

### 实现插件 [#实现插件-4]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode-4)
- [`start`](#start-4)
- [`stop`](#stop-4)
- [`getVideoFormatWanted`](#getvideoformatwanted-4)
- [`adaptVideoFrame`](#adaptvideoframe-4)
- [`pendVideoFrame`](#pendvideoframe-4)
- [`deliverVideoFrame`（见 Control 定义）](#start-4)
- [`setProperty`](#setproperty-4)
- [`getProperty`](#getproperty-4)

#### getProcessMode [#getprocessmode-4]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-3-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-3-1-1-1) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-3-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-3-2-1-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-3-1-1-1"></a>

**video-filter-android-ios-macos-windows-html-3-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 deliverVideoFrame 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-3-2-1-1"></a>

**video-filter-android-ios-macos-windows-html-3-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以参考以下标准设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度和是否使用 OpenGL 设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
  - 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start-4]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop-4]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted-4]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-4-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-4-2-1-1) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-4-2-1-1"></a>

**video-filter-android-ios-macos-windows-html-4-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe-4]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe-4]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty-4]

设置视频插件属性。App 开发者调用 `setExtensionPropertyWithVendor` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之前的序列和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty-4]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码-8]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "BDVideoFilterCpp.h"
#include "BDVideoFilterCpp.h"

namespace agora {
    namespace rtc {
        BDVideoFilterCpp::BDVideoFilterCpp(): mode_(ProcessMode::kSync), opengl_released_(false) {

        }

        BDVideoFilterCpp::~BDVideoFilterCpp() {
            releaseOpenGL();
            opengl_released_ = true;
            delete processor_;
            processor_ = nullptr;
        }

        // 设置 SDK 与视频插件的交互模式。
        void BDVideoFilterCpp::getProcessMode(ProcessMode & mode, bool & isolated) {
            mode = ProcessMode::kSync;
            isolated = false;
            mode_ = mode;
        }

        // 在 start 回调中传递 Control 对象和初始化 OpenGL
        int BDVideoFilterCpp::start(agora::agora_refptr < Control > control) {
            if (control) {
                control_ = control;
            }

            processor_ = new ByteDance::Extension::BDProcessorCpp(control_);
            initOpenGL();
            makeCurrent();
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int BDVideoFilterCpp::stop() {
            releaseOpenGL();
            opengl_released_ = true;
            return 0;
        }

        // 设置待处理视频数据的类型和格式。
        void BDVideoFilterCpp::getVideoFormatWanted(VideoFrameData::Type & type,
            RawPixelBuffer::Format & format) {
            type = VideoFrameData::Type::kRawPixels;
            format = RawPixelBuffer::Format::kI420;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        IExtensionVideoFilter::ProcessResult BDVideoFilterCpp::pendVideoFrame(agora::agora_refptr < IVideoFrame > frame) {
            @autoreleasepool {
                if (!frame) {
                    return kBypass;
                }

                bool isAsyncMode = (mode_ == ProcessMode::kAsync);
                if (isAsyncMode) {
                    if (processor_ && control_) {
                        VideoFrameData data;
                        frame -> getVideoFrameData(data);
                        processor_ -> processFrame(data);

                        auto dst = control_ -> getMemoryPool() -> createVideoFrame(data);
                        control_ -> deliverVideoFrame(dst);
                        return kSuccess;
                    }
                    return kBypass;
                }
                return kBypass;
            }
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        IExtensionVideoFilter::ProcessResult BDVideoFilterCpp::adaptVideoFrame(agora::agora_refptr < IVideoFrame > src,
            agora::agora_refptr < IVideoFrame > & dst) {
            @autoreleasepool {
                bool isSyncMode = (mode_ == ProcessMode::kSync);
                if (isSyncMode) {
                    if (processor_ && control_) {
                        VideoFrameData srcData;
                        src -> getVideoFrameData(srcData);
                        processor_ -> processFrame(srcData);
                        dst = control_ -> getMemoryPool() -> createVideoFrame(srcData);
                        return kSuccess;
                    }

                    return kBypass;
                }
                return kBypass;
            }
        }

        // 设置视频插件属性。
        int BDVideoFilterCpp::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            @autoreleasepool {
                if (!buf || !key || * key == 0) {
                    return -1;
                }
                if (!processor_) {
                    return -1;
                }
                std::string param((char * ) buf);
                processor_ -> setParameters(param);
                return 0;
            }
        }

        // 获取视频插件属性
        int BDVideoFilterCpp::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }

        void BDVideoFilterCpp::initOpenGL() {
            if (_context == nil) {
                _context = [
                    [EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2
                ];
            }
            [EAGLContext setCurrentContext: _context];
        }

        void BDVideoFilterCpp::releaseOpenGL() {
            [EAGLContext setCurrentContext: nil];
            _context = nil;
        }

        void BDVideoFilterCpp::makeCurrent() {
            EAGLContext * prev = [EAGLContext currentContext];
            if (prev != _context) {
                [EAGLContext setCurrentContext: _context];
            }
        }

    }
}
```

### 封装插件 [#封装插件-4]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions-4)
- [ `createVideoFilter`](#createvideofilter-4)

#### enumerateExtensions [#enumerateextensions-4]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数             | 描述                                 |
| :--------------- | :----------------------------------- |
| `extension_list` | 插件的信息，包括插件类型和插件名称。 |
| `extension_count`  | 支持封装的插件的总数量。             |

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 音频处理插件
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 预留参数
    UNKNOWN,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter-4]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-9]
参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#import "BDVideoFilterProviderCpp.h"
#include "BDVideoFilterCpp.h"
#include <AgoraRtcKit/AgoraRefCountedObject.h>
#include <AgoraRtcKit/NGIAgoraExtensionControl.h>
#include <AgoraRtcKit/AgoraExtensionProviderEntry.h>

// 注册视频插件
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::rtc::BDVideoFilterProviderCpp);

namespace agora {
    namespace rtc {
        BDVideoFilterProviderCpp::BDVideoFilterProviderCpp(): control_(nullptr) {

        }

        BDVideoFilterProviderCpp::~BDVideoFilterProviderCpp() {
            control_ = nullptr;
        }

        // 提供所有支持封装的插件的信息。
        void BDVideoFilterProviderCpp::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "Beauty";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < IExtensionVideoFilter > BDVideoFilterProviderCpp::createVideoFilter(const char * name) {
            return new RefCountedObject < BDVideoFilterCpp > ();
        }

        int BDVideoFilterProviderCpp::log(int level,
            const char * message) {
            if (!control_) {
                return -1;
            }

            return control_ -> log(static_cast < commons::LOG_LEVEL > (level), message);
        }

        int BDVideoFilterProviderCpp::fireEvent(const char * provider_name,
            const char * ext_name,
                const char * event_key,
                    const char * event_json_str) {
            if (!control_) {
                return -1;
            }
            return control_ -> fireEvent(provider_name, ext_name, event_key, event_json_str);
        }

    }
}
```

## 打包视频插件 [#打包视频插件-2]

完成插件开发后，你需要对其进行注册、打包，并将最终的 `.framework` 或 `.xcframework` 文件，连同一个包含了插件名称、服务商名称和 Filter 名称的文件提交给声网进行验证。

插件通过宏 `REGISTER_AGORA_EXTENSION_PROVIDER` 进行注册，该宏位于 `AgoraExtensionProviderEntry.h` 文件中。

你需要在插件的入口使用这个宏。SDK 在加载插件时，该宏会自动向 SDK 注册你的插件。注意填入 `PROVIDER_NAME` 时不要填写标点符号。示例：

```cpp
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::extension::ExtensionProvider);
```
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
本文展示如何开发一个与声网 Web SDK 4.x 搭配使用的视频插件。

## 技术原理 [#技术原理]

声网为开发视频插件提供以下抽象类：

- 必需的基础类：
  - `Extension`：实现插件的初始化，包括创建插件、设置日志上报、设置事件上报等。
  - `VideoProcessor`：实现视频数据的处理能力，包括接收、处理和返回处理完的视频数据。
- 可选的辅助类：
  - `Ticker`：帮助管理周期性任务。
  - `Logger`：向声网 SDK 上传日志。
  - `Reporter`：向声网 SDK 报告事件。

## 前提条件 [#前提条件-3]

开发前，请确保你的开发环境满足以下要求：

- Windows 或 macOS 计算机，需满足以下要求：
  - 下载声网 Web SDK [支持的浏览器](/zh-CN/realtime-media/rtc/reference/browser-compatibility)。声网强烈推荐使用[最新稳定版 Google Chrome 浏览器](https://www.google.com/chrome/)。
  - 具备物理音视频采集设备。
  - 可连接到互联网。如果你的网络环境部署了防火墙，请参考[应对防火墙限制](/zh-CN/realtime-media/rtc/build/setup-and-access/firewall)以正常使用声网服务。
  - 搭载 2.2 GHz Intel 第二代 i3/i5/i7 处理器或同等性能的其他处理器。
- 安装 [Node.js 及 npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)。

## 准备开发环境 [#准备开发环境-3]

通过 npm 将插件开发模块（[agora-rte-extension](https://www.npmjs.com/package/agora-rte-extension)）集成到你的项目中：

1. 运行以下命令安装：

   ```shell
   npm install --save agora-rte-extension
   ```

2. 在你的 `.js` 文件中加入以下代码导入插件开发模块：

   ```javascript
   import {AudioExtension, AudioProcessor} from 'agora-rte-extension'
   ```

## 开发视频插件 [#开发视频插件-3]

本节详细介绍开发视频插件必须实现的 API。你还可以根据实际需要实现一些辅助类 API，优化插件的设计和性能，详见 [API 参考](#ref)。

### 实现插件初始化 [#实现插件初始化]

插件初始化通过 `Extension` 类实现。你需要实现 `Extension` 类的 `createProcessor` 方法：

```typescript
abstract class Extension<T extends BaseProcessor> {
    createProcessor(): T;
}
```

创建 `VideoProcessor` 实例。

插件使用者调用 `extension.createProcessor` 时，SDK 会调用该方法。你需要在该方法中返回创建好的 `VideoProcessor` 实例。

### 实现视频数据处理 [#实现视频数据处理]

实现视频数据处理包括以下步骤：

1. 接收待处理的视频：通过 `VideoProcessor` 类的 `onTrack` 方法实现。
2. 处理视频：需要你自行实现。
3. 输出处理后的视频：通过 `VideoProcessor` 类的 `output` 方法实现。

#### onTrack [#ontrack]

```typescript
abstract onTrack?(track: MediaStreamTrack, context: IProcessorContext): void;
```

从前一个节点接收到待处理的视频数据回调。

**参数**

- `track`：前一个 `MediaStreamTrack`。`MediaStreamTrack` 是 Web API 提供的接口类，详见 [`MediaStreamTrack`](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack)。

- `context`：当前视频处理管道的 context。

#### output [#output]

```typescript
output(track: MediaStreamTrack, context: IProcessorContext): void;
```

输出处理后的视频数据。

**参数**

- `track`：处理后的 `MediaStreamTrack`。

- `context`：当前视频处理管道的 context。

### 示例项目 [#示例项目-2]

声网提供一个 Web 插件开发的示例项目供你参考，你可以前往下载或查看其中的源代码。

- Gitee：[ExtensionDemo](https://gitee.com/agoraio-community/API-Examples-Web/tree/main/ExtensionDemo)
- GitHub：[ExtensionDemo](https://github.com/Shengwang-Lab/API-Examples-Web/tree/main/ExtensionDemo)

以下代码展示了如何开发一个视频插件：

```typescript
class YourExtension extends Extension<YourProcessor> {
    // 创建 Processor
    protected _createProcessor(): YourProcessor {
        return new YourProcessor();
    }
}

class CustomVideoProcessor extends VideoProcessor {
    // 从前一个 MediaStreamTrack 接收到视频数据
    onTrack(track: MediaStreamTrack, context: IProcessorContext) {
        this.videoElement.srcObject = new MediaStream([track]);
        this.videoElement.play();

        this.loop();
    }

    // 连续抽取视频帧为 ImageData 对象，用于后续的视频处理
    loop() {
        this.ctx.drawImage(this.videoElement, 0, 0);
        const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);

        this.process(imageData);

        requestAnimationFrame(() => this.loop());
    }

    process() {
        //TODO：添加你的插件的视频处理逻辑
    }

    doneProcessing() {
        // 组装成 MediaStreamTrack
        const msStream = this.canvas.captureStream(30);
        const outputTrack = msStream.getVideoTracks()[0];

        // 输出处理后的视频数据
        if (this.context) {
            this.output(outputTrack, this.context);
        }
    }
}
```

<a id="ref"></a>
## API 参考 [#api-参考]

本节介绍开发视频插件的辅助类 API。

### VideoProcessor [#videoprocessor]

#### name [#name]

```typescript
name: string;
```

`Processor` 的名称。

#### enabled [#enabled]

```typescript
enabled :boolean;
```

`Processor` 的开启状态。

#### ID [#id]

```typescript
public readonly ID:string;
```

`Processor` 的标识符。

#### Kind [#kind]

```typescript
public get Kind():'video' | 'audio';
```

`Processor` 的类型，分为视频和音频两种。

#### context [#context]

```typescript
protected context?: IProcessorContext;
```

用于 `Processor` 请求原始媒体流并重新进行采集。

#### onPiped [#onpiped]

```typescript
abstract onPiped?(context: IProcessorContext): void;
```

`LocalVideoTrack` 连接到当前媒体处理管道的回调。

:::warning[注意]
如果只是连接多个 `Processor`，不会触发该回调。例如：插件使用者调用 `processorA.pipe(processorB)`。
:::

#### onUnPiped [#onunpiped]

```typescript
abstract onUnPiped?(): void;
```

`Processor` 断开与媒体处理管道的连接回调。

#### onEnableChange [#onenablechange]

```typescript
abstract onEnableChange?(enabled: boolean): void | Promise<void>;
```

`Processor` 的开启状态变化回调。

#### inputTrack [#inputtrack]

```typescript
protected inputTrack?:MediaStreamTrack;
```

当前 `Processor` 从前一个 `Processor` 或者 `LocalVideoTrack` 接收到的视频。

#### outputTrack [#outputtrack]

```typescript
protected outputTrack?:MediaStreamTrack;
```

当前 `Processor` 处理后的视频。调用 `output` 方法会自动设置 `outputTrack`。

### Ticker [#ticker]

#### constructor [#constructor]

```typescript
public constructor(type:"Timer" | "RAF" | "Oscillator", interval: number):Ticker;
```

`Ticker` 的构造函数。

**参数**

- `type`：类型，支持以下三种：

  - `Timer`: 使用 `setTimeout` 作为插件内部的计时器。

  - （推荐）`RAF`: 使用 `requestAnimationFrame` 作为插件内部的计时器。大多数情况下，该类型的  `Ticker` 渲染性能最好。

  - `Oscillator`: 使用 `WebAudio` 的 `OscillatorNode` 作为插件内部的计时器。该类型的  `Ticker` 在浏览器页面不被浏览时也能运行。

- `interval`：两次回调之间的间隔。 `Ticker` 会按照这个间隔执行，但无法保证 100% 精准。

#### Ticker.add [#tickeradd]

```typescript
public add(fn: Function): void;
```

添加计时任务。

#### Ticker.remove [#tickerremove]

```typescript
public remove():void;
```

取消添加的计时任务。

#### Ticker.start [#tickerstart]

```typescript
public start():void;
```

开始计时。

#### Ticker.stop [#tickerstop]

```typescript
public stop():void;
```

停止计时。

### Logger [#logger]

```typescript
interface IExtensionLogger {
    debug(...args: any): void;
    error(...args: any): void;
    info(...args: any): void;
    warning(...args: any): void;
}
```

提供四种级别的日志。

插件使用者如果在调用 `AgoraRTC.registerExtension` 注册插件时选择了上传日志，则通过 `Logger` 类上报的日志会被上传。

### Reporter [#reporter]

#### Reporter.reportApiInvoke [#reporterreportapiinvoke]

```typescript
public reportApiInvoke<T>(params: ReportApiInvokeParams): AgoraApiExecutor<T>;
```

向 SDK 报告 API 调用的相关事件。

其中 `ReportApiInvokeParams` 和 `AgoraApiExecutor` 的定义如下：

```typescript
interface ReportApiInvokeParams {
    // 被调用的 API 的名称
    name: string;
    // 与该 API 有关的参数或选项
    options: any;
    // 是否报告该 API 的调用结果
    reportResult?: boolean;
    // 定义多久为调用超时
    timeout?: number;
}
interface AgoraApiExecutor<T> {
    // API 调用成功
    onSuccess: (result: T) => void;
    // API 调用失败
    onError: (err: Error) => void;
}

```
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
本文展示如何使用声网 SDK 提供的 API 开发一个视频插件。

开发视频插件需要使用以下接口：

- `IExtensionVideoFilter`：实现视频数据的处理能力，包括接收、处理和返回处理完的视频帧。
- `IExtensionProvider`：将视频数据的处理能力封装为插件。

## 前提条件 [#前提条件-4]

开发前，请确保你的开发环境满足以下要求：

- Microsoft Visual Studio 2017 或以上版本。
- Windows 7 或以上版本的设备。

## 准备开发环境 [#准备开发环境-4]

参考如下步骤将声网云市场的 API 添加到你的项目中：

1. 下载[视频 SDK v4.x](/zh-CN/realtime-media/rtc/reference/downloads)，然后解压下载的 SDK 包。
2. 将 SDK 包中的 `/rtc/sdk/low_level_api/include` 路径下的头文件拷贝至你的项目文件夹中。

## 开发视频插件 [#开发视频插件-4]

### 实现插件 [#实现插件-5]

视频插件通过 `IExtensionVideoFilter` 接口实现。该接口位于 `NGIAgoraMediaNode.h` 文件中。`IExtensionVideoFilter` 接口类包含如下方法：

- [`getProcessMode`](#getprocessmode-5)
- [`start`](#start-5)
- [`stop`](#stop-5)
- [`getVideoFormatWanted`](#getvideoformatwanted-5)
- [`adaptVideoFrame`](#adaptvideoframe-5)
- [`pendVideoFrame`](#pendvideoframe-5)
- [`deliverVideoFrame`（见 Control 定义）](#start-5)
- [`setProperty`](#setproperty-5)
- [`getProperty`](#getproperty-5)

#### getProcessMode [#getprocessmode-5]

设置 SDK 与视频插件的交互模式。SDK 在加载插件时，会首先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定自己需要的交互模式。

```cpp
virtual void getProcessMode(ProcessMode& mode, bool& independent_thread) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `mode` | [video-filter-android-ios-macos-windows-html-0-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-1-1-3) |
| `independent_thread` | [video-filter-android-ios-macos-windows-html-0-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-0-2-1-3) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-1-1-3"></a>

**video-filter-android-ios-macos-windows-html-0-1-1 details**

SDK 和插件互相传递视频数据的模式，支持设为以下值：

- `Sync`: 同步模式。该模式下，SDK 和插件通过 `adaptVideoFrame` 传递视频数据。
- `Async`: 异步模式。该模式下，SDK 通过 `pendVideoFrame` 将待处理的数据传给插件，插件通过 `deliverVideoFrame` 将处理后的数据返回给 SDK。

<a id="table-slot-video-filter-android-ios-macos-windows-html-0-2-1-3"></a>

**video-filter-android-ios-macos-windows-html-0-2-1 details**

是否为插件创建独立的线程：

- `true`: 为插件创建独立的线程。创建后，SDK 向插件发送的所有回调都在该独立线程上进行。
- `false`: 不为插件创建独立的线程。SDK 向插件发送的所有回调都在 SDK 的视频处理线程上进行。

你可以根据 YUV 运算的复杂度和是否使用 OpenGL 设置 `mode` 和 `independent_thread` 参数：

- 根据 YUV 运算的复杂度设置：
  - 如果视频插件内部使用较复杂的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Async` 和 `false`。
  - 如果视频插件内部使用简单的 YUV 运算处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `false`。
- 如果视频插件内部使用了 OpenGL 处理数据，推荐将 `mode` 和 `independent_thread` 分别设为 `Sync` 和 `true`。

#### start [#start-5]

```cpp
virtual int start(agora::agora_refptr<Control> control) = 0;
```

创建视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中初始化 OpenGL。

SDK 会在该方法中传递一个 `Control` 对象，方便插件和 SDK 的后续交互。你需要保留收到的 `Control` 对象，并根据场景需要实现该对象中的相关方法：

```cpp
class Control : public RefCountInterface {
    public:
    /**
    * 在异步模式下（mode 设为 Async），插件需要调用该方法向 SDK 返回处理后的视频帧。
    * 使用该方法前，请确保 SDK 已经通过 pendVideoFrame 提交了待处理的视频帧。
    */
    virtual ProcessResult deliverVideoFrame(agora::agora_refptr<IVideoFrame> frame) = 0;
    /**
    * 插件需要开辟新的内存空间时，可以调用该方法创建一个新的 IVideoFrame 对象。
    * 例如：美颜插件如果需要同时保存原图和美化后的图片，可以调用该方法优化内存管理。你也可以自行管理内存。
    */
    virtual agora::agora_refptr<IVideoFrameMemoryPool> getMemoryPool() = 0;
    /**
    * 插件可以调用该方法向 SDK 报告事件，SDK 会将该事件通知给 App。
    */
    virtual int postEvent(const char* key, const char* value) = 0;
    /**
    * 插件可以调用该方法向 SDK 打印日志。
    */
    virtual void printLog(commons::LOG_LEVEL level, const char* format, ...) = 0;
    /**
    * 当插件内部出错且不可恢复时，可以调用该方法让 SDK 停止向插件发送视频帧。
    * SDK 也会同步将该方法中设置的错误及具体报错信息同步给 App。
    */
    virtual void disableMe(int error, const char* msg) = 0;
};
```

#### stop [#stop-5]

```cpp
virtual int stop() = 0;
```

销毁视频插件实例 `IExtensionVideoFilter` 后，会触发该回调。你可以在该回调中释放 OpenGL。

#### getVideoFormatWanted [#getvideoformatwanted-5]

设置待处理视频数据的类型和格式。SDK 每向插件提供一帧待处理的视频数据，都会先调用该方法向插件发送回调。收到该回调后，你需要在返回值中指定这一帧的数据类型和格式。你可以给不同的视频帧指定不同的数据类型和格式。

```cpp
virtual void getVideoFormatWanted(VideoFrameData::Type& type, RawPixelBuffer::Format& format) = 0;
```

| 参数 | 描述 |
| --- | --- |
| `type` | 视频数据的类型，目前仅支持设为 `RawPixels`，代表原始数据。 |
| `format` | [video-filter-android-ios-macos-windows-html-1-2-1 details](#table-slot-video-filter-android-ios-macos-windows-html-1-2-1-3) |

<a id="table-slot-video-filter-android-ios-macos-windows-html-1-2-1-3"></a>

**video-filter-android-ios-macos-windows-html-1-2-1 details**

视频数据的格式，支持设为以下值：

- `Unknown`：未知格式。
- `I420`：I420 格式。
- `I422`：I422 格式。
- `NV21`：NV21 格式。
- `NV12`：NV12 格式。
- `RGBA`：RGBA 格式。
- `ARGB`：ARGB 格式。
- `BGRA`：BGRA 格式。

#### adaptVideoFrame [#adaptvideoframe-5]

处理视频帧。在同步模式下（`mode` 设为 `Sync`），SDK 通过该方法与视频插件互相传递数据。SDK 通过 `in` 将待处理的视频数据传递给插件，插件通过 `out` 将处理后的数据返回给 SDK。

```cpp
virtual ProcessResult adaptVideoFrame(agora::agora_refptr<IVideoFrame> in, agora::agora_refptr<IVideoFrame>& out) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述                       |
| :----- | :------------------------- |
| `in`     | 输入参数。待处理的视频帧。 |
| `out`    | 输出参数。处理后的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### pendVideoFrame [#pendvideoframe-5]

提交待处理的视频帧。在异步模式下（`mode` 设为 `Async`），SDK 通过该方法向视频插件提交待处理的视频帧。使用该方法后，插件必须通过 `Control` 类的 `deliverVideoFrame` 方法返回处理后的视频帧。

```cpp
virtual ProcessResult pendVideoFrame(agora::agora_refptr<IVideoFrame> frame) {
    return ProcessResult::kBypass;
}
```

**参数**

| 参数名 | 描述             |
| :----- | :--------------- |
| `frame`  | 待处理的视频帧。 |

**返回值**

处理该视频帧的结果：

- `Success`: 成功处理该视频帧。
- `ByPass`: 跳过处理该视频帧，并将它传递到后续链路。
- `Drop`: 丢弃该视频帧。

#### setProperty [#setproperty-5]

设置视频插件属性。App 开发者调用 `setExtensionProperty` 时，SDK 会调用该方法。你需要返回视频插件的属性。

```cpp
int ExtensionVideoFilter::setProperty(const char *key, const void *buf, size_t buf_size)
```

| 参数     | 描述                                                         |
| :------- | :----------------------------------------------------------- |
| `key`      | 插件属性的 `key`。                                             |
| `buf`      | 插件属性 `key` 值对应的 buffer 地址，数据形式为 JSON 字符串。我们推荐使用第三方的 `nlohmann/json library` 开源库，帮助实现 C++ 的 struct 和 JSON 字符串之间的序列化和反序列化。 |
| `buf_size` | 插件属性 buffer 的内存大小。                                 |

#### getProperty [#getproperty-5]

获取视频插件属性。App 开发者调用 `getProperty` 时，SDK 会调用该方法获取视频插件的属性。

```cpp
int ExtensionVideoFilter::getProperty(const char *key, void *buf, size_t buf_size)
```

| 参数     | 描述                         |
| :------- | :--------------------------- |
| `key`      | 插件属性的 `key`。             |
| `property` | 插件属性指针。               |
| `buf_size` | 插件属性 buffer 的内存大小。 |

#### 示例代码 [#示例代码-10]

参考如下示例代码了解如何使用上述方法实现一个视频插件：

```cpp
#include "ExtensionVideoFilter.h"
#include "../logutils.h"
#include <sstream>

namespace agora {
    namespace extension {

        ExtensionVideoFilter::ExtensionVideoFilter(agora_refptr < ByteDanceProcessor > byteDanceProcessor): threadPool_(1) {
            byteDanceProcessor_ = byteDanceProcessor;
        }

        ExtensionVideoFilter::~ExtensionVideoFilter() {
            byteDanceProcessor_ -> releaseOpenGL();
        }

        // 设置 SDK 与视频插件的交互模式。
        void ExtensionVideoFilter::getProcessMode(ProcessMode & mode, bool & independent_thread) {
            mode = ProcessMode::kSync;
            independent_thread = false;
            mode_ = mode;
        }

        // 设置待处理视频数据的类型和格式。
        void ExtensionVideoFilter::getVideoFormatWanted(rtc::VideoFrameData::Type & type,
            rtc::RawPixelBuffer::Format & format) {
            type = rtc::VideoFrameData::Type::kRawPixels;
            format = rtc::RawPixelBuffer::Format::kI420;
        }

        // 在 start 回调中保留 Control 对象并初始化 OpenGL
        int ExtensionVideoFilter::start(agora::agora_refptr < Control > control) {
            PRINTF_INFO("ExtensionVideoFilter::start");
            if (!byteDanceProcessor_) {
                return -1;
            }
            if (control) {
                control_ = control;
                byteDanceProcessor_ -> setExtensionControl(control);
            }
            if (mode_ == ProcessMode::kAsync) {
                invoker_id = threadPool_.RegisterInvoker("thread_videofilter");
                auto res = threadPool_.PostTaskWithRes(invoker_id, [byteDanceProcessor = byteDanceProcessor_] {
                    return byteDanceProcessor -> initOpenGL();
                });
                isInitOpenGL = res.get();
            } else {
                isInitOpenGL = byteDanceProcessor_ -> initOpenGL();
            }
            return 0;
        }

        // 在 stop 回调中释放 OpenGL
        int ExtensionVideoFilter::stop() {
            PRINTF_INFO("ExtensionVideoFilter::stop");
            if (byteDanceProcessor_) {
                byteDanceProcessor_ -> releaseOpenGL();
                isInitOpenGL = false;
            }
            return 0;
        }

        // 如果 mode 设为 Async，则 SDK 和视频插件通过 pendVideoFrame 和 deliverVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::pendVideoFrame(agora::agora_refptr < rtc::IVideoFrame > frame) {
            if (!frame || !isInitOpenGL) {
                return kBypass;
            }

            bool isAsyncMode = (mode_ == ProcessMode::kAsync);
            if (isAsyncMode && byteDanceProcessor_ && control_ && invoker_id >= 0) {
                threadPool_.PostTask(invoker_id, [videoFrame = frame, byteDanceProcessor = byteDanceProcessor_, control = control_] {
                    rtc::VideoFrameData srcData;
                    videoFrame -> getVideoFrameData(srcData);
                    byteDanceProcessor -> processFrame(srcData);
                    control -> deliverVideoFrame(videoFrame);
                });
                return kSuccess;
            }
            return kBypass;
        }

        // 如果 mode 设为 Sync，则 SDK 和视频插件通过 adaptVideoFrame 传递数据。
        rtc::IExtensionVideoFilter::ProcessResult ExtensionVideoFilter::adaptVideoFrame(agora::agora_refptr < rtc::IVideoFrame > src,
            agora::agora_refptr < rtc::IVideoFrame > & dst) {
            if (!isInitOpenGL) {
                return kBypass;
            }
            bool isSyncMode = (mode_ == ProcessMode::kSync);
            if (isSyncMode && byteDanceProcessor_) {
                rtc::VideoFrameData srcData;
                src -> getVideoFrameData(srcData);
                byteDanceProcessor_ -> processFrame(srcData);
                dst = src;
                return kSuccess;
            }
            return kBypass;
        }

        // 设置视频插件属性。
        int ExtensionVideoFilter::setProperty(const char * key,
            const void * buf, size_t buf_size) {
            PRINTF_INFO("setProperty  %s  %s", key, buf);
            std::string stringParameter((char * ) buf);
            byteDanceProcessor_ -> setParameters(stringParameter);
            return 0;
        }

        // 获取视频插件属性
        int ExtensionVideoFilter::getProperty(const char * key, void * buf, size_t buf_size) {
            return -1;
        }
    }
}
```

### 封装插件 [#封装插件-5]

封装插件通过 `IExtensionProvider` 接口类实现。该接口位于 `NGIAgoraExtensionProvider.h` 文件中。你需要先实现这个接口类，并至少实现如下方法：

- [`enumerateExtensions`](#enumerateextensions-5)
- [`createVideoFilter`](#createvideofilter-5)

`IExtensionProvider` 是插件动态库的提供者，由全局唯一的名字标识。每一个插件动态库可以提供一个或多个有不同功能的插件，如视频前处理插件库可以提供不同功能的视频插件（Video filter）。每个插件是一个具体的 Extension 对象。

#### enumerateExtensions [#enumerateextensions-5]

提供所有支持封装的插件的信息。SDK 在加载插件时，会调用该方法向插件发送回调。收到该回调后，你需要通过返回值提供所有支持封装的插件的信息。

```cpp
virtual void enumerateExtensions(ExtensionMetaInfo* extension_list,
                                 int& extension_count) {
    (void) extension_list;
    extension_count = 0;
}
```

| 参数 | 描述 |
| --- | --- |
| `extension_list` | [video-filter-android-ios-macos-windows-html-2-1-1 details](#table-slot-video-filter-android-ios-macos-windows-html-2-1-1-3) |
| `extension_count` | 支持封装的插件的总数量。 |

<a id="table-slot-video-filter-android-ios-macos-windows-html-2-1-1-3"></a>

**video-filter-android-ios-macos-windows-html-2-1-1 details**

插件的信息，包括插件类型和插件名称。支持的视频插件类型有：

- `VIDEO_PRE_PROCESSING_FILTER`（上行）
- `VIDEO_POST_PROCESSING_FILTER`（下行）

插件名由插件服务商指定，需要在 `IExtensionProvider` 对象内唯一。插件名为 String 型，支持如下字符：

- 26 个小写字母 `a`~`z`
- 数字和下划线（不能是第一个或最后一个）
如果插件名不符合规则，会无法通过编译。

其中，插件的信息定义如下：

```cpp
// 插件类型指插件在音视频传输通道中的位置，分类如下：
enum EXTENSION_TYPE {
    // 已废弃，请改用 AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER 或 AUDIO_POST_PROCESSING_FILTER
    AUDIO_FILTER,
    // 视频前处理插件
    VIDEO_PRE_PROCESSING_FILTER,
    // 视频后处理插件
    VIDEO_POST_PROCESSING_FILTER,
    // 预留参数
    AUDIO_SINK,
    // 预留参数
    VIDEO_SINK,
    // 用于处理本地采集（如耳返采集）的，用于本地播放的音频数据
    AUDIO_RECORDING_LOCAL_PLAYBACK_FILTER = 10000,
    // 经过 3A 算法后的本地音频数据后处理
    AUDIO_POST_PROCESSING_FILTER = 10001,
    // 用于对远端用户的音频数据进行处理
    AUDIO_REMOTE_USER_PLAYBACK_FILTER = 10002,
};

// 插件的信息，包括插件类型和插件名称
struct ExtensionMetaInfo {
    EXTENSION_TYPE type;
    const char* extension_name;
};
```

如果你在 `enumerateExtensions` 方法中返回的插件类型为 `VIDEO_PRE_PROCESSING_FILTER` 或 `VIDEO_POST_PROCESSING_FILTER`，则在 App 开发者初始化 `RtcEngine` 并创建 `IExtensionVideoProvider` 对象后，SDK 会调用 `createVideoFilter` 方法。

#### createVideoFilter [#createvideofilter-5]

创建视频插件。SDK 调用该方法后，你需要返回 `IExtensionVideoFilter` 实例。

```cpp
virtual agora_refptr<IExtensionVideoFilter> createVideoFilter(const char* name) {
    return NULL;
}
```

成功创建 `IExtensionVideoFilter` 实例后，视频插件会在合适的时机通过 `IExtensionFilter` 类对输入的视频数据进行处理。

#### 示例代码 [#示例代码-11]

参考如下示例代码了解如何使用上述方法封装视频插件：

```cpp
#include "ExtensionProvider.h"
#include "../logutils.h"
#include "VideoProcessor.h"
#include "plugin_source_code/JniHelper.h"

namespace agora {
    namespace extension {
        ExtensionProvider::ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider create");
            byteDanceProcessor_ = new agora::RefCountedObject < ByteDanceProcessor > ();
            audioProcessor_ = new agora::RefCountedObject < AdjustVolumeAudioProcessor > ();
        }

        ExtensionProvider::~ExtensionProvider() {
            PRINTF_INFO("ExtensionProvider destroy");
            byteDanceProcessor_.reset();
            audioProcessor_.reset();
        }

        // 提供所有支持封装的插件的信息。
        void ExtensionProvider::enumerateExtensions(ExtensionMetaInfo * extension_list,
            int & extension_count) {
            extension_count = 1;
            ExtensionMetaInfo i;
            i.type = EXTENSION_TYPE::VIDEO_PRE_PROCESSING_FILTER;
            i.extension_name = "YourExtensionName";
            extension_list[0] = i;
        }

        // 创建视频插件。
        agora_refptr < agora::rtc::IExtensionVideoFilter > ExtensionProvider::createVideoFilter(const char * name) {
            PRINTF_INFO("ExtensionProvider::createVideoFilter %s", name);
            auto videoFilter = new agora::RefCountedObject < agora::extension::ExtensionVideoFilter > (byteDanceProcessor_);
            return videoFilter;
        }

        void ExtensionProvider::setExtensionControl(rtc::IExtensionControl * control) {}
    }
}
```

## 打包视频插件 [#打包视频插件-3]

完成插件开发后，你需要对其进行注册、打包，并将最终的 `.dll` 文件，连同一个包含了插件名称、服务商名称和 Filter 名称的文件提交给声网进行验证。

### 注册插件 [#注册插件-1]

插件通过宏 `REGISTER_AGORA_EXTENSION_PROVIDER` 进行注册，该宏位于 `AgoraExtensionProviderEntry.h` 文件中。

你需要在插件的入口使用这个宏。SDK 在加载插件时，该宏会自动向 SDK 注册你的插件。注意填入 `PROVIDER_NAME` 时不要填写标点符号。示例：

```cpp
REGISTER_AGORA_EXTENSION_PROVIDER(ByteDance, agora::extension::ExtensionProvider);
```

### 提供插件信息 [#提供插件信息-1]

新建一个 `.md` 文件，并在其中填入如下信息：

- `EXTENSION_NAME`：插件包的名称，如 `libagora-bytedance.dll` 文件的 `EXTENSION_NAME` 就是 `agora-bytedance`。
- `EXTENSION_VENDOR_NAME`：服务商名称，即插件的 `Provider` 对象名称，用于在 `.cpp` 文件中进行注册。
- `EXTENSION_FILTER_NAME`：`ExtensionProvider.h` 文件中定义的 Filter 名称。
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
