視頻的編解碼-編碼篇

轉載:http://www.lxweimin.com/p/9e078dfabf0a

轉載時間:2017年4月6日

在此之前我們通常使用的FFmpeg多媒體庫,利用CPU來進行視頻的編解碼,占用CPU資源,效率低下,俗稱軟編解碼.而蘋果在2014年的iOS8中,開放了VideoToolbox.framwork框架,此框架使用GPU或專用的處理器來進行編解碼,俗稱硬編解碼.而此框架在此之前只有MAC

OS系統中可以使用,在iOS作為私有框架.終于蘋果在iOS8.0中得到開放引入.

2014年的WWDCDirect Access to Video Encoding and Decoding中,蘋果介紹了使用videoToolbox硬編解碼.

使用硬編解碼有幾個優點: * 提高性能; * 增加效率; * 延長電量的使用

(一)對于編解碼,AVFoundation框架只有以下幾個功能:

1. 直接解壓后顯示;

2. 直接壓縮到一個文件當中;

(二)而對于Video Toolbox,我們可以通過以下功能獲取到數據,進行網絡流傳輸等多種保存:

1. 解壓為圖像的數據結構;

2. 壓縮為視頻圖像的容器數據結構.

一、videoToolbox的基本數據

Video Toolbox視頻編解碼前后概念說明:

CVPixelBuffer:編碼前和解碼后的圖像數據結構。此內容包含一系列的CVPixelBufferPool內容

CMTime、CMClock和CMTimebase:時間戳相關。時間以64-bit/32-bit的形式出現。

pixelBufferAttributes:字典設置.可能包括Width/height、pixel format type、? Compatibility (e.g., OpenGL ES, Core Animation)

CMBlockBuffer:編碼后,結果圖像的數據結構。

CMVideoFormatDescription:圖像存儲方式,編解碼器等格式描述。

(CMSampleBuffer:存放編解碼前后的視頻圖像的容器數據結構。

CMTimebase: 關于CMClock的一個控制視圖,包含CMClock、時間映射(Time mapping)、速率控制(Rate control)

由二、采集視頻數據可知,我們獲取到的數據(CMSampleBufferRef)sampleBuffer為未編碼的數據;

上圖中,編碼前后的視頻圖像都封裝在CMSampleBuffer中,編碼前以CVPixelBuffer進行存儲;編碼后以CMBlockBuffer進行存儲。除此之外兩者都包括CMTime、CMVideoFormatDesc.

二、視頻數據流編碼并上傳到服務器

1.將CVPixelBuffer使用VTCompressionSession進行數據流的硬編碼。


(1)初始化VTCompressionSession

VT_EXPORT

OSStatus VTCompressionSessionCreate(? ?? CM_NULLABLE CFAllocatorRef

allocator,? ? int32_t

width,? ? int32_t

height,? ? CMVideoCodecType

codecType,? ? CM_NULLABLE CFDictionaryRef

encoderSpecification,? ? CM_NULLABLE CFDictionaryRef

sourceImageBufferAttributes,? ? CM_NULLABLE CFAllocatorRef

compressedDataAllocator,? ? CM_NULLABLE

VTCompressionOutputCallback? ? ? ? ? ?? outputCallback,? ? void *

CM_NULLABLE? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? outputCallbackRefCon,

CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef *

CM_NONNULL compressionSessionOut)

__OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);

VTCompressionSession的初始化參數說明:

allocator:分配器,設置NULL為默認分配

width: 寬

height: 高

codecType: 編碼類型,如kCMVideoCodecType_H264

encoderSpecification: 編碼規范。設置NULL由videoToolbox自己選擇

sourceImageBufferAttributes: 源像素緩沖區屬性.設置NULL不讓videToolbox創建,而自己創建

compressedDataAllocator: 壓縮數據分配器.設置NULL,默認的分配

outputCallback:

