日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS H264,H265视频编码(Video encode)

發(fā)布時(shí)間:2024/9/21 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS H264,H265视频编码(Video encode) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本例需求:使用H264, H265實(shí)現(xiàn)視頻數(shù)據(jù)的編碼并錄制開始200幀存為文件.

原理:比如做直播功能,需要將客戶端的視頻數(shù)據(jù)傳給服務(wù)器,如果分辨率過大如2K,4K則傳輸壓力太大,所以需要對視頻數(shù)據(jù)進(jìn)行編碼,傳給服務(wù)器后再解碼以實(shí)現(xiàn)大數(shù)據(jù)量的視頻數(shù)據(jù)的傳輸,而利用硬件編碼則可以極大限度減小CPU壓力,

H264進(jìn)行編碼,iOS 11 之后,iPhone 7以上的設(shè)備可以支持新的編碼器H265編碼器,使得同等質(zhì)量視頻占用的存儲空間更小。所以本例中可以使用兩種方式實(shí)現(xiàn)視頻數(shù)據(jù)的編碼


最終效果如下 : h264

h265 :


GitHub地址(附代碼) : H264,H265Encode

簡書地址 : H264,H265Encode

博客地址 : H264,H265Encode

掘金地址 : H264,H265Encode


實(shí)現(xiàn)方式:

1. H264 : H264是當(dāng)前主流編碼標(biāo)準(zhǔn),以高壓縮高質(zhì)量和支持多種網(wǎng)絡(luò)的流媒體傳輸著稱

2. H265 :H264編碼器的下一代,它的主要優(yōu)點(diǎn)提供的壓縮比高,相同質(zhì)量的視頻是H264的兩倍。


一.本文需要基本知識點(diǎn)

注意:可以先通過H264,H265編碼器介紹, H.264 Data Structure了解預(yù)備知識。

1. 軟編與硬編概念

  • 軟編碼:使用CPU進(jìn)行編碼。
  • 硬編碼:不使用CPU進(jìn)行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進(jìn)行編碼。
    • 比較
      • 軟編碼:實(shí)現(xiàn)直接、簡單,參數(shù)調(diào)整方便,升級易,但CPU負(fù)載重,性能較硬編碼低,低碼率下質(zhì)量通常比硬編碼要好一點(diǎn)。
      • 性能高,低碼率下通常質(zhì)量低于軟編碼器,但部分產(chǎn)品在GPU硬件平臺移植了優(yōu)秀的軟編碼算法(如X264)的,質(zhì)量基本等同于軟編碼。
      • 蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能,不過Mac OS系統(tǒng)一直有,被稱為Video ToolBox的框架來處理硬件的編碼和解碼,終于在iOS 8.0后,蘋果將該框架引入iOS系統(tǒng)

2.H265優(yōu)點(diǎn)

  • 壓縮比高,在相同圖片質(zhì)量情況下,比JPEG高兩倍
  • 能增加如圖片的深度信息,透明通道等輔助圖片。
  • 支持存放多張圖片,類似相冊和集合。(實(shí)現(xiàn)多重曝光的效果)
  • 支持多張圖片實(shí)現(xiàn)GIF和livePhoto的動(dòng)畫效果。
  • 無類似JPEG的最大像素限制
  • 支持透明像素
  • 分塊加載機(jī)制
  • 支持縮略圖

二.代碼解析

1.實(shí)現(xiàn)流程

  • 初始化相機(jī)參數(shù),設(shè)置相機(jī)代理,這里就固定只有豎屏模式。
  • 初始化編碼器參數(shù),并啟動(dòng)編碼器
  • 在編碼成功的回調(diào)中從開始錄制200幀(文件大小可自行修改)的視頻,存到沙盒中,可以通過連接數(shù)據(jù)線到電腦從itunes中將文件(test0.asf)提取出來

