diff --git a/dita/RTC-NG/API/api_imediaplayer_getstreamcount.dita b/dita/RTC-NG/API/api_imediaplayer_getstreamcount.dita
index 93254aa3690..3d8e16bab09 100644
--- a/dita/RTC-NG/API/api_imediaplayer_getstreamcount.dita
+++ b/dita/RTC-NG/API/api_imediaplayer_getstreamcount.dita
@@ -24,7 +24,7 @@
+ 请在 后并收到 回调报告播放状态为 后再调用该方法。
参数
@@ -41,4 +41,4 @@
< 0: 方法调用失败,详见 。
-
\ No newline at end of file
+
diff --git a/dita/RTC-NG/API/class_channelmediaoptions.dita b/dita/RTC-NG/API/class_channelmediaoptions.dita
index 9204a03f87b..f29818144a0 100644
--- a/dita/RTC-NG/API/class_channelmediaoptions.dita
+++ b/dita/RTC-NG/API/class_channelmediaoptions.dita
@@ -502,7 +502,9 @@ class ChannelMediaOptions {
- 自 v4.0.0 起,该参数名称由 publishAudioTrack 改为 publishMicrophoneTrack。
- 自 v6.0.0 起,该参数名称由 publishAudioTrack 改为 publishMicrophoneTrack。
- - 如果你想要发布麦克风采集到的音频流,请确保 enableAudioRecordingOrPlayout 设为 。
+
如果你将该参数设为 ,SDK 也会关闭麦克风采集。
+ 如果你想要发布麦克风采集到的音频流,请确保 enableAudioRecordingOrPlayout 设为 。
+
diff --git a/dita/RTC-NG/API/class_screencaptureparameters.dita b/dita/RTC-NG/API/class_screencaptureparameters.dita
index aded6de0715..fcd2c60dc70 100644
--- a/dita/RTC-NG/API/class_screencaptureparameters.dita
+++ b/dita/RTC-NG/API/class_screencaptureparameters.dita
@@ -258,6 +258,7 @@
:(默认)采集鼠标。
: 不采集鼠标。
+ 受 macOS 系统限制,在共享屏幕时将该参数设置为 无效(共享窗口时无影响)。
diff --git a/dita/RTC-NG/API/enum_networktype.dita b/dita/RTC-NG/API/enum_networktype.dita
index 1427b9b4509..55920c21e60 100644
--- a/dita/RTC-NG/API/enum_networktype.dita
+++ b/dita/RTC-NG/API/enum_networktype.dita
@@ -21,7 +21,7 @@
- 2: 网络类型为 Wi-Fi (包含热点)。
+ 2: 网络类型为 Wi-Fi(包含热点)。
diff --git a/dita/RTC-NG/API/rtc_api_overview.dita b/dita/RTC-NG/API/rtc_api_overview.dita
index 5fc4ed439d8..468325b9a10 100644
--- a/dita/RTC-NG/API/rtc_api_overview.dita
+++ b/dita/RTC-NG/API/rtc_api_overview.dita
@@ -57,7 +57,7 @@
-
+
@@ -77,7 +77,7 @@
-
+
@@ -109,7 +109,7 @@
-
+
@@ -1013,7 +1013,7 @@
-
+
@@ -1021,7 +1021,7 @@
-
+
@@ -1029,7 +1029,7 @@
-
+
@@ -1070,7 +1070,7 @@
-
+
@@ -1082,7 +1082,7 @@
-
+
@@ -1307,7 +1307,7 @@
方法/回调
描述
-
+
@@ -2003,7 +2003,7 @@
-
+
@@ -2131,7 +2131,51 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
移动端摄像头管理
@@ -2227,9 +2271,9 @@
-
+
桌面端音频设备管理
- 该组方法仅适用于 Windows 和 macOS 平台。
+ 该组方法仅适用于 Windows 和 macOS 平台。
方法/回调
@@ -2405,9 +2449,9 @@
-
+
桌面端视频设备管理
- 该组方法仅适用于 Windows 和 macOS 平台。
+ 该组方法仅适用于 Windows 和 macOS 平台。
方法/回调
@@ -2539,11 +2583,11 @@
方法/回调
描述
-
+
-
+
diff --git a/dita/RTC-NG/API/rtc_api_overview_ng.dita b/dita/RTC-NG/API/rtc_api_overview_ng.dita
index e6a02e718f8..20505bfeb3f 100644
--- a/dita/RTC-NG/API/rtc_api_overview_ng.dita
+++ b/dita/RTC-NG/API/rtc_api_overview_ng.dita
@@ -627,7 +627,7 @@
描述
-
+
视频设备管理
diff --git a/dita/RTC-NG/API/toc_common_device.dita b/dita/RTC-NG/API/toc_common_device.dita
new file mode 100644
index 00000000000..5f21c4f5a11
--- /dev/null
+++ b/dita/RTC-NG/API/toc_common_device.dita
@@ -0,0 +1,7 @@
+
+
+
+ 通用设备管理
+
+
+
diff --git "a/dita/RTC-NG/RTC_NG_API_CS(\345\276\205\345\210\240).ditamap" "b/dita/RTC-NG/RTC_NG_API_CS(\345\276\205\345\210\240).ditamap"
deleted file mode 100644
index fafa0541bea..00000000000
--- "a/dita/RTC-NG/RTC_NG_API_CS(\345\276\205\345\210\240).ditamap"
+++ /dev/null
@@ -1,638 +0,0 @@
-
-
-
diff --git a/dita/RTC-NG/RTC_NG_API_Electron.ditamap b/dita/RTC-NG/RTC_NG_API_Electron.ditamap
index c2a1aae2b80..333363be4bf 100644
--- a/dita/RTC-NG/RTC_NG_API_Electron.ditamap
+++ b/dita/RTC-NG/RTC_NG_API_Electron.ditamap
@@ -274,6 +274,7 @@
+
diff --git a/dita/RTC-NG/RTC_NG_API_iOS.ditamap b/dita/RTC-NG/RTC_NG_API_iOS.ditamap
index 3d30af35129..7781430d130 100644
--- a/dita/RTC-NG/RTC_NG_API_iOS.ditamap
+++ b/dita/RTC-NG/RTC_NG_API_iOS.ditamap
@@ -702,6 +702,7 @@
+
diff --git a/dita/RTC-NG/RTC_NG_API_macOS.ditamap b/dita/RTC-NG/RTC_NG_API_macOS.ditamap
index 8428d6fdf60..b14ca17b91c 100644
--- a/dita/RTC-NG/RTC_NG_API_macOS.ditamap
+++ b/dita/RTC-NG/RTC_NG_API_macOS.ditamap
@@ -242,6 +242,7 @@
+
@@ -263,19 +264,18 @@
+
-
-
+
-
-
+
@@ -584,24 +584,20 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -661,6 +657,7 @@
+
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-cpp.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-cpp.ditamap
index ccb5ed64f91..ba7db99f619 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-cpp.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-cpp.ditamap
@@ -11901,7 +11901,7 @@
- VideoContentHint
+ VIDEO_CONTENT_HINT
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-flutter.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-flutter.ditamap
index 7f5df3fd91b..3c6eac26100 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-flutter.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-flutter.ditamap
@@ -9301,6 +9301,13 @@
+
+
+
+ networkTypeMobile5g
+
+
+
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
index 68d5073df61..daf656e1267 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
@@ -3493,7 +3493,7 @@
- connectionChangedToState
+ connectionStateChanged
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
index 09a6825bfec..167a70f1d30 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
@@ -3259,7 +3259,7 @@
- connectionChangedToState
+ connectionStateChanged
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-rn.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-rn.ditamap
index d0fefe8caf1..b464b3083e9 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-rn.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-rn.ditamap
@@ -9193,6 +9193,13 @@
+
+
+
+ NetworkTypeMobile5g
+
+
+
@@ -11979,4 +11986,4 @@
-
\ No newline at end of file
+
diff --git a/dita/RTC-NG/config/keys-rtc-ng-api-unity.ditamap b/dita/RTC-NG/config/keys-rtc-ng-api-unity.ditamap
index 1400d43da9c..61bf93f8538 100644
--- a/dita/RTC-NG/config/keys-rtc-ng-api-unity.ditamap
+++ b/dita/RTC-NG/config/keys-rtc-ng-api-unity.ditamap
@@ -9670,6 +9670,13 @@
+
+
+
+ NETWORK_TYPE_MOBILE_5G
+
+
+
diff --git a/en-US/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap b/en-US/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
index 7bf2a156103..399d9508865 100644
--- a/en-US/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
+++ b/en-US/dita/RTC-NG/config/keys-rtc-ng-api-ios.ditamap
@@ -3465,7 +3465,7 @@
- connectionChangedToState
+ connectionStateChanged
diff --git a/en-US/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap b/en-US/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
index 7b7a3f21f7a..a3f363c2c07 100644
--- a/en-US/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
+++ b/en-US/dita/RTC-NG/config/keys-rtc-ng-api-macos.ditamap
@@ -3231,7 +3231,7 @@
- connectionChangedToState
+ connectionStateChanged
diff --git a/markdown/RTC 4.x/custom video source/ custom_video_source_android_ng.md b/markdown/RTC 4.x/custom video source/ custom_video_source_android_ng.md
index b4f3cc2dc1b..ae13050595c 100644
--- a/markdown/RTC 4.x/custom video source/ custom_video_source_android_ng.md
+++ b/markdown/RTC 4.x/custom video source/ custom_video_source_android_ng.md
@@ -55,14 +55,10 @@
下图展示在单频道和多频道中实现自定义视频采集时,视频数据的传输过程:
-### 单频道
-
仅在一个频道内发布自采集视频流:
![](https://web-cdn.agora.io/docs-files/1683598621022)
-### 多频道
-
在多个频道内发布不同的自采集视频流:
![](https://web-cdn.agora.io/docs-files/1683598671853)
@@ -75,157 +71,165 @@
## 实现自定义视频采集
-参考如下内容,在你的 app 中实现自定义视频采集功能。
-
-### API 调用时序
-
参考下图调用时序,在你的 app 中实现自定义视频采集:
-![](https://web-cdn.agora.io/docs-files/1683598705647)
-
-### 实现步骤
+![](https://web-cdn.agora.io/docs-files/1686295832877)
参考如下步骤,在你的 app 中实现自定义视频采集功能:
-1. 初始化 `RtcEngine` 后,调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要,你可以创建多个自定义视频轨道。
+### 1. 创建自定义视频轨道
+
+初始化 `RtcEngine` 后,调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要,你可以创建多个自定义视频轨道。
```java
// 如需创建多个自定义视频轨道,可以多次调用 createCustomVideoTrack
int videoTrackId = RtcEngine.createCustomVideoTrack();
```
-2. 调用 `joinChannel` 加入频道,或调用 `joinChannelEx` 加入多频道, 在每个频道的 `ChannelMediaOptions` 中,将 `customVideoTrackId` 参数设置为步骤 1 中获得的视频轨道 ID,并将 `publishCustomVideoTrack` 设置为 `true`,`publishCameraTrack` 设置为 `false`,即可在多个频道中发布指定的自定义视频轨道。
+### 2. 加入频道并发布自定义视频轨道
+
+调用 `joinChannel` 加入频道,或调用 `joinChannelEx` 加入多频道, 在每个频道的 `ChannelMediaOptions` 中,将 `customVideoTrackId` 参数设置为步骤 1 中获得的视频轨道 ID,并将 `publishCustomVideoTrack` 设置为 `true`,即可在多个频道中发布指定的自定义视频轨道。
+
+加入主频道:
```java
-// 如需在多个频道发布自定义视频轨道,则需要多次设置 ChannelMediaOptions 并多次调用 joinChannelEx
ChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
-// 取消发布摄像头流
-option.publishCameraTrack = false;
// 发布自采集视频流
option.publishCustomVideoTrack = true;
+// 设置自定义视频轨道 ID
option.customVideoTrackId = videoTrackId;
// 加入主频道
-int res = RtcEngine.joinChannel(accessToken, option, new IRtcEngineEventHandler(){});
-// 或加入多频道
-int res = RtcEngine.joinChannelEx(accessToken, connection, option, new IRtcEngineEventHandler(){});
+int res = engine.joinChannel(accessToken, channelId, 0, option);
```
-3. 实现视频采集。声网提供 [VideoFileReader.java](https://github.com/AgoraIO/API-Examples/blob/main/Android/APIExample/app/src/main/java/io/agora/api/example/utils/VideoFileReader.java) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中,声网 SDK 不提供自定义视频处理 API,你需要结合业务需求使用 Android SDK 为你的设备创建自定义视频模块。
+加入多频道:
-4. 将采集到的视频帧发送至 SDK 之前,通过设置 `VideoFrame` 集成你的视频模块。你可以参考以下代码,将采集到的 YUV 视频数据转换为不同类型的 `VideoFrame`。为确保音视频同步,声网建议你调用 `getCurrentMonotonicTimeInMs` 获取 SDK 当前的 Monotonic Time 后,将该值传入采集的 `VideoFrame` 的时间戳参数。
+```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() {});
+```
- ```java
- // 创建不同类型的 VideoFrame
- VideoFrame.Buffer frameBuffer;
- // 将 YUV 视频数据转换为 NV21 格式
- if ("NV21".equals(selectedItem)) {
- int srcStrideY = width;
- int srcHeightY = height;
- int srcSizeY = srcStrideY * srcHeightY;
- ByteBuffer srcY = ByteBuffer.allocateDirect(srcSizeY);
- srcY.put(yuv, 0, srcSizeY);
-
- int srcStrideU = width / 2;
- int srcHeightU = height / 2;
- int srcSizeU = srcStrideU * srcHeightU;
- ByteBuffer srcU = ByteBuffer.allocateDirect(srcSizeU);
- srcU.put(yuv, srcSizeY, srcSizeU);
-
- int srcStrideV = width / 2;
- int srcHeightV = height / 2;
- int srcSizeV = srcStrideV * srcHeightV;
- ByteBuffer srcV = ByteBuffer.allocateDirect(srcSizeV);
- srcV.put(yuv, srcSizeY + srcSizeU, srcSizeV);
-
- int desSize = srcSizeY + srcSizeU + srcSizeV;
- ByteBuffer des = ByteBuffer.allocateDirect(desSize);
- YuvHelper.I420ToNV12(srcY, srcStrideY, srcV, srcStrideV, srcU, srcStrideU, des, width, height);
-
- byte[] nv21 = new byte[desSize];
- des.position(0);
- des.get(nv21);
-
- frameBuffer = new NV21Buffer(nv21, width, height, null);
+### 3. 实现自采集模块
+
+声网提供 [VideoFileReader.java](https://github.com/AgoraIO/API-Examples/blob/main/Android/APIExample/app/src/main/java/io/agora/api/example/utils/VideoFileReader.java) 演示从本地文件读取 YUV 格式的视频数据。在实际的生产环境中,你需要结合业务需求使用 Android SDK 为你的设备创建自定义视频模块。
+
+### 4. 通过视频轨道推送视频数据到 SDK
+
+将采集到的视频帧发送至 SDK 之前,通过设置 `VideoFrame` 集成你的视频模块。你可以参考以下代码,推送不同类型的自采集视频数据。为确保音视频同步,声网建议你调用 `getCurrentMonotonicTimeInMs` 获取 SDK 当前的 Monotonic Time 后,将该值传入采集的 `VideoFrame` 的时间戳参数。
+
+调用 `pushExternalVideoFrameEx` 将采集到的视频帧通过视频轨道推送至 SDK。其中, `videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致,`VideoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。
+
+- 以下代码演示推送 I420、NV21、NV12 和 Texture 格式的视频数据。。
- 为确保音视频同步,声网建议你将
VideoFrame
的时间戳参数设置为系统 Monotonic Time。你可以调用 getCurrentMonotonicTimeInMs
获取当前的 Monotonic Time。
+
+```java
+private void pushVideoFrameByI420(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 videoFrame = new VideoFrame(i420Buffer, 0, currentMonotonicTimeInMs * 1000000);
+
+ // 通过视频轨道将视频帧推送到 SDK
+ int ret = engine.pushExternalVideoFrameEx(videoFrame, videoTrack);
+ // 推送成功后,释放 i420Buffer 对象占用的内存资源
+ i420Buffer.release();
+
+ if (!success) {
+ Log.w(TAG, "pushExternalVideoFrame error");
}
- // 将 YUV 视频数据转换为 NV12 格式
- else if ("NV12".equals(selectedItem)) {
- int srcStrideY = width;
- int srcHeightY = height;
- int srcSizeY = srcStrideY * srcHeightY;
- ByteBuffer srcY = ByteBuffer.allocateDirect(srcSizeY);
- srcY.put(yuv, 0, srcSizeY);
-
- int srcStrideU = width / 2;
- int srcHeightU = height / 2;
- int srcSizeU = srcStrideU * srcHeightU;
- ByteBuffer srcU = ByteBuffer.allocateDirect(srcSizeU);
- srcU.put(yuv, srcSizeY, srcSizeU);
-
- int srcStrideV = width / 2;
- int srcHeightV = height / 2;
- int srcSizeV = srcStrideV * srcHeightV;
- ByteBuffer srcV = ByteBuffer.allocateDirect(srcSizeV);
- srcV.put(yuv, srcSizeY + srcSizeU, srcSizeV);
-
- int desSize = srcSizeY + srcSizeU + srcSizeV;
- ByteBuffer des = ByteBuffer.allocateDirect(desSize);
- YuvHelper.I420ToNV12(srcY, srcStrideY, srcU, srcStrideU, srcV, srcStrideV, des, width, height);
-
- frameBuffer = new NV12Buffer(width, height, width, height, des, null);
- }
- // 将 YUV 视频数据转换为 Texture 格式
- else if ("Texture2D".equals(selectedItem)) {
- if (textureBufferHelper == null) {
- textureBufferHelper = TextureBufferHelper.create("PushExternalVideoYUV", EglBaseProvider.instance().getRootEglBase().getEglBaseContext());
- }
- if (yuvFboProgram == null) {
- textureBufferHelper.invoke((Callable) () -> {
- yuvFboProgram = new YuvFboProgram();
- return null;
- });
- }
- Integer textureId = textureBufferHelper.invoke(() -> yuvFboProgram.drawYuv(yuv, width, height));
- frameBuffer = textureBufferHelper.wrapTextureBuffer(width, height, VideoFrame.TextureBuffer.Type.RGB, textureId, new Matrix());
- }
- // 将 YUV 视频数据转换为 I420 格式
- else if("I420".equals(selectedItem))
- {
- 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());
- frameBuffer = i420Buffer;
- }
-
+}
+
+
+private void pushVideoFrameByNV21(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,并将 SDK 当前的 Monotonic Time 赋值到 VideoFrame 的时间戳参数
- VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs);
-
- // 通过视频轨道推送视频帧到 SDK
+ // 创建一个 VideoFrame 对象,传入要推送的 NV21 视频帧和视频帧的 Monotonic Time (单位为纳秒)
+ VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);
+
+ // 通过视频轨道将视频帧推送到 SDK
int ret = engine.pushExternalVideoFrameEx(videoFrame, videoTrack);
- if (ret < 0) {
- Log.w(TAG, "pushExternalVideoFrameEx error code=" + ret);
+
+ if (!success) {
+ Log.w(TAG, "pushExternalVideoFrame error");
}
- ```
+}
-5. 调用 `pushExternalVideoFrameEx` 并将 `videoTrackId` 指定为步骤 2 中指定的视频轨道 ID,将视频帧通过视频轨道发送给 SDK。
-```java
-// 通过视频轨道推送视频帧到 SDK
-int ret = engine.pushExternalVideoFrameEx(videoFrame, videoTrack);
-if (ret < 0) {
- Log.w(TAG, "pushExternalVideoFrameEx error code=" + ret);
+private void pushVideoFrameByNV12(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 对象,传入要推送的 NV12 视频帧和视频帧的 Monotonic Time (单位为纳秒)
+ VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);
+
+ // 通过视频轨道将视频帧推送到 SDK
+ int ret = engine.pushExternalVideoFrameEx(videoFrame, videoTrack);
+
+ if (!success) {
+ Log.w(TAG, "pushExternalVideoFrame error");
+ }
+}
+
+
+private void pushVideoFrameByTexture(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 对象,传入要推送的 Texture 视频帧和视频帧的 Monotonic Time (单位为纳秒)
+ VideoFrame videoFrame = new VideoFrame(frameBuffer, 0, currentMonotonicTimeInMs * 1000000);
+
+ // 通过视频轨道将视频帧推送到 SDK
+ int ret = engine.pushExternalVideoFrameEx(videoFrame, videoTrack);
+
+ if (!success) {
+ Log.w(TAG, "pushExternalVideoFrame error");
+ }
}
```
-6. 如需停止自定义视频采集,调用 `destroyCustomVideoTrack` 来销毁视频轨道。如需销毁多个视频轨道,可多次调用 `destroyCustomVideoTrack`。
+### 5. 销毁自定义视频轨道
+
+如需停止自定义视频采集,调用 `destroyCustomVideoTrack` 来销毁视频轨道。如需销毁多个视频轨道,可多次调用 `destroyCustomVideoTrack`。
```java
+// 销毁自定义视频轨道
engine.destroyCustomVideoTrack(videoTrack);
+// 离开频道
+engine.leaveChannelEx(connection);
```
@@ -245,4 +249,6 @@ engine.destroyCustomVideoTrack(videoTrack);
- [`createCustomVideoTrack`](https://docs.agora.io/cn/extension_customer/API%20Reference/java_ng/API/toc_video_process.html#api_irtcengine_createcustomvideotrack)
- [`destroyCustomVideoTrack`](https://docs.agora.io/cn/extension_customer/API%20Reference/java_ng/API/toc_video_process.html#api_irtcengine_destroycustomvideotrack)
+- [`getCurrentMonotonicTimeInMs`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/java_ng/API/toc_video_process.html#api_irtcengine_getcurrentmonotonictimeinms)
+- [`joinChannelEx`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/java_ng/API/toc_multi_channel.html#api_irtcengineex_joinchannelex)
- [`pushExternalVideoFrameEx` [2/2]](https://docs.agora.io/cn/extension_customer/API%20Reference/java_ng/API/toc_multi_channel.html#api_irtcengineex_pushvideoframeex2)
\ No newline at end of file
diff --git a/markdown/RTC 4.x/custom video source/ custom_video_source_windows_ng.md b/markdown/RTC 4.x/custom video source/ custom_video_source_windows_ng.md
new file mode 100644
index 00000000000..395267a7ec2
--- /dev/null
+++ b/markdown/RTC 4.x/custom video source/ custom_video_source_windows_ng.md
@@ -0,0 +1,203 @@
+# 自定义视频采集 (Windows)
+
+自定义视频采集是指通过自定义的视频采集源实现对视频流的采集。
+
+与 SDK 默认的视频采集方式不同,自定义视频采集支持用户自行控制采集源,实现更加精细的视频属性调整。例如,支持通过高清摄像头、无人机摄像头或其他类型的摄像头实现视频采集,同时支持动态调整视频质量、分辨率和帧率等参数,以适应不同的应用场景和需求。
+
+声网推荐优先使用更为稳定、可靠、集成维护难度低的 SDK 视频采集,如果你有特定的视频采集需求或无法使用 SDK 采集,那么自定义视频采集为你提供灵活、可定制的方案。
+
+
+## 适用场景
+
+你可以在多种行业的多种场景下使用到自定义视频采集:
+
+### 视频特殊处理和增强
+
+在某些游戏或虚拟现实应用中,需要对视频流进行实时的特效处理、滤镜处理或其他增强效果。在这种情况下,使用自定义视频采集可以直接获取原始视频流,并进行实时处理,从而实现更加逼真的游戏或虚拟现实效果。
+
+### 高精度视频采集
+
+在视频监控领域,需要对场景中的细节进行精细的观察和分析,此时使用自定义视频采集可以获得更高的图像质量和更精细的采集控制。
+
+### 特定视频源采集
+
+在 IoT、直播等行业需要使用特定的摄像头、监控设备或其他非摄像头设备视频源,例如视频捕捉卡、录屏数据等。在这种情况下,使用 SDK 内部采集可能无法满足需求,必须使用自定义视频采集来实现对特定视频源的采集。
+
+### 与特定设备或第三方应用无缝对接
+
+在智能家居或物联网领域,需要将设备中的视频传输到用户的手机或电脑上进行监控和控制,此时可能需要使用特定的设备或应用程序进行视频采集。在这种情况下,使用自定义视频采集可以方便地将特定设备或应用程序与 RTC SDK 进行对接。
+
+### 特定的视频编码格式
+
+在特定直播场景中,可能需要使用特定的视频编码格式来满足业务需求,此时使用 SDK 内部采集可能无法满足需求,必须使用自定义视频采集来实现对特定编码格式视频的采集和自定义编码。
+
+
+## 优势介绍
+
+使用自定义视频采集,你可以体验到:
+
+### 更多类型的视频流
+
+自定义视频采集功能可以使用更高质量、更多类型的采集设备和摄像头,从而获得更清晰、更流畅的视频流。这有助于提高用户的观看体验,并使产品更具竞争力。
+
+### 更灵活的视频特效
+
+自定义视频采集功能可以帮助用户实现更丰富、更个性化的视频特效和过滤器,从而提高用户的体验和应用程序的吸引力。用户可以通过自定义视频采集功能实现各种特效,如美颜、滤镜、动态贴纸等。
+
+### 更适应各种场景的需求
+
+自定义视频采集功能可以帮助应用程序更好地适应各种场景的需求,如直播、视频会议、在线教育等。用户可以根据不同的场景需求,定制不同的视频采集方案,从而提供适应性更强的应用程序。
+
+
+## 技术原理
+
+声网 SDK 提供自定义视频轨道方式实现视频自采集。你可以创建一个或多个自定义视频轨道,加入频道并在每个频道中发布已创建的视频轨道。你需要使用自采集模块驱动采集设备对视频进行采集,并将采集的视频帧通过视频轨道发送给 SDK。
+
+下图展示在单频道和多频道中实现自定义视频采集时,视频数据的传输过程:
+
+仅在一个频道内发布自采集视频流:
+
+![](https://web-cdn.agora.io/docs-files/1683598621022)
+
+在多个频道内发布不同的自采集视频流:
+
+![](https://web-cdn.agora.io/docs-files/1683598671853)
+
+
+## 前提条件
+
+在进行操作之前,请确保你已经在项目中实现了基本的实时音视频功能,详见[实现视频通话](https://docs.agora.io/cn/video-call-4.x/start_call_windows_ng)或[实现视频直播](https://docs.agora.io/cn/live-streaming-premium-4.x/start_live_windows_ng)。
+
+
+## 实现自定义视频采集
+
+参考下图调用时序,在你的 app 中实现自定义视频采集:
+
+![](https://web-cdn.agora.io/docs-files/1686294982670)
+
+参考如下步骤,在你的 app 中实现自定义视频采集功能:
+
+### 1. 创建自定义视频轨道
+
+初始化 `IRtcEngine` 后,调用 `createCustomVideoTrack` 创建自定义视频轨道并获得视频轨道 ID。根据场景需要,你可以创建多个自定义视频轨道。
+
+```cpp
+// 如需创建多个自定义视频轨道,可以多次调用 createCustomVideoTrack
+int videoTrackId = m_rtcEngine->createCustomVideoTrack();
+m_trackVideoTrackIds[trackIndex] = videoTrackId;
+```
+
+### 2. 加入频道并发布自定义视频轨道
+
+调用 `joinChannel` 加入频道,或调用 `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. 实现自采集模块
+
+声网提供 [YUVReader.cpp](https://github.com/AgoraIO/API-Examples/blob/main/windows/APIExample/APIExample/YUVReader.cpp) 和 [YUVReader.h](https://github.com/AgoraIO/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
+
+调用 `pushVideoFrame` 将采集到的视频帧通过视频轨道推送至 SDK。其中, `videoTrackId` 要与步骤 2 加入频道时指定视频轨道 ID 一致,`videoFrame` 中可以设置视频帧的像素格式、数据类型和时间戳等参数。
+
+- 以下代码演示将 YUV 格式转换为 I420 格式的原始视频数据。声网视频自采集还支持推送其他格式的外部视频帧,详见 VIDEO_PIXEL_FORMAT。
- 为确保音视频同步,声网建议你将
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. 销毁自定义视频轨道并离开频道
+
+如需停止自定义视频采集,调用 `destroyCustomVideoTrack` 来销毁视频轨道。如需销毁多个视频轨道,可多次调用 `destroyCustomVideoTrack`。
+
+```cpp
+// 停止视频数据的自采集
+m_yuvReaders[trackIndex].stop();
+m_yuvReaderHandlers[trackIndex].Release();
+// 销毁自定义视频轨道
+m_rtcEngine->destroyCustomVideoTrack(m_trackVideoTrackIds[trackIndex]);
+// 离开频道
+m_rtcEngine->leaveChannelEx(m_trackConnections[trackIndex]);
+```
+
+
+## 参考信息
+
+本节介绍本文中使用方法的更多信息以及相关页面的链接。
+
+### 注意事项
+
+如果采集到的自定义视频格式为 Texture,并且远端用户在本地自定义采集视频中看到闪烁、失真等异常情况时,建议先复制视频数据,再将原始视频数据和复制的视频数据发回至 SDK,从而消除内部数据编码过程中出现的异常情况。
+
+### 示例项目
+
+声网在 GitHub 上提供了开源的示例项目 [MultiVideoSourceTracks](https://github.com/AgoraIO/API-Examples/tree/main/windows/APIExample/APIExample/Advanced/MultiVideoSourceTracks) 供你参考。
+
+### API 参考
+
+- [`createCustomVideoTrack`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/windows_ng/API/toc_video_process.html?platform=Windows#api_irtcengine_createcustomvideotrack)
+- [`destroyCustomVideoTrack`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/windows_ng/API/toc_video_process.html?platform=Windows#api_irtcengine_destroycustomvideotrack)
+- [`getCurrentMonotonicTimeInMs`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/windows_ng/API/toc_video_process.html?platform=Windows#api_irtcengine_getcurrentmonotonictimeinms)
+- [`joinChannelEx`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/windows_ng/API/toc_multi_channel.html?platform=Windows#api_irtcengineex_joinchannelex)
+- [`pushVideoFrame`](https://docportal.shengwang.cn/cn/video-call-4.x/API%20Reference/windows_ng/API/toc_video_process.html?platform=Windows#api_imediaengine_pushvideoframe)
\ No newline at end of file
diff --git a/markdown/RTC 4.x/release-notes/en-US/native/release_android_ng.md b/markdown/RTC 4.x/release-notes/en-US/native/release_android_ng.md
index 0f058ba28c2..f49ea28ec70 100644
--- a/markdown/RTC 4.x/release-notes/en-US/native/release_android_ng.md
+++ b/markdown/RTC 4.x/release-notes/en-US/native/release_android_ng.md
@@ -2,6 +2,10 @@
v4.2.2 was released on July xx, 2023.
+#### Compatibility changes
+
+In this version, some constructors have been removed from the `VideoCanvas` class.
+
#### New features
1. **Wildcard token**
@@ -68,6 +72,10 @@ This release fixed the following issues:
- `codecLevels` in `CodecCapInfo`
- `REMOTE_VIDEO_STATE_REASON_CODEC_NOT_SUPPORT`
+**Deleted**
+
+- Some constructors in `VideoCanvas`
+
## v4.2.1
This version was released on June 21, 2023.
@@ -465,18 +473,6 @@ This release fixed the following issues:
- `adjustUserPlaybackSignalVolumeEx`
-- `IAgoraMusicContentCenter` interface class and methods in it
-
-- `IAgoraMusicPlayer` interface class and methods in it
-
-- `IMusicContentCenterEventHandler` interface class and callbacks in it
-
-- `Music` class
-
-- `MusicChartInfo` class
-
-- `MusicContentCenterConfiguration` class
-
- `MvProperty` class
- `ClimaxSegment` class
diff --git a/markdown/RTC 4.x/release-notes/zh-CN/release_android_ng.md b/markdown/RTC 4.x/release-notes/zh-CN/release_android_ng.md
index 4ba05a7d81a..e7499606253 100644
--- a/markdown/RTC 4.x/release-notes/zh-CN/release_android_ng.md
+++ b/markdown/RTC 4.x/release-notes/zh-CN/release_android_ng.md
@@ -8,6 +8,10 @@
该版本废弃了 `IAgoraMusicContentCenter` 类下的 `preload [1/2]` 方法并新增 `preload [2/2]` 方法。如果你使用了 `preload [1/2]` 方法实现预加载音乐资源,请在升级到该版本后更新 app 代码。
+**视频画布属性**
+
+该版本的 `VideoCanvas` 类中删除了部分构造函数。
+
#### 新增特性
1. **通配 Token**
@@ -309,7 +313,7 @@
- `startRecording`、`stopRecording`、`setMediaRecorderObserver` 中的 `connection`
- `onScreenCaptureVideoFrame`
- `onPreEncodeScreenVideoFrame`
-
+- `VideoCanvas` 的部分构造函数
## v4.1.1