在 iOS下WebRTC视频采集 一文中,向大家介绍了 WebRTC 是如何在 iOS下进行视频采集的。本文则介绍一下 iOS 下 WebRTC 是如何进行视频编码的。
WebRTC在初始化时,先要创建并配置好编码器,然后开始采集视频数据。视频采集到一帧数据后,通过回调接口,将采集到的数据传递给VideoStreamEncoder 类的 OnFrame() 函数。在该函数中,会为每一个视频帧创建一个 EncodeTask 任务,并将其插入到编码队列中。
而在编码线程,则不断的从编码队列中取出任务进行编码,并最终通过编码器的回调函数将编码后的数据输出。
通过上面的描述,我们知道有两个重要的回调函数,一个是在Camera采集到视频数据后进行回调;另一个是在编码完成后进行回调。 在WebRTC中大量使用了回调函数。回调函数是一条主线,大家要牢记。否则很难弄懂WebRTC的代码。
在详细介绍WebRTC编码之前,首先我们介绍一下在iOS下视频编码经常使用的一些基本数据结构。这些数据结构对我们阅读 WebRTC 代码也是至关重要的。
CV: CoreVideo CM: CoreMedia VT: VideoToolbox
下面是 CMSampleBuffer 的示意图:
在iOS下进行视频编码的最重要的数据类型就是 VTCompressionSession,它管理着 VideoEncoder。
创建 VTCompressionSession 对象。
VTCompressionSessionCreate(
allocator: CFAllocator, //session分配器,NULL使用默认分配器
width: Int32, //视频帧的像素宽度
height: Int32, //视频帧的像素高度
codecType: CMVideoCodecType, //编码类型,如 kCMVideoCodecType_H264
encoderSpecification: CFDictionary, //使用的视频编码器,NULL让VideoToolbox自己选择。
sourceImageBufferAttributes: CFDictionary, //指定源图像属性,如YUV类型为 NV12
compressedDataAllocator: CFAllocator, //压缩数据分配器,NULL使用默认的分配器。
outputCallback: VTCompressionOutputCallback, //编码后的回调函数。该函数会在不同的线程中异步调用。
outputCallbackRefCon: UnsafeMutableRawPointer, //用户自定义的回调上下文,一般设置为NULL。
compressionSessionOut: UnsafeMutablePointer<VTCompressionSession> //compression session的返回值。
) -> OSStatus //创建是否成功的状态
各参数详解:
在iOS下通过VTSessionSetProperty函数来配置 CompressionSession。函数原型如下:
VTSessionSetProperty(
session: VTSession, //就是上面创建的 VTCompressionSession 对象。
propertyKey: CFString, // 属性
propertyValue: CFTypeRef //属性值
) -> OSStatus //返回的状态值
一般情况下都会配置以下几项:
调用 VTCompressionSessionEncodeFrame 进行编码,函数原型如下:
VTCompressionSessionEncodeFrame(
session: VTCompressionSession, //上面定义的 Session
imageBuffer: CVImageBuffer, // 它里面包含了被压缩的视频帧。
presentationTimeStamp: CMTime, //pts
duration: CMTime, // 没什么用
frameProperties: CFDictionary, //k/v键值对,指明了额外的属性。
sourceFrameRefCon: UnsafeMutableRawPointer, //可用于存放上下文,它将被透传给回调函数。
infoFlagsOut: UnsafeMutablePointer<VTEncodeInfoFlags> //不知道啥作用
) -> OSStatus //返回的状态值
参数详细介绍:
WebRTC专门写了一个类,用于封装iOS编码相关的操作。下面我们就详细看下 WebRTC 是如何使用 iOS 硬编码器的。
封装的文件位于 webrtc/sdk/obj/Framework/Classes/Video/VideoToolbox/RTCVideoEncoderH264.mm
在 RTCVideoEncoderH264.mm:512 调用了 VTCompressionSessionCreate 函数。
OSStatus status =
VTCompressionSessionCreate(nullptr, // use default allocator
_width,
_height,
kCMVideoCodecType_H264,
encoder_specs, // use hardware accelerated encoder if available
sourceAttributes,
nullptr, // use default compressed data allocator
compressionOutputCallback,
nullptr,
&_compressionSession);
下面对 VTCompressionSessionCreate 几个重要参数做下分析:
至此,编码器的创建工作就完成了。下一步是配置编码器。
在 RTCVideoEncoderH264.mm:561 的 configureCompressionSession()中对编码器的配置工作。
- (void)configureCompressionSession {
SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true); //实时编码
SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile);
SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false);
[self setEncoderBitrateBps:_targetBitrateBps];
......
// Set a relatively large value for keyframe emission (7200 frames or 4 minutes).
SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200);
SetVTSessionProperty(
_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240);
}
在WebRTC中,设置编码为实时编码,profile为WebRTC-H264HighProfile
,不允许录制,关键帧最大间隔是7200。其中 setEncoderBitrateBps 函数是设置编码码流,代码如下:
VTSessionSetProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps);
......
VTSessionSetProperty(
_compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits);
WebRTC会根据分辨率大小设置码流。而 DataRateLimits 是平均码流的1.5倍。
配置好编码器后,就可以对 Camera 采集到的数据进行编码了。在 iOS 下WebRTC视频采集 一文中,我们已经介绍了视频采集的过程。视频数据被采集后,最终会通过回调函数一层层传到 RTCVideoEncoderH264.mm:329 的encode()内。
最终调用 VTCompressionSessionEncodeFrame() 进行视频编码。
OSStatus status =
VTCompressionSessionEncodeFrame(_compressionSession,
pixelBuffer,
presentationTimeStamp,
kCMTimeInvalid,
frameProperties,
encodeParams.release(),
nullptr);
下面是该函数的重要参数的详细说明:
数据被编码后,编码器就会调用,(创建CompressionSession时)注册的编码器回调函数做进一频的处理。
需要注意的是,该回调函数可以在不同的线程被异频调用。
当编码结束后,在iOS中需要主动释放编码器。释放的接口为VTCompressionSessionInvalidate()。代码如下:
- (void)destroyCompressionSession {
if (_compressionSession) {
VTCompressionSessionInvalidate(_compressionSession);
CFRelease(_compressionSession);
_compressionSession = nullptr;
}
}
上面详细介绍了WebRTC在 iOS 下进行编码的细节。本来还打算将数据从采集阶段到编码阶段的整个数据的流转描述清楚的,但由于水平所限,几经努力还是无法清晰,简明的描述它。
它的难点在于如果概略的描术就无法将 WebRTC 的细节讲清楚,而细节里又存在‘魔鬼’。如果描术的太细,篇幅又太长,人们的思想很难长时间高度集中。
虽然有以上困难,但我还是要完成这项工作,不过需要一些时间。
请大家多多观注,谢谢!