視頻的編解碼-編碼篇

四、視頻的編解碼-編碼篇

時間?2016-08-05 10:22:59Twenty's 時間念

原文http://blog.img421.com/si-shi-pin-de-bian-jie-ma-bian-ma-pian/

主題iOS開發(fā)MacOS

在此之前我們通常使用的FFmpeg多媒體庫,利用CPU來進行視頻的編解碼,占用CPU資源,效率低下,俗稱軟編解碼.而蘋果在2014年的iOS8中,開放了VideoToolbox.framwork框架,此框架使用GPU或專用的處理器來進行編解碼,俗稱硬編解碼.而此框架在此之前只有MAC OS系統(tǒng)中可以使用,在iOS作為私有框架.終于蘋果在iOS8.0中得到開放引入.

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

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

對于編解碼,AVFoundation框架只有以下幾個功能: 1. 直接解壓后顯示;

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

而對于Video Toolbox,我們可以通過以下功能獲取到數(shù)據(jù),進行網(wǎng)絡流傳輸?shù)榷喾N保存: 1. 解壓為圖像的數(shù)據(jù)結構;

2. 壓縮為視頻圖像的容器數(shù)據(jù)結構.

一、videoToolbox的基本數(shù)據(jù)

Video Toolbox視頻編解碼前后需要應用的數(shù)據(jù)結構進行說明。

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

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

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

CMBlockBuffer:編碼后,結果圖像的數(shù)據(jù)結構。

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

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

CMClock

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

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

圖1.1

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

二、視頻數(shù)據(jù)流編碼并上傳到服務器

1.將CVPixelBuffer使用VTCompressionSession進行數(shù)據(jù)流的硬編碼。

(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的初始化參數(shù)說明:

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

width: 寬

height: 高

codecType: 編碼類型,如kCMVideoCodecType_H264

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

sourceImageBufferAttributes: 源像素緩沖區(qū)屬性.設置NULL不讓videToolbox創(chuàng)建,而自己創(chuàng)建

compressedDataAllocator: 壓縮數(shù)據(jù)分配器.設置NULL,默認的分配

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

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

compressionSessionOut: 壓縮會話變量。

(2)配置VTCompressionSession

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

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

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

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

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數(shù)據(jù)的展示時間戳。每一個傳給這個session的時間戳都要大于前一個展示時間戳.

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

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

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

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

(4)執(zhí)行VTCompressionOutputCallback回調函數(shù)

typedefvoid(*VTCompressionOutputCallback)(void* CM_NULLABLE outputCallbackRefCon,void* CM_NULLABLE sourceFrameRefCon,? ? ? ? OSStatus status,? ? ? ? VTEncodeInfoFlags infoFlags,? ? ? ? CM_NULLABLE CMSampleBufferRef sampleBuffer );

outputCallbackRefCon: 回調函數(shù)的參考值

sourceFrameRefCon: VTCompressionSessionEncodeFrame函數(shù)中設置的幀的參考值

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

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

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

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

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

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

5.1 先判斷壓縮的數(shù)據(jù)是否正確

//不存在則代表壓縮不成功或幀丟失if(!sampleBuffer)return;if(status != noErr)return;//返回sampleBuffer中包括可變字典的不可變數(shù)組,如果有錯誤則為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從字面意思理解即為以上的說明,但是網(wǎng)上看到很多都是做為查詢是否是視頻關鍵幀,而查詢文檔看到有此關鍵幀key值kCMSampleBufferAttachmentKey_ForceKeyFrame存在,因此對此值如若有了解情況者敬請告知詳情.

5.2 獲取CMVideoFormatDesc數(shù)據(jù)由三、解碼篇可知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參數(shù)集合中的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并轉換成數(shù)據(jù)

CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);? ? size_t? lengthAtOffset,totalLength;char*dataPointer;//接收到的數(shù)據(jù)展示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個字節(jié)到目標dest所指的內存地址的起始位置中

*/memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);//字節(jié)從高位反轉到低位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數(shù)據(jù)應用于后面的RTMP協(xié)議做推流準備。

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

推薦閱讀更多精彩內容