當VTCompressionSessionEncodeFrame被調用壓縮一次后會被異步調用.注:當你設置NULL的時候,你需要調用VTCompressionSessionEncodeFrameWithOutputHandler方法進行壓縮幀處理,支持iOS9.0以上

outputCallbackRefCon: 回調客戶定義的參考值.

compressionSessionOut: 壓縮會話變量。

(2)配置VTCompressionSession

使用VTSessionSetProperty()調用進行配置compression。 * kVTCompressionPropertyKeyAllowFrameReordering: 允許幀重新排序.默認為true * kVTCompressionPropertyKeyAverageBitRate: 設置需要的平均編碼率 * kVTCompressionPropertyKeyH264EntropyMode:H264的熵編碼模式。有兩種模式:一種基于上下文的二進制算數編碼CABAC和可變長編碼VLC.在slice層之上(picture和sequence)使用定長或變長的二進制編碼,slice層及其以下使用VLC或CABAC.詳情請參考* kVTCompressionPropertyKeyRealTime: 視頻編碼壓縮是否是實時壓縮。可設置CFBoolean或NULL.默認為NULL * kVTCompressionPropertyKeyProfileLevel: 對于編碼流指定配置和標準 .比如kVTProfileLevelH264MainAutoLevel

配置過VTCompressionSession后,可以可選的調用VTCompressionSessionPrepareToEncodeFrames進行準備工作編碼幀。

(3)開始硬編碼流入的數據

使用VTCompressionSessionEncodeFrame方法進行編碼.當編碼結束后調用outputCallback回調函數。

VT_EXPORT

OSStatus? VTCompressionSessionEncodeFrame(? ? ? CM_NONNULL

VTCompressionSessionRef? session,? ? CM_NONNULL CVImageBufferRef

imageBuffer,? ? CMTime

presentationTimeStamp,? ? CMTime

duration,// may be kCMTimeInvalidCM_NULLABLE CFDictionaryRef

frameProperties,void* CM_NULLABLE? ? ? ? ? ? ? ? ? sourceFrameRefCon,

VTEncodeInfoFlags * CM_NULLABLE? ?? infoFlagsOut )

__OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_8_0);

presentationTimeStamp: 獲取到的這個sample buffer數據的展示時間戳。每一個傳給這個session的時間戳都要大于前一個展示時間戳.

duration: 對于獲取到sample buffer數據,這個幀的展示時間.如果沒有時間信息,可設置kCMTimeInvalid.

frameProperties: 包含這個幀的屬性.幀的改變會影響后邊的編碼幀.

sourceFrameRefCon: 回調函數會引用你設置的這個幀的參考值.

infoFlagsOut:

指向一個VTEncodeInfoFlags來接受一個編碼操作.如果使用異步運行,kVTEncodeInfo_Asynchronous被設置;同步運行,kVTEncodeInfo_FrameDropped被設置;設置NULL為不想接受這個信息.

(4)執行VTCompressionOutputCallback回調函數

typedefvoid(*VTCompressionOutputCallback)(void*

CM_NULLABLE outputCallbackRefCon,void* CM_NULLABLE sourceFrameRefCon,

OSStatus status,? ? ? ?? VTEncodeInfoFlags infoFlags,

CM_NULLABLE CMSampleBufferRef sampleBuffer );

outputCallbackRefCon: 回調函數的參考值

sourceFrameRefCon: VTCompressionSessionEncodeFrame函數中設置的幀的參考值

status: 壓縮的成功為noErr,如失敗有錯誤碼

infoFlags: 包含編碼操作的信息標識

sampleBuffer: 如果壓縮成功或者幀不丟失,則包含這個已壓縮的數據CMSampleBuffer,否則為NULL

(5)將壓縮成功的sampleBuffer數據進行處理為基本流NSData上傳到服務器

MPEG-4是一套用于音頻、視頻信息的壓縮編碼標準.

圖1.1可知,已壓縮 $$CMSampleBuffer = CMTime(可選) + CMBlockBuffer + CMVideoFormatDesc$$。

