1. 参考文章
FFmpeg 4.x 从入门到精通(二)—— QT 中用 FFmpeg 实现硬解码并使用QImage显示_q2nAmor的博客-CSDN博客_av_hwdevice_ctx_create
2.环境
Qt + ffmpeg + cuda 下载ffmpeg431,因为网上现版本初入较难,我用的是先前前辈的库等内容
bin文件下内容:
include文件下内容
lib文件下内容
3.代码功能:打开H264编码格式的.mp4文件,通过硬件设备对MP4文件进行转码。即不占用CPU资源的情况下解析视频
1).pro工程文件需要识别库
$$PWD代表当前工程(.pro)所在的路径
INCLUDEPATH += $$PWD/ffmpeg/include
LIBS += $$PWD/ffmpeg/lib/avcodec.lib \
$$PWD/ffmpeg/lib/avdevice.lib \
$$PWD/ffmpeg/lib/avfilter.lib \
$$PWD/ffmpeg/lib/avformat.lib \
$$PWD/ffmpeg/lib/avutil.lib \
$$PWD/ffmpeg/lib/postproc.lib \
$$PWD/ffmpeg/lib/swresample.lib \
$$PWD/ffmpeg/lib/swscale.lib
###################链接动态库
# -L表示路径 -l(小写的L)表示动态库名
LIBS += -L$$PWD/ffmpeg/bin -lavcodec-58
LIBS += -L$$PWD/ffmpeg/bin -lavdevice-58
LIBS += -L$$PWD/ffmpeg/bin -lavformat-58
LIBS += -L$$PWD/ffmpeg/bin -lpostproc-55
LIBS += -L$$PWD/ffmpeg/bin -lswresample-3
LIBS += -L$$PWD/ffmpeg/bin -lswscale-5
2)main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.init();
w.play();
return a.exec();
}
3)mainwindow.h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include<thread>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include <libavutil/pixdesc.h>
#include <libavutil/hwcontext.h>
#include <libavutil/opt.h>
#include <libavutil/avassert.h>
#include <libavutil/imgutils.h>
}
typedef struct DecodeContext {
AVBufferRef *hw_device_ref;
} DecodeContext;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void init();
void play();
private:
AVBufferRef *hw_device_ctx = nullptr;
static enum AVPixelFormat hw_pix_fmt;
int ret;
enum AVHWDeviceType type;
std::thread m_decodecThread;
Ui::MainWindow *ui;
AVFormatContext *pAVFormatCtx;
AVCodecContext *pAVCodecCtx;
SwsContext *pSwsCtx = nullptr;
uint8_t *pRgbBuffer = nullptr;
AVPacket packet;
AVFrame *pAVFrameRGB = nullptr;
int iVideoIndex = -1;
QImage m_image;
bool isFinish =false;
void decodec();
signals:
void signalDraw();
public slots:
void slotDraw();
protected:
void paintEvent(QPaintEvent *event) override;
private:
int hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type);
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx,const enum AVPixelFormat *pix_fmts);
};
#endif // MAINWINDOW_H
4)mainwindow.c文件
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QPainter>
#include<thread>
#include <QDateTime>
enum AVPixelFormat MainWindow::hw_pix_fmt = AV_PIX_FMT_NONE;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(this,&MainWindow::signalDraw,this,&MainWindow::slotDraw);
}
MainWindow::~MainWindow()
{
delete ui;
}
int MainWindow::hw_decoder_init(AVCodecContext *ctx, const enum AVHWDeviceType type)
{
int err = 0;
//初始化硬件,打开硬件,绑定到具体硬件的指针函数上
//创建硬件设备相关的上下文信息AVHWDeviceContext,包括分配内存资源、对硬件设备进行初始化
if ((err = av_hwdevice_ctx_create(&hw_device_ctx, type,
nullptr, nullptr, 0)) < 0) {
qDebug()<<"Failed to create specified HW device.\n";
return err;
}
/* 需要把这个信息绑定到AVCodecContext
* 如果使用软解码则默认有一个软解码的缓冲区(获取AVFrame的),而硬解码则需要额外创建硬件解码的缓冲区
* 这个缓冲区变量为hw_frames_ctx,不手动创建,则在调用avcodec_send_packet()函数内部自动创建一个
* 但是必须手动赋值硬件解码缓冲区引用hw_device_ctx(它是一个AVBufferRef变量)
*/
ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
return err;
}
enum AVPixelFormat MainWindow::get_hw_format(AVCodecContext *ctx,const enum AVPixelFormat *pix_fmts)
{
const enum AVPixelFormat *p;
for (p = pix_fmts; *p != -1; p++) {
if (*p == hw_pix_fmt)
return *p;
}
qDebug()<<"Failed to get HW surface format.\n";
return AV_PIX_FMT_NONE;
}
void MainWindow::init()
{
//文件路径
std::string file = "C:\\Users\\qf\\Desktop\\image\\15.mp4";
//描述多媒体文件的构成及其基本信息,MP4文件的操作句柄
if (avformat_open_input(&pAVFormatCtx, file.data(), nullptr, nullptr) != 0)
{
qDebug() <<"open file fail";
avformat_free_context(pAVFormatCtx);
return;
}
//解析音视频流信息
if (avformat_find_stream_info(pAVFormatCtx, nullptr) < 0)
{
qDebug() <<"vformat find stream fail";
avformat_close_input(&pAVFormatCtx);
return;
}
//根据解码器枚举类型找到解码器
AVCodec *pAVCodec;
int ret = av_find_best_stream(pAVFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pAVCodec, 0);
if (ret < 0) {
qDebug()<< "av_find_best_stream faliture";
avformat_close_input(&pAVFormatCtx);
return;
}
qDebug()<< "av_find_best_stream 成功";
iVideoIndex = ret;
//查找解码器
type = av_hwdevice_find_type_by_name("cuda");
if (type == AV_HWDEVICE_TYPE_NONE) {
qDebug()<<"不支持所输入的解码器"<<type;
qDebug()<<"Available device types:";
while((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)
qDebug()<<av_hwdevice_get_type_name(type);
}
qDebug()<< "av_hwdevice_find_type_by_name 寻找目标解码器成功";
// 所有支持的硬件解码器保存在AVCodec的hw_configs变量中。对于硬件编码器来说又是单独的AVCodec
for (int i = 0;; i++)
{
//获取到该解码器codec的硬件属性,比如可以支持的目标像素格式等
const AVCodecHWConfig *config = avcodec_get_hw_config(pAVCodec, i);
if (!config) {
qDebug()<< "avcodec_get_hw_config 失败";
qDebug()<< "Decoder %s does not support device type %s.\n"<<pAVCodec->name, av_hwdevice_get_type_name(type);
}
if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX &&config->device_type == type) {
hw_pix_fmt = config->pix_fmt;
qDebug()<< "avcodec_get_hw_config 成功";
break;
}
}
//申请空间 用于流中内容传参
pAVCodecCtx = avcodec_alloc_context3(pAVCodec);
if (pAVCodecCtx == nullptr)
{
qDebug() <<"avcodec_alloc_context3 申请空间失败";
avformat_close_input(&pAVFormatCtx);
return;
}
//传参
ret = avcodec_parameters_to_context(pAVCodecCtx,pAVFormatCtx->streams[iVideoIndex]->codecpar);
if (ret < 0)
{
qDebug() <<"avcodec_parameters_to_context 传参过程失败";
avformat_close_input(&pAVFormatCtx);
return;
}
//配置获取硬件加速器像素格式的函数;该函数实际上就是将AVCodec中AVHWCodecConfig中的pix_fmt返回
pAVCodecCtx->get_format = get_hw_format;
//使用自定义函数进行判断
if (hw_decoder_init(pAVCodecCtx, type) < 0)
{
qDebug()<<"hw_decoder_init 自定义函数失败";
return ;
}
//打开解码器
if (avcodec_open2(pAVCodecCtx, pAVCodec, nullptr) < 0)
{
qDebug()<<"avcodec_open2 解码器打开失败";
return;
}
//为用于解码的帧申请内存,打印视频宽高信息
pAVFrameRGB = av_frame_alloc();
qDebug()<<"pAVCodecCtx->width:" << pAVCodecCtx->width<<"pAVCodecCtx->height:" << pAVCodecCtx->height;
int size = av_image_get_buffer_size(AVPixelFormat(AV_PIX_FMT_RGB32), pAVCodecCtx->width, pAVCodecCtx->height, 1);
pRgbBuffer = (uint8_t *)(av_malloc(size));
//旧版本avpicture_fill,将申请好的空间进行分配
av_image_fill_arrays(pAVFrameRGB->data, pAVFrameRGB->linesize, pRgbBuffer, AV_PIX_FMT_RGB32,
pAVCodecCtx->width, pAVCodecCtx->height, 1);
//AVpacket 用来存放解码数据
av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
qDebug()<<"pAVCodecCtx->pix_fmt:" << pAVCodecCtx->pix_fmt;
qDebug()<<"init 完成";
}
void MainWindow::slotDraw()
{
update();
}
void MainWindow::paintEvent(QPaintEvent *event)
{
qDebug()<<"绘画事件进入";
QPainter painter(this);
painter.setBrush(Qt::black);
painter.drawRect(0, 0, this->width(), this->height());
if (m_image.size().width() <= 0)
return;
//比例缩放
QImage img = m_image.scaled(this->size(),Qt::KeepAspectRatio);
int x = this->width() - img.width();
int y = this->height() - img.height();
x /= 2;
y /= 2;
//QPoint(x,y)为中心绘制图像
painter.drawImage(QPoint(x,y),img);
}
void MainWindow::play()
{
qDebug()<<"播放开始";
m_decodecThread = std::thread([this](){decodec();});
m_decodecThread.detach();
}
void MainWindow::decodec()
{
qDebug()<<"进入解析";
//读取码流中视频帧
while (true)
{
AVFrame *frame = nullptr, *sw_frame = nullptr;
AVFrame *tmp_frame = nullptr;
int ret = av_read_frame(pAVFormatCtx, &packet);
if(ret != 0)
{
qDebug()<<"file end";
isFinish = !isFinish;
return;
}
if (packet.stream_index != iVideoIndex)
{
av_packet_unref(&packet);
continue;
}
int iGotPic = AVERROR(EAGAIN);
//解码一帧视频数据
iGotPic = avcodec_send_packet(pAVCodecCtx, &packet);
if(iGotPic!=0){
qDebug()<<"avcodec_send_packet error";
continue;
}
if (!(frame = av_frame_alloc()) || !(sw_frame = av_frame_alloc())) {
qDebug()<< "Can not alloc frame\n";
ret = AVERROR(ENOMEM);
continue;
}
while (0 == avcodec_receive_frame(pAVCodecCtx, frame))
{
qDebug()<<"帧数据解析开始";
qDebug()<<"frame->format:" << frame->format;//53 AV_PIX_FMT_DXVA2_VLD
if (frame->format == hw_pix_fmt) {
int64_t time = QDateTime::currentDateTime().toMSecsSinceEpoch();
qDebug() << "qhttime1:" << time;
if ((ret = av_hwframe_transfer_data(sw_frame, frame, 0)) < 0)
{
qDebug()<<"从GPU到CPU数据失败";
break;
}
qDebug()<<"屏幕投影GPU数据";
tmp_frame = sw_frame;
} else
{
qDebug()<<"屏幕投影开始数据";
tmp_frame = frame;
qDebug()<<"frame img";
}
pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, (AVPixelFormat)tmp_frame->format,
pAVCodecCtx->width, pAVCodecCtx->height, AV_PIX_FMT_RGB32,
SWS_BILINEAR, nullptr, nullptr, nullptr);
int ret = sws_scale(pSwsCtx, (uint8_t const * const *) tmp_frame->data, tmp_frame->linesize, 0,
tmp_frame->height, pAVFrameRGB->data, pAVFrameRGB->linesize);
qDebug()<<"ret:" << ret;
QImage img((uint8_t *)pAVFrameRGB->data[0], tmp_frame->width, tmp_frame->height, QImage::Format_RGB32);
qDebug()<<"开始画图";
m_image = img;
qDebug()<<"发送信号";
emit signalDraw();
std::this_thread::sleep_for(std::chrono::milliseconds(40));
qDebug()<<"一帧数据结束";
qDebug()<<"开始回收一帧结束后的资源";
sws_freeContext(pSwsCtx);
}
av_frame_free(&frame);
av_frame_free(&sw_frame);
}
qDebug()<<"开始回收资源";
av_free(pAVFrameRGB);
avcodec_close(pAVCodecCtx);
avformat_close_input(&pAVFormatCtx);
}