iOS視頻 硬編碼代碼

為什么視頻可以壓縮編碼?

  • 存在冗余信息

    • 空間冗余:圖像相鄰像素之間有較強(qiáng)的相關(guān)性
    • 時(shí)間冗余:視頻序列的相鄰圖像之間內(nèi)容相似
    • 視覺冗余:人的視覺系統(tǒng)對(duì)某些細(xì)節(jié)不敏感
    • 其他冗余信息
  • 空間冗余

    • 同一張圖像中茬暇,有很多像素點(diǎn)表示的信息是完全一樣的
    • 如果對(duì)每一個(gè)像素進(jìn)行單獨(dú)的存儲(chǔ)舅世,必然會(huì)非常浪費(fèi)空間辛慰,也完全沒有必要
  • 時(shí)間冗余

    • 多張圖像之間螺垢,有非常多的相關(guān)性尖昏,由于一些小運(yùn)動(dòng)造成了細(xì)小差別
    • 如果對(duì)每張圖像進(jìn)行單獨(dú)的像素存儲(chǔ)瓷翻,在下一張圖片中又出現(xiàn)了相同的聚凹。那么相當(dāng)于很多像素都存儲(chǔ)了多份,必然會(huì)非常浪費(fèi)空間齐帚,也是完全沒有必要的
  • 視覺冗余

    • 人類視覺系統(tǒng)HVS(Human Visual System)
      • 對(duì)高頻信息不敏感
      • 對(duì)高對(duì)比度更敏感
      • 對(duì)亮度信息比色度信息更敏感
      • 對(duì)運(yùn)動(dòng)的信息更敏感
    • 數(shù)字視頻系統(tǒng)的設(shè)計(jì)應(yīng)該考慮HVS的特點(diǎn):
      • 丟棄高頻信息妒牙,只編碼低頻信息
      • 提高邊緣信息的主觀質(zhì)量
      • 降低色度的解析度
      • 對(duì)感興趣區(qū)域(Region of Interesting,ROI)進(jìn)行特殊處理

壓縮編碼的標(biāo)準(zhǔn)

  • ITU:International Telecommunications Union VECG:Video Coding Experts Group(國際電傳視訊聯(lián)盟)
  • ISO:International Standards Organization MPEG:Motion Picture Experts Group(國際標(biāo)準(zhǔn)組織機(jī)構(gòu))

ios8.0 之后 使用VideoToolBox框架
流程:

  • 采集
  • 獲取到視頻幀
  • 對(duì)視頻幀進(jìn)行編碼
  • 獲取到視頻幀信息
  • 將編碼后的數(shù)據(jù)以NALU方式寫入到文件

視頻采集

視頻硬件編碼

  • 初始化壓縮編碼會(huì)話(VTCompressionSessionRef)

    • 在VideoToolbox框架的使用過程中对妄,基本都是C語言函數(shù)
  • 初始化后通過VTSessionSetProperty設(shè)置對(duì)象屬性

    • 編碼方式:H.264編碼
    • 幀率:每秒鐘多少幀畫面
    • 碼率:?jiǎn)挝粫r(shí)間內(nèi)保存的數(shù)據(jù)量
    • 關(guān)鍵幀(GOPsize)間隔:多少幀為一個(gè)GOP
  • 準(zhǔn)備編碼

- (void)prepareEncodeWithWidth:(int)width height:(int)height{
    //0 定義幀的下標(biāo)值
    frameIndex = 0;
    //1.創(chuàng)建VTCompressionSessionRef 對(duì)象
    //1.創(chuàng)建VTCompressionSessionRef 對(duì)象
    // 參數(shù)一: CoreFoundation 創(chuàng)建對(duì)象的方式 湘今,NULL -> Default
    // 參數(shù)二:編碼的視頻寬度
    // 參數(shù)三: 編碼的視頻高度
    // 參數(shù)四: 編碼的標(biāo)準(zhǔn) H.264/ H.265
    // 參數(shù)五 ~ 參數(shù)七 NULL
    // 參數(shù)八: 編碼成功一幀數(shù)據(jù)后的函數(shù)回調(diào)
    // 參數(shù)九: 回調(diào)函數(shù)的第一個(gè)參數(shù)
    //  VTCompressionSessionRef session;
    VTCompressionSessionCreate(kCFAllocatorDefault, 
      width, height, kCMVideoCodecType_H264, 
      NULL, NULL, NULL, 
      compressionCallback, (__bridge void * _Nullable)(self),
             &_session);

    //2.設(shè)置VTCompressionSessionRef 屬性
   // 2.1 如果是直播,需要設(shè)置視頻編碼是實(shí)時(shí)輸出
    VTSessionSetProperty(self.session, kVTCompressionPropertyKey_RealTime, (__bridge CFTypeRef _Nullable)(@YES));
    // 2.2 設(shè)置幀率 (16/24/30)
    // 幀/s
    VTSessionSetProperty(self.session, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef _Nullable)(@30));
    //2.3 設(shè)置比特率 (碼率) bit/s  單位時(shí)間的數(shù)據(jù)量
    VTSessionSetProperty(self.session, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef _Nullable)(@(1500000))); // bit
    CFArrayRef dataLimits = (__bridge CFArrayRef)(@[@(1500000/8),@1]); //byte
    VTSessionSetProperty(self.session, kVTCompressionPropertyKey_DataRateLimits, dataLimits);
    // 2.4 設(shè)置GOP的大小
    VTSessionSetProperty(self.session, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef _Nullable)(@(20)));
    //3.準(zhǔn)備開始編碼
    VTCompressionSessionPrepareToEncodeFrames(self.session);
}

