編碼方式
-
在iOS中編碼方式有兩種
- 硬編碼: 在
iOS8.0
之后,使用原生框架VideoToolBox
&AudioToolbox
對視屏和音頻進行硬編碼. - 軟編碼: 使用CPU進行編碼,通常使用的框架為
ffmpeg
+x264
.-
ffmpeg
:是一套開源的框架, 用于對音視頻進行編碼&解碼&轉化計算機程序 -
x264
:x264
是一種免費的、開源的、具有更優秀算法的H.264
/MPEG-4
AVC
視頻壓縮編碼方式.
-
- 硬編碼: 在
-
編碼方式對比:
- 硬編碼性能高于軟編碼,對
CPU
無要求和壓力,對硬件要求較高(如GPU
) - 軟編碼對
CPU
的負載較高,容易造成手機發熱,但其實現簡單,編碼自由度高(可以自由調整想要的參數)
- 硬編碼性能高于軟編碼,對
硬編碼
編碼流程:采集視屏信息--> 獲取到視頻幀--> 對視頻幀進行編碼 --> 獲取到視頻幀信息 --> 將編碼后的數據以NALU方式寫入到文件
-
采集視屏信息
- (void)startCapture:(UIView *)preview{ // 1.創建捕捉會話 AVCaptureSession *session = [[AVCaptureSession alloc] init]; session.sessionPreset = AVCaptureSessionPreset1280x720; self.captureSession = session; // 2.設置輸入設備 AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; NSError *error = nil; AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error]; [session addInput:input]; // 3.添加輸出設備 AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init]; self.captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); [output setSampleBufferDelegate:self queue:self.captureQueue]; [session addOutput:output]; // 設置錄制視頻的方向 AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo]; [connection setVideoOrientation:AVCaptureVideoOrientationPortrait]; // 4.添加預覽圖層 AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session]; previewLayer.frame = preview.bounds; [preview.layer insertSublayer:previewLayer atIndex:0]; self.previewLayer = previewLayer; // 5.開始捕捉 [self.captureSession startRunning]; }
-
獲取到視屏幀
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { // 對sampleBuffer進行硬編碼處理 [self.encoder encodeSampleBuffer:sampleBuffer]; }
-
對視屏幀進行硬編碼
(1). 初始化寫文件對象,用于保存編碼完的視屏信息- (void)setupFileHandle { // 1.獲取沙盒路徑 NSString *file = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"video.h264"]; // 2.如果原來有文件,則刪除 [[NSFileManager defaultManager] removeItemAtPath:file error:nil]; [[NSFileManager defaultManager] createFileAtPath:file contents:nil attributes:nil]; // 3.創建對象 self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:file]; }
(2). 初始化編碼會話
通過
VTSessionSetProperty
設置對象屬性:- 編碼方式:H.264編碼
- 幀率:每秒鐘多少幀畫面
- 碼率:單位時間內保存的數據量
- 關鍵幀(GOPsize)間隔:多少幀為一個GOP
- (void)setupVideoSession { // 1.用于記錄當前是第幾幀數據(畫面幀數非常多) self.frameID = 0; // 2.錄制視頻的寬度&高度 int width = [UIScreen mainScreen].bounds.size.width; int height = [UIScreen mainScreen].bounds.size.height; // 3.創建CompressionSession對象,該對象用于對畫面進行編碼 // kCMVideoCodecType_H264 : 表示使用h.264進行編碼 // didCompressH264 : 當一次編碼結束會在該函數進行回調,可以在該函數中將數據,寫入文件中 VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &_compressionSession); // 4.設置實時編碼輸出(直播必然是實時輸出,否則會有延遲) VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); // 5.設置期望幀率(每秒多少幀,如果幀率過低,會造成畫面卡頓) int fps = 30; CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps); VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef); // 6.設置碼率(碼率: 編碼效率, 碼率越高,則畫面越清晰, 如果碼率較低會引起馬賽克 --> 碼率高有利于還原原始畫面,但是也不利于傳輸) int bitRate = 800*1024; CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate); VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef); NSArray *limit = @[@(bitRate * 1.5/8), @(1)]; VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit); // 7.設置關鍵幀(GOPsize)間隔 int frameInterval = 30; CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval); VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef); // 8.基本設置結束, 準備進行編碼 VTCompressionSessionPrepareToEncodeFrames(self.compressionSession); }
(3). 開始編碼
- (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer { // 1.將sampleBuffer轉成imageBuffer CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer); // 2.根據當前的幀數,創建CMTime的時間 CMTime presentationTimeStamp = CMTimeMake(self.frameID++, 1000); VTEncodeInfoFlags flags; // 3.開始編碼該幀數據 OSStatus statusCode = VTCompressionSessionEncodeFrame(self.compressionSession, imageBuffer, presentationTimeStamp, kCMTimeInvalid, NULL, (__bridge void * _Nullable)(self), &flags); if (statusCode == noErr) { NSLog(@"H264: VTCompressionSessionEncodeFrame Success"); } }
(4). 編碼回調,對視屏做真正的編碼處理
- 編碼成功后會回調之前輸入的函數
didCompressH264
.
- 1> 先判斷是否是關鍵幀:
如果是關鍵幀,則需要在寫入關鍵幀之前,先寫入PPS、SPS的NALU
取出PPS、SPS數據,并且封裝成NALU單元寫入文件- 2> 將I幀、P幀、B幀分別封裝成NALU單元寫入文件
-
寫入后,數據存儲方式:
image8.png
// 編碼完成回調 void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) { // 1.判斷狀態是否等于沒有錯誤 if (status != noErr) { return; } // 2.根據傳入的參數獲取對象 VideoEncode* encoder = (__bridge VideoEncode*)outputCallbackRefCon; // 3.判斷是否是關鍵幀 bool isKeyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync); // 判斷當前幀是否為關鍵幀 // 獲取sps & pps數據 if (isKeyframe) { // 獲取編碼后的信息(存儲于CMFormatDescriptionRef中) CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer); // 獲取SPS信息 size_t sparameterSetSize, sparameterSetCount; const uint8_t *sparameterSet; CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0 ); // 獲取PPS信息 size_t pparameterSetSize, pparameterSetCount; const uint8_t *pparameterSet; CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 ); // 裝sps/pps轉成NSData,以方便寫入文件 NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize]; NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize]; // 寫入文件 [encoder gotSpsPps:sps pps:pps]; } // 獲取數據塊 CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); size_t length, totalLength; char *dataPointer; OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer); if (statusCodeRet == noErr) { size_t bufferOffset = 0; static const int AVCCHeaderLength = 4; // 返回的nalu數據前四個字節不是0001的startcode,而是大端模式的幀長度length // 循環獲取nalu數據 while (bufferOffset < totalLength - AVCCHeaderLength) { uint32_t NALUnitLength = 0; // Read the NAL unit length memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength); // 從大端轉系統端 NALUnitLength = CFSwapInt32BigToHost(NALUnitLength); NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength]; [encoder gotEncodedData:data isKeyFrame:isKeyframe]; // 移動到寫一個塊,轉成NALU單元 // Move to the next NAL unit in the block buffer bufferOffset += AVCCHeaderLength + NALUnitLength; } } }
(5). 碼流存儲
// sps和pps存儲 - (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps{ // 1.拼接NALU的header const char bytes[] = "\x00\x00\x00\x01"; size_t length = (sizeof bytes) - 1; NSData *ByteHeader = [NSData dataWithBytes:bytes length:length]; // 2.將NALU的頭&NALU的體寫入文件 [self.fileHandle writeData:ByteHeader]; [self.fileHandle writeData:sps]; [self.fileHandle writeData:ByteHeader]; [self.fileHandle writeData:pps]; }
// 其他信息存儲 - (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame{ NSLog(@"gotEncodedData %d", (int)[data length]); if (self.fileHandle != NULL){ const char bytes[] = "\x00\x00\x00\x01"; size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0' NSData *ByteHeader = [NSData dataWithBytes:bytes length:length]; [self.fileHandle writeData:ByteHeader]; [self.fileHandle writeData:data]; } }
-
結束編碼
- (void)endEncode{ VTCompressionSessionCompleteFrames(self.compressionSession, kCMTimeInvalid); VTCompressionSessionInvalidate(self.compressionSession); CFRelease(self.compressionSession); self.compressionSession = NULL; }
軟編碼
安裝 FFmpeg
- 安裝
$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"
$ brew install ffmpeg
- 使用
- 轉化格式:
$ ffmpeg -i xxx.webm xxx.mp4
- 分離視頻:
$ ffmpeg -i xxx.mp4 -vcodec copy -an xxx.mp4
- 分離音頻:
$ ffmpeg -i xxx.mp4 -acodec copy -vn xxx.aac
- 轉化格式:
編譯FFmpeg-iOS
- 下載編譯
FFmpeg
所需要的腳本文件gas-preprocessor.pl
- 下載地址(新版本):https://github.com/libav/gas-preprocessor
- 復制
gas-preprocessor.pl
到/usr/local/bin
下 - 修改文件權限:
$ chmod 777 /usr/local/bin/gas-preprocessor.pl
如果
/usr/local
沒有權限Operation not permitted
按如下操作:
1、關閉 Rootless
- 重啟MAC電腦
- 開機時(聽到開機啟動聲音),按下
Command+R
或Command+Option+R
, 進入恢復模式- 在上面的菜單實用工具中找到并打開
Terminal
終端, 輸入如下命令$ csrutil disable
- 重啟電腦.
2、給
/usr/local
增加讀寫權限
- 進入終端輸入
$ sudo chown -R $(whoami) /usr/local
3、開啟 Rootless
- 為了系統安全,重新開啟Rootless
- 重復步驟1,開啟Rootless:
$ csrutil enable
-
下載腳本
FFmpeg-iOS
腳本- 下載地址:https://github.com/kewlbear/FFmpeg-iOS-build-script
- 執行腳本文件:
$ ./build-ffmpeg.sh
編譯過程中有可能需要安裝
yasm
和nasm
,如果需要,直接安裝即可
$ brew install yasm
、$ brew install nasm
編譯X264
- 下載
x264
: http://www.videolan.org/developers/x264.html - 下載
x264 build shell
: https://github.com/kewlbear/x264-ios- 將
build-x264.sh
放在和x264
同一級目錄下
- 將
- 執行腳本:
$ sudo chmod u+x build-x264.sh
$ sudo ./build-x264.sh
集成FFmpeg
將執行腳本
./build-ffmpeg.sh
和./build-x264.sh
生成的文件FFmpeg-iOS
和x264-iOS
拖入到工程中。添加依賴庫:
VideoToolbox.framework
、CoreMedia.framework
、AVFoundation.framework
、libiconv.tbd
、libbz2.tbd
、libz.tbd
采集視屏信息
- (void)startCapture:(UIView *)preview
{
// 1.獲取沙盒路徑
NSString *file = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"video.h264"];
// 2.開始編碼
[self.encoder setFileSavedPath:file];
// 特別注意: 寬度&高度
[self.encoder setX264ResourceWithVideoWidth:480 height:640 bitrate:1500000];
// 1.創建捕捉會話
AVCaptureSession *session = [[AVCaptureSession alloc] init];
session.sessionPreset = AVCaptureSessionPreset640x480;
self.captureSession = session;
// 2.設置輸入設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
[session addInput:input];
// 3.添加預覽圖層
AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
previewLayer.frame = preview.bounds;
[preview.layer insertSublayer:previewLayer atIndex:0];
self.previewLayer = previewLayer;
// 4.添加輸出設備
AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
self.captureQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[output setSampleBufferDelegate:self queue:self.captureQueue];
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithUnsignedInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange],
kCVPixelBufferPixelFormatTypeKey,
nil];
output.videoSettings = settings;
output.alwaysDiscardsLateVideoFrames = YES;
[session addOutput:output];
// 5.設置錄制視頻的方向
AVCaptureConnection *connection = [output connectionWithMediaType:AVMediaTypeVideo];
[connection setVideoOrientation:previewLayer.connection.videoOrientation];
// 6.開始捕捉
[self.captureSession startRunning];
}
- 獲取到視屏幀
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
[self.encoder encoderToH264:sampleBuffer];
}
-
對視屏幀進行軟編碼
(1). 初始化X264- (int)setX264ResourceWithVideoWidth:(int)width height:(int)height bitrate:(int)bitrate { // 1.默認從第0幀開始(記錄當前的幀數) framecnt = 0; // 2.記錄傳入的寬度&高度 encoder_h264_frame_width = width; encoder_h264_frame_height = height; // 3.注冊FFmpeg所有編解碼器(無論編碼還是解碼都需要該步驟) av_register_all(); // 4.初始化AVFormatContext: 用作之后寫入視頻幀并編碼成 h264,貫穿整個工程當中(釋放資源時需要銷毀) pFormatCtx = avformat_alloc_context(); // 5.設置輸出文件的路徑 fmt = av_guess_format(NULL, out_file, NULL); pFormatCtx->oformat = fmt; // 6.打開文件的緩沖區輸入輸出,flags 標識為 AVIO_FLAG_READ_WRITE ,可讀寫 if (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE) < 0){ printf("Failed to open output file! \n"); return -1; } // 7.創建新的輸出流, 用于寫入文件 video_st = avformat_new_stream(pFormatCtx, 0); // 8.設置 20 幀每秒 ,也就是 fps 為 20 video_st->time_base.num = 1; video_st->time_base.den = 25; if (video_st==NULL){ return -1; } // 9.pCodecCtx 用戶存儲編碼所需的參數格式等等 // 9.1.從媒體流中獲取到編碼結構體,他們是一一對應的關系,一個 AVStream 對應一個 AVCodecContext pCodecCtx = video_st->codec; // 9.2.設置編碼器的編碼格式(是一個id),每一個編碼器都對應著自己的 id,例如 h264 的編碼 id 就是 AV_CODEC_ID_H264 pCodecCtx->codec_id = fmt->video_codec; // 9.3.設置編碼類型為 視頻編碼 pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO; // 9.4.設置像素格式為 yuv 格式 pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; // 9.5.設置視頻的寬高 pCodecCtx->width = encoder_h264_frame_width; pCodecCtx->height = encoder_h264_frame_height; // 9.6.設置幀率 pCodecCtx->time_base.num = 1; pCodecCtx->time_base.den = 15; // 9.7.設置碼率(比特率) pCodecCtx->bit_rate = bitrate; // 9.8.視頻質量度量標準(常見qmin=10, qmax=51) pCodecCtx->qmin = 10; pCodecCtx->qmax = 51; // 9.9.設置圖像組層的大小(GOP-->兩個I幀之間的間隔) pCodecCtx->gop_size = 250; // 9.10.設置 B 幀最大的數量,B幀為視頻圖片空間的前后預測幀, B 幀相對于 I、P 幀來說,壓縮率比較大,也就是說相同碼率的情況下, // 越多 B 幀的視頻,越清晰,現在很多打視頻網站的高清視頻,就是采用多編碼 B 幀去提高清晰度, // 但同時對于編解碼的復雜度比較高,比較消耗性能與時間 pCodecCtx->max_b_frames = 5; // 10.可選設置 AVDictionary *param = 0; // H.264 if(pCodecCtx->codec_id == AV_CODEC_ID_H264) { // 通過--preset的參數調節編碼速度和質量的平衡。 av_dict_set(¶m, "preset", "slow", 0); // 通過--tune的參數值指定片子的類型,是和視覺優化的參數,或有特別的情況。 // zerolatency: 零延遲,用在需要非常低的延遲的情況下,比如視頻直播的編碼 av_dict_set(¶m, "tune", "zerolatency", 0); } // 11.輸出打印信息,內部是通過printf函數輸出(不需要輸出可以注釋掉該局) av_dump_format(pFormatCtx, 0, out_file, 1); // 12.通過 codec_id 找到對應的編碼器 pCodec = avcodec_find_encoder(pCodecCtx->codec_id); if (!pCodec) { printf("Can not find encoder! \n"); return -1; } // 13.打開編碼器,并設置參數 param if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0) { printf("Failed to open encoder! \n"); return -1; } // 13.初始化原始數據對象: AVFrame pFrame = av_frame_alloc(); // 14.通過像素格式(這里為 YUV)獲取圖片的真實大小,例如將 480 * 720 轉換成 int 類型 avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); // 15.h264 封裝格式的文件頭部,基本上每種編碼都有著自己的格式的頭部,想看具體實現的同學可以看看 h264 的具體實現 avformat_write_header(pFormatCtx, NULL); // 16.創建編碼后的數據 AVPacket 結構體來存儲 AVFrame 編碼后生成的數據 av_new_packet(&pkt, picture_size); // 17.設置 yuv 數據中 y 圖的寬高 y_size = pCodecCtx->width * pCodecCtx->height; return 0; }
(2). 對視屏幀進行編碼
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer{ // 1.通過CMSampleBufferRef對象獲取CVPixelBufferRef對象 CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); // 2.鎖定imageBuffer內存地址開始進行編碼 if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess) { // 3.從CVPixelBufferRef讀取YUV的值 // NV12和NV21屬于YUV格式,是一種two-plane模式,即Y和UV分為兩個Plane,但是UV(CbCr)為交錯存儲,而不是分為三個plane // 3.1.獲取Y分量的地址 UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0); // 3.2.獲取UV分量的地址 UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1); // 3.3.根據像素獲取圖片的真實寬度&高度 size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); // 獲取Y分量長度 size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,0); size_t bytesrow1 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,1); UInt8 *yuv420_data = (UInt8 *)malloc(width * height *3/2); // 3.4.將NV12數據轉成YUV420數據 UInt8 *pY = bufferPtr ; UInt8 *pUV = bufferPtr1; UInt8 *pU = yuv420_data + width*height; UInt8 *pV = pU + width*height/4; for(int i =0;i<height;i++) { memcpy(yuv420_data+i*width,pY+i*bytesrow0,width); } for(int j = 0;j<height/2;j++) { for(int i =0;i<width/2;i++) { *(pU++) = pUV[i<<1]; *(pV++) = pUV[(i<<1) + 1]; } pUV+=bytesrow1; } // 3.5.分別讀取YUV的數據 picture_buf = yuv420_data; pFrame->data[0] = picture_buf; // Y pFrame->data[1] = picture_buf+ y_size; // U pFrame->data[2] = picture_buf+ y_size*5/4; // V // 4.設置當前幀 pFrame->pts = framecnt; int got_picture = 0; // 4.設置寬度高度以及YUV各式 pFrame->width = encoder_h264_frame_width; pFrame->height = encoder_h264_frame_height; pFrame->format = AV_PIX_FMT_YUV420P; // 5.對編碼前的原始數據(AVFormat)利用編碼器進行編碼,將 pFrame 編碼后的數據傳入pkt 中 int ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture); if(ret < 0) { printf("Failed to encode! \n"); } // 6.編碼成功后寫入 AVPacket 到 輸入輸出數據操作著 pFormatCtx 中,當然,記得釋放內存 if (got_picture==1) { framecnt++; pkt.stream_index = video_st->index; ret = av_write_frame(pFormatCtx, &pkt); av_free_packet(&pkt); } // 7.釋放yuv數據 free(yuv420_data); } CVPixelBufferUnlockBaseAddress(imageBuffer, 0); }
(3). 結束采集后,對資源釋放
- (void)freeX264Resource{ // 1.釋放AVFormatContext int ret = flush_encoder(pFormatCtx,0); if (ret < 0) { printf("Flushing encoder failed\n"); } // 2.將還未輸出的AVPacket輸出出來 av_write_trailer(pFormatCtx); // 3.關閉資源 if (video_st){ avcodec_close(video_st->codec); av_free(pFrame); } avio_close(pFormatCtx->pb); avformat_free_context(pFormatCtx); }
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index){ int ret; int got_frame; AVPacket enc_pkt; if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities & CODEC_CAP_DELAY)) return 0; while (1) { enc_pkt.data = NULL; enc_pkt.size = 0; av_init_packet(&enc_pkt); ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt, NULL, &got_frame); av_frame_free(NULL); if (ret < 0) break; if (!got_frame){ ret=0; break; } ret = av_write_frame(fmt_ctx, &enc_pkt); if (ret < 0) break; } return ret; }