您的位置:首页 > 其它

利用FFmpge进行视频解码(从H264视频流到图像)

2014-07-13 19:29 615 查看
    同上面所写的两篇文章,本篇依然是介绍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,如果有需要可以到这个地址下载。

----------------------------------------我是分割线-------------------------------------------

-----------------------------下面是本人维护的一个ffmpeg解码应用类----------------------------

------该类仅供本人自己使用,为方便放到这里,君若有兴趣请自行实现里面要求的外围代码支持-------

#include "stdafx.h"
#include <string.h>//字符串操作头文件

#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif

#ifdef __cplusplus
extern "C" {
#endif
/*Include ffmpeg header file*/
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>

#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/samplefmt.h>

#ifdef __cplusplus
}
#endif

#define   WIDTHBYTES(bits) (((bits)+31)/32*4)//用于使图像宽度所占字节数为4byte的倍数

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 *buf[1];
int linesize[1];
SwsContext *scxt;		  //图像格式转换对象
bool intparsercontext;    //查找帧头结构体初始化
bool finishInitScxt;      //标志是否完成初始化YUV转RGB的对象
int nDataLen;			  //rgb图像数据区长度
int nOutSize;			  //用以记录帧数据长度
int haveread;			  //用以记录已读buf长度
int decodelen;			  //解码器返回长度
int piclen;			      //解码器返回图片长度
LPVOID lpParam;           //主窗体对象指针
public:
void Ffmpeg_Decoder_Show(uint8_t *data,int datalenght, queue<char*>& queueimgobj, queue<char*>& queueimgprepareobj, CCriticalSection &lock, CCriticalSection &imgdatapreparelock)
{
nDataLen = datalenght;
haveread = 0;
if (intparsercontext == false)
{
avParserContext = av_parser_init(CODEC_ID_H264);
intparsercontext = true;
}
int l_width = WIDTHBYTES(c->width * 24);
while (nDataLen > 0)
{
int nLength = av_parser_parse2(avParserContext, c, &yuv_buff,&nOutSize, data + haveread, nDataLen, 0, 0, 0);//查找帧头
if (nLength<0)
{
if (nDataLen>0)
{
//printf("数据丢失数据丢失数据丢失数据丢失数据丢失数据丢失数据丢失数据丢失数据丢失");
}
break;
}
nDataLen -= nLength;//查找过后指针移位标志
haveread += nLength;
if (nOutSize <= 0)
{
continue;
}
avpkt.size = nOutSize;//将帧数据放进包中
avpkt.data = yuv_buff;
while (avpkt.size > 0)
{
decodelen = avcodec_decode_video2(c, m_pYUVFrame, &piclen, &avpkt);//解码
if (decodelen < 0)
{
break;
}
if (piclen)
{
if (finishInitScxt == false)//初始化格式转换函数
{
finishInitScxt = true;
scxt = sws_getContext(c->width, c->height, c->pix_fmt, c->width, c->height, PIX_FMT_BGRA, SWS_POINT, NULL, NULL, NULL);//初始化格式转换函数
linesize[0] = c->width * 4;
}
if (scxt != NULL)
{
if (queueimgprepareobj.size() > 0)
{
//20170324修改前延时230ms,时有超过
//拷贝次数减少一次后稳定在230
//不再创建删除内存
imgdatapreparelock.Lock();
char* myimg = queueimgprepareobj.front();
queueimgprepareobj.pop();//弹出该元素;
imgdatapreparelock.Unlock();
buf[0] = (uint8_t*)myimg;
//YUV转rgb
sws_scale(scxt, m_pYUVFrame->data, m_pYUVFrame->linesize, 0, c->height, buf, linesize);
lock.Lock();
queueimgobj.push(myimg);
lock.Unlock();
}
else
{
printf("数据丢失数据丢失数据丢失");
}
Sleep(0);
}
}
avpkt.size -= decodelen;
avpkt.data += decodelen;
}
//Sleep(0);
}
}

void Ffmpeg_Decoder_Init(LPVOID mlpParam)//初始化
{
lpParam = mlpParam;//主窗体指针传值
intparsercontext = false;
finishInitScxt = false;
avcodec_register_all();     //注册编解码器
av_init_packet(&avpkt);     //初始化包结构
m_pYUVFrame = new AVFrame[1];
//这里的大小和实际解码出来的图像大小由关
yuv_buff = new uint8_t[1280 * 1400 * 4];//初始化YUV图像数据区

pCodecH264 = avcodec_find_decoder(CODEC_ID_H264);     //查找h264解码器
if (!pCodecH264)
{
//fprintf(stderr, "h264 codec not found\n");
exit(1);
}
if (!pCodecH264)return;
c = avcodec_alloc_context3(pCodecH264);//函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放
//多线程解码设置
//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;
}
void Ffmpeg_Decoder_Close()//关闭
{
if (finishInitScxt)
{
//avpicture_free((AVPicture *)m_pRGBFrame);//释放帧资源
sws_freeContext(scxt);//释放格式转换器资源
}
delete[]yuv_buff;
av_free(m_pYUVFrame);//释放帧资源
avcodec_close(c);//关闭解码器
av_free(c);
}
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: