QT+ffmpeg实现视频解析的示例详解

2022-11-13 16:11:09 示例 解析 详解

一、创建QT项目

首先安装了最新的CommUnity版本,Creator是8.0.1版本了。

然后进行项目的创建。

得到的项目没有pro文件,而是CMakeLists.txt。

二、引入ffmpeg

从下面下载的ffmpeg-5.0.1-full_build-shared.7z。

https://www.gyan.dev/ffmpeg/builds/

1、复制头文件和lib

在项目内创建一个文件夹,我这里起名叫lib,然后在lib文件夹下创建了ffmpeg文件夹,然后将上面下载的压缩包内的include和lib文件夹复制到ffmpeg文件夹下。

然后修改CMakeLists.txt,添加如下的内容。

include_directories(${CMAKE_SOURCE_DIR}/lib/ffmpeg/include)
 
target_link_libraries(QtFFmpegApp1 PRIVATE ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/avcodec.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/avdevice.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/avfilter.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/avfORMat.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/avutil.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/postproc.lib
                                          ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/swresample.lib
                                           ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/swscale.lib
                                       )

2、复制bin文件

将下面的dll文件复制到build-QtFFmpegApp1-Desktop_Qt_6_3_1_MinGW_64_bit-Debug文件夹下。

3、简单测试

在mainwindow.h文件内添加以下引用

extern "C"
{
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/buffer.h"
#include "libavutil/error.h"
#include "libavutil/mem.h"
#include "libavutil/imgutils.h"
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavfilter/avfilter.h>
}

在mainwindow.cpp文件内的MainWindow::MainWindow方法内添加

std::string s = avcodec_configuration();
QString dlgTitle = "information消息框";
QString strInfo = QString::fromStdString(s);
QMessageBox::information(this, dlgTitle, strInfo, QMessageBox::Ok, QMessageBox::NoButton);

运行,会看到如下消息窗口,其中显示的ffmpeg的编译参数,看到这个就表明ffmpeg引入成功。

三、视频解析

1、创建线程

(1)MyThread.h文件

#ifndef MYTHREAD_H
#define MYTHREAD_H
 
 
#include <QObject>
#include <QThread>
#include <QImage>
 
#include <iOStream>
#include <fstream>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"
#include "libavutil/buffer.h"
#include "libavutil/error.h"
#include "libavutil/mem.h"
#include "libavutil/imgutils.h"
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavfilter/avfilter.h>
}
 
 
class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
protected:
    void run() override;
signals:
    void minNumToamin(int min);
    void getPicFromFrame(QImage image);
};
 
#endif // MYTHREAD_H

(2)MyThread.cpp文件

#include <MyThread.h>
#include <QDebug>
 
#define INBUF_SIZE 4096
 
MyThread::MyThread()
{
 
}
 
