ffmpeg编码器+VS2008的环境配置以及视频编码过程详解
Jun_L
http://blog.csdn.net/jingjun1822
由于项目上的需要,连续六天被ffmpeg给坑了。跟X264不一样,ffmpeg编码器的资料五花八门,就一个编码的编译和环境配置过程,就让我头疼了好一段时间,为了让后面的享用ffmpeg的人不会像我这样晕,在完成任务的第六个晚上,我总结了一下我的ffmpeg编码器使用过程,我的了解也不是很深入,希望我的一点经验能给大家带来帮助,讲错的地方也希望大家指正。
一、ffmpeg+vs2008环境配置
网上有诸多编译生成库文件.lib和动态库.dll的的博客,那些主要是针对前期版本的ffmpeg。感谢ying1feng23同学的博客对我的启发,我从他的博客上了解到了快捷的办法:
首先从ffmpeg官网中下载ffmpeg数据包:
下载的时候要注意自己的VS的编译器是WIN32的还是WIN64的,注意只看编译器,跟系统位数没有关系。
官网提供四个版本下载:
1.静态编译版本:提供编译好的指令文件如ffmpeg、ffmplay等可执行文件
2.共享库编译版本(shared):提供编译好的指令文件如ffmpeg、ffmplay等可执行文件和对应的dll
3.开发版本(dev):提供编译好的库文件、dll和头文件
4. 源码:直接下载ffmpeg的所有源码。
2.共享库编译版本(shared):提供编译好的指令文件如ffmpeg、ffmplay等可执行文件和对应的dll
3.开发版本(dev):提供编译好的库文件、dll和头文件
4. 源码:直接下载ffmpeg的所有源码。
我们这里要用到的是shared版本和dev版本,在shared版本下有我们需要用到的.dll动态链接库文件,dev版本中有.lib库文件(lib文件夹)和.h头文件(include文件夹)。下载以后,进行一下配置步骤:
①将include文件夹下的lib开头的文件夹libavcodec.......等拷贝到你的工程文件夹下,然后在文件前面include它们就OK了,因为ffmpeg是用C语言写的,所以要加上extern“C”这一句:
extern "C" { #include <libavutil/opt.h> #include <libavcodec/avcodec.h> #include <libavutil/channel_layout.h> #include <libavutil/common.h> #include <libavutil/imgutils.h> #include <libavutil/mathematics.h> #include <libavutil/samplefmt.h> #include <libswscale/swscale.h> };
②将lib文件夹下的.lib文件拷贝到工程文件夹下,在文件前面加上:
#pragma comment(lib, "avcodec.lib") #pragma comment(lib, "avformat.lib") #pragma comment(lib, "avutil.lib") #pragma comment(lib, "swscale.lib")</span>
③将.dll文件拷贝到工程中debug和release文件夹中。OK,配置完成。接下来就可以使用ffmpeg了。头文件还需要用到这些:
#include "stdint.h" #include <stdio.h> #include <stdlib.h> #include <vector> #include<iostream></span>
二、我这边实现的是将视频处理算法产生的结果帧压缩成H.264的视频,网上很多代码要不就是编解码都有的程序,要不就是对几张图片进行编码压缩的程序,资料并不多,调试的过程中我遭遇了很多的问题。感谢EightDegree和ymsdu2004两位的程序,给了我很大的启发,ffmpeg不断在更新,我们在前辈的代码上做一些修改就可以得到我们想要的结果了。EightDegree的代码是将5幅1280*720大小的图片进行编码,并且写到文件中,ymsdu2004更牛逼,深入分析了ffmpeg源代码中关于编码延时的过程。大家编代码的时候可以参考他们的博客:http://blog.csdn.net/eightdegree/article/details/7425635,http://blog.csdn.net/ymsdu2004/article/details/8565822
其实在官方下载的ffmpeg里面有示例代码,个人觉得这些代码有点坑,有点误导人:
下面用注释的代码说一下ffmpeg编码器使用的流程:
①定义编码器,设置编码器参数,查找编码器,申请内存
关于几个结构体的形式参考:http://blog.csdn.net/leixiaohua1020/article/details/14214577
AVCodec *codec;//编码器 AVCodecContext *c; //描述编解码器上下文的数据结构 AVFrame *picture; //图像数据存储结构体 AVPacket pkt;//编码暂存容器 avcodec_register_all();//注册各种编解码器 //av_register_all();//新版本不需要这一句了 //查找编码器 codec = avcodec_find_encoder(AV_CODEC_ID_H264);//h.264编码器查找 if (!codec){ fprintf(stderr,"codec not found\n"); exit(1); } //设置编码器参数 c = avcodec_alloc_context3(codec); c->bit_rate = 400000;//比特率,最低默认20000,设置太低编码视频效果差,必须为2的倍数 c->width = video_width; c->height = video_height; c->time_base.den = video_fps;//帧率=den/num c->time_base.num = 1; c->gop_size = 10;//每10帧插入一个I帧 c->max_b_frames = 1;//非B帧之间的最大B帧数 c->pix_fmt = AV_PIX_FMT_YUV420P;//设置像素格式 if (avcodec_open2(c, codec, NULL)<0){ fprintf(stderr,"could not open codec\n"); exit(1); } //av_opt_set(c->priv_data, "preset", "slow", 0); av_opt_set(c->priv_data, "preset", "super fast", 0); av_opt_set(c->priv_data, "tune", "zero latency", 0);//这两句实现实时编码模式,也可以用slow模式,slow///模式延迟帧更多,还会导致你的pts不对,注意这个设置不要放在上一个if以前,否则会打不开编码器<span style="font-family: KaiTi_GB2312;">could not o//pen codec!</span> //初始化picture picture = avcodec_alloc_frame(); if (!picture){ fprintf(stderr,"could not allocate video frame\n"); exit(1); } picture->format = c->pix_fmt; picture->width = c->width; picture->height = c->height; //开辟内存 ret = av_image_alloc(picture->data,picture->linesize,c->width,c->height,c->pix_fmt,32); if (ret<0){ fprintf(stderr,"could not alloc raw picture buffer\n"); exit(1); }
②打开要编码的文件,转换图像格式,编码
fp_264 = fopen(fileOutName, "ab+");//执行fopen以后,会自动生成名字为fileOutName的文件,这个名字先自己///定义好 uint8_t * pYuvBuff = NULL; // 开始循环获取每一帧,并编码 int yuvimg_size = video_width * video_height; pYuvBuff = (uint8_t *) malloc((yuvimg_size * 3) / 2); uint8_t * outbuf = (uint8_t *) malloc((yuvimg_size * 3) / 2); int frame_number = 0; int j = 0; picture->pts = 0;//和下面picture->pts=frame_number这一句配合,使得生成的视频帧率过快 //看了我这里的写法,希望对你的代码有帮助,pRGBDatas是我的opencv处理以后得到的图像数据 while (frame_number < frame_count)//每次压缩frame_count帧 { for ( i = 0; i < video_fps; i++) { av_init_packet(&pkt);//初始化暂存容器 pkt.data = outbuf; pkt.size = yuvimg_size; frame_number = j * video_fps + i; if (frame_number >= frame_count) break; read_yuv_image(picture, video_width, video_height, pRGBDatas[frame_number],pYuvBuff);//图像RGB数据向YUV数据的转换,函数代码在下面 fflush(stdout); /* encode the image */ ret = avcodec_encode_video2(c, &pkt, picture, &go_output); picture->pts=frame_number; if (ret < 0) { fprintf(stderr, "error encoding frame\n"); exit(1); } if (go_output) { fwrite(pkt.data, 1, pkt.size, fp_264); av_free_packet(&pkt); } } j++; //官方参考代码里面的延迟帧处理,后来发现实时编码模式中不需要这一段 /*for (i= 1; go_output; i++) { fflush(stdout); ret = avcodec_encode_video2(c, &pkt, NULL, &go_output); picture->pts++; if (ret < 0) { fprintf(stderr, "error encoding frame\n"); exit(1); } if (go_output) { fwrite(pkt.data, 1, pkt.size, fp_264); av_free_packet(&pkt); } }*/ } read_yuv_image函数完成图像RGB数据向YUV数据的转换 void read_yuv_image(AVFrame *_picture, int video_width, int video_height, unsigned char * pRGBData,uint8_t * yuv_buff) { AVFrame *m_pRGBFrame = new AVFrame(); //仔细看清楚转换的流程,然后大家去查一下sws_scale的用法就会明白这个转换的过程///了,这里我就不多说了 avpicture_fill((AVPicture*)m_pRGBFrame, (uint8_t*)pRGBData, AV_PIX_FMT_RGB24, video_width, video_height); //初始化SwsContext SwsContext * scxt = sws_getContext(video_width,video_height,AV_PIX_FMT_BGR24, \ video_width,video_height,AV_PIX_FMT_YUV420P, \ SWS_POINT,NULL,NULL,NULL); int yuv_size = video_width * video_height * 3 / 2; //yuv_buff = new uint8_t[yuv_size]; avpicture_fill((AVPicture *) _picture, (uint8_t *) yuv_buff, AV_PIX_FMT_YUV420P, video_width, video_height); //参考代码中的图像翻转,这里不需要 //m_pRGBFrame->data[0] += m_pRGBFrame->linesize[0] * (video_height - 1); //m_pRGBFrame->linesize[0] *= -1; //m_pRGBFrame->data[1] += m_pRGBFrame->linesize[1] * (video_height / 2 - 1); //m_pRGBFrame->linesize[1] *= -1; //m_pRGBFrame->data[2] += m_pRGBFrame->linesize[2] * (video_height / 2 - 1); //m_pRGBFrame->linesize[2] *= -1; sws_scale(scxt, m_pRGBFrame->data, m_pRGBFrame->linesize, 0, video_height, _picture->data, _picture->linesize); //什么时候用delete什么时候用free要注意,小心一直在用的空间被free了,很容易出错 sws_freeContext(scxt); //av_free(m_pRGBFrame); if(m_pRGBFrame) { delete m_pRGBFrame; } return; }
③关闭文件,释放内存
使用ffmpeg的时候很容易不小心使得内存泄露,所以要仔细检查哪里该用delete,哪里该free,只有在该内存空间用完时才能free
if(pYuvBuff) { free(pYuvBuff); } fclose(fp_264); av_free(picture); avcodec_close(c); av_free(c); free(outbuf);