2.編碼器實(shí)現(xiàn)流程

  • 創(chuàng)建編碼器需要的session (h264, h265 或同時(shí)創(chuàng)建)
  • 設(shè)置session屬性,如實(shí)時(shí)編碼,碼率,fps, 編碼的分辨率的寬高,相鄰I幀的最大間隔等等
    • 注意H265目前不支持碼率的限制
  • 當(dāng)相機(jī)回調(diào)AVCaptureVideoDataOutputSampleBufferDelegate采集到一幀數(shù)據(jù)的時(shí)候則使用H264/H265編碼器對每一幀數(shù)據(jù)進(jìn)行編碼。
  • 若編碼成功會(huì)觸發(fā)回調(diào),回調(diào)函數(shù)首先檢測是否有I幀出現(xiàn),如果有I幀出現(xiàn)則將sps,pps信息寫入否則遍歷NALU碼流并將startCode替換成{0x00, 0x00, 0x00, 0x01}

3.主要方法解析

  • 初始化編碼器 首先選擇使用哪種方式實(shí)現(xiàn),在本例中可以設(shè)置[XDXHardwareEncoder getInstance].enableH264 = YES 或者 [XDXHardwareEncoder getInstance].enableH265 = YES,也可以同時(shí)設(shè)置,如果同時(shí)設(shè)置需要將其中一個(gè)回調(diào)函數(shù)中的writeFile的方法屏蔽掉,并且只有較新的iPhone(> iPhone8 穩(wěn)定)才支持同時(shí)打開兩個(gè)session。

判斷當(dāng)前設(shè)備是否支持H265編碼,必須滿足兩個(gè)條件,一是iPhone 7 以上設(shè)備,二是版本大于iOS 11

if (@available(iOS 11.0, *)) {BOOL hardwareDecodeSupported = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);if (hardwareDecodeSupported) {_deviceSupportH265 = YES;NSLog(@"XDXHardwareEncoder : Support H265 Encode/Decode!");}}else {_deviceSupportH265 = NO;NSLog(@"XDXHardwareEncoder : Not support H265 Encode/Decode!");} 復(fù)制代碼

系統(tǒng)已經(jīng)提供VTIsHardwareDecodeSupported判斷當(dāng)前設(shè)備是否支持H265編碼

初始化編碼器操作

