今天介绍一下 iOS下WebRTC 是如何进行视频解码的。关于iOS下WebRTC视频采集与编码可以看下面的文章:
与编码器流程基本一致,流程如下:
下面我们看一下几个关键函数的原型。
在 iOS 中使用 VTDecompressionSessionCreate 方法创建解码器。其函数原型如下:
VTDecompressionSessionCreate(
allocator: CFAllocator, //session分配器,NULL使用默认分配器
videoFormatDescription: CMVideoFormatDescription, //视频帧格式描述信息
videoDecoderSpecification: CFDictionary, //视频解码器,如果NULL,表示让VideoToolbox选择视频解码器。
destinationImageBufferAttributes: CFDictionary, //像素缓冲区要求的属性
outputCallback: VTDecompressionOutputCallbackRecord *, //解码完一帧后的回调函数。
decompressionSessionOut: VTDecompressionSession* //创建出的解码Session实例。
) -> OSStatus
各参数详细介绍:
在iOS下通过VTSessionSetProperty函数来配置 CompressionSession。函数原型如下:
VTSessionSetProperty(
session: VTSession, //就是上面创建的 VTCompressionSession 对象。
propertyKey: CFString, // 属性
propertyValue: CFTypeRef //属性值
) -> OSStatus //返回的状态值
其与编码器的设置是一样的。在设置解码器时只需要设置是否实时解码即可。
使用 VTDecompressionSessionDecodeFrame 函数进行解码。其原型如下:
OSStatus
VTDecompressionSessionDecodeFrame(
VTDecompressionSessionRef session, //解码器 Session
CMSampleBufferRef sampleBuffer, // 源视频帧
VTDecodeFrameFlags decodeFlags, // 解码标志位。bit 0 is enableAsynchronousDecompression
void * sourceFrameRefCon, //用户自定义参数指针。
VTDecodeInfoFlags * infoFlagsOut //解码输出标志
)
与释放编码器一样,iOS下使用 VTDecompressionSessionInvalidate 释放解码器。其原型如下:
- (void)destroyCompressionSession {
if (_compressionSession) {
VTCompressionSessionInvalidate(_compressionSession);
CFRelease(_compressionSession);
_compressionSession = nullptr;
}
}
与编码器一样,WebRTC专门写了一个类,用于封装iOS解码相关的操作。文件地址如下:
webrtc/sdk/obj/Framework/Classes/Video/VideoToolbox/RTCVideoDecoderH264.mm
在 RTCVideoDecoderH264.mm:208 行调用了 VTDecompressionSessionCreate 函数。用于创建解码器。代码如下:
OSStatus status = VTDecompressionSessionCreate(
nullptr,
_videoFormat,
nullptr,
attributes,
&record,
&_decompressionSession
);
- decompressionOutputRefCon: 是 VTDecompressionOutputCallbackRecord结构体的第2个参数的引用。
- sourceFrameRefCon: 是VTDecompressionSessionDecodeFrame函数的sourceFrameRefCon参数的引用。
- 存放解码成功还是失败的状态。
- infoFlags: 包括了下面转码操作的信息
- 如果设置了 kVTDecodeInfo\_Asynchronous 则是异步解码。
- 如果设置了 kVTDecodeInfo\_FrameDropped 则可以丢帧。
- 如果设置了 kVTDecodeInfo\_ImageBufferModifiable 可以安全的修改 imageBuffer.
- imageBuffer: 存放解码后的视频数据。
- presentationTimeStamp: 存放 pts。
在解码中,主要是设置实时解码。代码如下:
...
#if defined(WEBRTC_IOS)
VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
#endif
...
在WebRTC中,调用 decode()函数进行解码。该函数最终调用 iOS的系统函数 VTDecompressionSessionDecodeFrame 进行解码。
- (NSInteger)decode:(RTCEncodedImage *)inputImage
missingFrames:(BOOL)missingFrames
fragmentationHeader:(RTCRtpFragmentationHeader *)fragmentationHeader
codecSpecificInfo:(__nullable id<RTCCodecSpecificInfo>)info
renderTimeMs:(int64_t)renderTimeMs {
...
VTDecodeFrameFlags decodeFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
status = VTDecompressionSessionDecodeFrame(
_decompressionSession,
sampleBuffer,
decodeFlags,
frameDecodeParams.release(),
nullptr);
...
这里需要重点讲一下 VTDecompressionSessionDecodeFrame 的第3和第4个参数。
对该回调函数的参数说明前面已经介绍过了,回调函数具体的逻辑操作过程见代码中的注释。
// This is the callback function that VideoToolbox calls when decode is complete.
void decompressionOutputCallback(void *decoder,
void *params,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CVImageBufferRef imageBuffer,
CMTime timestamp,
CMTime duration) {
// 该函数的第2个参数就是解码方法中设置的用户自定义参数
// 先进行类型转换
std::unique_ptr<RTCFrameDecodeParams> decodeParams(
reinterpret_cast<RTCFrameDecodeParams *>(params));
// status存放着转码状态,如果不是noErr说明转码失败了。
if (status != noErr) {
LOG(LS_ERROR) << "Failed to decode frame. Status: " << status;
return;
}
// TODO(tkchin): Handle CVO properly.
// 将转码后的数据重新进行了封装,以便于标准 C++ 使用。
RTCCVPixelBuffer *frameBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:imageBuffer];
RTCVideoFrame *decodedFrame =
[[RTCVideoFrame alloc] initWithBuffer:frameBuffer
rotation:RTCVideoRotation_0
timeStampNs:CMTimeGetSeconds(timestamp) * rtc::kNumNanosecsPerSec];
//在这里又调了另外一个回调函数。也就是我们解码时设置在自定议参数里的回调函数。
decodedFrame.timeStamp = decodeParams->timestamp;
decodeParams->callback(decodedFrame);
}
在上面回调函数里的 callback 函数是在 objc_video_decoder_factory.mm:72行定义的。WebRTC解码后就会一层一层的回调上去。最终将解码后的数据交给使用者,如视频渲染模块。
本文首先介绍了iOS下 WebRTC 解码用到的几个函数的原型及其参数的详细介绍。最后以 WebRTC为例,讲解了 WebRTC是如何使用这几个函数的。其中还介绍了一些 WebRTC处理数据的逻辑流程。