前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS下WebRTC视频解码

iOS下WebRTC视频解码

作者头像
音视频_李超
发布2020-04-02 18:42:32
2K0
发布2020-04-02 18:42:32
举报

前言

今天介绍一下 iOS下WebRTC 是如何进行视频解码的。关于iOS下WebRTC视频采集与编码可以看下面的文章:

解码的基本流程

与编码器流程基本一致,流程如下:

  • 创建解码器实例。
  • 配置解码器。
  • 解码。
  • 释放解码器。

下面我们看一下几个关键函数的原型。

创建解器

在 iOS 中使用 VTDecompressionSessionCreate 方法创建解码器。其函数原型如下:

代码语言:javascript
复制
VTDecompressionSessionCreate(
    allocator: CFAllocator, //session分配器,NULL使用默认分配器
    videoFormatDescription: CMVideoFormatDescription, //视频帧格式描述信息
    videoDecoderSpecification: CFDictionary, //视频解码器,如果NULL,表示让VideoToolbox选择视频解码器。
    destinationImageBufferAttributes: CFDictionary, //像素缓冲区要求的属性
    outputCallback: VTDecompressionOutputCallbackRecord *, //解码完一帧后的回调函数。
    decompressionSessionOut: VTDecompressionSession* //创建出的解码Session实例。
) -> OSStatus

各参数详细介绍:

  • allocator : session分配器,NULL使用默认分配器。
  • videoFormatDescription : 源视频帧格式描述信息。
  • videoDecoderSpecification : 视频解码器。如果是NULL表式让 VideoToolbox自己选择视频解码器。
  • destinationImageBufferAttributes: 像素缓冲区要求的属性。
  • outputCallback: 解码后的回调函数。
  • decompressionSessionOut: 输出Session实列。

配置参数

在iOS下通过VTSessionSetProperty函数来配置 CompressionSession。函数原型如下:

代码语言:javascript
复制
VTSessionSetProperty(
    session: VTSession, //就是上面创建的 VTCompressionSession 对象。
    propertyKey: CFString, // 属性
    propertyValue: CFTypeRef //属性值
) -> OSStatus //返回的状态值

其与编码器的设置是一样的。在设置解码器时只需要设置是否实时解码即可。

解码

使用 VTDecompressionSessionDecodeFrame 函数进行解码。其原型如下:

代码语言:javascript
复制
OSStatus
VTDecompressionSessionDecodeFrame(
    VTDecompressionSessionRef   session, //解码器 Session
    CMSampleBufferRef   sampleBuffer, // 源视频帧
    VTDecodeFrameFlags  decodeFlags, // 解码标志位。bit 0 is enableAsynchronousDecompression
    void *  sourceFrameRefCon,  //用户自定义参数指针。
    VTDecodeInfoFlags * infoFlagsOut //解码输出标志
)   
  • session : 创建解码器时创建的 Session。
  • sampleBuffer : 准备被解码的视频帧。
  • decodeFlags : 解码标志符。 0:代表异步解码。
  • sourceFrameRefCon : 用户自定义参数。
  • infoFlagsOut : 输出参数标记。

释放解码器

与释放编码器一样,iOS下使用 VTDecompressionSessionInvalidate 释放解码器。其原型如下:

代码语言:javascript
复制
- (void)destroyCompressionSession {
  if (_compressionSession) {
    VTCompressionSessionInvalidate(_compressionSession);
    CFRelease(_compressionSession);
    _compressionSession = nullptr;
  }
}

看看WebRTC是如何使用解码器的

与编码器一样,WebRTC专门写了一个类,用于封装iOS解码相关的操作。文件地址如下:

webrtc/sdk/obj/Framework/Classes/Video/VideoToolbox/RTCVideoDecoderH264.mm

创建解码器

在 RTCVideoDecoderH264.mm:208 行调用了 VTDecompressionSessionCreate 函数。用于创建解码器。代码如下:

