同上面所写的两篇文章,本篇依然是介绍FFmpge的相关操作,前一篇讲的是视频压缩,本篇则相反的讲视频的解码。废话不多说,直接上代码吧。
同理于上篇,本篇先设计一个视频解码相关的类,定义如下:
class Ffmpeg_Decoder { public: AVCodecParserContext *avParserContext; AVPacket avpkt; //数据包结构体 AVFrame *m_pRGBFrame; //帧对象 AVFrame *m_pYUVFrame; //帧对象 AVCodec *pCodecH264; //解码器 AVCodecContext *c; //解码器数据结构对象 uint8_t *yuv_buff; //yuv图像数据区 uint8_t *rgb_buff; //rgb图像数据区 SwsContext *scxt; //图像格式转换对象 uint8_t *filebuf; //读入文件缓存 uint8_t *outbuf; //解码出来视频数据缓存 int nDataLen; //rgb图像数据区长度 IplImage* img; //OpenCV图像显示对象 uint8_t *pbuf; //用以存放帧数据 int nOutSize; //用以记录帧数据长度 int haveread; //用以记录已读buf长度 int decodelen; //解码器返回长度 int piclen; //解码器返回图片长度 bool finishInitScxt; //完成格式转换器初始化标志 public: void Ffmpeg_Decoder_Init();//初始化 void Ffmpeg_Decoder_Show(AVFrame *pFrame, int width, int height);//显示图片 void Ffmpeg_Decoder_Close();//关闭 };
对类中声明的三个函数进行定义
void Ffmpeg_Decoder::Ffmpeg_Decoder_Init() { avcodec_register_all(); //注册编解码器 av_init_packet(&avpkt); //初始化包结构 m_pRGBFrame = new AVFrame[1];//RGB帧数据赋值 m_pYUVFrame = avcodec_alloc_frame(); filebuf = new uint8_t[1024 * 1024];//初始化文件缓存数据区 pbuf = new uint8_t[200 * 1024];//初始化帧数据区 yuv_buff = new uint8_t[200 * 1024];//初始化YUV图像数据区 rgb_buff = new uint8_t[1024 * 1024];//初始化RGB图像帧数据区 pCodecH264 = avcodec_find_decoder(CODEC_ID_H264); //查找h264解码器 if (!pCodecH264) { fprintf(stderr, "h264 codec not found\n"); exit(1); } avParserContext = av_parser_init(CODEC_ID_H264); if (!pCodecH264)return; c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放 //设置多线程解码模式,对于高清视频,多核cpu,会有很大的速度提升如果只要单核则下面两句话可以不写 c->thread_count = 2; c->thread_type = FF_THREAD_FRAME; if (pCodecH264->capabilities&CODEC_CAP_TRUNCATED) c->flags |= CODEC_FLAG_TRUNCATED; /* we do not send complete frames */ if (avcodec_open2(c, pCodecH264, NULL) < 0)return; nDataLen = 0; finishInitScxt = false;//将格式转换器初始化标志设为否 }
void Ffmpeg_Decoder::Ffmpeg_Decoder_Show(AVFrame *pFrame, int width, int height) { CvSize rectangle_img_size; rectangle_img_size.height = height; rectangle_img_size.width = width; img = cvCreateImage(rectangle_img_size, IPL_DEPTH_8U, 3); uchar* imgdata = (uchar*)(img->imageData); //图像的数据指针 for (int y = 0; y<height; y++) { memcpy(imgdata + y*width * 3, pFrame->data[0] + y*pFrame->linesize[0], width * 3); } cvShowImage("解码图像", img); cvWaitKey(1);//可以将图像停留时间设的长点以便观察 cvReleaseImage(&img); imgdata = NULL; }
void Ffmpeg_Decoder::Ffmpeg_Decoder_Close() { avpicture_free((AVPicture *)m_pRGBFrame);//释放帧资源 sws_freeContext(scxt);//释放格式转换器资源 finishInitScxt = false;//将格式转换器初始化标志设为否 delete[]filebuf; delete[]pbuf; delete[]yuv_buff; delete[]rgb_buff; av_free(m_pYUVFrame);//释放帧资源 avcodec_close(c);//关闭解码器 av_free(c); }
后面是主函数部分,因为通常解码都是从文件中读取数据流或者从网络中得到数据缓存,所以出于方便和操作性本人把解码的部分代码写在了主函数中
#include "Ffmpeg_Decode.h" long consumptiontime; long playframecount = 0; void main() { Ffmpeg_Decoder ffmpegobj; ffmpegobj.Ffmpeg_Decoder_Init();//初始化解码器 FILE *pf = NULL; fopen_s(&pf, "1.h264", "rb"); consumptiontime = clock(); while (true) { ffmpegobj.nDataLen = fread(ffmpegobj.filebuf, 1, 1024 * 100, pf);//读取文件数据 if (ffmpegobj.nDataLen<=0) { fclose(pf); break; } else { ffmpegobj.haveread = 0; while (ffmpegobj.nDataLen > 0) { int nLength = av_parser_parse2(ffmpegobj.avParserContext, ffmpegobj.c, &ffmpegobj.yuv_buff, &ffmpegobj.nOutSize, ffmpegobj.filebuf + ffmpegobj.haveread, ffmpegobj.nDataLen, 0, 0, 0);//查找帧头 ffmpegobj.nDataLen -= nLength;//查找过后指针移位标志 ffmpegobj.haveread += nLength; if (ffmpegobj.nOutSize <= 0) { break; } ffmpegobj.avpkt.size = ffmpegobj.nOutSize;//将帧数据放进包中 ffmpegobj.avpkt.data = ffmpegobj.yuv_buff; ffmpegobj.decodelen = avcodec_decode_video2(ffmpegobj.c, ffmpegobj.m_pYUVFrame, &ffmpegobj.piclen, &ffmpegobj.avpkt);//解码 if (ffmpegobj.decodelen < 0) { break; } if (ffmpegobj.piclen) { if (ffmpegobj.finishInitScxt == false)//初始化格式转换函数 { ffmpegobj.finishInitScxt = true; ffmpegobj.scxt = sws_getContext(ffmpegobj.c->width, ffmpegobj.c->height, ffmpegobj.c->pix_fmt, ffmpegobj.c->width, ffmpegobj.c->height, PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL); avpicture_fill((AVPicture*)ffmpegobj.m_pRGBFrame, (uint8_t*)ffmpegobj.rgb_buff, PIX_FMT_RGB24, ffmpegobj.c->width, ffmpegobj.c->height); //这个函数的使用本质上是为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间,即将rgb_buff挂到m_pRGBFrame avpicture_alloc((AVPicture *)ffmpegobj.m_pRGBFrame, PIX_FMT_RGB24, ffmpegobj.c->width, ffmpegobj.c->height);//指定的目标格式为PIX_FMT_RGB24 } if (ffmpegobj.scxt != NULL) { playframecount++; //YUV转rgb sws_scale(ffmpegobj.scxt, ffmpegobj.m_pYUVFrame->data, ffmpegobj.m_pYUVFrame->linesize, 0, ffmpegobj.c->height, ffmpegobj.m_pRGBFrame->data, ffmpegobj.m_pRGBFrame->linesize); //ffmpegobj.Ffmpeg_Decoder_Show(ffmpegobj.m_pRGBFrame, ffmpegobj.c->width, ffmpegobj.c->height);//解码图像显示 } } } } } printf("消耗时间%dms\n", clock() - consumptiontime);//消耗时间计算 printf("平均1s解码帧数%lf\n", playframecount / ((clock() - consumptiontime)/(double)1000));//消耗时间计算 printf("解码帧数%d\n", playframecount); getchar(); ffmpegobj.Ffmpeg_Decoder_Close();//关闭解码器 }
OK,到此用FFmpeg进行视频解码的博客就告一段落了,下面是整个工程的下载地址:点击打开链接。同上一篇编码的博文工程,如果想要工程运行起来需要自行配置OpenCV。
由于这份代码写的比较久,后来在用的时候发现存在一些性能上的问题,遂修改了代码,然而因为无法更新上传工程的缘故,下载了本人老工程的朋友如果需要高性能解码可对比本博文的代码进行修改。
之前的上传的工程问题比较多,而且由于ffmpeg本身的迭代,有些函数已经过时了,最近用最新版本的ffmpeg库写了个x64平台的demo,如果有需要可以到这个地址下载。