webrtc (6) 在Webrtc中集成VideoToolbox

来源:http://blog.csdn.net/wangruihit/article/details/46550853css

 

VideoToolbox是iOS平台在iOS8以后开放的一个Framework,提供了在iOS平台利用硬件实现H264编解码的能力。git


这套接口的合成主要我一我的参与,花费了四五天的时间,中间主要参考了WWDC 2014  513关于hardware codec的视频教程github

OpenWebrtc的vtenc/vtdec模块web

chromium的一部分代码xcode

https://src.chromium.org/svn/trunk/src/content/common/gpu/media/vt_video_decode_accelerator.ccsession

https://chromium.googlesource.com/chromium/src/media/+/cea1808de66191f7f1eb48b5579e602c0c781146/cast/sender/h264_vt_encoder.cc
app

还有stackoverflow的一些帖子,如 ide

http://stackoverflow.com/questions/29525000/how-to-use-videotoolbox-to-decompress-h-264-video-stream svn

http://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream
性能

另外还有apple forum的帖子如:

https://devforums.apple.com/message/1063536#1063536


中间须要注意的是,

1,YUV数据格式

Webrtc传递给Encoder的是数据是I420,对应VT里的kCVPixelFormatType_420YpCbCr8Planar,若是VT使用

kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange格式(即NV12),那么须要将I420转换为NV12再进行编码。转换可以使用libyuv库。

I420格式有3个Planar,分别存放YUV数据,而且数据连续存放。相似:YYYYYYYYYY.......UUUUUU......VVVVVV......

NV12格式只有2个Planar,分别存放YUV数据,首先是连续的Y数据,而后是UV数据。相似YYYYYYYYY......UVUVUV......


选择使用I420格式编码仍是NV12进行编码,取决于在初始化VT时所作的设置。设置代码以下:

[objc] view plain copy
  1. CFMutableDictionaryRef source_attrs = CFDictionaryCreateMutable (NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);  
  2.   
  3. CFNumberRef number;  
  4.   
  5. number = CFNumberCreate (NULL, kCFNumberSInt16Type, &codec_settings->width);  
  6. CFDictionarySetValue (source_attrs, kCVPixelBufferWidthKey, number);  
  7. CFRelease (number);  
  8.   
  9. number = CFNumberCreate (NULL, kCFNumberSInt16Type, &codec_settings->height);  
  10. CFDictionarySetValue (source_attrs, kCVPixelBufferHeightKey, number);  
  11. CFRelease (number);  
  12.   
  13. OSType pixelFormat = kCVPixelFormatType_420YpCbCr8Planar;  
  14. number = CFNumberCreate (NULL, kCFNumberSInt32Type, &pixelFormat);  
  15. CFDictionarySetValue (source_attrs, kCVPixelBufferPixelFormatTypeKey, number);  
  16. CFRelease (number);  
  17.   
  18. CFDictionarySetValue(source_attrs, kCVPixelBufferOpenGLESCompatibilityKey, kCFBooleanTrue);  
  19.   
  20. OSStatus ret = VTCompressionSessionCreate(NULL, codec_settings->width, codec_settings->height, kCMVideoCodecType_H264, NULL, source_attrs, NULL, EncodedFrameCallback, this, &encoder_session_);  
  21. if (ret != 0) {  
  22.     WEBRTC_TRACE(webrtc::kTraceError, webrtc::kTraceVideoCoding, -1,  
  23.                  "vt_encoder::InitEncode() fails to create encoder ret_val %d",  
  24.                  ret);  
  25.     return WEBRTC_VIDEO_CODEC_ERROR;  
  26. }  
  27.   
  28. CFRelease(source_attrs);  


2,VT编码出来的数据是AVCC格式,须要转换为Annex-B格式,才能回调给Webrtc。主要区别在于数据开头是长度字段仍是startCode,具体见stackoverflow的帖子。

同理,编码时,须要将webrtc的Annex-B格式转换为AVCC格式。

Annex-B:StartCode + Nalu1 + StartCode + Nalu2 + ...

AVCC :Nalu1 length + Nalu1 + Nalu2 length + Nalu2 + ...

注意⚠:AVCC格式中的length字段须要是big endian顺序。length字段的长度可定制,通常为1/2/4byte,须要经过接口配置给解码器。


3,建立VideoFormatDescription

解码时须要建立VTDecompressionSession,须要一个VideoFormatDescription参数。

建立VideoFormatDescription须要首先从码流中获取到SPS和PPS,而后使用以下接口建立VideoFormatDescription

[objc] view plain copy
  1. /*! 
  2.     @function   CMVideoFormatDescriptionCreateFromH264ParameterSets 
  3.     @abstract   Creates a format description for a video media stream described by H.264 parameter set NAL units. 
  4.     @discussion This function parses the dimensions provided by the parameter sets and creates a format description suitable for a raw H.264 stream. 
  5.                 The parameter sets' data can come from raw NAL units and must have any emulation prevention bytes needed. 
  6.                 The supported NAL unit types to be included in the format description are 7 (sequence parameter set), 8 (picture parameter set) and 13 (sequence parameter set extension). At least one sequence parameter set and one picture parameter set must be provided. 
  7. */  
  8. CM_EXPORT  
  9. OSStatus CMVideoFormatDescriptionCreateFromH264ParameterSets(  
  10.      CFAllocatorRef allocator,                      /*! @param allocator 
  11.                                                     CFAllocator to be used when creating the CMFormatDescription. Pass NULL to use the default allocator. */  
  12.      size_t parameterSetCount,                      /*! @param parameterSetCount 
  13.                                                     The number of parameter sets to include in the format description. This parameter must be at least 2. */  
  14.      const uint8_t * constconst * parameterSetPointers,  /*! @param parameterSetPointers 
  15.                                                     Points to a C array containing parameterSetCount pointers to parameter sets. */  
  16.      const size_tsize_t * parameterSetSizes,              /*! @param parameterSetSizes 
  17.                                                     Points to a C array containing the size, in bytes, of each of the parameter sets. */  
  18.      int NALUnitHeaderLength,                       /*! @param NALUnitHeaderLength 
  19.                                                     Size, in bytes, of the NALUnitLength field in an AVC video sample or AVC parameter set sample. Pass 1, 2 or 4. */  
  20.      CMFormatDescriptionRef *formatDescriptionOut ) /*! @param formatDescriptionOut 
  21.                                                     Returned newly-created video CMFormatDescription */  
  22.                             __OSX_AVAILABLE_STARTING(__MAC_10_9,__IPHONE_7_0);  