代码语言:javascript
复制
OSStatus status = VTDecompressionSessionCreate(
      nullptr, 
      _videoFormat, 
      nullptr, 
      attributes, 
      &record, 
      &_decompressionSession
);
  • WebRTC在创建解码器Session时,使用默认Session分配器。
  • 第2个参数 _videoFormat 存放的是视频解码格式。它是通过解析 sps, pps 得到的。在iOS WebRTC中,并不是先创建解码器,然后再开始接收数据进行解码的。而是反过来先接收视频数据,然后解码。在解码的过程中判断是不是 SPS,PPS包。如果是的话,这时才正式创建解码器。不明白的同学可以仔细看一下 decode()函数
  • 第3个参数设置为 null,表式让 VideoToolbox 自己选择解码器。
  • 第4个参数 attributes 设置如下: static size_t const attributesSize = 3; CFTypeRef keysattributesSize = { #if defined(WEBRTC_IOS) kCVPixelBufferOpenGLESCompatibilityKey, #elif defined(WEBRTC_MAC) kCVPixelBufferOpenGLCompatibilityKey, #endif kCVPixelBufferIOSurfacePropertiesKey, kCVPixelBufferPixelFormatTypeKey }; CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0); int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type); CFTypeRef valuesattributesSize = {kCFBooleanTrue, ioSurfaceValue, pixelFormat}; CFDictionaryRef attributes = CreateCFTypeDictionary(keys, values, attributesSize); 指明了解码后yuv数据格式,与OpenGL的兼容性。
  • 第5个参数,指明解码后的回调函数是record. 其设置如下: VTDecompressionOutputCallbackRecord record = { decompressionOutputCallback, //回调函数 nullptr,//回调函数参数 }; VTDecompressionOutputCallbackRecord类型是一个结构体,它由真正的回调函数和回调函数指针参数构成。下面是回调函数原型: typedef void (*VTDecompressionOutputCallback)( void * decompressionOutputRefCon, void * sourceFrameRefCon, OSStatus status, VTDecodeInfoFlags infoFlags, CM_NULLABLE CVImageBufferRef imageBuffer, CMTime presentationTimeStamp, CMTime presentationDuration ); struct VTDecompressionOutputCallbackRecord { VTDecompressionOutputCallback decompressionOutputCallback; void * decompressionOutputRefCon; }; typedef struct VTDecompressionOutputCallbackRecord VTDecompressionOutputCallbackRecord; VTDecompressionOutputCallback函数参数解释如下:
代码语言:txt
复制
- decompressionOutputRefCon: 是 VTDecompressionOutputCallbackRecord结构体的第2个参数的引用。
- sourceFrameRefCon: 是VTDecompressionSessionDecodeFrame函数的sourceFrameRefCon参数的引用。
- 存放解码成功还是失败的状态。
-  infoFlags: 包括了下面转码操作的信息
代码语言:txt
复制
    - 如果设置了 kVTDecodeInfo\_Asynchronous 则是异步解码。
    - 如果设置了 kVTDecodeInfo\_FrameDropped 则可以丢帧。
    - 如果设置了 kVTDecodeInfo\_ImageBufferModifiable 可以安全的修改 imageBuffer.
- imageBuffer: 存放解码后的视频数据。
- presentationTimeStamp: 存放 pts。

配置解码器参数

在解码中,主要是设置实时解码。代码如下:

代码语言:javascript
复制
...

#if defined(WEBRTC_IOS)
  VTSessionSetProperty(_decompressionSession, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
#endif

...

解码

在WebRTC中,调用 decode()函数进行解码。该函数最终调用 iOS的系统函数 VTDecompressionSessionDecodeFrame 进行解码。

代码语言:javascript
复制
- (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个参数。

  • 第3个参数 decodeFlags: 设置为 kVTDecodeFrame_EnableAsynchronousDecompression, 表示开启异步方式进行解码。
  • 第4个参数 frameDecodeParams 是用户自定义参数。它里面存放了解码的 callback 函数。在 WebRTC中,frameDecodeParams 设置如下: frameDecodeParams.reset(new RTCFrameDecodeParams( _callback, inputImage.timeStamp )); 自定义参数里,设置了另外一个回调函数。也就是说,视频在解码后,调用解码回调函数。而在解码回调函数中,又调用了frameDecodeParams里设置的回调函数。这块稍微有点绕,我们看一下回调的代码就清楚是怎么回事了。

解码回调函数

对该回调函数的参数说明前面已经介绍过了,回调函数具体的逻辑操作过程见代码中的注释。

代码语言:javascript
复制
// 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处理数据的逻辑流程。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 解码的基本流程
    • 创建解器
      • 配置参数
        • 解码
          • 释放解码器
          • 看看WebRTC是如何使用解码器的
            • 创建解码器
              • 配置解码器参数
                • 解码
                  • 解码回调函数
                  • 小结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档