void MyThread::run()
{
    const char* filename, * outfilename;
    const AVCodec* codec;
    AVCodecParserContext* parser;
    AVCodecContext* c = NULL;
    FILE* f;
    AVFrame* frame;
    AVFrame* pFrameBGR;
    uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t* data;
    size_t   data_size;
    int ret;
    AVPacket* pkt;
    AVFormatContext* inFmtCtx = NULL;
    int video_in_stream_index = -1, audio_in_stream_index = -1;
    AVCodecID src_video_id = AVCodecID::AV_CODEC_ID_NONE, src_audio_id = AVCodecID::AV_CODEC_ID_NONE;
    SwsContext* sws_ctx;
    uint8_t* buffer = nullptr;
 
    //文件路径
    filename = "C:\\Users\\zyh\\Desktop\\2.mp4";
    outfilename = "";
 
    pkt = av_packet_alloc();
    if (!pkt)
        exit(1);
 
 
    // 打开输入文件
    if ((ret = avformat_open_input(&inFmtCtx, filename, NULL, NULL)) < 0) {
        //LOGD("avformat_open_input() fail");
        //releaseSources();
        return;
    }
    if ((ret = avformat_find_stream_info(inFmtCtx, NULL)) < 0) {
        //LOGD("avformat_find_stream_info fail %d", ret);
        //releaseSources();
        return;
    }
    // 输出输入文件信息
    av_dump_format(inFmtCtx, 0, filename, 0);
 
    for (int i = 0; i < inFmtCtx->nb_streams; i++) {
        AVCodecParameters* codecpar = inFmtCtx->streams[i]->codecpar;
        if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO && video_in_stream_index == -1) {
            src_video_id = codecpar->codec_id;
            video_in_stream_index = i;
        }
        if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_in_stream_index == -1) {
            src_audio_id = codecpar->codec_id;
            audio_in_stream_index = i;
        }
    }
 
 
    
    memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE);
 
    
    codec = avcodec_find_decoder(src_video_id);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
 
    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "parser not found\n");
        exit(1);
    }
 
    c = avcodec_alloc_context3(codec);
    if (!c) {
        fprintf(stderr, "Could not allocate video codec context\n");
        exit(1);
    }
 
    AVCodecParameters* codecpar = inFmtCtx->streams[video_in_stream_index]->codecpar;
    if ((ret = avcodec_parameters_to_context(c, codecpar)) < 0) {
        //LOGD("avcodec_parameters_to_context fail %d", ret);
        //releaseSources();
        return;
    }
 
    
    
    if (avcodec_open2(c, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }
 
    f = fopen(filename, "rb");
    if (!f) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
 
    frame = av_frame_alloc();
    pFrameBGR = av_frame_alloc();
 
    if (!frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
 
    int index = 0;
    while (av_read_frame(inFmtCtx, pkt) >= 0) {
 
        // 迭代结束后释放 av_read_frame 分配的 packet 内存
        //std::shared_ptr<AVPacket> packetDeleter(&pkt, av_packet_unref);
 
        if(index >0) break;
        // 说明读取的视频数据
        if (pkt->stream_index == video_in_stream_index) {
            if ((ret = avcodec_send_packet(c, pkt)) < 0) {
                //LOGD("video avcodec_send_packet fail %s", av_err2str(ret));
                //releaseSources();
                return;
            }
            while (true) {
                // 从解码缓冲区接收解码后的数据
                if ((ret = avcodec_receive_frame(c, frame)) < 0) {
                    if (ret == AVERROR_EOF) {
                        exit(1);
                        // 解码缓冲区结束了,那么也要flush编码缓冲区
                        //doEncodeVideo(NULL);
                    }
                    break;
                }
 
                int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, c->width, c->height, 1);
                if(buffer == nullptr) buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
                av_image_fill_arrays(pFrameBGR->data, pFrameBGR->linesize, buffer, AV_PIX_FMT_RGB24,  c->width, c->height, 1);
 
 
                sws_ctx = sws_getContext(codecpar->width, codecpar->height, (enum AVPixelFormat)codecpar->format,
                    frame->width, frame->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
 
                sws_scale(sws_ctx, frame->data, frame->linesize, 0, c->height, pFrameBGR->data, pFrameBGR->linesize);
 
                // 图像转换
                sws_scale(sws_ctx, frame->data, frame->linesize, 0, c->height, pFrameBGR->data, pFrameBGR->linesize);
                
                // 得到QImage
                QImage tempImage((uchar*)pFrameBGR->data[0], c->width, c->height, QImage::Format_RGB888);
                // 对于qt还是不是很熟悉,理解是传递给主线程
                getPicFromFrame(tempImage);
            }
        }
 
        // 因为每一次读取的AVpacket的数据大小不一样,所以用完之后要释放
        av_packet_unref(pkt);
    }
 
    fclose(f);
 
    av_parser_close(parser);
    avcodec_free_context(&c);
    av_frame_free(&frame);
    av_packet_free(&pkt);
}

2、创建自定义绘制控件

(1)PlayImage.h

#ifndef PLAYIMAGE_H
#define PLAYIMAGE_H
 
#include <QWidget>
 
class PlayImage : public QWidget
{
    Q_OBJECT
public:
    explicit PlayImage(QWidget *parent = nullptr);
 
    void updateImage(const QImage& image);
    void updatePixmap(const QPixmap& pixmap);
 
signals:
 
protected:
    void paintEvent(QPaintEvent *event) override;
 
private:
    QPixmap m_pixmap;
};
 
#endif // PLAYIMAGE_H

(2)PlayImage.cpp

#include "playimage.h"
 
#include <QPainter>
 
PlayImage::PlayImage(QWidget *parent) : QWidget(parent)
{
 
}
 

void PlayImage::updateImage(const QImage& image)
{
    updatePixmap(QPixmap::fromImage(image));
}
 

void PlayImage::updatePixmap(const QPixmap &pixmap)
{
    m_pixmap = pixmap;
    update();
}
 

void PlayImage::paintEvent(QPaintEvent *event)
{
    if(!m_pixmap.isNull())
    {
        QPainter painter(this);
#if 0
        // 经过粗略测试,QImage先缩放后转为QPixmap的方式在图像比较小时耗时少,图片越大耗时远大
        QPixmap pixmap = QPixmap::fromImage(m_image.scaled(this->size(), Qt::KeepAspectRatio));
        // 先将QImage转换为QPixmap再进行缩放则耗时比较少,并且稳定,不会因为缩放图片大小而产生太大影响
        QPixmap pixmap1 = QPixmap::fromImage(m_image).scaled(this->size(), Qt::KeepAspectRatio);
#endif
        QPixmap pixmap = m_pixmap.scaled(this->size(), Qt::KeepAspectRatio);
        int x = (this->width() - pixmap.width()) / 2;
        int y = (this->height() - pixmap.height()) / 2;
        painter.drawPixmap(x, y, pixmap);
    }
    QWidget::paintEvent(event);
}

3、使用自定义控件

在mainwindow.ui的设计界面,拖一个Widget到主界面,然后在Widget上点击右键,然后选择提升为,在提升的类名称处输入上面自定义控件的类名。

如果选择下面的全局包含,就不用再单独包含头文件了。

4、开启线程,进行视频解析

(1)mainwindow.cpp

开启线程

m_thread  =  new MyThread;
connect(m_thread,&MyThread::getPicFromFrame,this,&MainWindow::getPicfromThread);
m_thread->start();

接收并绘制图片

void MainWindow::getPicfromThread(QImage image)
{
    ui->widget->updateImage(image);
}

(2)绘制结果

差不多下面这个样子。

到此这篇关于QT+ffmpeg实现视频解析的示例详解的文章就介绍到这了,更多相关QT ffmpeg视频解析内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章