QT+ffmpeg實(shí)現(xiàn)視頻解析的示例詳解
一、創(chuàng)建QT項(xiàng)目
首先安裝了最新的Community版本,Creator是8.0.1版本了。

然后進(jìn)行項(xiàng)目的創(chuàng)建。

得到的項(xiàng)目沒(méi)有pro文件,而是CMakeLists.txt。
二、引入ffmpeg
從下面下載的ffmpeg-5.0.1-full_build-shared.7z。
https://www.gyan.dev/ffmpeg/builds/
1、復(fù)制頭文件和lib
在項(xiàng)目?jī)?nèi)創(chuàng)建一個(gè)文件夾,我這里起名叫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、簡(jiǎn)單測(cè)試
在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)行,會(huì)看到如下消息窗口,其中顯示的ffmpeg的編譯參數(shù),看到這個(gè)就表明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);
// 打開(kāi)輸入文件
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;
// 說(shuō)明讀取的視頻數(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);
// 對(duì)于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)過(guò)粗略測(cè)試,QImage先縮放后轉(zhuǎn)為QPixmap的方式在圖像比較小時(shí)耗時(shí)少,圖片越大耗時(shí)遠(yuǎn)大
QPixmap pixmap = QPixmap::fromImage(m_image.scaled(this->size(), Qt::KeepAspectRatio));
// 先將QImage轉(zhuǎn)換為QPixmap再進(jìn)行縮放則耗時(shí)比較少,并且穩(wěn)定,不會(huì)因?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ì)界面,拖一個(gè)Widget到主界面,然后在Widget上點(diǎn)擊右鍵,然后選擇提升為,在提升的類名稱處輸入上面自定義控件的類名。
如果選擇下面的全局包含,就不用再單獨(dú)包含頭文件了。

4、開(kāi)啟線程,進(jìn)行視頻解析
(1)mainwindow.cpp
開(kāi)啟線程
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é)果
差不多下面這個(gè)樣子。

到此這篇關(guān)于QT+ffmpeg實(shí)現(xiàn)視頻解析的示例詳解的文章就介紹到這了,更多相關(guān)QT ffmpeg視頻解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語(yǔ)言swap(a,b)值交換的4種實(shí)現(xiàn)方法
c語(yǔ)言swap(a,b)值交換的4種實(shí)現(xiàn)方法,這么好的東西,盡管簡(jiǎn)單,但值得發(fā)表,以此共享。2013-02-02
Qt與Web混合開(kāi)發(fā)實(shí)現(xiàn)雙向通信的示例
本文主要介紹了Qt與Web混合開(kāi)發(fā)實(shí)現(xiàn)雙向通信的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
C++語(yǔ)言實(shí)現(xiàn)線性表之?dāng)?shù)組實(shí)例
這篇文章主要介紹了C++語(yǔ)言實(shí)現(xiàn)線性表之?dāng)?shù)組,實(shí)例分析了C++實(shí)現(xiàn)數(shù)組形式線性表的原理與方法,需要的朋友可以參考下2015-04-04
C/C++實(shí)現(xiàn)快速排序算法的思路及原理解析
這篇文章主要介紹了C/C++實(shí)現(xiàn)快速排序算法的思路及原理解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01
C語(yǔ)言調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言如何調(diào)用攝像頭實(shí)現(xiàn)生成yuv未壓縮圖片,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以參考一下2023-11-11

