对于FFmpeg相信做视频或图像处理这一块的都不会陌生,在网上也能找到很多相关的代码,但由于版本不同等原因,往往找到的代码都是需要自行修改才可以用,为此本人希望能尽绵薄之力,将开发包和自行编写的代码都放出来,如果初学者想要可以直接运行的代码做参考的话,可以下载我放出的FFmpeg开发包进行配置(配置的教程地址如下:点击打开链接),然后参考我写的编解码代码来进行程序的开发。
下面贴出的是我自己封装的FFmpeg视频压缩代码,如有更好的建议请告诉我,转载请注明出处。
首先我们设计一个视频压缩相关的类,定义如下
class Ffmpeg_Encoder { public: AVFrame *m_pRGBFrame; //帧对象 AVFrame *m_pYUVFrame; //帧对象 AVCodec *pCodecH264; //编码器 AVCodecContext *c; //编码器数据结构对象 uint8_t *yuv_buff; //yuv图像数据区 uint8_t *rgb_buff; //rgb图像数据区 SwsContext *scxt; //图像格式转换对象 uint8_t *outbuf; //编码出来视频数据缓存 int outbuf_size; //编码输出数据去大小 int nDataLen; //rgb图像数据区长度 int width; //输出视频宽度 int height; //输出视频高度 avpacket pkt; //数据包结构体 public: void Ffmpeg_Encoder_Init();//初始化 void Ffmpeg_Encoder_Setpara(CodecID mycodeid,int vwidth,int vheight);//设置参数,第一个参数为编码器,第二个参数为压缩出来的视频的宽度,第三个视频则为其高度 void Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data);//编码并写入数据到文件 void Ffmpeg_Encoder_Close();//关闭 };对类中声明的四个函数进行定义
void Ffmpeg_Encoder::Ffmpeg_Encoder_Init() { av_register_all(); avcodec_register_all(); m_pRGBFrame = new AVFrame[1];//RGB帧数据赋值 m_pYUVFrame = new AVFrame[1];//YUV帧数据赋值 c = NULL;//解码器指针对象赋初值 }
void Ffmpeg_Encoder::Ffmpeg_Encoder_Setpara(CodecID mycodeid, int vwidth, int vheight) { pCodecH264 = avcodec_find_encoder(mycodeid);//查找h264编码器 if (!pCodecH264) { fprintf(stderr, "h264 codec not found\n"); exit(1); } width = vwidth; height = vheight; c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放 c->bit_rate = 400000; //设置采样参数,即比特率 c->width = vwidth;//设置编码视频宽度 c->height = vheight; //设置编码视频高度 c->time_base.den = 2;//设置帧率,num为分子和den为分母,如果是1/25则表示25帧/s c->time_base.num = 1; c->gop_size = 10; //设置GOP大小,该值表示每10帧会插入一个I帧 c->max_b_frames = 1;//设置B帧最大数,该值表示在两个非B帧之间,所允许插入的B帧的最大帧数 c->pix_fmt = PIX_FMT_YUV420P;//设置像素格式 av_opt_set(c->priv_data, "tune", "zerolatency", 0);//设置编码器的延时,解决前面的几十帧不出数据的情况 if (avcodec_open2(c, pCodecH264, NULL) < 0)return;//打开编码器 nDataLen = vwidth*vheight * 3;//计算图像rgb数据区长度 yuv_buff = new uint8_t[nDataLen/2];//初始化数据区,为yuv图像帧准备填充缓存 rgb_buff = new uint8_t[nDataLen];//初始化数据区,为rgb图像帧准备填充缓存 outbuf_size = 100000;////初始化编码输出数据区 outbuf = new uint8_t[outbuf_size]; scxt = sws_getContext(c->width, c->height, PIX_FMT_BGR24, c->width, c->height, PIX_FMT_YUV420P, SWS_POINT, NULL, NULL, NULL);//初始化格式转换函数 }
void Ffmpeg_Encoder::Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data) { memcpy(rgb_buff, data, nDataLen);//拷贝图像数据到rgb图像帧缓存中准备处理 avpicture_fill((AVPicture*)m_pRGBFrame, (uint8_t*)rgb_buff, PIX_FMT_RGB24, width, height);//将rgb_buff填充到m_pRGBFrame avpicture_fill((AVPicture*)m_pYUVFrame, (uint8_t*)yuv_buff, PIX_FMT_YUV420P, width, height);//将yuv_buff填充到m_pYUVFrame sws_scale(scxt, m_pRGBFrame->data, m_pRGBFrame->linesize, 0, c->height, m_pYUVFrame->data, m_pYUVFrame->linesize);// 将RGB转化为YUV int myoutputlen = avcodec_encode_video(c, outbuf, outbuf_size, m_pYUVFrame); fwrite(outbuf, 1, myoutputlen, file); }
void Ffmpeg_Encoder::Ffmpeg_Encoder_Close() { delete[]m_pRGBFrame; delete[]m_pYUVFrame; delete[]rgb_buff; delete[]yuv_buff; delete[]outbuf; sws_freeContext(scxt); avcodec_close(c);//关闭编码器 av_free(c); }最后我们只需要在主函数对这几个函数进行调用就可以了,由于本人为了方便直接用OpencCV来打开图像并取得图像的数据区,所以如果希望直接运行本人后面所发的工程的话还需要自行去配置OpenCV,不过这个在网上实在是说烂了,随便找找就能配置出来。如果不希望用OpenCV来打开图像的话,本人在代码中也注明了应该修改的位置,可自行想办法得到图像数据区并放入视频压缩函数即可。
void main() { Ffmpeg_Encoder ffmpegobj; ffmpegobj.Ffmpeg_Encoder_Init();//初始化编码器 ffmpegobj.Ffmpeg_Encoder_Setpara(CODEC_ID_H264,800,600);//设置编码器参数 //图象编码 FILE *f = NULL; char * filename = "myData.h264"; fopen_s(&f, filename, "wb");//打开文件存储编码完成数据 IplImage* img = NULL;//OpenCV图像数据结构指针 IplImage* resizeimg = NULL;//尺寸 int picturecount = 1; while (picturecount != 9) { /**此部分用的是OpenCV读入图像对象并取得图像的数据区,也可以用别的方法获得图像数据区**/ char chpicname[100]; sprintf(chpicname, "testpicture\\Horse%d.jpg", picturecount);//获得图片路径 //sprintf(chpicname, "1.jpg", picturecount);//获得图片路径 img = cvLoadImage(chpicname, 1);//打开图像 //由于OpenCV图像数据区是以BGR排列的,所以要将其数据转换为正常的RGB排列才能做进一步的压缩,不然压出来的视频颜色会不正确 uchar* data = (uchar*)(img->imageData); uchar mid = 0; for (int row = 0; row<img->height; row++) for (int cols = 0; cols < img->width; cols++) { mid = data[row*img->widthStep / sizeof(uchar)+cols*img->nChannels + 0];//G data[row*img->widthStep / sizeof(uchar)+cols*img->nChannels + 0] = data[row*img->widthStep / sizeof(uchar)+cols*img->nChannels + 2]; data[row*img->widthStep / sizeof(uchar)+cols*img->nChannels + 2] = mid; } resizeimg = cvCreateImage(cvSize(800, 600), 8, 3); cvResize(img, resizeimg, CV_INTER_LINEAR);//调整图像大小 /**此部分用的是OpenCV读入图像对象并取得图像的数据区,也可以用别的方法获得图像数据区**/ ffmpegobj.Ffmpeg_Encoder_Encode(f, (uchar*)resizeimg->imageData);//编码 cvReleaseImage(&img);//释放图像数据结构指针对像所指内容 cvReleaseImage(&resizeimg); picturecount++; } fclose(f); ffmpegobj.Ffmpeg_Encoder_Close(); }
OK,到此用FFmpeg进行视频压缩的博客就告一段落了,下面是整个工程的下载地址:点击打开链接,不过如果想要运行起来配置OpenCV还是需要自行动手的哈哈。
这个工程是之前闲时所写,没有经过严格的压力测试,后来在做项目的时候用到发现 avcodec_encode_video 这个函数在经过多次调用之后程序会出现崩溃的情况。于是乎换了最新版本的ffmpeg库来解决这个问题,关于如何换已在这篇博客提到。由于ffmpeg库新旧版本有个别的函数和变量名称改动,是故上面的代码需要将变量CodecID改为AVCodecID,将 avcodec_encode_video 改为 avcodec_encode_video2。由于函数的输入参数不同了,所以原来的Ffmpeg_Encoder_Encode函数改为如下的形式
void Ffmpeg_Encoder::Ffmpeg_Encoder_Encode(FILE *file, uint8_t *data) { av_init_packet(&pkt); memcpy(rgb_buff, data, nDataLen);//拷贝图像数据到rgb图像帧缓存中准备处理 avpicture_fill((AVPicture*)m_pRGBFrame, (uint8_t*)rgb_buff, PIX_FMT_RGB24, width, height);//将rgb_buff填充到m_pRGBFrame avpicture_fill((AVPicture*)m_pYUVFrame, (uint8_t*)yuv_buff, PIX_FMT_YUV420P, width, height);//将yuv_buff填充到m_pYUVFrame sws_scale(scxt, m_pRGBFrame->data, m_pRGBFrame->linesize, 0, c->height, m_pYUVFrame->data, m_pYUVFrame->linesize);// 将RGB转化为YUV int myoutputlen = 0; int returnvalue=avcodec_encode_video2(c, &pkt, m_pYUVFrame, &myoutputlen); if (returnvalue == 0) { fwrite(pkt.data, 1, pkt.size, file); } av_free_packet(&pkt); }这样该工程就能适用于最新版本的ffmpeg库了。
另外在做项目的过程中在编码这块还遇到一个很奇怪的问题,就是在编码调用到avcodec_encode_video2特别是工程也调用到解码函数的时候,在VS里面点击运行是没有问题的,但一运行release文件夹下面编译出来的exe文件,程序偶尔会报内存错误,错误的位置在avcodec_encode_video2这个函数里面。这个让人百思不得其解,后来想想或者VS在运行的时候兼容性比较好,所以没有这个问题,是故如下图对exe文件的兼容性进行设置
这个问题就解决了,是故怀疑ffmpeg库的avcodec_encode_video2这个函数所在的dll对于windows或者是windows64位系统的兼容性不大好。