引言
OpenCV中用VideoCapture类,可以很方便处理图片和视频,它既支持视频文件(.avi , .mpg格式)的读取,也支持从笔记本自带摄像头中读取。 然而现在是要处理ffmpeg解码h264文件得到的视频数据流,由于后续要使用得到的视频数据流,所以要将ffmpeg中得到的视频数据流转换成可以被opencv处理的Mat对象来操作。
以下我将以H264视频流为例,利用ffmpeg结合opencv讲解视频解码操作
一、初始化解码器
av_register_all();
这一步注册库中含有的所有可用的文件格式和编码器,这样当打开一个文件时,它们才能够自动选择相应的文件格式和编码器。要注意你只需调用一次 av_register_all(),所以,尽可能的在你的初始代码中使用它。这里注册了所有的文件格式和编解码器的库,所以它们将被自动的使用在被打开的合适格式的文件上。注意你只需要调用 av_register_all()一次,通常放在构造函数里面。
二、打开视频文件
c++原型:
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
?avformat_open_input的四个参数分别为:
- 保存视频文件头部信息的AVFormatContext?结构体对象
- 视频文件的路径URL
- 文件的格式
- 字典类型的参数
我们通过简单地指明NULL或0告诉 libavformat 去自动探测文件格式并且使用默认的缓冲区大小,如下所示。
int videoDecoder::open_InputVedio(QString url)
{
this->formatInfo = avformat_alloc_context(); //开辟内存空间
this->vedio_index = -1;
//开辟内存空间
// this->packet = av_packet_alloc();
this->picture_s = av_frame_alloc();
// 打开对应url的视频
return avformat_open_input(&this->formatInfo,url.toStdString().data(),nullptr,nullptr);
}
三、查找视频流
c++原型:
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
查找文件的流信息,avformat_open_input函数只是检测了文件的头部,接着要检查在文件中的流的信息。因此,这一步会用有效的信息把 AVFormatContext 的流域(streams field)填满。我们仅仅处理视频流,而不是音频流。
int videoDecoder::find_streamInfo()
{ //查找流媒体数据
int res = avformat_find_stream_info(this->formatInfo,nullptr);
if(res < 0)
{
qDebug()<<"打开流媒体信息失败";
return res;
}
//找到流媒体数据 循环判读是否为我们需要的视频流 如果是记录下标到vedio_index
for (int i = 0;i < this->formatInfo->nb_streams;i++) {
if(this->formatInfo->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
this->vedio_index = i;
break;
}
}
if(this->vedio_index == -1)
{
qDebug()<<"没有找到视频流媒体数据";
return -1;
}
return res;
}
四、找到视频流编码器并打开
得到一个指向视频流的上下文结构体AVCodecContext的指针codec后,接着寻找视频流的解码器并打开(两个API)
c++原型:
AVCodec *avcodec_find_decoder(enum AVCodecID id);
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
int videoDecoder::find_open_decoder()
{ //给解码器上下文环境初始化 找到解码器
this->codec = this->formatInfo->streams[this->vedio_index]->codec;
this->decoder = avcodec_find_decoder(this->codec->codec_id);
if(this->decoder == nullptr)
{
qDebug()<<"没有找到对应的解码器";
return -1;
}
//打开解码器
int res = avcodec_open2(this->codec,decoder,nullptr);
if(res != 0)
{
qDebug()<<"打开解码器失败";
}
return res;
}
五、数据准备:给视频帧分配空间
给视频帧分配空间,以便存储解码后的图片数据。因为视频文件包含数个音频和视频流,并且他们各个独自被分开存储在固定大小的包里。我们要做的就是依次读取这些包,过滤掉所有那些视频流中我们不感兴趣的部分再进行解码
六、解码
1、通过该API读入一帧
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
2、利用以下API进行解码一帧数据,将有效的图像数据存储到AVFrame结构体的picture中
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
int *got_picture_ptr,
const AVPacket *avpkt);
七、YUV420p转成BGR编码
1、首先得到图片转换上下文img_convert_ctx,这里注意的是,opencv的RGB编码顺序为BGR,所以选用AV_PIX_FMT_BGR24的编码方式。
this->pFrameRGB->width = this->incodecContext->width;
this->pFrameRGB->height = this->incodecContext->height;
this->pFrameRGB->format = this->incodecContext->pix_fmt;
img_convert_ctx = sws_getContext(this->incodecContext->width,this->incodecContext->height,
this->incodecContext->pix_fmt,this->incodecContext->width,this->incodecContext->height,
AV_PIX_FMT_BGR24,SWS_BICUBIC,nullptr,nullptr,nullptr);
2、为BGR格式帧分配内存
AVFrame *pFrameRGB = NULL;
uint8_t *out_bufferRGB = NULL;
pFrameRGB = avcodec_alloc_frame();
int size_bgr = avpicture_get_size(AV_PIX_FMT_BGR24, incodecContext->width, incodecContext->height);
out_bufferRGB = new uint8_t[size_bgr];
avpicture_fill((AVPicture *)pFrameRGB, out_bufferRGB, AV_PIX_FMT_BGR24, incodecContext->width, incodecContext->height);
3、转换格式(YUV to RGB)
if (pCvMat->empty())
{
//创建Mat对象矩阵(图片)
pCvMat->create(cv::Size(incodecContext->width, incodecContext->height),CV_8UC3);
}
//YUV to RGB
sws_scale(img_convert_ctx, decode_frame->data, decode_frame->linesize, 0, incodecContext->height, pFrameRGB->data, pFrameRGB->linesize);
4、OpenCV Mat数据复制
cv::Mat对象中有data指针,指向内存中存放矩阵数据的一块内存 (uchar *)。所以,要将ffmpeg解码之后得到的RGB色彩的帧数据复制给该指针,这样就实现了ffmpeg解码数据到opencv中Mat格式的转换,进而就可以对Mat对象进行相应的处理。
memcpy(pCvMat->data,out_bufferRGB,size_bgr);//内存拷贝
?解码并转换格式代码如下
while (av_read_frame(this->informat,this->packet)==0)
{
if(this->packet->stream_index == this->vediostream_index)
{
int got_picture_ptc = -1;
avcodec_decode_video2(this->incodecContext,this->decode_frame,&got_picture_ptc,this->packet);
if(got_picture_ptc != 0)
{
//------------------opencv
if (pCvMat->empty())
{
pCvMat->create(cv::Size(incodecContext->width, incodecContext->height),CV_8UC3);
}
YUV to RGB
sws_scale(img_convert_ctx, decode_frame->data, decode_frame->linesize, 0, incodecContext->height, pFrameRGB->data, pFrameRGB->linesize);
memcpy(pCvMat->data,out_bufferRGB,size_bgr);
qDebug()<<"pCvMat->data"<<pCvMat->data;
emit sendBGRImage(*pCvMat);
//------------------opencv
//以下为RGB图像数据(展示在页面)和YUV图像数据(编码生成文件)
sws_scale(sws_play,this->decode_frame->data,this->decode_frame->linesize,
0,this->decode_frame->height,this->sws_frame->data,this->sws_frame->linesize);
QImage img((uchar *)f_data,this->sws_frame->width,this->sws_frame->height,QImage::Format_RGB32);
//发送图片给主界面接收
emit sendImage(img);
if(frames==0)//第一帧
{
QRegExp rx("[0-9]{14}");//正则表达,取视频文件的yyyyMMddhhss
rx.indexIn(outfile);
//第一帧图片保存到文件中
img.save("picture/firstframe/"+rx.cap()+".png");
}
sws_scale(sws_yuv,this->decode_frame->data,this->decode_frame->linesize,
0,this->decode_frame->height,sws_frame_yuv->data,sws_frame_yuv->linesize);
encoderFrame(sws_frame_yuv);
frames++;
qDebug()<<"frames"<<frames;
}
}
av_packet_unref(this->packet);//不是free
}
参考链接如下:
利用ffmpeg和opencv进行视频的解码播放
|