- (void)prepareForEncode {if(self.width == 0 || self.height == 0) {NSLog(@"XDXHardwareEncoder : VTSession need with and height for init,with = %d,height = %d",self.width, self.height);return;}if(g_isSupportRealTimeEncoder) NSLog(@"XDXHardwareEncoder : Device processor is 64 bit");else NSLog(@"XDXHardwareEncoder : Device processor is not 64 bit");NSLog(@"XDXHardwareEncoder : Current h264 open state : %d, h265 open state : %d",self.enableH264, self.enableH265);OSStatus h264Status,h265Status;BOOL isRestart = NO;if (self.enableH264) {if (h264CompressionSession != NULL) {NSLog(@"XDXHardwareEncoder : H264 session not NULL");return;}[m_h264_lock lock];NSLog(@"XDXHardwareEncoder : Prepare H264 hardware encoder");//[self.delegate willEncoderStart];self.h264ErrCount = 0;h264Status = VTCompressionSessionCreate(NULL, self.width, self.height, kCMVideoCodecType_H264, NULL, NULL, NULL, vtCallBack,(__bridge void *)self, &h264CompressionSession);if (h264Status != noErr) {self.h265ErrCount++;NSLog(@"XDXHardwareEncoder : H264 VTCompressionSessionCreate Failed, status = %d",h264Status);}[self getSupportedPropertyFlags];[self applyAllSessionProperty:h264CompressionSession propertyArr:self.h264propertyFlags];h264Status = VTCompressionSessionPrepareToEncodeFrames(h264CompressionSession);if(h264Status != noErr) {NSLog(@"XDXHardwareEncoder : H264 VTCompressionSessionPrepareToEncodeFrames Failed, status = %d",h264Status);}else {initializedH264 = true;NSLog(@"XDXHardwareEncoder : H264 VTSession create success, with = %d, height = %d, framerate = %d",self.width,self.height,self.fps);}if(h264Status != noErr && self.h264ErrCount != 0) isRestart = YES;[m_h264_lock unlock];}if (self.enableH265) {if (h265CompressionSession != NULL) {NSLog(@"XDXHardwareEncoder : H265 session not NULL");return;}[m_h265_lock lock];NSLog(@"XDXHardwareEncoder : Prepare h265 hardware encoder");// [self.delegate willEncoderStart];self.h265ErrCount = 0;h265Status = VTCompressionSessionCreate(NULL, self.width, self.height, kCMVideoCodecType_HEVC, NULL, NULL, NULL, vtH265CallBack,(__bridge void *)self, &h265CompressionSession);if (h265Status != noErr) {self.h265ErrCount++;NSLog(@"XDXHardwareEncoder : H265 VTCompressionSessionCreate Failed, status = %d",h265Status);}[self getSupportedPropertyFlags];[self applyAllSessionProperty:h265CompressionSession propertyArr:self.h265PropertyFlags];h265Status = VTCompressionSessionPrepareToEncodeFrames(h265CompressionSession);if(h265Status != noErr) {NSLog(@"XDXHardwareEncoder : H265 VTCompressionSessionPrepareToEncodeFrames Failed, status = %d",h265Status);}else {initializedH265 = true;NSLog(@"XDXHardwareEncoder : H265 VTSession create success, with = %d, height = %d, framerate = %d",self.width,self.height,self.fps);}if(h265Status != noErr && self.h265ErrCount != 0) isRestart = YES;[m_h265_lock unlock];}if (isRestart) {NSLog(@"XDXHardwareEncoder : VTSession create failured!");static int count = 0;count ++;if (count == 3) {NSLog(@"TVUEncoder : restart 5 times failured! exit!");return;}sleep(1);NSLog(@"TVUEncoder : try to restart after 1 second!");NSLog(@"TVUEncoder : vtsession error occured!,resetart encoder width: %d, height %d, times %d",self.width,self.height,count);[self tearDownSession];[self prepareForEncode];} } 復(fù)制代碼

1> g_isSupportRealTimeEncoder = (is64Bit == 8) ? true : false;用來判斷當(dāng)前設(shè)備是32位還是64位

2> 創(chuàng)建H264/H265Session 區(qū)別僅僅為參數(shù)的不同,h264為kCMVideoCodecType_H264。 h265為kCMVideoCodecType_HEVC,在創(chuàng)建Session指定了回調(diào)函數(shù)后,當(dāng)編碼成功一幀就會(huì)調(diào)用相應(yīng)的回調(diào)函數(shù)。

3> 通過[self getSupportedPropertyFlags];獲取當(dāng)前編碼器支持設(shè)置的屬性,經(jīng)過測試,H265不支持碼率的限制。目前暫時(shí)得不到解決。等待蘋果后續(xù)處理。

4> 之后設(shè)置編碼器相關(guān)屬性,下面會(huì)具體介紹,設(shè)置完成后則調(diào)用VTCompressionSessionPrepareToEncodeFrames準(zhǔn)備編碼。

  • 設(shè)置編碼器相關(guān)屬性
- (OSStatus)setSessionProperty:(VTCompressionSessionRef)session key:(CFStringRef)key value:(CFTypeRef)value {OSStatus status = VTSessionSetProperty(session, key, value);if (status != noErr) {NSString *sessionStr;if (session == h264CompressionSession) {sessionStr = @"h264 Session";self.h264ErrCount++;}else if (session == h265CompressionSession) {sessionStr = @"h265 Session";self.h265ErrCount++;}NSLog(@"XDXHardwareEncoder : Set %s of %s Failed, status = %d",CFStringGetCStringPtr(key, kCFStringEncodingUTF8),sessionStr.UTF8String,status);}return status; }- (void)applyAllSessionProperty:(VTCompressionSessionRef)session propertyArr:(NSArray *)propertyArr {OSStatus status;if(!g_isSupportRealTimeEncoder) {/* increase max frame delay from 3 to 6 to reduce encoder pressure*/int value = 3;CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);[self setSessionProperty:session key:kVTCompressionPropertyKey_MaxFrameDelayCount value:ref];CFRelease(ref);}if(self.fps) {if([self isSupportPropertyWithKey:Key_ExpectedFrameRate inArray:propertyArr]) {int value = self.fps;CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);[self setSessionProperty:session key:kVTCompressionPropertyKey_ExpectedFrameRate value:ref];CFRelease(ref);}}else {NSLog(@"XDXHardwareEncoder : Current fps is 0");}if(self.bitrate) {if([self isSupportPropertyWithKey:Key_AverageBitRate inArray:propertyArr]) {int value = self.bitrate;if (session == h265CompressionSession) value = 2*1000; // if current session is h265, Set birate 2M.CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);[self setSessionProperty:session key:kVTCompressionPropertyKey_AverageBitRate value:ref];CFRelease(ref);}}else {NSLog(@"XDXHardwareEncoder : Current bitrate is 0");}/*2016-11-15,@gang, iphone7/7plus do not support realtime encoding, so disable itotherwize ,we can not control encoding bit rate*/if (![[self deviceVersion] isEqualToString:@"iPhone9,1"] && ![[self deviceVersion] isEqualToString:@"iPhone9,2"]) {if(g_isSupportRealTimeEncoder) {if([self isSupportPropertyWithKey:Key_RealTime inArray:propertyArr]) {NSLog(@"use RealTimeEncoder");NSLog(@"XDXHardwareEncoder : use realTimeEncoder");[self setSessionProperty:session key:kVTCompressionPropertyKey_RealTime value:kCFBooleanTrue];}}}if([self isSupportPropertyWithKey:Key_AllowFrameReordering inArray:propertyArr]) {[self setSessionProperty:session key:kVTCompressionPropertyKey_AllowFrameReordering value:kCFBooleanFalse];}if(g_isSupportRealTimeEncoder) {if([self isSupportPropertyWithKey:Key_ProfileLevel inArray:propertyArr]) {[self setSessionProperty:session key:kVTCompressionPropertyKey_ProfileLevel value:self.enableH264 ? kVTProfileLevel_H264_Main_AutoLevel : kVTProfileLevel_HEVC_Main_AutoLevel];}}else {if([self isSupportPropertyWithKey:Key_ProfileLevel inArray:propertyArr]) {[self setSessionProperty:session key:kVTCompressionPropertyKey_ProfileLevel value:self.enableH264 ? kVTProfileLevel_H264_Baseline_AutoLevel : kVTProfileLevel_HEVC_Main_AutoLevel];}if (self.enableH264) {if([self isSupportPropertyWithKey:Key_H264EntropyMode inArray:propertyArr]) {[self setSessionProperty:session key:kVTCompressionPropertyKey_H264EntropyMode value:kVTH264EntropyMode_CAVLC];}}}if([self isSupportPropertyWithKey:Key_MaxKeyFrameIntervalDuration inArray:propertyArr]) {int value = 1;CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);[self setSessionProperty:session key:kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration value:ref];CFRelease(ref);} } 復(fù)制代碼

上述方法主要設(shè)置啟動(dòng)編碼器所需的各個(gè)參數(shù)

1> kVTCompressionPropertyKey_MaxFrameDelayCount : 壓縮器被允許保持的最大幀數(shù)在輸出一個(gè)壓縮幀之前。例如如果最大幀延遲數(shù)是M,那么在編碼幀N返回的調(diào)用之前,幀N-M必須被排出。

2> kVTCompressionPropertyKey_ExpectedFrameRate : 設(shè)置fps

3> kVTCompressionPropertyKey_AverageBitRate : 它不是強(qiáng)制的限制,bit rate可能會(huì)超出峰值

4> kVTCompressionPropertyKey_RealTime : 設(shè)置編碼器是否實(shí)時(shí)編碼,如果設(shè)置為False則不是實(shí)時(shí)編碼,視頻效果會(huì)更好一點(diǎn)。

5> kVTCompressionPropertyKey_AllowFrameReordering : 是否讓幀進(jìn)行重新排序。為了編碼B幀,編碼器必須對幀重新排序,這將意味著解碼的順序與顯示的順序不同。將其設(shè)置為false以防止幀重新排序。

6> kVTCompressionPropertyKey_ProfileLevel : 指定編碼比特流的配置文件和級別

7> kVTCompressionPropertyKey_H264EntropyMode :如果支持h264該屬性設(shè)置編碼器是否應(yīng)該使用基于CAVLC 還是 CABAC

8> kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration : 兩個(gè)I幀之間最大持續(xù)時(shí)間,該屬性特別有用當(dāng)frame rate是可變

  • 相機(jī)回調(diào)中對每一幀數(shù)據(jù)進(jìn)行編碼
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {if( !CMSampleBufferDataIsReady(sampleBuffer)) {NSLog( @"sample buffer is not ready. Skipping sample" );return;}if([XDXHardwareEncoder getInstance] != NULL) {[[XDXHardwareEncoder getInstance] encode:sampleBuffer];} } 復(fù)制代碼

以上方法在每采集到一幀視頻數(shù)據(jù)后會(huì)調(diào)用一次,我們將拿到的每一幀數(shù)據(jù)進(jìn)行編碼。

  • 編碼具體實(shí)現(xiàn)
-(void)encode:(CMSampleBufferRef)sampleBuffer {if (self.enableH264) {[m_h264_lock lock];if(h264CompressionSession == NULL) {[m_h264_lock unlock];return;}if(initializedH264 == false) {NSLog(@"TVUEncoder : h264 encoder is not ready\n");return;}}if (self.enableH265) {[m_h265_lock lock];if(h265CompressionSession == NULL) {[m_h265_lock unlock];return;}if(initializedH265 == false) {NSLog(@"TVUEncoder : h265 encoder is not ready\n");return;}}CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);CMTime duration = CMSampleBufferGetOutputDuration(sampleBuffer);frameID++;CMTime presentationTimeStamp = CMTimeMake(frameID, 1000);[self doSetBitrate];OSStatus status;VTEncodeInfoFlags flags;if (self.enableH264) {status = VTCompressionSessionEncodeFrame(h264CompressionSession, imageBuffer, presentationTimeStamp, duration, NULL, imageBuffer, &flags);if(status != noErr) NSLog(@"TVUEncoder : H264 VTCompressionSessionEncodeFrame failed");[m_h264_lock unlock];if (status != noErr) {NSLog(@"TVUEncoder : VTCompressionSessionEncodeFrame failed");VTCompressionSessionCompleteFrames(h264CompressionSession, kCMTimeInvalid);VTCompressionSessionInvalidate(h264CompressionSession);CFRelease(h264CompressionSession);h264CompressionSession = NULL;}else {// NSLog(@"TVUEncoder : Success VTCompressionSessionCompleteFrames");}}if (self.enableH265) {status = VTCompressionSessionEncodeFrame(h265CompressionSession, imageBuffer, presentationTimeStamp, duration, NULL, imageBuffer, &flags);if(status != noErr) NSLog(@"TVUEncoder : H265 VTCompressionSessionEncodeFrame failed");[m_h265_lock unlock];if (status != noErr) {NSLog(@"TVUEncoder : VTCompressionSessionEncodeFrame failed");VTCompressionSessionCompleteFrames(h265CompressionSession, kCMTimeInvalid);VTCompressionSessionInvalidate(h265CompressionSession);CFRelease(h265CompressionSession);h265CompressionSession = NULL;}else {NSLog(@"TVUEncoder : Success VTCompressionSessionCompleteFrames");}}} 復(fù)制代碼

1> 通過frameID的遞增構(gòu)造時(shí)間戳為了使編碼后的每一幀數(shù)據(jù)連續(xù)

2> 設(shè)置最大碼率的限制,注意:H265目前不支持設(shè)置碼率的限制,等待官方后續(xù)通知??梢詫264進(jìn)行碼率限制

3> kVTCompressionPropertyKey_DataRateLimits : 將數(shù)據(jù)的bytes和duration封裝到CFMutableArrayRef傳給API進(jìn)行調(diào)用

4> VTCompressionSessionEncodeFrame : 調(diào)用此方法成功后觸發(fā)回調(diào)函數(shù)完成編碼。

  • 回調(diào)函數(shù)中處理頭信息
#pragma mark H264 Callback static void vtCallBack(void *outputCallbackRefCon,void *souceFrameRefCon,OSStatus status,VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {XDXHardwareEncoder *encoder = (__bridge XDXHardwareEncoder*)outputCallbackRefCon;if(status != noErr) {NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];NSLog(@"H264: vtCallBack failed with %@", error);NSLog(@"XDXHardwareEncoder : encode frame failured! %s" ,error.debugDescription.UTF8String);return;}if (!CMSampleBufferDataIsReady(sampleBuffer)) {NSLog(@"didCompressH265 data is not ready ");return;}if (infoFlags == kVTEncodeInfo_FrameDropped) {NSLog(@"%s with frame dropped.", __FUNCTION__);return;}CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);BOOL isKeyframe = false;CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);if(attachments != NULL) {CFDictionaryRef attachment =(CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);CFBooleanRef dependsOnOthers = (CFBooleanRef)CFDictionaryGetValue(attachment, kCMSampleAttachmentKey_DependsOnOthers);isKeyframe = (dependsOnOthers == kCFBooleanFalse);}if(isKeyframe) {CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);static uint8_t *spsppsNALBuff = NULL;static size_t spsSize, ppsSize;size_t parmCount;const uint8_t*sps, *pps;int NALUnitHeaderLengthOut;CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut );CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut );spsppsNALBuff = (uint8_t*)malloc(spsSize+4+ppsSize+4);memcpy(spsppsNALBuff, "\x00\x00\x00\x01", 4);memcpy(&spsppsNALBuff[4], sps, spsSize);memcpy(&spsppsNALBuff[4+spsSize], "\x00\x00\x00\x01", 4);memcpy(&spsppsNALBuff[4+spsSize+4], pps, ppsSize);NSLog(@"XDXHardwareEncoder : H264 spsSize : %zu, ppsSize : %zu",spsSize, ppsSize);writeFile(spsppsNALBuff,spsSize+4+ppsSize+4,encoder->_videoFile, 200);}size_t blockBufferLength;uint8_t *bufferDataPointer = NULL;CMBlockBufferGetDataPointer(block, 0, NULL, &blockBufferLength, (char **)&bufferDataPointer);size_t bufferOffset = 0;while (bufferOffset < blockBufferLength - startCodeLength) {uint32_t NALUnitLength = 0;memcpy(&NALUnitLength, bufferDataPointer+bufferOffset, startCodeLength);NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);memcpy(bufferDataPointer+bufferOffset, startCode, startCodeLength);bufferOffset += startCodeLength + NALUnitLength;}writeFile(bufferDataPointer, blockBufferLength,encoder->_videoFile, 200); }#pragma mark H265 Callback static void vtH265CallBack(void *outputCallbackRefCon,void *souceFrameRefCon,OSStatus status,VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {XDXHardwareEncoder *encoder = (__bridge XDXHardwareEncoder*)outputCallbackRefCon;if(status != noErr) {NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];NSLog(@"H264: H265 vtH265CallBack failed with %@", error);NSLog(@"XDXHardwareEncoder : H265 encode frame failured! %s" ,error.debugDescription.UTF8String);return;}if (!CMSampleBufferDataIsReady(sampleBuffer)) {NSLog(@"didCompressH265 data is not ready ");return;}if (infoFlags == kVTEncodeInfo_FrameDropped) {NSLog(@"%s with frame dropped.", __FUNCTION__);return;}CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);BOOL isKeyframe = false;CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);if(attachments != NULL) {CFDictionaryRef attachment =(CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);CFBooleanRef dependsOnOthers = (CFBooleanRef)CFDictionaryGetValue(attachment, kCMSampleAttachmentKey_DependsOnOthers);isKeyframe = (dependsOnOthers == kCFBooleanFalse);}if(isKeyframe) {CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);static uint8_t *vpsspsppsNALBuff = NULL;static size_t vpsSize, spsSize, ppsSize;size_t parmCount;const uint8_t *vps, *sps, *pps;if (encoder.deviceSupportH265) { // >= iPhone 7 && support ios11CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 0, &vps, &vpsSize, &parmCount, 0);CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 1, &sps, &spsSize, &parmCount, 0);CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 2, &pps, &ppsSize, &parmCount, 0);vpsspsppsNALBuff = (uint8_t*)malloc(vpsSize+4+spsSize+4+ppsSize+4);memcpy(vpsspsppsNALBuff, "\x00\x00\x00\x01", 4);memcpy(&vpsspsppsNALBuff[4], vps, vpsSize);memcpy(&vpsspsppsNALBuff[4+vpsSize], "\x00\x00\x00\x01", 4);memcpy(&vpsspsppsNALBuff[4+vpsSize+4], sps, spsSize);memcpy(&vpsspsppsNALBuff[4+vpsSize+4+spsSize], "\x00\x00\x00\x01", 4);memcpy(&vpsspsppsNALBuff[4+vpsSize+4+spsSize+4], pps, ppsSize);NSLog(@"XDXHardwareEncoder : H265 vpsSize : %zu, spsSize : %zu, ppsSize : %zu",vpsSize,spsSize, ppsSize);}writeFile(vpsspsppsNALBuff, vpsSize+4+spsSize+4+ppsSize+4,encoder->_videoFile, 200);}size_t blockBufferLength;uint8_t *bufferDataPointer = NULL;CMBlockBufferGetDataPointer(block, 0, NULL, &blockBufferLength, (char **)&bufferDataPointer);size_t bufferOffset = 0;while (bufferOffset < blockBufferLength - startCodeLength) {uint32_t NALUnitLength = 0;memcpy(&NALUnitLength, bufferDataPointer+bufferOffset, startCodeLength);NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);memcpy(bufferDataPointer+bufferOffset, startCode, startCodeLength);bufferOffset += startCodeLength + NALUnitLength;}writeFile(bufferDataPointer, blockBufferLength,encoder->_videoFile, 200); } 復(fù)制代碼

1> 首先在回調(diào)函數(shù)中截取到I幀,從I幀中提取到(h265中新增vps),sps,pps信息并寫入文件 2> 遍歷其他幀將頭信息0000,0001寫入每個(gè)頭信息中,再將該數(shù)據(jù)寫入文件即可

二.碼流數(shù)據(jù)結(jié)構(gòu)介紹

這里我們簡單介紹一下H264,H265碼流信息

  • H264流數(shù)據(jù)是由一系列NAL單元(NAL Unit)組成的。

  • 一個(gè)NALU可能包含:視頻幀,視頻幀也就是視頻片段,具體有I,P,B幀

  • H.264屬性合集-FormatDesc(包含 SPS和PPS)
  • 注意在H265流數(shù)據(jù)中新增vps在最前。

    • H.264屬性合集-FormatDesc(包含 SPS和PPS)

    流數(shù)據(jù)中,屬性集合可能是這樣的:

    經(jīng)過處理之后,在Format Description中則是:

    • NALU header 對于流數(shù)據(jù)來說,一個(gè)NALU的Header中,可能是0x00 00 01或者是0x00 00 00 01作為開頭(兩者都有可能,下面以0x00 00 01作為例子)。0x00 00 01因此被稱為開始碼(Start code).所以我們需要在提取的數(shù)據(jù)中用0x00 00 00 01對數(shù)據(jù)內(nèi)容進(jìn)行替換

    總結(jié)以上知識,我們知道H264的碼流由NALU單元組成,NALU單元包含視頻圖像數(shù)據(jù)和H264的參數(shù)信息。其中視頻圖像數(shù)據(jù)就是CMBlockBuffer,而H264的參數(shù)信息則可以組合成FormatDesc。具體來說參數(shù)信息包含SPS(Sequence Parameter Set)和PPS(Picture Parameter Set).如下圖顯示了一個(gè)H.264碼流結(jié)構(gòu):

    • 提取sps和pps生成FormatDesc

      • 每個(gè)NALU的開始碼是0x00 00 01,按照開始碼定位NALU
      • 通過類型信息找到sps和pps并提取,開始碼后第一個(gè)byte的后5位,7代表sps,8代表pps
      • 使用CMVideoFormatDescriptionCreateFromH264ParameterSets函數(shù)來構(gòu)建CMVideoFormatDescriptionRef
    • 提取視頻圖像數(shù)據(jù)生成CMBlockBuffer

      • 通過開始碼,定位到NALU
      • 確定類型為數(shù)據(jù)后,將開始碼替換成NALU的長度信息(4 Bytes)
      • 使用CMBlockBufferCreateWithMemoryBlock接口構(gòu)造CMBlockBufferRef
    • 根據(jù)需要,生成CMTime信息。(實(shí)際測試時(shí),加入time信息后,有不穩(wěn)定的圖像,不加入time信息反而沒有,需要進(jìn)一步研究,這里建議不加入time信息)

    根據(jù)上述得到CMVideoFormatDescriptionRef、CMBlockBufferRef和可選的時(shí)間信息,使用CMSampleBufferCreate接口得到CMSampleBuffer數(shù)據(jù)這個(gè)待解碼的原始的數(shù)據(jù)。如下圖所示的H264數(shù)據(jù)轉(zhuǎn)換示意圖。

    編碼器知識可參考:H264,H265編碼器介紹

    總結(jié)

    以上是生活随笔為你收集整理的iOS H264,H265视频编码(Video encode)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。