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幀,碼率控制的精確性也更高。

相關文章
相關標籤/搜索