# 自定义视频采集 (/zh-CN/realtime-media/rtc/build/video/custom-video-source)

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

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="android" />
自定义视频采集是指通过自定义的视频采集源实现对视频流的采集。与 SDK 默认的视频采集方式不同，自定义视频采集支持用户自行控制采集源，实现更加精细的视频属性调整。例如，支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集，同时支持动态调整视频质量、分辨率和帧率等参数，以适应不同的应用场景和需求。

声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集，如果你有特定的视频采集需求或无法使用 SDK 采集，那么自定义视频采集为你提供灵活、可定制的方案。

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

声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道，加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集，并将采集的视频帧通过视频轨道发送给 SDK。

下图展示在单频道和多频道中实现自定义视频采集时，视频数据的传输过程：

仅在一个频道内发布自采集视频流：

![单频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source1.png)

在多个频道内发布不同的自采集视频流：

![多频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source2.png)

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

在进行操作之前，请确保你已经在项目中实现了基本的实时音视频功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现自定义视频采集 [#实现自定义视频采集]

参考下图调用时序，在你的 App 中实现自定义视频采集：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source.svg" displayWidth="70%" />

参考如下步骤，在你的 App 中实现自定义视频采集功能：

### 1. 创建自定义视频轨道 [#1-创建自定义视频轨道]

初始化 `RtcEngine` 后，调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要，你可以创建多个自定义视频轨道。

```java
// 如需创建多个自定义视频轨道，可以多次调用 createCustomVideoTrack
int videoTrackId = RtcEngine.createCustomVideoTrack();
```

### 2. 加入频道并发布自定义视频轨道 [#2-加入频道并发布自定义视频轨道]

调用 `joinChannel`[2/2] 加入频道，或调用 `joinChannelEx` 加入多频道，在每个频道的 `ChannelMediaOptions` 中，将 `customVideoTrackId` 参数设置为步骤 1 中获得的视频轨道 ID，并将 `publishCustomVideoTrack` 设置为 `true`，即可在多个频道中发布指定的自定义视频轨道。

加入单频道：

```java
ChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
// 发布自采集视频流
option.publishCustomVideoTrack = true;
// 设置自定义视频轨道 ID
option.customVideoTrackId = videoTrackId;
// 加入单频道
int res = engine.joinChannel(accessToken, channelId, 0, option);
```

加入多频道：

```java
// 如需在多个频道发布自定义视频轨道，则需要多次设置 ChannelMediaOptions 并多次调用 joinChannelEx
ChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
// 发布自采集视频流
option.publishCustomVideoTrack = true;
// 设置自定义视频轨道 ID
option.customVideoTrackId = videoTrackId;
// 加入多频道
int res = engine.joinChannelEx(accessToken, connection, option, new IRtcEngineEventHandler() {});
```

### 3. 实现自采集模块 [#3-实现自采集模块]

声网提供 [VideoFileReader.java](https://github.com/Shengwang-Community/API-Examples/blob/main/Android/APIExample/app/src/main/java/io/agora/api/example/utils/VideoFileReader.java) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中，你需要结合业务需求使用 Android SDK 为你的设备创建自定义视频模块。

### 4. 通过视频轨道推送视频数据到 SDK [#4-通过视频轨道推送视频数据到-sdk]

将采集到的视频帧发送至 SDK 之前，你需要通过设置 `VideoFrame` 的各项参数来封装视频帧数据。你可以参考以下代码，推送不同类型的自采集视频数据。为确保音视频同步，声网建议你调用 `getCurrentMonotonicTimeInMs` 获取 SDK 当前的 Monotonic Time 后，将该值传入采集的 `VideoFrame` 的时间戳参数。

调用 `pushExternalVideoFrameById`[2/2] 将采集到的视频帧通过视频轨道推送至 SDK。其中，`videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致，`VideoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。

:::info[信息]
- 以下代码演示推送 I420、NV21、NV12 和 Texture 格式的视频数据。
- 本节示例调用的是 `pushExternalVideoFrameById(VideoFrame frame, int videoTrackId)` 重载，其中 `VideoFrame` 指 `io.agora.base.VideoFrame`；如需使用 `AgoraVideoFrame`，请参见 `pushExternalVideoFrameById` 的另一重载。
- `getCurrentMonotonicTimeInMs` 返回的单调时间单位为毫秒；以下示例中 `VideoFrame` 构造函数的第三个参数对应 `timestampNs`，单位为纳秒，因此需要将毫秒转换为纳秒后再传入。
:::

```java tab="I420" tabGroup="pushExternalVideoFrameById"
private void pushVideoFrameByI420(int trackId, byte[] yuv, int width, int height) {
    // 创建一个 i420Buffer 对象，将原始的 YUV 数据存储到 I420 格式的缓冲区中
    JavaI420Buffer i420Buffer = JavaI420Buffer.allocate(width, height);
    i420Buffer.getDataY().put(yuv, 0, i420Buffer.getDataY().limit());
    i420Buffer.getDataU().put(yuv, i420Buffer.getDataY().limit(), i420Buffer.getDataU().limit());
    i420Buffer.getDataV().put(yuv, i420Buffer.getDataY().limit() + i420Buffer.getDataU().limit(), i420Buffer.getDataV().limit());

    // 获取 SDK 当前的 Monotonic Time（单位为毫秒）
    long currentMonotonicTimeInMs = engine.getCurrentMonotonicTimeInMs();
    // 创建一个 VideoFrame 对象，传入要推送的 I420 视频帧和视频帧的 Monotonic Time
    // VideoFrame 的第三个参数为 timestampNs，单位为纳秒，需要将毫秒转换为纳秒
    VideoFrame videoFrame = new VideoFrame(i420Buffer, 0, currentMonotonicTimeInMs * 1000000);

    // 通过视频轨道将视频帧推送到 SDK
    int ret = engine.pushExternalVideoFrameById(videoFrame, trackId);
    // 推送成功后，释放 i420Buffer 对象占用的内存资源
    i420Buffer.release();

    if (ret != Constants.ERR_OK) {
        Log.w(TAG, "pushExternalVideoFrame error");
    }
}
```

```java tab="NV21" tabGroup="pushExternalVideoFrameById"
private void pushVideoFrameByNV21(int trackId, byte[] nv21, int width, int height) {
    // 创建一个 frameBuffer 对象，将原始的 YUV 数据存储到 NV21 格式的缓冲区中
    VideoFrame.Buffer frameBuffer = new NV21Buffer(nv21, width, height, null);

    // 获取 SDK 当前的 Monotonic Time（单位为毫秒）
    long currentMonotonicTimeInMs = engine.getCurrentMonotonicTimeInMs();
    // 创建一个 VideoFrame 对象，传入要推送的 I420 视频帧和视频帧的 Monotonic Time
    // VideoFrame 的第三个参数为 timestampNs，单位为纳秒，这里需要将毫秒转换为纳秒
    VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);

    // 通过视频轨道将视频帧推送到 SDK
    int ret = engine.pushExternalVideoFrameById(videoFrame, trackId);

    if (ret != Constants.ERR_OK) {
        Log.w(TAG, "pushExternalVideoFrame error");
    }
}
```

```java tab="NV12" tabGroup="pushExternalVideoFrameById"
private void pushVideoFrameByNV12(int trackId, ByteBuffer nv12, int width, int height) {
    // 创建一个 frameBuffer 对象，将原始的 YUV 数据存储到 NV12 格式的缓冲区中
    VideoFrame.Buffer frameBuffer = new NV12Buffer(width, height, width, height, nv12, null);

    // 获取 SDK 当前的 Monotonic Time（单位为毫秒）
    long currentMonotonicTimeInMs = engine.getCurrentMonotonicTimeInMs();
    // 创建一个 VideoFrame 对象，传入要推送的 I420 视频帧和视频帧的 Monotonic Time
    // VideoFrame 的第三个参数为 timestampNs，单位为纳秒，这里需要将毫秒转换为纳秒
    VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);

    // 通过视频轨道将视频帧推送到 SDK
    int ret = engine.pushExternalVideoFrameById(videoFrame, trackId);

    if (ret != Constants.ERR_OK) {
        Log.w(TAG, "pushExternalVideoFrame error");
    }
}
```

```java tab="Texture" tabGroup="pushExternalVideoFrameById"
private void pushVideoFrameByTexture(int trackId, int textureId, VideoFrame.TextureBuffer.Type textureType, int width, int height) {
    // 创建一个 frameBuffer 对象，用于存储 Texture 格式的视频帧
    VideoFrame.Buffer frameBuffer = new TextureBuffer(
            EglBaseProvider.getCurrentEglContext(),
            width,
            height,
            textureType,
            textureId,
            new Matrix(),
            null,
            null,
            null
    );

    // 获取 SDK 当前的 Monotonic Time（单位为毫秒）
    long currentMonotonicTimeInMs = engine.getCurrentMonotonicTimeInMs();
    // 创建一个 VideoFrame 对象，传入要推送的 I420 视频帧和视频帧的 Monotonic Time (单位为纳秒)
    // VideoFrame 的第三个参数为 timestampNs，单位为纳秒，这里需要将毫秒转换为纳秒
    VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);

    // 通过视频轨道将视频帧推送到 SDK
    int ret = engine.pushExternalVideoFrameById(videoFrame, trackId);

    if (ret != Constants.ERR_OK) {
        Log.w(TAG, "pushExternalVideoFrame error");
    }
}
```

### 5. 销毁自定义视频轨道 [#5-销毁自定义视频轨道]

如果你需要停止发布自定义采集的视频，调用 `destroyCustomVideoTrack` 销毁视频轨道。如需销毁多个视频轨道，可多次调用 `destroyCustomVideoTrack`。

```java
// 销毁自定义视频轨道
engine.destroyCustomVideoTrack(videoTrack);
// 离开频道
engine.leaveChannelEx(connection);
```

## 参考信息 [#参考信息]

### 适用场景 [#适用场景]

你可以在多种行业的多种场景下使用到自定义视频采集：

#### 视频特殊处理和增强 [#视频特殊处理和增强]

在某些游戏或虚拟现实应用中，需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下，使用自定义视频采集可以直接获取原始视频流，并进行实时处理，从而实现更加逼真的游戏或虚拟现实效果。

#### 高精度视频采集 [#高精度视频采集]

在视频监控领域，需要对场景中的细节进行精细的观察和分析，此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。

#### 特定视频源采集 [#特定视频源采集]

在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源，例如视频捕捉卡、录屏数据等。在这种情况下，使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定视频源的采集。

#### 与特定设备或第三方应用无缝对接 [#与特定设备或第三方应用无缝对接]

在智能家居或物联网领域，需要将设备中的视频传输到用户的手机或电脑上进行监控和控制，此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下，使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。

#### 特定的视频编码格式 [#特定的视频编码格式]

在特定直播场景中，可能需要使用特定的视频编码格式来满足业务需求，此时使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。

### 优势介绍 [#优势介绍]

使用自定义视频采集，你可以体验到：

#### 更多类型的视频流 [#更多类型的视频流]

自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头，从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验，并使产品更具竞争力。

#### 更灵活的视频特效 [#更灵活的视频特效]

自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器，从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效，如美颜、滤镜、动态贴纸等。

#### 更适应各种场景的需求 [#更适应各种场景的需求]

自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求，如直播、视频会议、在线教育等。用户可以根据不同的场景需求，定制不同的视频采集方案，从而提供适应性更强的应用程序。

### 注意事项 [#注意事项]

如果采集到的自定义视频格式为 Texture，并且远端用户看到的自定义采集视频画面出现闪烁、失真等异常情况时，建议先复制视频数据，再将原始视频数据和复制的视频数据发回至 SDK，从而消除内部数据编码过程中出现的异常情况。

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

声网提供了开源的视频自采集示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">MultiVideoSourceTracks</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/API-Examples/blob/main/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java" />
  <Card title={<RepositoryCardTitle provider="github">MultiVideoSourceTracks</RepositoryCardTitle>} href="https://github.com/Shengwang-Community/API-Examples/blob/main/Android/APIExample/app/src/main/java/io/agora/api/example/examples/advanced/MultiVideoSourceTracks.java" />
</Cards>

### API 参考 [#api-参考]

- [`createCustomVideoTrack`](/zh-CN/api-reference/rtc/android/video/video-custom-capturenrendering#api_irtcengine_createcustomvideotrack)
- [`destroyCustomVideoTrack`](/zh-CN/api-reference/rtc/android/video/video-custom-capturenrendering#api_irtcengine_destroycustomvideotrack)
- [`getCurrentMonotonicTimeInMs`](/zh-CN/api-reference/rtc/android/network#api_irtcengine_getcurrentmonotonictimeinms)
- [`joinChannelEx`](/zh-CN/api-reference/rtc/android/channel#api_irtcengineex_joinchannelex)
- [`pushExternalVideoFrameById`[2/2]](/zh-CN/api-reference/rtc/android/video/video-custom-capturenrendering#pushexternalvideoframebyid-22)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="ios" />
自定义视频采集是指通过自定义的视频采集源实现对视频流的采集。

与 SDK 默认的视频采集方式不同，自定义视频采集支持用户自行控制采集源，实现更加精细的视频属性调整。例如，支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集，同时支持动态调整视频质量、分辨率和帧率等参数，以适应不同的应用场景和需求。

声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集，如果你有特定的视频采集需求或无法使用 SDK 采集，那么自定义视频采集为你提供灵活、可定制的方案。

## 技术原理 [#技术原理-1]

声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道，加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集，并将采集的视频帧通过视频轨道发送给 SDK。

下图展示在单频道和多频道中实现自定义视频采集时，视频数据的传输过程：

仅在一个频道内发布自采集视频流：

![单频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source1.png)

在多个频道内发布不同的自采集视频流：

![多频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source2.png)

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

在进行操作之前，请确保你已经在项目中实现了基本的实时音视频功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现自定义视频采集 [#实现自定义视频采集-1]

参考下图调用时序，在你的 App 中实现自定义视频采集：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source.svg" displayWidth="70%" />

参考如下步骤，在你的 App 中实现自定义视频采集功能：

### 1. 创建自定义视频轨道 [#1-创建自定义视频轨道-1]

初始化 `AgoraRtcEngineKit` 后，调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要，你可以创建多个自定义视频轨道。

```swift
// 如需创建多个自定义视频轨道，可以多次调用 createCustomVideoTrack
customCamera?.trackId = agoraKit.createCustomVideoTrack()
```

### 2. 加入频道并发布自定义视频轨道 [#2-加入频道并发布自定义视频轨道-1]

调用 `joinChannelByToken`[2/4] 加入单频道，或调用 `joinChannelExByToken`[1/2] 加入多频道。在 `AgoraRtcChannelMediaOptions` 中，将 `customVideoTrackId` 设置为步骤 1 中获得的视频轨道 ID，并将 `publishCustomVideoTrack` 设置为 `true`，即可发布指定的自定义视频轨道。

```swift tab="加入单频道"
let option = AgoraRtcChannelMediaOptions()
option.clientRoleType = .broadcaster
option.autoSubscribeAudio = true
option.autoSubscribeVideo = true
// highlight-start
// 发布自采集视频流
option.publishCustomVideoTrack = true
// 设置自定义视频轨道 ID
option.customVideoTrackId = Int(customCamera?.trackId ?? 0)
// highlight-end
NetworkManager.shared.generateToken(channelName: channel, success: { token in
    // 加入单频道
    let result = self.agoraKit.joinChannel(byToken: token, channelId: channel, uid: 0, mediaOptions: option)
    if result != 0 {
        self.isProcessing = false
        self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
    }
})
```

```swift tab="加入多频道"
// 如需在多个频道发布自定义视频轨道，则需要多次设置 AgoraRtcChannelMediaOptions 并多次调用 joinChannelEx
let option = AgoraRtcChannelMediaOptions()
option.clientRoleType = .broadcaster
option.autoSubscribeAudio = true
option.autoSubscribeVideo = true
// highlight-start
// 发布自采集视频流
option.publishCustomVideoTrack = true
// 设置自定义视频轨道 ID
option.customVideoTrackId = Int(trackId)
// highlight-end
let connection = AgoraRtcConnection()
connection.localUid = uid
connection.channelId = channelName
NetworkManager.shared.generateToken(channelName: channelName, uid: uid) { token in
    // 加入多频道
    let result = self.agoraKit.joinChannelEx(byToken: token,
                                        connection: connection,
                                        delegate: self,
                                        mediaOptions: option,
                                        joinSuccess: nil)
    if result != 0 {
        self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
    }
}
```

### 3. 实现自采集模块 [#3-实现自采集模块-1]

声网提供 [CustomVideoSourcePushMulti.swift](https://github.com/Shengwang-Community/API-Examples/blob/main/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中，你需要结合业务需求为你的设备创建自定义视频采集模块。

### 4. 通过视频轨道推送视频数据到 SDK [#4-通过视频轨道推送视频数据到-sdk-1]

调用 `pushExternalVideoFrame`[1/2] 将采集到的视频帧通过视频轨道推送至 SDK。其中，`videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致，`AgoraVideoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。

:::info[信息]
- 以下代码演示推送 CVPixelBufferRef 格式的视频数据。其他支持的视频帧格式详见 [`AgoraVideoFrame`](/zh-CN/api-reference/rtc/ios/class-externalvideoframe) 的 `format`。
- 为确保音视频同步，声网建议你将 `AgoraVideoFrame` 的 `time`（时间戳）设置为系统 Monotonic Time。你可以调用 `getCurrentMonotonicTimeInMs` 获取当前的 Monotonic Time。
:::

```swift
let videoFrame = AgoraVideoFrame()
videoFrame.format = 12
videoFrame.textureBuf = buffer
videoFrame.rotation = Int32(rotation)
agoraKit?.pushExternalVideoFrame(videoFrame, videoTrackId: trackId)
```

### 5. 销毁自定义视频轨道 [#5-销毁自定义视频轨道-1]

如果你需要停止发布自定义采集的视频，调用 `destroyCustomVideoTrack` 销毁视频轨道。如需销毁多个视频轨道，可多次调用 `destroyCustomVideoTrack`。

```swift
// 销毁自定义视频轨道
agoraKit.destroyCustomVideoTrack(UInt(userModel?.trackId ?? 0))
// 离开频道
agoraKit.leaveChannelEx(connection) { state in
    LogUtils.log(message: "warning: \(state.description)", level: .info)
}
```

## 参考信息 [#参考信息-1]

### 适用场景 [#适用场景-1]

你可以在多种行业的多种场景下使用到自定义视频采集：

#### 视频特殊处理和增强 [#视频特殊处理和增强-1]

在某些游戏或虚拟现实应用中，需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下，使用自定义视频采集可以直接获取原始视频流，并进行实时处理，从而实现更加逼真的游戏或虚拟现实效果。

#### 高精度视频采集 [#高精度视频采集-1]

在视频监控领域，需要对场景中的细节进行精细的观察和分析，此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。

#### 特定视频源采集 [#特定视频源采集-1]

在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源，例如视频捕捉卡、录屏数据等。在这种情况下，使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定视频源的采集。

#### 与特定设备或第三方应用无缝对接 [#与特定设备或第三方应用无缝对接-1]

在智能家居或物联网领域，需要将设备中的视频传输到用户的手机或电脑上进行监控和控制，此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下，使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。

#### 特定的视频编码格式 [#特定的视频编码格式-1]

在特定直播场景中，可能需要使用特定的视频编码格式来满足业务需求，此时使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。

### 优势介绍 [#优势介绍-1]

使用自定义视频采集，你可以体验到：

#### 更多类型的视频流 [#更多类型的视频流-1]

自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头，从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验，并使产品更具竞争力。

#### 更灵活的视频特效 [#更灵活的视频特效-1]

自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器，从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效，如美颜、滤镜、动态贴纸等。

#### 更适应各种场景的需求 [#更适应各种场景的需求-1]

自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求，如直播、视频会议、在线教育等。用户可以根据不同的场景需求，定制不同的视频采集方案，从而提供适应性更强的应用程序。

### 注意事项 [#注意事项-1]

如果采集到的自定义视频格式为 Texture，并且远端用户看到的自定义采集视频画面出现闪烁、失真等异常情况时，建议先复制视频数据，再将原始视频数据和复制的视频数据发回至 SDK，从而消除内部数据编码过程中出现的异常情况。

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

声网提供了开源的视频自采集示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">CustomVideoSourcePushMulti.swift</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/API-Examples/blob/main/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift" />
  <Card title={<RepositoryCardTitle provider="github">CustomVideoSourcePushMulti.swift</RepositoryCardTitle>} href="https://github.com/Shengwang-Community/API-Examples/blob/main/iOS/APIExample/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift" />
</Cards>

### API 参考 [#api-参考-1]

- [`createCustomVideoTrack`](/zh-CN/api-reference/rtc/ios/video/video-custom-capturenrendering#api_irtcengine_createcustomvideotrack)
- [`destroyCustomVideoTrack`](/zh-CN/api-reference/rtc/ios/video/video-custom-capturenrendering#api_irtcengine_destroycustomvideotrack)
- [`pushExternalVideoFrame`[1/2]](/zh-CN/api-reference/rtc/ios/video/video-custom-capturenrendering#api_irtcengine_pushvideoframe)
- [`getCurrentMonotonicTimeInMs`](/zh-CN/api-reference/rtc/ios/network#api_irtcengine_getcurrentmonotonictimeinms)
- [`joinChannelExByToken`[1/2]](/zh-CN/api-reference/rtc/ios/channel#api_irtcengineex_joinchannelex)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="macos" />
自定义视频采集是指通过自定义的视频采集源实现对视频流的采集。

与 SDK 默认的视频采集方式不同，自定义视频采集支持用户自行控制采集源，实现更加精细的视频属性调整。例如，支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集，同时支持动态调整视频质量、分辨率和帧率等参数，以适应不同的应用场景和需求。

声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集，如果你有特定的视频采集需求或无法使用 SDK 采集，那么自定义视频采集为你提供灵活、可定制的方案。

## 技术原理 [#技术原理-2]

声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道，加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集，并将采集的视频帧通过视频轨道发送给 SDK。

下图展示在单频道和多频道中实现自定义视频采集时，视频数据的传输过程：

仅在一个频道内发布自采集视频流：

![单频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source1.png)

在多个频道内发布不同的自采集视频流：

![多频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source2.png)

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

在进行操作之前，请确保你已经在项目中实现了基本的实时音视频功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现自定义视频采集 [#实现自定义视频采集-2]

参考下图调用时序，在你的 App 中实现自定义视频采集：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source.svg" displayWidth="70%" />

参考如下步骤，在你的 App 中实现自定义视频采集功能：

### 1. 创建自定义视频轨道 [#1-创建自定义视频轨道-2]

初始化 `AgoraRtcEngineKit` 后，调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要，你可以创建多个自定义视频轨道。

```swift
// 如需创建多个自定义视频轨道，可以多次调用 createCustomVideoTrack
customCamera?.trackId = agoraKit.createCustomVideoTrack()
```

### 2. 加入频道并发布自定义视频轨道 [#2-加入频道并发布自定义视频轨道-2]

调用 `joinChannelByToken`[2/4] 加入单频道，或调用 `joinChannelExByToken`[1/2] 加入多频道。在 `AgoraRtcChannelMediaOptions` 中，将 `customVideoTrackId` 设置为步骤 1 中获得的视频轨道 ID，并将 `publishCustomVideoTrack` 设置为 `true`，即可发布指定的自定义视频轨道。

```swift tab="加入单频道"
let option = AgoraRtcChannelMediaOptions()
option.clientRoleType = .broadcaster
option.autoSubscribeAudio = true
option.autoSubscribeVideo = true
// highlight-start
// 发布自采集视频流
option.publishCustomVideoTrack = true
// 设置自定义视频轨道 ID
option.customVideoTrackId = Int(customCamera?.trackId ?? 0)
// highlight-end
NetworkManager.shared.generateToken(channelName: channel, success: { token in
    // 加入单频道
    let result = self.agoraKit.joinChannel(byToken: token, channelId: channel, uid: 0, mediaOptions: option)
    if result != 0 {
        self.isProcessing = false
        self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
    }
})
```

```swift tab="加入多频道"
// 如需在多个频道发布自定义视频轨道，则需要多次设置 AgoraRtcChannelMediaOptions 并多次调用 joinChannelEx
let option = AgoraRtcChannelMediaOptions()
option.clientRoleType = .broadcaster
option.autoSubscribeAudio = true
option.autoSubscribeVideo = true
// highlight-start
// 发布自采集视频流
option.publishCustomVideoTrack = true
// 设置自定义视频轨道 ID
option.customVideoTrackId = Int(trackId)
// highlight-end
let connection = AgoraRtcConnection()
connection.localUid = uid
connection.channelId = channelName
NetworkManager.shared.generateToken(channelName: channelName, uid: uid) { token in
    // 加入多频道
    let result = self.agoraKit.joinChannelEx(byToken: token,
                                        connection: connection,
                                        delegate: self,
                                        mediaOptions: option,
                                        joinSuccess: nil)
    if result != 0 {
        self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
    }
}
```

### 3. 实现自采集模块 [#3-实现自采集模块-2]

声网提供 [CustomVideoSourcePushMulti.swift](https://github.com/Shengwang-Community/API-Examples/blob/main/macOS/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中，你需要结合业务需求为你的设备创建自定义视频采集模块。

### 4. 通过视频轨道推送视频数据到 SDK [#4-通过视频轨道推送视频数据到-sdk-2]

调用 `pushExternalVideoFrame`[1/2] 将采集到的视频帧通过视频轨道推送至 SDK。其中，`videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致，`AgoraVideoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。

:::info[信息]
- 以下代码演示推送 CVPixelBufferRef 格式的视频数据。其他支持的视频帧格式详见 [`AgoraVideoFrame`](/zh-CN/api-reference/rtc/macos/class-externalvideoframe) 的 `format`。
- 为确保音视频同步，声网建议你将 `AgoraVideoFrame` 的 `time`（时间戳）设置为系统 Monotonic Time。你可以调用 `getCurrentMonotonicTimeInMs` 获取当前的 Monotonic Time。
:::

```swift
let videoFrame = AgoraVideoFrame()
videoFrame.format = 12
videoFrame.textureBuf = buffer
videoFrame.rotation = Int32(rotation)
agoraKit.pushExternalVideoFrame(videoFrame, videoTrackId: trackId)
```

### 5. 销毁自定义视频轨道 [#5-销毁自定义视频轨道-2]

如果你需要停止发布自定义采集的视频，调用 `destroyCustomVideoTrack` 销毁视频轨道。如需销毁多个视频轨道，可多次调用 `destroyCustomVideoTrack`。

```swift
// 销毁自定义视频轨道
agoraKit.destroyCustomVideoTrack(UInt(userModel?.trackId ?? 0))
// 离开频道
agoraKit.leaveChannelEx(connection) { state in
    LogUtils.log(message: "warning: \(state.description)", level: .info)
}
```

## 参考信息 [#参考信息-2]

### 适用场景 [#适用场景-2]

你可以在多种行业的多种场景下使用到自定义视频采集：

#### 视频特殊处理和增强 [#视频特殊处理和增强-2]

在某些游戏或虚拟现实应用中，需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下，使用自定义视频采集可以直接获取原始视频流，并进行实时处理，从而实现更加逼真的游戏或虚拟现实效果。

#### 高精度视频采集 [#高精度视频采集-2]

在视频监控领域，需要对场景中的细节进行精细的观察和分析，此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。

#### 特定视频源采集 [#特定视频源采集-2]

在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源，例如视频捕捉卡、录屏数据等。在这种情况下，使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定视频源的采集。

#### 与特定设备或第三方应用无缝对接 [#与特定设备或第三方应用无缝对接-2]

在智能家居或物联网领域，需要将设备中的视频传输到用户的手机或电脑上进行监控和控制，此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下，使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。

#### 特定的视频编码格式 [#特定的视频编码格式-2]

在特定直播场景中，可能需要使用特定的视频编码格式来满足业务需求，此时使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。

### 优势介绍 [#优势介绍-2]

使用自定义视频采集，你可以体验到：

#### 更多类型的视频流 [#更多类型的视频流-2]

自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头，从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验，并使产品更具竞争力。

#### 更灵活的视频特效 [#更灵活的视频特效-2]

自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器，从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效，如美颜、滤镜、动态贴纸等。

#### 更适应各种场景的需求 [#更适应各种场景的需求-2]

自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求，如直播、视频会议、在线教育等。用户可以根据不同的场景需求，定制不同的视频采集方案，从而提供适应性更强的应用程序。

### 注意事项 [#注意事项-2]

如果采集到的自定义视频格式为 `Texture`，并且远端用户看到的自定义采集视频画面出现闪烁、失真等异常情况时，建议先复制视频数据，再将原始视频数据和复制的视频数据发回至 SDK，从而消除内部数据编码过程中出现的异常情况。

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

声网提供了开源的视频自采集示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">CustomVideoSourcePushMulti.swift</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/API-Examples/blob/main/macOS/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift" />
  <Card title={<RepositoryCardTitle provider="github">CustomVideoSourcePushMulti.swift</RepositoryCardTitle>} href="https://github.com/Shengwang-Community/API-Examples/blob/main/macOS/APIExample/Examples/Advanced/CustomVideoSourcePushMulti/CustomVideoSourcePushMulti.swift" />
</Cards>

### API 参考 [#api-参考-2]

- [`createCustomVideoTrack`](/zh-CN/api-reference/rtc/macos/video/video-custom-capturenrendering#api_irtcengine_createcustomvideotrack)
- [`destroyCustomVideoTrack`](/zh-CN/api-reference/rtc/macos/video/video-custom-capturenrendering#api_irtcengine_destroycustomvideotrack)
- [`pushExternalVideoFrame`[1/2]](/zh-CN/api-reference/rtc/macos/video/video-custom-capturenrendering#api_irtcengine_pushvideoframe)
- [`getCurrentMonotonicTimeInMs`](/zh-CN/api-reference/rtc/macos/network#api_irtcengine_getcurrentmonotonictimeinms)
- [`joinChannelExByToken`[1/2]](/zh-CN/api-reference/rtc/macos/channel#api_irtcengineex_joinchannelex)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="web" />
实时音视频传输过程中，声网 SDK 通常会启动默认的音视频模块进行采集。在以下场景中，你可能会发现默认的音视频模块无法满足开发需求：

- App 中已有自己的音频或视频模块。
- 希望使用非摄像头采集的视频源，比如 Canvas 数据。
- 需要使用自定义的视频前处理库。

本文介绍如何使用声网 Web SDK 在项目中实现自定义的视频采集。

## 实现方法 [#实现方法]

开始前，请确保你已参考[快速开始](/zh-CN/realtime-media/rtc/get-started/quick-start)在你的项目中实现基本的实时音视频功能。

SDK 提供 `createCustomVideoTrack`，支持通过传入一个 [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) 对象来创建本地视频轨道。

你可以通过获取 `MediaStreamTrack` 对象来实现自定义视频采集或者前处理。例如，你可以自己调用 `getUserMedia` 方法来获取 `MediaStreamTrack`，再通过 `createCustomVideoTrack` 来创建可以在 SDK 中使用的本地视频对象。

```javascript
navigator.mediaDevices.getUserMedia({ video: true, audio: false })
  .then((mediaStream) => {
    const videoMediaStreamTrack = mediaStream.getVideoTracks()[0];
    // Create a custom video track
    return AgoraRTC.createCustomVideoTrack({
      mediaStreamTrack: videoMediaStreamTrack,
    });
  })
  .then((localVideoTrack) => {
    // ...
  });
```

:::info[信息]
- `MediaStreamTrack` 对象是指浏览器原生支持的 `MediaStreamTrack` 对象，具体用法和浏览器支持状况请参考 [MediaStreamTrack API 说明](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaStreamTrack)。
- 你也可以利用 [HTMLMediaElement.captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/captureStream) 或 [HTMLCanvasElement.captureStream](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/captureStream) 来获取 `MediaStreamTrack` 对象。
:::

## API 参考 [#api-参考-3]

- [`createCustomVideoTrack`](/zh-CN/api-reference/rtc/web/interfaces/iagorartc#createcustomvideotrack)
- [`LocalVideoTrack`](/zh-CN/api-reference/rtc/web/interfaces/ilocalvideotrack)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="windows" />
自定义视频采集是指通过自定义的视频采集源实现对视频流的采集。

与 SDK 默认的视频采集方式不同，自定义视频采集支持用户自行控制采集源，实现更加精细的视频属性调整。例如，支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集，同时支持动态调整视频质量、分辨率和帧率等参数，以适应不同的应用场景和需求。

声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集，如果你有特定的视频采集需求或无法使用 SDK 采集，那么自定义视频采集为你提供灵活、可定制的方案。

## 技术原理 [#技术原理-3]

声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道，加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集，并将采集的视频帧通过视频轨道发送给 SDK。

下图展示在单频道和多频道中实现自定义视频采集时，视频数据的传输过程：

仅在一个频道内发布自采集视频流：

![单频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source1.png)

在多个频道内发布不同的自采集视频流：

![多频道视频自采集](https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source2.png)

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

在进行操作之前，请确保你已经在项目中实现了基本的实时音视频功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现自定义视频采集 [#实现自定义视频采集-3]

参考下图调用时序，在你的 App 中实现自定义视频采集：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source.svg" displayWidth="70%" />

参考如下步骤，在你的 App 中实现自定义视频采集功能：

### 1. 创建自定义视频轨道 [#1-创建自定义视频轨道-3]

初始化 `IRtcEngine` 后，调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要，你可以创建多个自定义视频轨道。

```cpp
// 如需创建多个自定义视频轨道，可以多次调用 createCustomVideoTrack
int videoTrackId = m_rtcEngine->createCustomVideoTrack();
m_trackVideoTrackIds[trackIndex] = videoTrackId;
```

### 2. 加入频道并发布自定义视频轨道 [#2-加入频道并发布自定义视频轨道-3]

调用 `joinChannel`[2/2] 加入频道，或调用 `joinChannelEx` 加入多频道， 在每个频道的 `ChannelMediaOptions` 中，将 `customVideoTrackId` 参数设置为步骤 1 中获得的视频轨道 ID，并将 `publishCustomVideoTrack` 设置为 `true`，即可在频道中发布指定的自定义视频轨道。

加入单频道：

```cpp
ChannelMediaOptions mediaOptions;
mediaOptions.clientRoleType = CLIENT_ROLE_BROADCASTER;
// 发布自采集视频流
mediaOptions.publishCustomVideoTrack = true;
mediaOptions.autoSubscribeVideo = false;
mediaOptions.autoSubscribeAudio = false;
// 设置自定义视频轨道 ID
mediaOptions.customVideoTrackId = videoTrackId;
// 加入频道
int ret = m_rtcEngine->joinChannel(APP_TOKEN, szChannelId.data(), 0, mediaOptions);
```

加入多频道：

```cpp
int uid = 10001 + trackIndex;
m_trackUids[trackIndex] = uid;
m_trackConnections[trackIndex].channelId = m_strChannel.c_str();
m_trackConnections[trackIndex].localUid = uid;
m_trackEventHandlers[trackIndex].SetId(trackIndex + 1);
m_trackEventHandlers[trackIndex].SetMsgReceiver(m_hWnd);

// 如需在多个频道发布自定义视频轨道，则需要多次设置 ChannelMediaOptions 并多次调用 joinChannelEx
ChannelMediaOptions mediaOptions;
mediaOptions.clientRoleType = CLIENT_ROLE_BROADCASTER;
// 发布自采集视频流
mediaOptions.publishCustomVideoTrack = true;
mediaOptions.autoSubscribeVideo = false;
mediaOptions.autoSubscribeAudio = false;
// 设置自定义视频轨道 ID
mediaOptions.customVideoTrackId = videoTrackId;
// 或加入多频道
int ret = m_rtcEngine->joinChannelEx(APP_TOKEN, m_trackConnections[trackIndex], mediaOptions, &m_trackEventHandlers[trackIndex]);
```

### 3. 实现自采集模块 [#3-实现自采集模块-3]

声网提供 [YUVReader.cpp](https://github.com/Shengwang-Community/API-Examples/blob/main/windows/APIExample/APIExample/YUVReader.cpp) 和 [YUVReader.h](https://github.com/Shengwang-Community/API-Examples/blob/main/windows/APIExample/APIExample/YUVReader.h) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中，你需要结合业务需求为你的采集设备创建自定义视频采集模块。

```cpp
// 通过自定义的 YUVReader 类，在 YUVReader 线程中不断读取 YUV 格式视频数据并将数据传递给 OnYUVRead 回调函数进行后续处理
m_yuvReaderHandlers[trackIndex].Setup(m_rtcEngine, m_mediaEngine.get(), videoTrackId);
m_yuvReaders[trackIndex].start(std::bind(&MultiVideoSourceTracksYUVReaderHander::OnYUVRead, m_yuvReaderHandlers[trackIndex], std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
```

### 4. 通过视频轨道推送视频数据到 SDK [#4-通过视频轨道推送视频数据到-sdk-3]

调用 `pushVideoFrame` 将采集到的视频帧通过视频轨道推送至 SDK。其中， `videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致，`videoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。

:::info[信息]
- 以下代码演示将 YUV 格式转换为 I420 格式的原始视频数据。声网视频自采集还支持推送其他格式的外部视频帧，详见 [`VIDEO_PIXEL_FORMAT`](/zh-CN/api-reference/rtc/cpp-all-platforms/enum-videopixelformat)。
- 为确保音视频同步，声网建议你将 `videoFrame` 的时间戳参数设置为系统 Monotonic Time。你可以调用 `getCurrentMonotonicTimeInMs` 获取当前的 Monotonic Time。
:::

```cpp
void MultiVideoSourceTracksYUVReaderHander::OnYUVRead(int width, int height,
                                                      unsigned char *buffer,
                                                      int size) {
    if (m_mediaEngine == nullptr || m_rtcEngine == nullptr) {
        return;
    }
    // 设置视频像素格式为 I420
    m_videoFrame.format = agora::media::base::VIDEO_PIXEL_I420;
    // 设置视频数据类型为原始数据
    m_videoFrame.type = agora::media::base::ExternalVideoFrame::
        VIDEO_BUFFER_TYPE::VIDEO_BUFFER_RAW_DATA;
    // 将采集到的 YUV 视频数据的宽、高、缓冲区传给 videoFrame
    m_videoFrame.height = height;
    m_videoFrame.stride = width;
    m_videoFrame.buffer = buffer;
    // 获取 SDK 当前的 Monotonic Time 并赋值给 videoFrame 的时间戳参数
    m_videoFrame.timestamp = m_rtcEngine->getCurrentMonotonicTimeInMs();
    // 推送视频帧至 SDK
    m_mediaEngine->pushVideoFrame(&m_videoFrame, m_videoTrackId);
}
```

### 5. 销毁自定义视频轨道并离开频道 [#5-销毁自定义视频轨道并离开频道]

如果你需要停止发布自定义采集的视频，调用 `destroyCustomVideoTrack` 销毁视频轨道。如需销毁多个视频轨道，可多次调用 `destroyCustomVideoTrack`。

```cpp
// 停止视频数据的自采集
m_yuvReaders[trackIndex].stop();
m_yuvReaderHandlers[trackIndex].Release();
// 销毁自定义视频轨道
m_rtcEngine->destroyCustomVideoTrack(m_trackVideoTrackIds[trackIndex]);
// 离开频道
m_rtcEngine->leaveChannelEx(m_trackConnections[trackIndex]);
```

## 参考信息 [#参考信息-3]

### 适用场景 [#适用场景-3]

你可以在多种行业的多种场景下使用到自定义视频采集：

#### 视频特殊处理和增强 [#视频特殊处理和增强-3]

在某些游戏或虚拟现实应用中，需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下，使用自定义视频采集可以直接获取原始视频流，并进行实时处理，从而实现更加逼真的游戏或虚拟现实效果。

#### 高精度视频采集 [#高精度视频采集-3]

在视频监控领域，需要对场景中的细节进行精细的观察和分析，此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。

#### 特定视频源采集 [#特定视频源采集-3]

在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源，例如视频捕捉卡、录屏数据等。在这种情况下，使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定视频源的采集。

#### 与特定设备或第三方应用无缝对接 [#与特定设备或第三方应用无缝对接-3]

在智能家居或物联网领域，需要将设备中的视频传输到用户的手机或电脑上进行监控和控制，此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下，使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。

#### 特定的视频编码格式 [#特定的视频编码格式-3]

在特定直播场景中，可能需要使用特定的视频编码格式来满足业务需求，此时使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。

### 优势介绍 [#优势介绍-3]

使用自定义视频采集，你可以体验到：

#### 更多类型的视频流 [#更多类型的视频流-3]

自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头，从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验，并使产品更具竞争力。

#### 更灵活的视频特效 [#更灵活的视频特效-3]

自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器，从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效，如美颜、滤镜、动态贴纸等。

#### 更适应各种场景的需求 [#更适应各种场景的需求-3]

自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求，如直播、视频会议、在线教育等。用户可以根据不同的场景需求，定制不同的视频采集方案，从而提供适应性更强的应用程序。

### 注意事项 [#注意事项-3]

如果采集到的自定义视频格式为 `Texture`，并且远端用户看到的自定义采集视频画面出现闪烁、失真等异常情况时，建议先复制视频数据，再将原始视频数据和复制的视频数据发回至 SDK，从而消除内部数据编码过程中出现的异常情况。

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

声网提供了开源的视频自采集示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">MultiVideoSourceTracks</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/API-Examples/tree/main/windows/APIExample/APIExample/Advanced/MultiVideoSourceTracks" />
  <Card title={<RepositoryCardTitle provider="github">MultiVideoSourceTracks</RepositoryCardTitle>} href="https://github.com/Shengwang-Community/API-Examples/tree/main/windows/APIExample/APIExample/Advanced/MultiVideoSourceTracks" />
</Cards>

### API 参考 [#api-参考-4]

- [`createCustomVideoTrack`](/zh-CN/api-reference/rtc/cpp-all-platforms/video/video-custom-capturenrendering#api_irtcengine_createcustomvideotrack)
- [`destroyCustomVideoTrack`](/zh-CN/api-reference/rtc/cpp-all-platforms/video/video-custom-capturenrendering#api_irtcengine_destroycustomvideotrack)
- [`getCurrentMonotonicTimeInMs`](/zh-CN/api-reference/rtc/cpp-all-platforms/network#api_irtcengine_getcurrentmonotonictimeinms)
- [`joinChannelEx`](/zh-CN/api-reference/rtc/cpp-all-platforms/channel#api_irtcengineex_joinchannelex)
- [`pushVideoFrame`](/zh-CN/api-reference/rtc/cpp-all-platforms/video/video-custom-capturenrendering#pushvideoframe)
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="web" platform="unity" />
自定义视频采集是指通过自定义的视频采集源实现视频采集。

与 SDK 默认的视频采集方式不同，自定义视频采集支持用户自行控制采集源，实现更加精细的视频属性调整。例如，支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集，同时支持动态调整视频质量、分辨率和帧率等参数，以适应不同的应用场景和需求。

声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集，如果你有特定的视频采集需求或无法使用 SDK 采集，那么自定义视频采集为你提供灵活、可定制的方案。

## 技术原理 [#技术原理-4]

声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道，加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集，并将采集的视频帧通过视频轨道发送给 SDK。

下图展示在单频道和多频道中实现自定义视频采集时，视频数据的传输过程：

仅在一个频道内发布自采集视频流：

![单频道视频自采集](https://web-cdn.agora.io/docs-files/1683598621022)

在多个频道内发布不同的自采集视频流：

![多频道视频自采集](https://web-cdn.agora.io/docs-files/1683598671853)

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

在进行操作之前，请确保你已经在项目中实现了基本的实时音视频功能，详见[实现音视频互动](/zh-CN/realtime-media/rtc/get-started/quick-start)。

## 实现自定义视频采集 [#实现自定义视频采集-4]

下图展示实现自定义视频采集的流程：

<DocsImage alt="实现流程" src="https://web-cdn.agora.io/docs-portal/img/rtc/custom-video-source.svg" displayWidth="70%" />

参考如下步骤，在你的 App 中实现自定义视频采集功能：

### 1. 创建自定义视频轨道 [#1-创建自定义视频轨道-4]

初始化 `IRtcEngine` 后，调用 `CreateCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要，你可以创建多个自定义视频轨道。

```csharp
private uint _videoTrack;

private void CreateCustomVideoTrack()
{
    _videoTrack = RtcEngine.CreateCustomVideoTrack();
    this.Log.UpdateLog("CreateCustomVideoTrack returns:" + _videoTrack);
}
```

### 2. 加入频道并发布自定义视频轨道 [#2-加入频道并发布自定义视频轨道-4]

调用 `JoinChannel`[2/2] 加入频道，或调用 `JoinChannelEx` 加入多频道， 在每个频道的 `ChannelMediaOptions` 中，将 `customVideoTrackId` 参数设置为步骤 1 中获得的视频轨道 ID，并将 `publishCustomVideoTrack` 设置为 `true`，即可在频道中发布指定的自定义视频轨道。

加入单频道：

```csharp
private void JoinChannel()
{
    RtcEngine.EnableAudio();
    RtcEngine.EnableVideo();
    RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
    ChannelMediaOptions options = new ChannelMediaOptions();
    options.publishCameraTrack.SetValue(false);
    // 发布自采集的视频流
    options.publishCustomVideoTrack.SetValue(true);
    // 设置自定义视频轨道的 ID
    options.customVideoTrackId.SetValue(_videoTrack);
    // 加入频道
    RtcEngine.JoinChannel(_token, _channelName, 0, options);
}
```

加入多频道：

```csharp
private void JoinChannelEx()
{
    RtcEngine.EnableAudio();
    RtcEngine.EnableVideo();
    // 创建自定义视频轨道
    uint videoTrack1 = RtcEngine.CreateCustomVideoTrack();
    ChannelMediaOptions options = new ChannelMediaOptions();
    options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
    options.publishCameraTrack.SetValue(false);
    // 发布自采集视频流
    options.publishCustomVideoTrack.SetValue(true);
    // 设置自定义视频轨道 ID
    options.customVideoTrackId.SetValue(videoTrack1);
    options.autoSubscribeAudio.SetValue(false);
    options.autoSubscribeVideo.SetValue(false);
    // 加入频道 1
    RtcEngine.JoinChannelEx(_token, new RtcConnection("channel_id_1", 123), options);

    // 创建自定义视频轨道
    uint videoTrack2 = RtcEngine.CreateCustomVideoTrack();
    // 在多个频道发布自定义视频轨道需要多次设置 ChannelMediaOptions 并多次调用 joinChannelEx
    ChannelMediaOptions options2 = new ChannelMediaOptions();
    options2.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
    options2.publishCameraTrack.SetValue(false);
    // 发布自采集视频流
    options2.publishCustomVideoTrack.SetValue(true);
    // 设置自定义视频轨道 ID
    options2.customVideoTrackId.SetValue(videoTrack2);
    options2.autoSubscribeAudio.SetValue(false);
    options2.autoSubscribeVideo.SetValue(false);
    // 加入频道 2
    RtcEngine.JoinChannelEx(_token, new RtcConnection("channel_id_2", 123), options2);
}
```

### 3. 实现自采集模块 [#3-实现自采集模块-4]

声网提供 [CustomCaptureVideo](https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart/blob/main/API-Example-Unity/Assets/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs) 示例项目演示通过 Unity 的 WebCamTexture 实现采集摄像头数据。在实际的生产环境中，你需要结合业务需求为你的采集设备创建自定义视频采集模块。

```csharp
private WebCamTexture _webCameraTexture;
public RawImage RawImage;
public Vector2 CameraSize = new Vector2(640, 480);
public int CameraFPS = 15;

private void InitCameraDevice()
{
    // 获取所有可用的摄像头设备
    WebCamDevice[] devices = WebCamTexture.devices;

    // 使用第一个摄像头设备初始化 WebCamTexture 对象，并设置分辨率和帧率
    _webCameraTexture = new WebCamTexture(devices[0].name, (int)CameraSize.x, (int)CameraSize.y, CameraFPS);

    // 将摄像头纹理赋值给 RawImage 的纹理属性，以便显示摄像头图像
    RawImage.texture = _webCameraTexture;

    // 开始播放摄像头捕获的图像
    _webCameraTexture.Play();
}
```

### 4. 通过视频轨道推送视频数据到 SDK [#4-通过视频轨道推送视频数据到-sdk-4]

调用 `PushVideoFrame` 将采集到的视频帧通过视频轨道推送至 SDK。其中， `_videoTrack` 要与步骤 2 加入频道时指定视频轨道 ID 一致，`videoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。

:::info[信息]
为确保音视频同步，声网建议你将 `videoFrame` 的时间戳参数设置为系统 Monotonic Time。你可以调用 `getCurrentMonotonicTimeInMs` 获取当前的 Monotonic Time。
:::

```csharp
private Texture2D _texture;
private Rect _rect;
private byte[] _shareData;


//初始化一个跟当前屏幕大小一致的 Texture2D 对象用来截屏
private void InitTexture()
{
    _rect = new UnityEngine.Rect(0, 0, Screen.width, Screen.height);
    _texture = new Texture2D((int)_rect.width, (int)_rect.height, TextureFormat.RGBA32, false);
}


//每帧截屏一次，并将截屏获得的视频数据推送给 SDK
private void Update()
{
    StartCoroutine(ShareScreen());
}
private IEnumerator ShareScreen()
{
    yield return new WaitForEndOfFrame();
    IRtcEngine rtc = Agora.Rtc.RtcEngine.Instance;
    if (rtc != null)
    {
        _texture.ReadPixels(_rect, 0, 0);
        _texture.Apply();

#if UNITY_2018_1_OR_NEWER
        NativeArray<byte> nativeByteArray = _texture.GetRawTextureData<byte>();
        if (_shareData == null || _shareData.Length != nativeByteArray.Length)
        {
            _shareData = new byte[nativeByteArray.Length];
        }
        nativeByteArray.CopyTo(_shareData);
#else
        _shareData = _texture.GetRawTextureData();
#endif
        // 设置推送的自采集视频的编码属性
        ExternalVideoFrame externalVideoFrame = new ExternalVideoFrame();
        // 设置视频数据类型为原始数据
        externalVideoFrame.type = VIDEO_BUFFER_TYPE.VIDEO_BUFFER_RAW_DATA;
        // 设置视频格式为 RGBA
        externalVideoFrame.format = VIDEO_PIXEL_FORMAT.VIDEO_PIXEL_RGBA;
        externalVideoFrame.buffer = _shareData;
        externalVideoFrame.stride = (int)_rect.width;
        externalVideoFrame.height = (int)_rect.height;
        externalVideoFrame.cropLeft = 10;
        externalVideoFrame.cropTop = 10;
        externalVideoFrame.cropRight = 10;
        externalVideoFrame.cropBottom = 10;
        externalVideoFrame.rotation = 180;
        externalVideoFrame.timestamp = System.DateTime.Now.Ticks / 10000;
        // 推送视频帧至 SDK
        var ret = rtc.PushVideoFrame(externalVideoFrame, _videoTrack);
        Debug.Log("PushVideoFrame ret = " + ret + "time: " + System.DateTime.Now.Millisecond);
    }
}
```

### 5. 销毁自定义视频轨道并离开频道 [#5-销毁自定义视频轨道并离开频道-1]

如果你需要停止发布自定义采集的视频，调用 `DestroyCustomVideoTrack` 销毁视频轨道。如需销毁多个视频轨道，可多次调用 `DestroyCustomVideoTrack`。

```csharp
private void OnDestroy()
{
    Debug.Log("OnDestroy");
    if (_webCameraTexture)
    {
        _webCameraTexture.Stop();
    }
    if (RtcEngine == null) return;
    // 取消注册频道事件
    RtcEngine.InitEventHandler(null);
    // 离开频道
    RtcEngine.LeaveChannel();
    // 销毁自定义视频轨道
    RtcEngine.DestroyCustomVideoTrack(_videoTrack);
    // 销毁引擎对象，释放所有资源
    RtcEngine.Dispose();
}
```

## 参考信息 [#参考信息-4]

### 适用场景 [#适用场景-4]

你可以在多种行业的多种场景下使用到自定义视频采集：

#### 视频特殊处理和增强 [#视频特殊处理和增强-4]

在某些游戏或虚拟现实应用中，需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下，使用自定义视频采集可以直接获取原始视频流，并进行实时处理，从而实现更加逼真的游戏或虚拟现实效果。

#### 高精度视频采集 [#高精度视频采集-4]

在视频监控领域，需要对场景中的细节进行精细的观察和分析，此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。

#### 特定视频源采集 [#特定视频源采集-4]

在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源，例如视频捕捉卡、录屏数据等。在这种情况下，使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定视频源的采集。

#### 与特定设备或第三方应用无缝对接 [#与特定设备或第三方应用无缝对接-4]

在智能家居或物联网领域，需要将设备中的视频传输到用户的手机或电脑上进行监控和控制，此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下，使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。

#### 特定的视频编码格式 [#特定的视频编码格式-4]

在特定直播场景中，可能需要使用特定的视频编码格式来满足业务需求，此时使用 SDK 内部采集可能无法满足需求，必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。

### 优势介绍 [#优势介绍-4]

使用自定义视频采集，你可以体验到：

#### 更多类型的视频流 [#更多类型的视频流-4]

自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头，从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验，并使产品更具竞争力。

#### 更灵活的视频特效 [#更灵活的视频特效-4]

自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器，从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效，如美颜、滤镜、动态贴纸等。

#### 更适应各种场景的需求 [#更适应各种场景的需求-4]

自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求，如直播、视频会议、在线教育等。用户可以根据不同的场景需求，定制不同的视频采集方案，从而提供适应性更强的应用程序。

### 注意事项 [#注意事项-4]

如果采集到的自定义视频格式为 `Texture`，并且远端用户看到的自定义采集视频画面出现闪烁、失真等异常情况时，建议先复制视频数据，再将原始视频数据和复制的视频数据发回至 SDK，从而消除内部数据编码过程中出现的异常情况。

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

声网提供了开源的视频自采集示例项目供你参考，你可以前往下载或查看其中的源代码。

<Cards>
  <Card title={<RepositoryCardTitle provider="gitee">CustomCaptureVideo</RepositoryCardTitle>} href="https://gitee.com/agoraio-community/Agora-Unity-Quickstart/blob/main/API-Example-Unity/Assets/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs" />
  <Card title={<RepositoryCardTitle provider="github">CustomCaptureVideo</RepositoryCardTitle>} href="https://github.com/AgoraIO-Extensions/Agora-Unity-Quickstart/blob/main/API-Example-Unity/Assets/API-Example/Examples/Advanced/CustomCaptureVideo/CustomCaptureVideo.cs" />
</Cards>

### API 参考 [#api-参考-5]

- [`CreateCustomVideoTrack`](/zh-CN/api-reference/rtc/unity/video/video-custom-capturenrendering#api_irtcengine_createcustomvideotrack)
- [`DestroyCustomVideoTrack`](/zh-CN/api-reference/rtc/unity/video/video-custom-capturenrendering#api_irtcengine_destroycustomvideotrack)
- [`GetCurrentMonotonicTimeInMs`](/zh-CN/api-reference/rtc/unity/network#api_irtcengine_getcurrentmonotonictimeinms)
- [`JoinChannelEx`](/zh-CN/api-reference/rtc/unity/channel#api_irtcengineex_joinchannelex)
- [`PushVideoFrame`](/zh-CN/api-reference/rtc/unity/video/video-custom-capturenrendering#api_imediaengine_pushvideoframe)
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
