QT+ffmpeg實(shí)現(xiàn)視頻解析的示例詳解
一、創(chuàng)建QT項(xiàng)目
首先安裝了最新的Community版本,Creator是8.0.1版本了。
然后進(jìn)行項(xiàng)目的創(chuàng)建。
得到的項(xiàng)目沒有pro文件,而是CMakeLists.txt。
二、引入ffmpeg
從下面下載的ffmpeg-5.0.1-full_build-shared.7z。
https://www.gyan.dev/ffmpeg/builds/
1、復(fù)制頭文件和lib
在項(xiàng)目內(nèi)創(chuàng)建一個文件夾,我這里起名叫l(wèi)ib,然后在lib文件夾下創(chuàng)建了ffmpeg文件夾,然后將上面下載的壓縮包內(nèi)的include和lib文件夾復(fù)制到ffmpeg文件夾下。
然后修改CMakeLists.txt,添加如下的內(nèi)容。
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、復(fù)制bin文件
將下面的dll文件復(fù)制到build-QtFFmpegApp1-Desktop_Qt_6_3_1_MinGW_64_bit-Debug文件夾下。
3、簡單測試
在mainwindow.h文件內(nèi)添加以下引用
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文件內(nèi)的MainWindow::MainWindow方法內(nèi)添加
std::string s = avcodec_configuration(); QString dlgTitle = "information消息框"; QString strInfo = QString::fromStdString(s); QMessageBox::information(this, dlgTitle, strInfo, QMessageBox::Ok, QMessageBox::NoButton);
運(yùn)行,會看到如下消息窗口,其中顯示的ffmpeg的編譯參數(shù),看到這個就表明ffmpeg引入成功。
三、視頻解析
1、創(chuàng)建線程
(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; } } /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */ memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); /* find the MPEG-1 video decoder */ 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; } /* For some codecs, such as msmpeg4 and mpeg4, width and height MUST be initialized there because this information is not available in the bitstream. */ /* open it */ 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) { // 迭代結(jié)束后釋放 av_read_frame 分配的 packet 內(nèi)存 //std::shared_ptr<AVPacket> packetDeleter(&pkt, av_packet_unref); if(index >0) break; // 說明讀取的視頻數(shù)據(jù) 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) { // 從解碼緩沖區(qū)接收解碼后的數(shù)據(jù) if ((ret = avcodec_receive_frame(c, frame)) < 0) { if (ret == AVERROR_EOF) { exit(1); // 解碼緩沖區(qū)結(jié)束了,那么也要flush編碼緩沖區(qū) //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); // 圖像轉(zhuǎn)換 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); } } // 因?yàn)槊恳淮巫x取的AVpacket的數(shù)據(jù)大小不一樣,所以用完之后要釋放 av_packet_unref(pkt); } fclose(f); av_parser_close(parser); avcodec_free_context(&c); av_frame_free(&frame); av_packet_free(&pkt); }
2、創(chuàng)建自定義繪制控件
(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) { } /** * @brief 傳入Qimage圖片顯示 * @param image */ void PlayImage::updateImage(const QImage& image) { updatePixmap(QPixmap::fromImage(image)); } /** * @brief 傳入QPixmap圖片 * @param pixmap */ void PlayImage::updatePixmap(const QPixmap &pixmap) { m_pixmap = pixmap; update(); } /** * @brief 使用Qpainter顯示圖片 * @param event */ void PlayImage::paintEvent(QPaintEvent *event) { if(!m_pixmap.isNull()) { QPainter painter(this); #if 0 // 經(jīng)過粗略測試,QImage先縮放后轉(zhuǎn)為QPixmap的方式在圖像比較小時耗時少,圖片越大耗時遠(yuǎn)大 QPixmap pixmap = QPixmap::fromImage(m_image.scaled(this->size(), Qt::KeepAspectRatio)); // 先將QImage轉(zhuǎn)換為QPixmap再進(jìn)行縮放則耗時比較少,并且穩(wěn)定,不會因?yàn)榭s放圖片大小而產(chǎn)生太大影響 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的設(shè)計(jì)界面,拖一個Widget到主界面,然后在Widget上點(diǎn)擊右鍵,然后選擇提升為,在提升的類名稱處輸入上面自定義控件的類名。
如果選擇下面的全局包含,就不用再單獨(dú)包含頭文件了。
4、開啟線程,進(jìn)行視頻解析
(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)繪制結(jié)果
差不多下面這個樣子。
到此這篇關(guān)于QT+ffmpeg實(shí)現(xiàn)視頻解析的示例詳解的文章就介紹到這了,更多相關(guān)QT ffmpeg視頻解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語言swap(a,b)值交換的4種實(shí)現(xiàn)方法
c語言swap(a,b)值交換的4種實(shí)現(xiàn)方法,這么好的東西,盡管簡單,但值得發(fā)表,以此共享。2013-02-02Qt與Web混合開發(fā)實(shí)現(xiàn)雙向通信的示例
本文主要介紹了Qt與Web混合開發(fā)實(shí)現(xiàn)雙向通信的示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07C++語言實(shí)現(xiàn)線性表之?dāng)?shù)組實(shí)例
這篇文章主要介紹了C++語言實(shí)現(xiàn)線性表之?dāng)?shù)組,實(shí)例分析了C++實(shí)現(xiàn)數(shù)組形式線性表的原理與方法,需要的朋友可以參考下2015-04-04C/C++實(shí)現(xiàn)快速排序算法的思路及原理解析
這篇文章主要介紹了C/C++實(shí)現(xiàn)快速排序算法的思路及原理解析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01C語言調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片
這篇文章主要為大家詳細(xì)介紹了C語言如何調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以參考一下2023-11-11