總結(jié)一下常用設(shè)置屬性:

  • kVTCompressionPropertyKey_RealTime 編碼是否實(shí)時(shí)輸出

  • kVTCompressionPropertyKey_ExpectedFrameRate 幀率剪菱,也就是一秒輸出多少幀圖像摩瞎,16張就可以形成動(dòng)畫,一般默認(rèn)30

  • kVTCompressionPropertyKey_AverageBitRate 碼率孝常,一般是單位時(shí)間內(nèi)的數(shù)據(jù)量 必須同時(shí)設(shè)置kVTCompressionPropertyKey_DataRateLimits

  • kVTCompressionPropertyKey_MaxKeyFrameInterval GOP,默認(rèn)設(shè)置20

  • 開始編碼

- (void)encodeFrame:(CMSampleBufferRef)sampleBuffer{
      //1.從CMSampleBufferRef 中獲取 CVImageBufferRef
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    //利用 VTCompressionSessionRef 編碼 CMSampleBufferRef
    //pts(presentationTimeStamp):展示時(shí)間戳旗们,用來解碼時(shí),計(jì)算每一幀時(shí)間的
    //dts(DecodeTimeStamp): 解碼時(shí)間戳,決定該幀在什么時(shí)間展示
    frameIndex ++;
    // 第幾幀 幀率
    CMTime pts = CMTimeMake(frameIndex, 30);
    
    VTCompressionSessionEncodeFrame(self.session, 
     imageBuffer, pts, kCMTimeInvalid, NULL, NULL, NULL);
}
編碼前后CMSampleBuffer區(qū)別

CMSampleBuffer = CMTime(時(shí)間戳) +CMVideoFormatDesc(圖片存儲(chǔ)方式) + CMBlockBuffer(編碼后的數(shù)據(jù))

  • 編碼成功一幀數(shù)據(jù)后的函數(shù)回調(diào)
