Qt之ffmpeg结合opencv进行视频的解码播放

引言

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的四个参数分别为:

  1. 保存视频文件头部信息的AVFormatContext 结构体对象
  2. 视频文件的路径URL
  3. 文件的格式
  4. 字典类型的参数

我们通过简单地指明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进行视频的解码播放

猜你喜欢

转载自blog.csdn.net/hml111666/article/details/123204335