5.1 先判斷壓縮的數據是否正確

//不存在則代表壓縮不成功或幀丟失if(!sampleBuffer)return;if(status

!=

noErr)return;//返回sampleBuffer中包括可變字典的不可變數組,如果有錯誤則為NULLCFArrayRefarray=

CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,true);if(!array)return;

CFDictionaryRef dic =

CFArrayGetValueAtIndex(array,0);if(!dic)return;//issue

3:kCMSampleAttachmentKey_NotSync:沒有這個鍵意味著同步, yes: 異步. no:同步BOOL keyframe

= !CFDictionaryContainsKey(dic,

kCMSampleAttachmentKey_NotSync);//此代表為同步

而對于issue 3從字面意思理解即為以上的說明,但是網上看到很多都是做為查詢是否是視頻關鍵幀,而查詢文檔看到有此關鍵幀key值kCMSampleBufferAttachmentKey_ForceKeyFrame存在,因此對此值如若有了解情況者敬請告知詳情.

5.2 獲取CMVideoFormatDesc數據由三、解碼篇可知CMVideoFormatDesc 包括編碼所用的profile,level,圖像的寬和高,deblock濾波器等.具體包含第一個NALU的SPS(Sequence Parameter Set)和第二個NALU的PPS(Picture Parameter Set).

//if

(keyframe && !encoder -> sps) {? ? //獲取sample buffer 中的

CMVideoFormatDesc? ? CMFormatDescriptionRef format =

CMSampleBufferGetFormatDescription(sampleBuffer);

//獲取H264參數集合中的SPS和PPS? ? const uint8_t * sparameterSet;size_t

sparameterSetSize,sparameterSetCount ;?? OSStatus statusCode =

CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0,

&sparameterSet, &sparameterSetSize,

&sparameterSetCount,0);if (statusCode == noErr) {? ? ? ? size_t

pparameterSetSize, pparameterSetCount;? ? ? ? const uint8_t

*pparameterSet;OSStatus statusCode =

CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1,

&pparameterSet, &pparameterSetSize,

&pparameterSetCount,0);if (statusCode == noErr) {

encoder->sps = [NSData

dataWithBytes:sparameterSetlength:sparameterSetSize];encoder->pps =

[NSData dataWithBytes:pparameterSetlength:pparameterSetSize];}? ? }}

5.3 獲取CMBlockBuffer并轉換成數據

CMBlockBufferRef

blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);? ? size_t

lengthAtOffset,totalLength;char*dataPointer;//接收到的數據展示OSStatus

blockBufferStatus = CMBlockBufferGetDataPointer(blockBuffer,0,

&lengthAtOffset, &totalLength,

&dataPointer);if(blockBufferStatus != kCMBlockBufferNoErr)? ? {

size_t bufferOffset =0;staticconstintAVCCHeaderLength

=4;while(bufferOffset < totalLength -? AVCCHeaderLength) {// Read the

NAL unit lengthuint32_t NALUnitLength =0;/**

*? void *memcpy(void *dest, const void *src, size_t n);

*? 從源src所指的內存地址的起始位置開始拷貝n個字節到目標dest所指的內存地址的起始位置中

*/memcpy(&NALUnitLength,

dataPointer + bufferOffset, AVCCHeaderLength);//字節從高位反轉到低位NALUnitLength

= CFSwapInt32BigToHost(NALUnitLength);? ? ? ? ? ? RTAVVideoFrame *

frame = [RTAVVideoFramenew];? ? ? ? ? ? frame.sps = encoder -> sps;

frame.pps = encoder -> pps;? ? ? ? ? ? frame.data = [NSData

dataWithBytes:(dataPointer+bufferOffset+AVCCHeaderLength)

length:NALUnitLength];? ? ? ? ? ? bufferOffset += NALUnitLength +

AVCCHeaderLength;? ? ? ? }? ? }

此得到的H264數據應用于后面的RTMP協議做推流準備。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容