4,判断VT编码出来的数据是不是keyframe

这个代码取自OpenWebrtc from Ericsson

  1. static bool  
  2. vtenc_buffer_is_keyframe (CMSampleBufferRef sbuf)  
  3. {  
  4.     bool result = FALSE;  
  5.     CFArrayRef attachments_for_sample;  
  6.       
  7.     attachments_for_sample = CMSampleBufferGetSampleAttachmentsArray (sbuf, 0);  
  8.     if (attachments_for_sample != NULL) {  
  9.         CFDictionaryRef attachments;  
  10.         CFBooleanRef depends_on_others;  
  11.           
  12.         attachments = (CFDictionaryRef)CFArrayGetValueAtIndex (attachments_for_sample, 0);  
  13.         depends_on_others = (CFBooleanRef)CFDictionaryGetValue (attachments,  
  14.                                                   kCMSampleAttachmentKey_DependsOnOthers);  
  15.         result = (depends_on_others == kCFBooleanFalse);  
  16.     }  
  17.       
  18.     return result;  
  19. }  


4,SPS和PPS变化后判断VT是否还能正确解码

经过下面的接口判断是否须要须要更新VT

[objc] view plain copy
  1. /*! 
  2. <span style="white-space:pre">    </span>@function VTDecompressionSessionCanAcceptFormatDescription 
  3. <span style="white-space:pre">    </span>@abstract Indicates whether the session can decode frames with the given format description. 
  4. <span style="white-space:pre">    </span>@discussion 
  5. <span style="white-space:pre">        </span>Some video decoders are able to accommodate minor changes in format without needing to be 
  6. <span style="white-space:pre">        </span>completely reset in a new session.  This function can be used to test whether a format change 
  7. <span style="white-space:pre">        </span>is sufficiently minor. 
  8. */  
  9. VT_EXPORT Boolean   
  10. VTDecompressionSessionCanAcceptFormatDescription(   
  11. <span style="white-space:pre">    </span>VTDecompressionSessionRef<span style="white-space:pre">      </span>session,   
  12. <span style="white-space:pre">    </span>CMFormatDescriptionRef<span style="white-space:pre">         </span>newFormatDesc ) __OSX_AVAILABLE_STARTING(__MAC_10_8,__IPHONE_8_0);  


5,PTS

PTS会影响VT编码质量,通常状况下,duration参数表示每帧数据的时长,用样点数表示,通常视频采样频率为90KHz,帧率为30fps,则duration就是sampleRate / frameRate = 90K/30 = 3000.

而pts表示当前帧的显示时间,也用样点数表示,即 n_samples * sampleRate / frameRate.

[objc] view plain copy
  1. VT_EXPORT OSStatus  
  2. VTCompressionSessionEncodeFrame(  
  3.     VTCompressionSessionRef     session,  
  4.     CVImageBufferRef            imageBuffer,   
  5.     CMTime                      presentationTimeStamp,  
  6.     CMTime                      duration, // may be kCMTimeInvalid  
  7.     CFDictionaryRef             frameProperties, // may be NULL  
  8.     voidvoid *                      sourceFrameRefCon,  
  9.     VTEncodeInfoFlags           *infoFlagsOut /* may be NULL */ ) __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);  



6,编码选项

[objc] view plain copy
  1. kVTCompressionPropertyKey_AllowTemporalCompression  
[objc] view plain copy
  1. kVTCompressionPropertyKey_AllowFrameReordering  


TemporalCompression控制是否产生P帧。

FrameReordering控制是否产生B帧。


7,使用自带的PixelBufferPool提升性能。

建立VTSession以后会自动建立一个PixelBufferPool,用作循环缓冲区,下降频繁申请释放内存区域形成的额外开销。


[objc] view plain copy
  1. VT_EXPORT CVPixelBufferPoolRef   
  2. VTCompressionSessionGetPixelBufferPool(  
  3.     VTCompressionSessionRef     session ) __OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);  

 

[objc] view plain copy
  1. CV_EXPORT CVReturn CVPixelBufferPoolCreatePixelBuffer(CFAllocatorRef allocator,   
  2.                                  CVPixelBufferPoolRef pixelBufferPool,  
  3.                              CVPixelBufferRef *pixelBufferOut) __OSX_AVAILABLE_STARTING(__MAC_10_4,__IPHONE_4_0);  



中间还有不少不少的细节,任何一处错误都是致使千奇百怪的crash/编码或解码失败等

多看看我提供的那几个连接,会颇有帮助。

通过测试,iOS8 硬件编解码效果确实很好,比OpenH264出来的视频质量更清晰,而且能轻松达到30帧,码率控制的精确性也更高。

相关文章
相关标签/搜索