编码方式
-
在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单元写入文件
-
写入后,数据存储方式:
// 编码完成回调 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; }