自定义视频数据处理
在实时互动过程中,你可能需要对 SDK 采集到的原始视频数据进行处理,也可能需要获取 SDK 采集并编码的视频数据。本文介绍如何通过不同类型的视频观测器获取 SDK 采集到的视频数据,并对其进行处理。
适用场景
自定义视频数据处理适用于以下场景。
- 自定义美颜处理:拉取本地采集的原始视频流,并使用第三方美颜处理工具进行处理。处理后的视频流可以再次推送到频道中,供观看者实时观看。
- 自定义视频编辑:在视频处理链路的不同阶段对视频流进行编辑处理,如添加水印、裁剪、剪辑、特效等。用户可以根据需求对视频流进行灵活编辑,编辑后的视频流可用于直播或存储。
- 拉流预览或监控:在视频处理链路的不同阶段拉取视频流进行本地或云端的监控和预览。用户可以实时查看视频流的质量、内容和状态,确保视频流的正常传输和播放。
技术原理
SDK 中视频模块的数据处理流程如下图所示。
根据上图所示,在不同的模式下,观测点对应的回调触发时机也有所不同。SDK 默认采用只读模式,你可以根据自己的需求设置读写模式(详见实现方法中的相应步骤)。
观测点对应的回调如下所示:
- 观测点 1:通过
onCaptureVideoFrame
回调获取未经过缩放的数据。 - 观测点 2:通过
onPreEncodeVideoFrame
回调获取编码前的数据。 - 观测点 3:通过
onEncodedVideoFrameReceived
回调获取编码后的数据。 - 观测点 4:通过
onRenderVideoFrame
回调获取渲染前的数据。
- 视频前处理:视频前处理插件(包括 SDK 内置插件以及拓展插件)的默认加载位置、位于本地预览默认位置和视频帧的抽帧或缩放处理之前。
- 编码前处理:位于本地预览默认位置和视频帧的抽帧或缩放处理之后。
- 视频后处理:在视频解码之后,对视频帧进行超分辨率、超级画质处理等处理。
前提条件
在进行操作之前,请确保你已经在项目中实现了基本的实时音视频功能。详见实现音视频互动。
实现方法
根据你的实际业务需求,在以下方案中选择实现视频数据处理的方法。
观测原始数据
参考如下步骤,获取 SDK 采集的原始数据并进行处理。
-
调用
setVideoFrameDelegate
方法注册视频观测器AgoraVideoFrameDelegate
,同时注册相应的回调。SwiftagoraKit.setVideoFrameDelegate(self)
-
(可选)视频观测器默认的观测位置是
AgoraVideoModulePositionPostCapture
和AgoraVideoModulePositionPreRenderer
,你可以在getObservedFramePosition
回调中返回预期的视频观测位置。以下示例代码以观测位置为AgoraVideoModulePositionPreEncoder
为例。Swiftfunc getObservedFramePosition() -> AgoraVideoFramePosition {
.preEncoder
} -
(可选)SDK 默认不接收经过处理后的视频数据,如果你要修改此行为,可在
getVideoFrameProcessMode
回调中返回预期的视频处理模式为AgoraVideoFrameProcessModeReadWrite
:- 0:(默认)只读模式,不需要将处理后的数据发回给 SDK。
- 1:读写模式,需要将处理后的数据发回给 SDK。
Swiftfunc getVideoFrameProcessMode() -> AgoraVideoFrameProcessMode {
.readWrite
} -
(可选)SDK 默认提供的视频数据格式为
I420
或CVPixelBufferRef
,如果你需要其他格式的视频数据,可在getVideoFormatPreference
回调中返回预期的数据格式:- 0:(默认)原始视频像素格式
- 1:I420 格式
- 4:RGBA 格式
- 12:CVPixelBufferRef NV12 格式
- 13:CVPixelBufferRef I420 格式
- 14:CVPixelBufferRef BGRA 格式
- 16:I422 格式
注意SDK 目前不支持 Alpha 通道。传入的 Alpha 值将被丢弃。
Swiftfunc getVideoFormatPreference() -> AgoraVideoFormat {
.cvPixelBGRA
} -
(可选)如果采集到视频帧中设置了旋转信息
rotation
,你可以在getRotationApplied
回调中返回true
,设置视频数据按照rotation
做旋转处理。注意该回调支持的视频数据格式有:I420、RGBA、CVPixelBuffer。
Swiftfunc getRotationApplied() -> Bool {
true
} -
(可选)如果你希望获取镜像处理后的视频数据,可在
getMirrorApplied
回调中返回true
,设置视频数据做镜像处理。注意- 该回调和
setVideoEncoderConfiguration
方法均支持设置镜像效果,声网建议你仅选择一种方法进行设置,同时使用两种方法会导致镜像效果叠加从而造成设置镜像不成功。
- 该回调支持的视频数据格式有:I420、RGBA、CVPixelBuffer。
Swiftfunc getMirrorApplied() -> Bool {
true
} - 该回调和
-
调用
joinChannelbyToken
方法加入频道,SDK 会在你设置好的观测位置回调相应的视频数据。Swiftlet result = self.agoraKit.joinChannel(byToken: token, channelId: channelId, uid: 0, mediaOptions: option, joinSuccess: nil)
-
(可选)如果需要将处理后的视频数据传回给 SDK,则在相应回调中传入处理后的视频数据,并将回调的返回值设置为
true
(设置 SDK 接收视频帧)。以下示例代码以onCaptureVideoFrame
回调为例。注意建议你在修改
videoFrame
中的参数时,需确保修改后的参数跟视频帧缓冲区中的视频帧实际情况保持一致,否则可能导致本地预览画面和对端的视频画面出现非预期的旋转、失真等问题。Swiftfunc onCapture(_ videoFrame: AgoraOutputVideoFrame,
sourceType: AgoraVideoSourceType) -> Bool {
// 将修改后的视频数据传回 SDK
videoFrame.pixelBuffer = modifiedBuffer
return true
} -
调用
setVideoFrameDelegate
方法,取消注册视频观测器AgoraVideoFrameDelegate
,即可停止观测视频数据。SwiftagoraKit.setVideoFrameDelegate(nil)
观测已编码数据
参考如下步骤,获取 SDK 采集的已编码数据并进行处理。
-
调用
setEncodedVideoFrameDelegate
方法注册视频编码观测器AgoraEncodedVideoFrameDelegate
,同时注册回调onEncodedVideoFrameReceived
。SwiftagoraKit.setVideoEncodedFrameDelegate(self)
-
调用
joinChannelbyToken
方法加入频道。Swiftlet result = self.agoraKit.joinChannel(byToken: token, channelId: channelName, uid: 0, mediaOptions: option)
-
通过
onEncodedVideoFrameReceived
回调获取编码后的视频数据。Swiftfunc onEncodedVideoFrameReceived(_ videoData: Data, length: Int, info videoFrameInfo: AgoraEncodedVideoFrameInfo) -> Bool {
if videoFrameInfo.frameType == .keyFrame {
// 处理关键帧数据
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("videodata")
try? videoData.write(to: fileURL)
print("write success")
}
return true
} -
调用
setVideoEncodedFrameDelegate
方法,取消注册视频观测器AgoraEncodedVideoFrameDelegate
,即可停止观测视频数据。SwiftagoraKit.setVideoEncodedFrameDelegate(nil)
同时观测已编码数据和渲染前数据
如果你想同时获取远端用户的已编码数据(简称为 A 组)和渲染前视频数据(简称为 B 组),可参考如下步骤。
-
调用
setVideoEncodedFrameDelegate
方法注册视频编码观测器AgoraEncodedVideoFrameDelegate
,同时注册回调onEncodedVideoFrameReceived
。SwiftagoraKit.setVideoEncodedFrameDelegate(self)
-
调用
setVideoFrameDelegate
方法注册视频观测器AgoraVideoFrameDelegate
,同时注册相应的回调。SwiftagoraKit.setVideoFrameDelegate(self)
-
调用
joinChannelbyToken
方法加入频道,并设置autoSubscribeVideo
为false
(不自动订阅任何视频流)。Swiftlet options = AgoraRtcChannelMediaOptions()
options.autoSubscribeVideo = false
let result = self.agoraKit.joinChannel(byToken: token, channelId: channelName, uid: 0, mediaOptions: options) -
(可选)SDK 默认订阅所有远端原始数据和编码后的数据(即
encodedFrameOnly
为false
)和视频大流,如果你想针对不同uid
设置不同的订阅选项,可调用setRemoteVideoSubscriptionOptions
方法设置。Swiftfunc rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
let options = AgoraVideoSubscriptionOptions()
options.type = .low
options.encodedFrameOnly = true
agoraKit.setRemoteVideoSubscriptionOptions(uid, options: options)
} -
调用
muteRemoteVideoStream
方法,并将mute
设置为false
(订阅远端用户的视频流),开始接收远端用户的视频流。SwiftagoraKit.muteRemoteVideoStream(uid, mute: false)
-
通过相应的回调获取预期的数据:
- 可通过
AgoraEncodedVideoFrameDelegate
中的onEncodedVideoFrameReceived
回调获取用户已编码的视频数据。 - 可通过
AgoraVideoFrameDelegate
中的onRenderVideoFrame
回调获取用户渲染前的视频数据。
Swiftfunc onEncodedVideoFrameReceived(_ videoData: Data, length: Int, info videoFrameInfo: AgoraEncodedVideoFrameInfo) -> Bool {
if videoFrameInfo.frameType == .keyFrame {
// 处理关键帧数据
let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("videodata")
try? videoData.write(to: fileURL)
print("write success")
}
return true
}
func onRenderVideoFrame(_ videoFrame: AgoraOutputVideoFrame, uid: UInt, channelId: String) -> Bool {
// 自定义渲染远端用户的画面
if let pixelBuffer = videoFrame.pixelBuffer {
// 处理 pixelBuffer
}
return true
} - 可通过
-
分别调用
setVideoEncodedFrameDelegate
和setVideoFrameDelegate
方法,取消注册视频观测器,即可停止观测视频数据。Swift// 取消注册已编码视频观测器
agoraKit.setEncodedVideoFrameDelegate(nil)
// 取消注册原始视频观测器
agoraKit.setVideoFrameDelegate(nil)
示例项目
声网提供了开源的原始视频数据示例项目供你参考,你可以前往下载或查看其中的源代码。