前言
IOS 8.0系統(tǒng)之后,蘋果提供了VideoToolbox框架看政,它可以將攝像頭采集的原始視頻數據編碼為指定的格式朴恳,如常見的h264/h265。攝像頭采集的原始視頻數據是很大的允蚣,以YUV顏色空間為例于颖,1280x720p 30fps分辨率的視頻,1秒的大小 = 1280x720x1.5x30 = 41.472mbps嚷兔,所以原始視頻數據不利于存儲和在網絡上進行傳輸森渐,一般在采集到原始視頻數據后都會進行一次有損壓縮,然后進行存儲或者傳輸冒晰。本文將記錄如何采集視頻然后編碼為h264碼流
h264碼流格式
1同衣、H264裸碼流是由一個接一個的NALU(Nal Unit)組成的,NALU = 開始碼 + NALU類型 + 視頻數據翩剪,h264裸碼流文件ffplay播放命令:
ffplay -f h264 test.h264
2乳怎、開始碼:必須是"00 00 00 01" 或"00 00 01"
3、NALU類型:
類型 | 說明 |
---|---|
0 | 未規(guī)定 |
1 | 非IDR圖像中不采用數據劃分的片段(P幀/B幀) |
2 | 非IDR圖像中A類數據劃分片段 |
3 | 非IDR圖像中B類數據劃分片段 |
4 | 非IDR圖像中C類數據劃分片段 |
5 | IDR圖像的片段(I幀/Idr幀) |
6 | 補充增強信息(SEI) |
7 | 序列參數集(SPS) |
8 | 圖像參數集(PPS) |
9 | 分割符 |
10 | 序列結束符 |
1 1 | 流結束符 |
1 2 | 填充數據 |
1 3 | 序列參數集擴展 |
14 | 帶前綴的NAL單元 |
15 | 子序列參數集 |
16 -18 | 保留 |
19 | 不采用數據劃分的輔助編碼圖像片段 |
20 | 編碼片段擴展 |
21-23 | 保留 |
24-31 | 未規(guī)定 |
一般只用到1前弯、5蚪缀、7、8這4個類型,類型為5表示這是一個I幀恕出,I幀前面必須有SPS和PPS數據询枚,也就是類型為7和8,類型為1表示這是一個P幀或B幀浙巫。
h264原始碼流一般按照如下順序:NALU(SPS)+NALU(PPS)+NALU(Idr幀)+NALU(P幀)+NALU(P/B幀)+..+NALU(SPS)+NALU(PPS)+NALU(I幀)+.....
tips:
h264編碼只支持yuv顏色空間男应;YUV顏色空間與RGB顏色空間表示視頻的區(qū)別就是,同等分辨率前者占用空間少一半弓乙。
視頻采集相關代碼
視頻采集使用AVFoundation框架完成足淆,如下圖所示
有如下幾個很重要的對象
1、AVCaptureSession:
管理視頻輸入輸出的會話(輸入:攝像頭丧裁;輸出:輸送數據給app端)
2、AVCaptureDevice:
代表了一個具體的物理設備二庵,比如攝像頭(前置/后置),揚聲器等等杭隙;備注:模擬器無法運行攝像頭相關代碼
3因妙、AVCaptureDeviceInput:
代表具體的視頻輸入兰迫,它要由具體的物理設備創(chuàng)建
4汁果、AVCaptureVideoDataOutput:
它是AVCaptureOutput(它是一個抽象類)的子類,用于輸出原始視頻數據
5鳄乏、AVCaptureConnection:
代表了AVCaptureInputPort和AVCaptureOutput橱野、AVCaptureVideoPreviewLayer之間的連接通道水援,通過它可以將視頻數據輸送給AVCaptureVideoPreviewLayer進行顯示蜗元,設置輸出視頻的輸出視頻的方向系冗,鏡像等等掌敬。
6、AVCaptureVideoPreviewLayer:
是一個可以顯示攝像頭內容的CAlayer的子類
具體采集相關代碼如下:
1楷兽、初始化AVCaptureSession
self.mCaptureSession = [[AVCaptureSession alloc] init];
self.mCaptureSession.sessionPreset = AVCaptureSessionPreset640x480; // 配置輸出圖像的分辨率
_width = 640;
_height = 480;
sessionPreset屬性用來配置最終輸出的原始視頻的分辨率
2芯杀、創(chuàng)建視頻輸入對象瘪匿,并添加到AVCaptureSession中
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInWideAngleCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionFront];
// 根據物理設備創(chuàng)建輸入對象
self.mCaptureInput = [[AVCaptureDeviceInput alloc] initWithDevice:videoDevice error:nil];
if ([self.mCaptureSession canAddInput:self.mCaptureInput]) {
[self.mCaptureSession addInput:self.mCaptureInput];
}
AVCaptureDevicePositionFront代表前置攝像頭
3、創(chuàng)建視頻輸出對象诚欠,設置輸出代理轰绵,并添加到AVCaptureSession中
self.mVideoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
// 當回調因為耗時操作還在進行時左腔,系統(tǒng)對新的一幀圖像的處理方式,如果設置為YES振亮,則立馬丟棄該幀坊秸。
// NO褒搔,則緩存起來(如果累積的幀過多喷面,緩存的內存將持續(xù)增長)乖酬;該值默認為YES
self.mVideoDataOutput.alwaysDiscardsLateVideoFrames = NO;
/** 設置采集的視頻數據幀的格式咬像。這里代表生成的圖像數據為YUV數據,顏色范圍是full-range的
* 并且是bi-planner存儲方式(也就是Y數據占用一個內存塊;UV數據占用另外一個內存塊)
*/
[self.mVideoDataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[self.mVideoDataOutput setSampleBufferDelegate:self queue:captureQueue];
if ([self.mCaptureSession canAddOutput:self.mVideoDataOutput]) {
[self.mCaptureSession addOutput:self.mVideoDataOutput];
}
由于使用h264方式編碼肮柜,所以這里必須設置為yuv顏色空間
4审洞、配置采集的視頻數據通過AVCaptureVideoPreviewLayer渲染出來(非必須)
AVCaptureConnection *connection = [self.mVideoDataOutput connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:AVCaptureVideoOrientationPortrait];
/** AVCaptureVideoPreviewLayer是一個可以顯示攝像頭內容的CAlayer的子類
* 以下代碼直接將攝像頭的內容渲染到AVCaptureVideoPreviewLayer上面
*/
self.mVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.mCaptureSession];
[self.mVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspect];
[self.mVideoPreviewLayer setFrame:self.view.bounds];
[self.view.layer addSublayer:self.mVideoPreviewLayer];
此步驟對于視頻采集來說也是很重要的仰剿,因為可以實時看到自己想要采集的具體內容
5南吮、開始采集
- (void)startRunCapSession
{
if (!self.mCaptureSession.isRunning) {
[self.mCaptureSession startRunning];
}
}
6部凑、通過代理獲取到原始的視頻數據
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
NSLog(@"采集到的數據 ==>%@",[NSThread currentThread]);
/** CVImageBufferRef 表示原始視頻數據的對象涂邀;
* 包含未壓縮的像素數據比勉,包括圖像寬度敷搪、高度等幢哨;
* 等同于CVPixelBufferRef
*/
// 獲取CMSampleBufferRef中具體的視頻數據
CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
/** 執(zhí)行編碼
* 參數1:已經創(chuàng)建并且準備好的VTCompressionSessionRef對象
* 參數2:具體的視頻原始數據;CVImageBufferRef類型
* 參數3:視頻數據開始編碼的時間;CMTime類型捞镰,一般是CMTimeMake(幀序號, 壓縮單位(比如1000));
* 參數4:該幀視頻的時長岸售,一般不需要計算(因為沒法算)凸丸,傳kCMTimeInvalid即可
* 參數5:要編碼的視頻相關屬性;CFDictionaryRef類型
* 參數6:傳遞給編碼輸出回調的參數;void* 類型
* 參數7:編碼結果標記瞭稼;通過回調函數獲取
*/
// 幀序號時間环肘,用于表示幀開始編碼的時間(備注:這個時間是相對時間悔雹,并不是真正時間)
CMTime presentationTime = CMTimeMake(_frameId++, 1000);
VTEncodeInfoFlags encodeflags;
OSStatus status = VTCompressionSessionEncodeFrame(_encodeSession, imageBuffer, presentationTime, kCMTimeInvalid, NULL, NULL, &encodeflags);
if (status != noErr) {
NSLog(@"VTCompressionSessionEncodeFrame fail %d",status);
// 釋放資源
VTCompressionSessionInvalidate(_encodeSession);
CFRelease(_encodeSession);
_encodeSession = NULL;
}
}
采集到的原始視頻數據將通過該回調函數傳回腌零,原始視頻數據存放在CMSampleBufferRef類型對象中益涧。
CMSampleBufferRef:
1饰躲、包含音視頻描述信息嘹裂,比如包含音頻的格式描述 AudioStreamBasicStreamDescription寄狼、包含視頻的格式描述 CMVideoFormatDescriptionRef
2泊愧、包含音視頻數據删咱,可以是原始數據也可以是壓縮數據;通過CMSampleBufferGetxxx()系列函數提取
CVImageBufferRef:
表示原始視頻數據的對象痰滋;包含未壓縮的像素數據续崖,包括圖像寬度严望、高度等像吻;
等同于CVPixelBufferRef
編碼相關代碼
在進行編碼之前得先初始化編碼器,設置編碼參數等等準備工作奸披,具體使用流程如下:
1阵面、初始化編碼器
OSStatus status = VTCompressionSessionCreate(NULL, _width, _height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)self, &_encodeSession);
if (status != noErr) {
NSLog(@"VTCompressionSessionCreate fail %d",status);
return;
}
/** 創(chuàng)建編碼器對象 VTCompressionSessionRef
VTCompressionSessionCreate(...)
* 參數1:創(chuàng)建對象內存使用的內存分配器样刷,NULL代表使用默認分配器kCFAllocatorDefault
* 參數2/3:要編碼的視頻幀的寬和高置鼻;單位像素
* 參數4:使用的編碼方式 比如H264(kCMVideoCodecType_H264)
* 參數5:設置編碼方式相關的參數箕母,比如H264編碼所需的參數;CFDictionaryRef類型钙勃,NULL辖源,則默認值;也可以
* 通過VTSessionSetProperty()函數設置
* 參數6:設置原始視頻數據緩存的方式克饶,CFDictionaryRef類型矾湃,NULL則代表使用默認值
* 參數7:設置編碼數據的內存分配器及其它保存方式洲尊,CFAllocatorRef類型坞嘀,NULL則使用默認值
* 參數8:設置編碼數據輸出回調函數
* 參數9:設置傳入給該回調函數的參數惊来;void類型
* 參數10:要創(chuàng)建的VTCompressionSessionRef對象
/
/ 遇到問題:返回-12902錯誤
* 分析問題:在VTErrors.h中查看錯誤說明裁蚁,意思參數錯誤枉证,經檢查是_width和_height沒有指定具體的值
* 解決問題:給_width和_height賦上具體的值
*/
在創(chuàng)建編碼器時一定要指定要編碼的原始視頻的寬和高室谚,否則會返回錯誤。
2猪瞬、設置編碼器參數
通過VTSessionSetProperty()接口設置編碼器相關參數陈瘦,比如編碼效率級別痊项,GOP,平均碼率遏弱,幀率,碼率上限值等等
/** VTSessionSetProperty()函數既可以設置編碼相關屬性游沿,又可以設置解碼相關屬性
* 對于H264編碼來說诀黍,以下屬性是必須的
* 1眯勾、編碼效率級別:kVTCompressionPropertyKey_ProfileLevel
* kVTProfileLevel_H264_Baseline_AutoLevel
* 2吃环、GOP(關鍵幀間隔):
* kVTCompressionPropertyKey_MaxKeyFrameInterval
* 3洋幻、編碼后的幀率:
* kVTCompressionPropertyKey_ExpectedFrameRate文留;
* 改變該值可以加快視頻速度或者減慢視頻速度
* 4燥翅、編碼后的平均碼率:
* kVTCompressionPropertyKey_AverageBitRate
* 平均碼率決定了壓縮的程度
* 5森书、編碼后的碼率上限:
* kVTCompressionPropertyKey_DataRateLimits
*/
// 設置實時編碼輸出(避免延遲)
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
/** 設置H264編碼的壓縮級別
* BP(Baseline Profile):基本畫質谎势。支持I/P 幀它浅,只支持無交錯(Progressive)和CAVLC姐霍;主要應用:可視電話镊折,會議
* 電視介衔,和無線通訊等實時視頻通訊領域
* EP(Extended profile):進階畫質。支持I/P/B/SP/SI 幀赃泡,只支持無交錯(Progressive)和CAVLC升熊;
* MP(Main profile):主流畫質级野。提供I/P/B 幀蓖柔,支持無交錯(Progressive)和交錯(Interlaced)况鸣,也支持CAVLC 和CABAC 的支持懒闷;主要應用:數字廣播電視和數字視頻存儲
* HP(High profile):高級畫質栈幸。在main Profile 的基礎上增加了8×8內部預測速址、自定義量化芍锚、 無損視頻編碼和更多的YUV 格式蔓榄;
* 應用于廣電和存儲領域
* iPhone上方案如下:
* 實時直播:
* 低清Baseline Level 1.3
* 標清Baseline Level 3
* 半高清Baseline Level 3.1
* 全高清Baseline Level 4.1
* 存儲媒體:
* 低清 Main Level 1.3
* 標清 Main Level 3
* 半高清 Main Level 3.1
* 全高清 Main Level 4.1
* 高清存儲:
* 半高清 High Level 3.1
* 全高清 High Level 4.1
*
* 參考文章:https://blog.csdn.net/sphone89/article/details/17492433
*/
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
// 設置是否開啟B幀編碼;默認開啟甥郑,注意只有EP澜搅,MP勉躺,HP級別才支持B幀饵溅,如果是BP級別蜕企,該設置無效轻掩。
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanTrue);
/** 設置關鍵幀GOP間隔
* 1轩端、碼率不變的前提下基茵,GOP值越大P拱层、B幀的數量會越多根灯,平均每個I烙肺、P氧卧、B幀所占用的字節(jié)數就越多沙绝,也就更容易獲取較好的圖像質量鼠锈;B幀的數量越多购笆,同
* 理也更容易獲得較好的圖像質量;
* 2同欠、需要說明的是行您,通過提高GOP值來提高圖像質量是有限度的娃循,在遇到場景切換的情況時捌斧,H.264編碼器會自動強制插入一個I幀捞蚂,此時實際的GOP值被縮短了姓迅。
* 另一方面丁存,在一個GOP中解寝,P聋伦、B幀是由I幀預測得到的界睁,當I幀的圖像質量比較差時抑片,會影響到一個GOP中后續(xù)P杨赤、B幀的圖像質量,直到下一個GOP開始才有
* 可能得以恢復衙解,所以GOP值也不宜設置過大焰枢。
* 3济锄、同時荐绝,由于P低滩、B幀的復雜度大于I幀恕沫,所以過多的P婶溯、B幀會影響編碼效率迄委,使編碼效率降低跑筝。另外曲梗,過長的GOP還會影響Seek操作的響應速度,由于P妓忍、B幀
* 是由前面的I或P幀預測得到的虏两,所以Seek操作需要直接定位,解碼某一個P或B幀時世剖,需要先解碼得到本GOP內的I幀及之前的N個預測幀才可以定罢,GOP值越長
* 需要解碼的預測幀就越多,seek響應的時間也越長旁瘫。
*/
int iFrameInternal = 10;
CFNumberRef iFrameRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &iFrameInternal);
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, iFrameRef);
// 設置期望幀率
int fps = 25;
CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps);
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);
/** 設置均值碼率祖凫,單位是bps琼蚯,它不是一個硬性指標,實際的碼率可能會上下浮動;VideoToolBox框架只支持ABR模式遭庶,而對于H264來說权埠,它有四種
* 碼率控制模式,如下:
* CBR:恒定比特率方式進行編碼叔扼,Motion發(fā)生時与柑,由于碼率恒定,只能通過增大QP來減少碼字大小,圖像質量變差嵌屎,當場景靜止時尼夺,圖像質量又變好
* 因此圖像質量不穩(wěn)定。這種算法優(yōu)先考慮碼率(帶寬)竞端。
* VBR:動態(tài)比特率统台,其碼率可以隨著圖像的復雜程度的不同而變化谤逼,因此其編碼效率比較高,Motion發(fā)生時,馬賽克很少球切。碼率控制算法根據圖像
* 內容確定使用的比特率,圖像內容比較簡單則分配較少的碼率(似乎碼字更合適),圖像內容復雜則分配較多的碼字焚鹊,這樣既保證了質量,又
* 兼顧帶寬限制嚷炉。這種算法優(yōu)先考慮圖像質量哗讥。
* CVBR:它是VBR的一種改進方法這種算法對應的Maximum bitRate恒定或者Average BitRate恒定决乎。這種方法的兼顧了以上兩種方法的優(yōu)點,
* 在圖像內容靜止時唤反,節(jié)省帶寬逆趋,有Motion發(fā)生時魄眉,利用前期節(jié)省的帶寬來盡可能的提高圖像質量,達到同時兼顧帶寬和圖像質量的目的
* ABR:在一定的時間范圍內達到設定的碼率,但是局部碼率峰值可以超過設定的碼率,平均碼率恒定告材〔涌剩可以作為VBR和CBR的一種折中選擇。
*
* H264各個分辨率推薦的碼率表:http://www.lighterra.com/papers/videoencodingh264/
*/
SInt32 avgbitRate = 0.96*1000000; // 注意單位是bit/s 這里是640x480的 為0.96Mbps
CFNumberRef avgRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &avgbitRate);
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_AverageBitRate, avgRateLimitRef);
/** 遇到問題:編碼的視頻馬賽克嚴重
* 原因分析:沒有正確的設置碼率上限值
* 解決思路:正確設置碼率上限
*
* 備注:碼率上限一個數組扫茅,按照@[比特數,時長.....]方式傳值排列,至少一對 比特數,時長怀喉;如果有多個,這些值必須平滑荒辕,內部會有一個算法算出最終值
* 均值碼率過低,也會造成馬賽克
*/
// 設置碼率上限
int bitRateLimits = avgbitRate; // 一秒鐘的最大碼率
NSArray *limit = @[@(bitRateLimits * 1.5), @(1)];
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);
3茧跋、準備編碼相關上下文
status = VTCompressionSessionPrepareToEncodeFrames(_encodeSession);
if (status == noErr) {
NSLog(@"CompressionSession 初始化成功 可以開始解碼了");
}
這里有一個地方要注意下蝇棉,如果沒有設置碼率上限值或者碼率上限值設置方式不對,平均碼率過小都會引起編碼出現馬賽克瓶您,請看上面具體的注釋
4乡革、開始編碼
開始編碼應該在采集的回調函數中,也就是采集相關代碼的最后一部中的代碼
// 幀序號時間岛啸,用于表示幀開始編碼的時間(備注:這個時間是相對時間,并不是真正時間)
CMTime presentationTime = CMTimeMake(_frameId++, 1000);
VTEncodeInfoFlags encodeflags;
OSStatus status = VTCompressionSessionEncodeFrame(_encodeSession, imageBuffer, presentationTime, kCMTimeInvalid, NULL, NULL, &encodeflags);
if (status != noErr) {
NSLog(@"VTCompressionSessionEncodeFrame fail %d",status);
// 釋放資源
VTCompressionSessionInvalidate(_encodeSession);
CFRelease(_encodeSession);
_encodeSession = NULL;
}
組裝為h264碼流
調用VTCompressionSessionEncodeFrame()函數后,系統(tǒng)內部會進行編碼从诲,編碼結果通過第一步創(chuàng)建的回調函數返回
編碼的NALU數據格式為:NALU長度(四字節(jié))+編碼類型+編碼數據定页,h264碼流的NALU數據格式為:起始碼+編碼類型+編碼數據,所以要先轉換一下在保存钦购,具體代碼如下
void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
{
NSLog(@"didCompressH264 called with status %d infoFlags %d", (int)status, (int)infoFlags);
if (status != noErr) {
NSLog(@"compress fail %d",status);
return;
}
// 返回該sampleBuffer是否可以進行操作了
if (!CMSampleBufferDataIsReady(sampleBuffer)) {
NSLog(@"CMSampleBufferDataIsReady is not ready");
return;
}
VideoEnDecodeViewController *mySelf = (__bridge VideoEnDecodeViewController*)outputCallbackRefCon;
// CMSampleBufferGetSampleAttachmentsArray獲取視頻幀的描述信息节猿,比如是否關鍵幀等等;kCMSampleAttachmentKey_NotSync標記是否關鍵幀
BOOL keyframe = CFDictionaryContainsKey(CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES), 0), kCMSampleAttachmentKey_NotSync);
if (keyframe) {
/** CMFormatDescriptionRef中包含了PPS/SPS/SEI煤墙,寬高劣针、顏色空間、編碼格式等描述信息的結構體契讲,它等同于
* CMVideoFormatDescriptionRef
* SPS在索引0處霹琼;PPS在索引1處
*/
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
size_t SPSSize, SPSCount;
const uint8_t *sps;
OSStatus retStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sps, &SPSSize, &SPSCount, 0);
if (retStatus == noErr) {
size_t PPSSize, PPSCount;
const uint8_t *pps;
retStatus = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pps, &PPSSize, &PPSCount, 0);
if (retStatus == noErr) {
NSData *spsData = [NSData dataWithBytes:sps length:SPSSize];
NSData *ppsData = [NSData dataWithBytes:pps length:PPSSize];
// 保存sps和pps
[mySelf saveSPS:spsData pps:ppsData];
}
}
}
// CMBlockBufferRef表示一個內存塊,用來存放編碼后的音頻/視頻數據
CMBlockBufferRef dataBlockRef = CMSampleBufferGetDataBuffer(sampleBuffer);
size_t lenght,totalLenght;
char *dataptr;
// 獲取指向內存塊數據的指針
OSStatus status1 = CMBlockBufferGetDataPointer(dataBlockRef, 0, &lenght, &totalLenght, &dataptr);
if (status1 == noErr) {
size_t bufferOffset = 0;
static const int AACStartCodeLenght = 4;
/** 一次編碼可能會包含多個nalu
* 所以要循環(huán)獲取所有的nalu數據挟伙,并解析出來
* 每個NALU的格式為
* 四字節(jié)(NALU總長度)+視頻數據(NALU總長度-4)
* 和正規(guī)的h264的nalu封裝格式0001開頭的有點不一樣
*/
while (bufferOffset < totalLenght - AACStartCodeLenght) {
uint32_t naluUnitLenght = 0;
// 讀取該NALU的數據總長度,該NALU就是一幀完整的編碼的視頻
memcpy(&naluUnitLenght, dataptr+bufferOffset, AACStartCodeLenght);
// 返回的nalu數據前四個字節(jié)不是0001的startcode永淌,而是大端模式的幀長度length
// 從大端轉系統(tǒng)端(必須,否則會造成長度錯誤問題)
naluUnitLenght = CFSwapInt32BigToHost(naluUnitLenght);
// 將真正的編碼后的視頻幀提取出來
NSData *data = [[NSData alloc] initWithBytes:(dataptr + bufferOffset + AACStartCodeLenght) length:naluUnitLenght];
// 然后添加0001開頭碼組成正規(guī)的h264封裝格式
[mySelf saveEncodedData:data isKeyFrame:keyframe];
// 循環(huán)讀取
bufferOffset += AACStartCodeLenght + naluUnitLenght;
}
}
}
備注:
1声诸、編碼的數據都存儲在CMSampleBufferRef對象變量sampleBuffer中,要注意一次編碼可能會包含多個nalu
2退盯、h264碼流文件存儲順序要注意下彼乌,一定要按照sps pps I幀 p幀 p幀/b幀....sps pps I幀 p幀 p幀/b幀....的順序,否則會導致無法播放
導出保存的h264文件渊迁,使用ffplay命令播放
由于只能使用手機進行視頻采集慰照,所以需要將保存在真機中的文件導出來,具體方法為:
然后使用ffplay 播放琉朽,命令如下
ffplay -f h264 /Users/feipai1/Desktop/qwe.media\ 2019-07-28\ 14:14.36.613.xcappdata/AppData/Documents/abc.h264
遇到問題
1毒租、創(chuàng)建編碼器時返回-12902錯誤;主要是因為寬高的參數沒有設置箱叁,正確設置即可
2墅垮、編碼后的視頻出現馬賽克惕医;因為碼率上限值設置不正確導致,正確設置方式為算色,kVTCompressionPropertyKey_DataRateLimits必須對應一個數組
int bitRateLimits = avgbitRate; // 一秒鐘的最大碼率
NSArray *limit = @[@(bitRateLimits * 1.5), @(1)];
VTSessionSetProperty(_encodeSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);
項目地址
參考VideoEnDecodeViewController.h/.m文件中代碼
Demo
參考文章
http://www.enkichen.com/2017/11/26/image-h264-encode/
http://www.enkichen.com/2018/03/24/videotoolbox/
https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html#//apple_ref/doc/uid/TP40010188-CH5-SW2