void compressionCallback(void * CM_NULLABLE outputCallbackRefCon,
              void * CM_NULLABLE sourceFrameRefCon,
              OSStatus status,
              VTEncodeInfoFlags infoFlags,
              CM_NULLABLE CMSampleBufferRef sampleBuffer){
    // 0 獲取到當(dāng)前對(duì)象
    H264Encoder *encoder = (__bridge H264Encoder *)(outputCallbackRefCon);
    
    // 1.CMSampleBufferRef
    // 2.判斷該幀是否是關(guān)鍵幀
    CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
    CFDictionaryRef dict = CFArrayGetValueAtIndex(attachments, 0);
    BOOL iskeyFrame = !CFDictionaryContainsKey(dict, kCMSampleAttachmentKey_NotSync);
    // 3. 如果是關(guān)鍵幀构灸,那么將關(guān)鍵幀寫入文件之前上渴,先寫入 PPS / SPS數(shù)據(jù)
    if (iskeyFrame) {
       //3.1 獲取參數(shù)信息
      CMFormatDescriptionRef format =   CMSampleBufferGetFormatDescription(sampleBuffer);
      //3.2 從format 中獲取sps信息
        //
        //參數(shù)二 : sps 0 pps 1
        //參數(shù)三
        const uint8_t *spsPointer;
        size_t spsSize,spsCount;
        
        CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &spsPointer, &spsSize, &spsCount, NULL);
      //3.3 從format 中獲取pps信息
        const uint8_t *ppsPointer;
        size_t ppsSize,ppsCount;
        CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &ppsPointer, &ppsSize, &ppsCount, NULL);
       // 3.4 將sps/pps 寫入 NAL單元
        NSData *spsData = [NSData dataWithBytes:spsPointer length:spsSize];
        NSData *ppsData = [NSData dataWithBytes:ppsPointer length:ppsSize];
        [encoder writeData:spsData];
        [encoder writeData:ppsData];
    }
    // 4.將編碼后的數(shù)據(jù)寫入文件
    // 4.1 獲取CMSampleBufferRef
    CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
    // 4.2 CMSampleBufferRef獲取內(nèi)存地址/長(zhǎng)度
    size_t totalLength;
    char *dataPointer;
    CMBlockBufferGetDataPointer(blockBuffer, 0, NULL, &totalLength, &dataPointer);
    // 4.3 從dataPointer開始讀取數(shù)據(jù),并且寫入NALU -> slice
    static const int h264HeaderLength = 4;
    size_t offsetLength = 0;
    // 4.4 通過循環(huán),不斷的讀取slice的切片數(shù)據(jù)驰贷,并且封裝成NALU 寫入文件
    while (offsetLength < totalLength - h264HeaderLength) {
         // 4.5 讀取slice的長(zhǎng)度
        uint32_t naluLength;
        memcpy(&naluLength, dataPointer+offsetLength, h264HeaderLength);
        // 4.6 H264 大端字節(jié)序/ 小端字節(jié)序
        naluLength = CFSwapInt32BigToHost(naluLength);
        // 4.7 根據(jù)長(zhǎng)度讀取字節(jié),并轉(zhuǎn)成NSData
        NSData *data = [NSData dataWithBytes:dataPointer+offsetLength+h264HeaderLength length:naluLength];
        //4.8 寫入文件
        [encoder writeData:data];
        //4.9 設(shè)置offsetLength
        offsetLength += naluLength + h264HeaderLength;
    }
}

需要注意的一點(diǎn)是,編碼后的數(shù)據(jù)需要通過切片的方式讀取數(shù)據(jù)洛巢,h264已經(jīng)提供好了切片后的數(shù)據(jù)括袒,并且默認(rèn)使用4個(gè)字節(jié)提供每一個(gè)切片的數(shù)據(jù)的長(zhǎng)度,在寫入文件時(shí)候是不能包括這個(gè)4字節(jié)長(zhǎng)度的稿茉。

  • 寫入數(shù)據(jù)
- (void)writeData:(NSData *)data{
   // NALU 的形式寫入
   // NALU 頭  0x 表示 16進(jìn)制的某個(gè)數(shù)字 x 表示16進(jìn)制的某個(gè)字節(jié)
    const char bytes[] = "\x00\x00\x00\x01";
    int headerLength = sizeof(bytes) - 1;
    NSData *headerData = [NSData dataWithBytes:bytes length:headerLength];
    // NALU 體
    [self.fileHandle writeData:headerData];
    [self.fileHandle writeData:data];
}
  • 結(jié)束編碼
- (void)endEncoding{
    VTCompressionSessionInvalidate(self.session);
    CFRelease(self.session);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锹锰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子漓库,更是在濱河造成了極大的恐慌恃慧,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渺蒿,死亡現(xiàn)場(chǎng)離奇詭異痢士,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茂装,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門怠蹂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人少态,你說我怎么就攤上這事城侧。” “怎么了彼妻?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵嫌佑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我侨歉,道長(zhǎng)屋摇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任幽邓,我火速辦了婚禮摊册,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颊艳。我一直安慰自己茅特,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布棋枕。 她就那樣靜靜地躺著白修,像睡著了一般。 火紅的嫁衣襯著肌膚如雪重斑。 梳的紋絲不亂的頭發(fā)上兵睛,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼祖很。 笑死笛丙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的假颇。 我是一名探鬼主播胚鸯,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼笨鸡!你這毒婦竟也來了姜钳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤形耗,失蹤者是張志新(化名)和其女友劉穎哥桥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體激涤,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拟糕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倦踢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已卸。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖硼一,靈堂內(nèi)的尸體忽然破棺而出累澡,到底是詐尸還是另有隱情,我是刑警寧澤般贼,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布愧哟,位于F島的核電站,受9級(jí)特大地震影響哼蛆,放射性物質(zhì)發(fā)生泄漏蕊梧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一腮介、第九天 我趴在偏房一處隱蔽的房頂上張望肥矢。 院中可真熱鬧,春花似錦叠洗、人聲如沸甘改。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽十艾。三九已至,卻和暖如春腾节,著一層夾襖步出監(jiān)牢的瞬間忘嫉,已是汗流浹背荤牍。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庆冕,地道東北人康吵。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像访递,于是被迫代替她去往敵國和親晦嵌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容