当前位置: 首页 > article >正文

FFmpeg + ‌Qt‌ 简单视频播放器代码

一个基于 ‌FFmpeg 4.x‌ 和 ‌Qt‌ 的简单视频播放器代码示例,实现视频解码和渲染到 Qt 窗口的功能。

1)ffmpeg库界面,视频解码支持软解和硬解方式。

2)QImage/QPixmap显示视频图片。

1. Qt 项目配置(.pro 文件)

QT       += core guigreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsCONFIG += c++11INCLUDEPATH += $$PWD/ffmpeg-4.2.2-win32/include
LIBS += -L$$PWD/ffmpeg-4.2.2-win32/lib -lavcodec -lavformat -lavutil -lswscale# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0SOURCES += \main.cpp \mainwindow.cpp \playimage.cpp \videodecode.cppHEADERS += \mainwindow.h \playimage.h \videodecode.hFORMS += \mainwindow.ui# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2. 视频解码类

‌文件 videodecode.h

#ifndef VIDEODECODE_H
#define VIDEODECODE_H//视频解码类
#include <QString>
#include <QImage>
#include <thread>extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}//流类型
enum StreamType
{StreamType_Video        = 0,StreamType_Audio        = 1,StreamType_Text         = 2,
};//格式类型
enum FormatType
{FormatType_RGB24        =   0,FormatType_RGB32        =   1,FormatType_YUV420       =   2,FormatType_YUV422       =   3,
};//文件状态
enum  FileStatus
{FileStatus_OverFileTail     =   0,  //达到文件尾FileStatus_OverFileHead     =   1,  //达到文件头FileStatus_TrigeException   =   2,  //发生异常
};//流解码回调函数
typedef void (*StreamDecodeCallback)(int nStreamType, int nFormatType, long long llDecodeTs, long long llPlayTs, int width, int height, unsigned char ** pStreamData, int * linesize, void * pUserData);//文件状态回调函数
typedef  void (*FileStatusCallback)(int FileStatus, int nErrorCode, void * pUserData);class VideoDecode
{
public:VideoDecode();~VideoDecode();public:void globalInit();//初始化ffmpeg库(整个程序中只需加载一次)void globalUnInit();//反初始化ffmpeg库(整个程序中只需加载一次)public:void setStreamDecodeCallback(StreamDecodeCallback funStreamDecodeCallback, void * userData);void setFileStatusCallback(FileStatusCallback funFileStatusCallback, void * userData);void setHWDecoder(bool flag);                 // 是否使用硬件解码器bool isHWDecoder();bool open(const QString& url);              // 打开媒体文件,或者流媒体rtmp、strp、httpvoid close();                               // 关闭bool isClose();public:void decodeProccessThread();                //解码线程static QImage ConvertRGB24FrameToQImage(unsigned char *data, int width, int height);protected:void initHWDecoder(const AVCodec *codec);bool dataCopy();    //硬件解码完成需要将数据从GPU复制到CPUvoid freeDecode();qreal rationalToDouble(AVRational* rational);private:// FFmpeg 相关对象AVFormatContext *formatCtx = nullptr;AVCodecContext *codecCtx = nullptr;AVFrame *frame = nullptr, *rgbFrame = nullptr;AVFrame *frameHW = nullptr;SwsContext *swsCtx = nullptr;uchar* buffer = nullptr;                      // YUV图像需要转换位RGBA图像,这里保存转换后的图形数据AVPacket* packet = nullptr;int videoStreamIndex = -1;  // 视频流索引qint64 totalTime    = 0;                    // 视频总时长qint64 totalFrames  = 0;                    // 视频总帧数qint64 obtainFrames = 0;                    // 视频当前获取到的帧数qint64 pts          = 0;                    // 图像帧的显示时间qreal  frameRate    = 0;                    // 视频帧率int  width = 0;         //视频分辨率大小widthint  height = 0;        //视频分辨率大小heightstd::vector<int> vecHWDeviceTypes;            // 保存当前环境支持的硬件解码器AVBufferRef* hw_device_ctx = nullptr;         // 对数据缓冲区的引用bool   hwDecoderFlag = false;                 // 记录是否使用硬件解码std::thread threadDecode;bool stopWorkFlag = true;StreamDecodeCallback funCallbackByStreamDecode = nullptr;void * userDataByStreamDecode = nullptr;FileStatusCallback funCallbackByFileStatus = nullptr;void * userDataByFileStatus = nullptr;
};#endif // VIDEODECODE_H

‌文件 videodecode.cpp

#include "videodecode.h"
#include <QTime>
#include <QDebug>
#include <QStringList>
#include <chrono>/*********************************** FFmpeg获取GPU硬件解码帧格式的回调函数 *****************************************/
static enum AVPixelFormat g_pixelFormat;/*** @brief      回调函数,获取GPU硬件解码帧的格式* @param s* @param fmt* @return*/
AVPixelFormat get_hw_format(AVCodecContext* s, const enum AVPixelFormat* fmt)
{Q_UNUSED(s)const enum AVPixelFormat* p;for (p = fmt; *p != -1; p++){if(*p == g_pixelFormat){return *p;}}qDebug() << "无法获取硬件表面格式.";         // 当同时打开太多路视频时,如果超过了GPU的能力,可能会返回找不到解码帧格式return AV_PIX_FMT_NONE;
}
/************************************************ END ******************************************************/VideoDecode::VideoDecode()
{}VideoDecode::~VideoDecode()
{
}void VideoDecode::globalInit()
{//        av_register_all();         // 已经从源码中删除/*** 初始化网络库,用于打开网络流媒体,此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。* 一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数不再有任何用途。*/avformat_network_init();
}void VideoDecode::globalUnInit()
{avformat_network_deinit();
}qreal VideoDecode::rationalToDouble(AVRational* rational)
{qreal frameRate = (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den);return frameRate;
}void VideoDecode::setStreamDecodeCallback(StreamDecodeCallback funStreamDecodeCallback, void * userData)
{funCallbackByStreamDecode = funStreamDecodeCallback;userDataByStreamDecode = userData;
}
void VideoDecode::setFileStatusCallback(FileStatusCallback funFileStatusCallback, void * userData)
{funCallbackByFileStatus = funFileStatusCallback;userDataByFileStatus = userData;
}//初始化硬件解码器
void VideoDecode::initHWDecoder(const AVCodec *codec)
{if(!codec) return;for(int i = 0; ; i++){const AVCodecHWConfig* config = avcodec_get_hw_config(codec, i);    // 检索编解码器支持的硬件配置。if(!config){qDebug() << "打开硬件解码器失败!";return;          // 没有找到支持的硬件配置}if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)       // 判断是否是设备类型{for(auto i : vecHWDeviceTypes){if(config->device_type == AVHWDeviceType(i))                 // 判断设备类型是否是支持的硬件解码器{g_pixelFormat = config->pix_fmt;// 打开指定类型的设备,并为其创建AVHWDeviceContext。int ret = av_hwdevice_ctx_create(&hw_device_ctx, config->device_type, nullptr, nullptr, 0);if(ret < 0){freeDecode();return ;}qDebug() << "打开硬件解码器:" << av_hwdevice_get_type_name(config->device_type);codecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx);  // 创建一个对AVBuffer的新引用。codecCtx->get_format = get_hw_format;                    // 由一些解码器调用,以选择将用于输出帧的像素格式return;}}}}
}//硬件解码完成需要将数据从GPU复制到CPU
bool VideoDecode::dataCopy()
{if(frame->format != g_pixelFormat){av_frame_unref(frame);return false;}// av_hwframe_map处理速度比av_hwframe_transfer_data快(av_hwframe_map在ffmpeg3.3以后才有)int ret = av_hwframe_map(frameHW, frame, AV_HWFRAME_MAP_DIRECT);                   // 映射硬件数据帧/*av_hwframe_map 映射硬件数据帧,第3个参数值有三种类型:AV_HWFRAME_MAP_READ:目标帧可读。AV_HWFRAME_MAP_WRITE:目标帧可写。AV_HWFRAME_MAP_DIRECT:避免数据拷贝(依赖硬件支持)‌。优先使用 AV_HWFRAME_MAP_DIRECT 减少内存拷贝开销‌。使用AV_HWFRAME_MAP_DIRECT时,你应该确保你的应用逻辑不会修改通过映射获得的软件帧内容,以避免不期望的副作用。使用AV_HWFRAME_MAP_READ时,你将获得数据的一致性但可能会有性能上的损失。*/if(ret >= 0){//映射硬件数据帧成功frameHW->width = frame->width;frameHW->height = frame->height;}else{//映射硬件数据帧失败ret = av_hwframe_transfer_data(frameHW, frame, 0);       // 将解码后的数据从GPU复制到CPU(frameHW) 比较耗时,但硬解码速度比软解码快很多if(ret < 0){av_frame_unref(frame);return false;}av_frame_copy_props(frameHW, frame);   // 仅将“metadata”字段从src复制到dst。}return true;
}void VideoDecode::setHWDecoder(bool flag)
{hwDecoderFlag = flag;
}
bool VideoDecode::isHWDecoder()
{return hwDecoderFlag;
}bool VideoDecode::open(const QString& url)
{if(url.isNull()) return false;AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;      // ffmpeg支持的硬件解码器QStringList strTypes;while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)       // 遍历支持的设备类型。{vecHWDeviceTypes.push_back(type);const char* ctype = av_hwdevice_get_type_name(type);  // 获取AVHWDeviceType的字符串名称。if(ctype){strTypes.append(QString(ctype));}}qDebug() << "支持的硬件解码器:";qDebug() << strTypes;AVDictionary* dict = nullptr;av_dict_set(&dict, "rtsp_transport", "tcp", 0);      // 设置rtsp流使用tcp打开,如果打开失败错误信息为【Error number -135 occurred】可以切换(UDP、tcp、udp_multicast、http),比如vlc推流就需要使用udp打开av_dict_set(&dict, "max_delay", "3", 0);             // 设置最大复用或解复用延迟(以微秒为单位)。当通过【UDP】 接收数据时,解复用器尝试重新排序接收到的数据包(因为它们可能无序到达,或者数据包可能完全丢失)。这可以通过将最大解复用延迟设置为零(通过max_delayAVFormatContext 字段)来禁用。av_dict_set(&dict, "timeout", "1000000", 0);         // 以微秒为单位设置套接字 TCP I/O 超时,如果等待时间过短,也可能会还没连接就返回了。// 打开输入流并返回解封装上下文int ret = avformat_open_input(&formatCtx,          // 返回解封装上下文url.toStdString().data(),  // 打开视频地址nullptr,                   // 如果非null,此参数强制使用特定的输入格式。自动选择解封装器(文件格式)&dict);                    // 参数设置// 释放参数字典if(dict){av_dict_free(&dict);}// 打开视频失败if(ret < 0){qDebug() << "Failed to avformat_open_input";return false;}// 读取媒体文件的数据包以获取流信息。ret = avformat_find_stream_info(formatCtx, nullptr);if(ret < 0){qDebug() << "Failed to avformat_find_stream_info";freeDecode();return false;}totalTime = formatCtx->duration / (AV_TIME_BASE / 1000); // 计算视频总时长(毫秒)qDebug() << QString("视频总时长:%1 ms,[%2]").arg(totalTime).arg(QTime::fromMSecsSinceStartOfDay(int(totalTime)).toString("HH:mm:ss zzz"));// 通过AVMediaType枚举查询视频流ID(也可以通过遍历查找),最后一个参数无用videoStreamIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if(videoStreamIndex < 0){qDebug() << "Failed to av_find_best_stream";freeDecode();return false;}AVStream* videoStream = formatCtx->streams[videoStreamIndex];  // 通过查询到的索引获取视频流// 获取视频图像分辨率(AVStream中的AVCodecContext在新版本中弃用,改为使用AVCodecParameters)width = videoStream->codecpar->width;height = videoStream->codecpar->height;frameRate = rationalToDouble(&videoStream->avg_frame_rate);  // 视频帧率// 通过解码器ID获取视频解码器(新版本返回值必须使用const)const AVCodec* codec = avcodec_find_decoder(videoStream->codecpar->codec_id);totalFrames = videoStream->nb_frames;qDebug() << QString("分辨率:[w:%1,h:%2] 帧率:%3  总帧数:%4  解码器:%5").arg(width).arg(height).arg(frameRate).arg(totalFrames).arg(codec->name);// 分配AVCodecContext并将其字段设置为默认值。codecCtx = avcodec_alloc_context3(codec);if(!codecCtx){qDebug() << "Failed to avcodec_alloc_context3";freeDecode();return false;}// 使用视频流的codecpar为解码器上下文赋值ret = avcodec_parameters_to_context(codecCtx, videoStream->codecpar);if(ret < 0){qDebug() << "Failed to avcodec_parameters_to_context";freeDecode();return false;}codecCtx->flags2 |= AV_CODEC_FLAG2_FAST;    // 允许不符合规范的加速技巧。codecCtx->thread_count = 8;                 // 使用8线程解码if(isHWDecoder()){initHWDecoder(codec);     // 初始化硬件解码器(在avcodec_open2前调用)}// 初始化解码器上下文,如果之前avcodec_alloc_context3传入了解码器,这里设置NULL就可以ret = avcodec_open2(codecCtx, nullptr, nullptr);if(ret < 0){qDebug() << "Failed to avcodec_open2";freeDecode();return false;}// 分配AVPacket并将其字段设置为默认值。packet = av_packet_alloc();if(!packet){qDebug() << "Failed to av_packet_alloc";freeDecode();return false;}// 初始化帧和转换上下文frame = av_frame_alloc();rgbFrame = av_frame_alloc();frameHW = av_frame_alloc();int size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecCtx->width, codecCtx->height, 1);buffer = (uint8_t *)av_malloc(size + 1000);av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,codecCtx->width, codecCtx->height, 1);/*// 初始化 SWS 上下文(YUV -> RGB 转换)swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB24,SWS_BILINEAR, nullptr, nullptr, nullptr);*/stopWorkFlag = false;std::thread t(std::bind(&VideoDecode::decodeProccessThread,this));threadDecode = std::move(t);return true;
}void VideoDecode::close()
{stopWorkFlag = true;// 因为avformat_flush不会刷新AVIOContext (s->pb)。如果有必要,在调用此函数之前调用avio_flush(s->pb)。if(formatCtx && formatCtx->pb){avio_flush(formatCtx->pb);}if(formatCtx){avformat_flush(formatCtx);   // 清理读取缓冲}if(threadDecode.joinable()){threadDecode.join();}freeDecode();
}bool VideoDecode::isClose()
{return stopWorkFlag;
}QImage VideoDecode::ConvertRGB24FrameToQImage(unsigned char *data, int width, int height)
{// 创建 QImage 并显示QImage img(data, width, height, QImage::Format_RGB888);return img;
}void VideoDecode::decodeProccessThread()
{std::chrono::high_resolution_clock::time_point tpStart = std::chrono::high_resolution_clock::now();int nWaitTimes = 40;if(frameRate != 0){nWaitTimes = 1000.0/frameRate;}long long llDecodeTs = 0;long long llPlayTs = 0;long long llStartPlayTs = 0;bool bStartPlayTsSetValueFlag = false;bool bProccessFileTail = false;while (true){if(stopWorkFlag){break;}// 读取下一帧数据int readRet = av_read_frame(formatCtx, packet);if(readRet < 0){if (readRet == AVERROR_EOF){int ret = avcodec_send_packet(codecCtx, packet); // 读取完成后向解码器中传如空AVPacket,否则无法读取出最后几帧if(ret < 0){av_packet_unref(packet);bProccessFileTail = true;break;}}else{break;}}else{if(stopWorkFlag){break;}if(packet->stream_index == videoStreamIndex)     // 如果是图像数据则进行解码{av_packet_rescale_ts(packet, formatCtx->streams[videoStreamIndex]->time_base, codecCtx->time_base); // 转换至解码器时间基‌// 将读取到的原始数据包传入解码器int ret = avcodec_send_packet(codecCtx, packet);if(ret < 0){qDebug() << "Error sending packet";av_packet_unref(packet);continue;}}else{//其他流(比如:音频)av_packet_unref(packet);continue;}}// 接收解码后的帧(这里一次只解码一帧)int ret = avcodec_receive_frame(codecCtx, frame);if (ret == AVERROR(EAGAIN)){av_packet_unref(packet);continue;}else if (ret == AVERROR_EOF){av_packet_unref(packet);//当无法读取到AVPacket并且解码器中也没有数据时表示读取完成bProccessFileTail = true;break;}else if (ret < 0){qDebug() << "Error during decoding";av_packet_unref(packet);continue;}else{// 这样写是为了兼容软解码或者硬件解码打开失败情况AVFrame*  frameTemp = frame;if(!frame->data[0])               // 如果是硬件解码就进入{// 将解码后的数据从GPU拷贝到CPUif(!dataCopy()){av_frame_unref(frameHW);continue;}frameTemp = frameHW;}// 处理时间戳的核心逻辑int64_t raw_pts = frameTemp->pts;int64_t raw_dts = frameTemp->pkt_dts;// 处理未定义时间戳的情况if (raw_pts == AV_NOPTS_VALUE){// 使用DTS或估算PTS(需要根据帧率等参数)if(raw_dts != AV_NOPTS_VALUE){raw_pts = raw_dts;}else{raw_pts = 0;raw_dts = 0;}}// 转换为显示时间戳(秒)double display_time = raw_pts * av_q2d(codecCtx->time_base);// 转换为全局时间基(例如用于音视频同步)AVRational timeBaseTemp{1, AV_TIME_BASE};//AV_TIME_BASE_QllPlayTs = av_rescale_q(raw_pts, codecCtx->time_base, timeBaseTemp);llDecodeTs = av_rescale_q(raw_dts, codecCtx->time_base, timeBaseTemp);if(!bStartPlayTsSetValueFlag){llStartPlayTs = llPlayTs;bStartPlayTsSetValueFlag = true;}qDebug("Frame:%4d PTS:%lld display_time:%.2f DTS:%lld llPlayTs:%lld llDecodeTs:%lld packet dts:%lld pts:%lld",codecCtx->frame_number, raw_pts, display_time, raw_dts, llPlayTs, llDecodeTs, packet->dts, packet->pts);av_packet_unref(packet);  // 释放数据包,引用计数-1,为0时释放空间if(!swsCtx || (frameTemp->width != width || frameTemp->height != height)){//重新申请width = frameTemp->width;height = frameTemp->height;if(swsCtx){sws_freeContext(swsCtx);swsCtx = nullptr;}if(buffer){av_free(buffer);buffer = nullptr;}int size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, frameTemp->width, frameTemp->height, 1);buffer = (uint8_t *)av_malloc(size + 1000);av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,frameTemp->width, frameTemp->height, 1);swsCtx = sws_getCachedContext(swsCtx,frameTemp->width,                     // 输入图像的宽度frameTemp->height,                    // 输入图像的高度(AVPixelFormat)frameTemp->format,     // 输入图像的像素格式frameTemp->width,                     // 输出图像的宽度frameTemp->height,                    // 输出图像的高度AV_PIX_FMT_RGB24,                    // 输出图像的像素格式SWS_BILINEAR,                       // 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEARnullptr,                            // 输入图像的滤波器信息, 若不需要传NULLnullptr,                            // 输出图像的滤波器信息, 若不需要传NULLnullptr);}//休眠等待long long llPlayTsDiff = llPlayTs - llStartPlayTs;auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - tpStart);// 计算需要等待的时间(单位:微秒)int64_t delay = llPlayTsDiff - duration.count();// 同步控制if (delay > 0){std::this_thread::sleep_for(std::chrono::microseconds(delay)); // 等待至目标时间‌}else if (delay < -100000){// 允许100ms误差阈值// 丢弃滞后帧,追赶进度‌av_frame_unref(frame);av_frame_unref(frameHW);continue;}// 转换颜色空间到 RGB24sws_scale(swsCtx, frameTemp->data, frameTemp->linesize, 0, frameTemp->height, rgbFrame->data, rgbFrame->linesize);//回调流书籍(方便渲染)if(funCallbackByStreamDecode){funCallbackByStreamDecode(StreamType_Video,FormatType_RGB24,llDecodeTs,llPlayTs,frameTemp->width,frameTemp->height,rgbFrame->data, rgbFrame->linesize, userDataByStreamDecode);}av_frame_unref(frame);av_frame_unref(frameHW);}}if(bProccessFileTail && !stopWorkFlag){if(funCallbackByFileStatus != nullptr){funCallbackByFileStatus(FileStatus_OverFileTail, 0, userDataByFileStatus);}}
}void VideoDecode::freeDecode()
{// 释放资源if (swsCtx){sws_freeContext(swsCtx);swsCtx = nullptr;}if (rgbFrame){av_frame_free(&rgbFrame);rgbFrame = nullptr;}if (frame){av_frame_free(&frame);frame = nullptr;}if(frameHW){av_frame_free(&frameHW);frameHW = nullptr;}if (codecCtx){avcodec_free_context(&codecCtx);codecCtx = nullptr;}if (formatCtx){avformat_close_input(&formatCtx);formatCtx = nullptr;}if(buffer != nullptr){av_free(buffer);buffer = nullptr;}
}

‌3.主窗口调用代码

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);connect(this, SIGNAL(sgnShowImage(QImage)), this, SLOT(sltShowImage(QImage)));vdVideoDecode.globalInit();m_playImages = this->findChildren<PlayImage *>();
}MainWindow::~MainWindow()
{delete ui;vdVideoDecode.globalUnInit();
}void MainWindow::sltShowImage(QImage qimage)
{if(vdVideoDecode.isClose())return;for(int i = 0; i < m_playImages.count(); i++){m_playImages.at(i)->updateImage(qimage);}
}void MainWindow::on_pushButtonOpenFile_clicked(bool checked)
{QString filename = QFileDialog::getOpenFileName(nullptr, "Open Video File");if (!filename.isEmpty()){//vdVideoDecode.setHWDecoder(true);vdVideoDecode.setStreamDecodeCallback([](int nStreamType, int nFormatType, long long llDecodeTs, long long llPlayTs, int width, int height, unsigned char ** pStreamData, int * linesize, void * pUserData){MainWindow *pMainWindow = (MainWindow *)pUserData;QImage qimage = VideoDecode::ConvertRGB24FrameToQImage(pStreamData[0],width,height);emit pMainWindow->sgnShowImage(qimage);},this);vdVideoDecode.setFileStatusCallback([](int FileStatus, int nErrorCode, void * pUserData){qDebug()<<"file is end";},this);vdVideoDecode.open(filename);/*if(player.openFile(filename)){player.show();}*/}
}void MainWindow::on_pushButtonCloseFile_clicked()
{vdVideoDecode.close();
}

完整代码下载:https://gitee.com/byxdaz/ffmpeg-qt-player

相关文章:

FFmpeg + ‌Qt‌ 简单视频播放器代码

一个基于 ‌FFmpeg 4.x‌ 和 ‌Qt‌ 的简单视频播放器代码示例&#xff0c;实现视频解码和渲染到 Qt 窗口的功能。 1&#xff09;ffmpeg库界面&#xff0c;视频解码支持软解和硬解方式。 2&#xff09;QImage/QPixmap显示视频图片。 ‌1. Qt 项目配置&#xff08;.pro 文件&…...

Unity跨平台构建快速回顾

知识点来源&#xff1a;人间自有韬哥在&#xff0c;豆包 目录 一、发布应用程序1. 修改发布必备设置1.1 打开设置面板1.2 修改公司名、游戏项目名、版本号和默认图标1.3 修改 Package Name 和 Minimum API Level 2. 发布应用程序2.1 配置 Build Settings2.2 选择发布选项2.3 构…...

【嵌入式学习2】内存管理

## C语言编译过程 预处理&#xff1a;宏定义展开、头文件展开、条件编译&#xff0c;这里并不会检查语法&#xff0c;将#include #define这些头文件内容插入到源码中 gcc -E main.c -o main.i 编译&#xff1a;检查语法&#xff0c;将预处理后文件编译生成汇编文件&#xff…...

密码学(Public-Key Cryptography and Discrete Logarithms)

Public-Key Cryptography and Discrete Logarithms Discrete Logarithm 核心概念&#xff1a;离散对数是密码学中一个重要的数学问题&#xff0c;特别是在有限域和循环群中。它基于指数运算在某些群中是单向函数这一特性。也就是说&#xff0c;给定一个群 G G G和一个生成元 …...

TDengine又新增一可视化工具 Perspective

概述 Perspective 是一款开源且强大的数据可视化库&#xff0c;由 Prospective.co 开发&#xff0c;运用 WebAssembly 和 Web Workers 技术&#xff0c;在 Web 应用中实现交互式实时数据分析&#xff0c;能在浏览器端提供高性能可视化能力。借助它&#xff0c;开发者可构建实时…...

【Linux文件IO】Linux中标准IO的API的描述和基本用法

Linux中标准IO的API的描述和基本用法 一、标准IO相关API1、文件的打开和关闭示例代码&#xff1a; 2、文件的读写示例代码&#xff1a;用标准IO&#xff08;fread、fwrite&#xff09;实现文件拷贝(任何文件均可拷贝) 3、文件偏移设置示例代码&#xff1a; 4、fgets fputs fget…...

深度学习篇---PaddleDetectionPaddleOCR

文章目录 前言1.代码2.代码介绍2.1 **导入模块**2.2 **配置区域**2.3 ExpressInfoProcessor类2.4 **主程序**&#xff1a; 3.使用说明3.1环境准备3.2模型准备3.3数据库初始化3.4串口配置3.5信息提取优化3.6注意事项 前言 本文简单介绍了PaddleDetection和PaddleOCR相结合的示例…...

Ant Design Vue Select 选择器 全选 功能

Vue.js的组件库Ant Design Vue Select 选择器没有全选功能&#xff0c;如下图所示&#xff1a; 在项目中&#xff0c;我们自己实现了全选和清空功能&#xff0c;如下所示&#xff1a; 代码如下所示&#xff1a; <!--* 参数配置 - 风力发电 - 曲线图 * 猴王软件学院 - 大强 …...

系统与网络安全------网络应用基础(1)

资料整理于网络资料、书本资料、AI&#xff0c;仅供个人学习参考。 TCP/IP协议及配置 概述 TCP/IP协议族 计算机之间进行通信时必须共同遵循的一种通信规定 最广泛使用的通信协议的集合 包括大量Internet应用中的标准协议 支持跨网络架构、跨操作系统平台的数据通信 主机…...

ZIP_STORED和ZIP_LZMA没有compresslevel参数!

在使用py的zipfile库进行压缩的时候&#xff0c;有这么一个函数&#xff1a; def write(self, filename, arcnameNone,compress_typeNone, compresslevelNone): 一般我们在压缩文件进去的时候都是用这个函数的&#xff1b; 对于compresslevel这个函数&#xff0c;它是用来指…...

坦克大战(c++)

今天我给大家分享一个c游戏。 废话不多说&#xff0c;作品展示&#xff1a; #include <stdio.h> #include <windows.h> #include <time.h> //里规格&#xff1a;长39*278 &#xff08;真坐标&#xff09;(假坐标宽为39) 高39 //外规格&#xff1a;长…...

论文阅读:2023 EMNLP SeqXGPT: Sentence-level AI-generated text detection

总目录 大模型安全相关研究:https://blog.csdn.net/WhiffeYF/article/details/142132328 SeqXGPT: Sentence-level AI-generated text detection https://aclanthology.org/2023.emnlp-main.73/ https://github.com/Jihuai-wpy/SeqXGPT https://www.doubao.com/chat/21003…...

JDK 24 发布,新特性解读!

一、版本演进与技术格局新动向 北京时间3月20日&#xff0c;Oracle正式发布Java SE 24。作为继Java 21之后的第三个非LTS版本&#xff0c;其技术革新力度远超预期——共集成24项JEP提案&#xff0c;相当于Java 22&#xff08;12项&#xff09;与Java 23&#xff08;12项&#…...

k8s中service概述(二)NodePort

NodePort 是 Kubernetes 中一种用于对外暴露服务的 Service 类型。它通过在集群的每个节点上开放一个静态端口&#xff08;NodePort&#xff09;&#xff0c;使得外部用户可以通过节点的 IP 地址和该端口访问集群内部的服务。以下是关于 NodePort Service 的详细说明&#xff1…...

Oracle归档配置及检查

配置归档位置到 USE_DB_RECOVERY_FILE_DEST&#xff0c;并设置存储大小 startup mount; !mkdir /db/archivelog ALTER SYSTEM SET db_recovery_file_dest_size100G SCOPEBOTH; ALTER SYSTEM SET db_recovery_file_dest/db/archivelog SCOPEBOTH; ALTER SYSTEM SET log_archive…...

计算机二级:函数基础题

函数基础题 第一题 rinput("请输入半径&#xff1a;") c3.1415926*r*2 print("{:.0f}".format(c))输出&#xff1a; Type Error第二题 a7 b2 print(a%2)输出 1第三题 ab4 def my_ab(ab,xy):abpow(ab,xy)print(ab,end"\n") my_ab(ab,2)prin…...

Python爬虫-爬取AliExpress商品搜索词排名数据

前言 本文是该专栏的第49篇,后面会持续分享python爬虫干货知识,记得关注。 本文,笔者以AliExpress平台为例。基于Python爬虫,通过某个指定的“搜索关键词”,批量获取该“搜索关键词”的商品排名数据。 具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。废…...

AI 时代,我们需要什么样的数据库?

AI 时代&#xff0c;我们需要什么样的数据库&#xff1f; 人工智能正在悄然改变软件开发的方式。过去一年间&#xff0c;诸如 GitHub Spark、Replit 和 Bolt 等新兴 AI 工具层出不穷&#xff0c;能够快速生成简单的前端应用&#xff0c;甚至无需传统意义上的后端服务就能部署上…...

刷机维修进阶教程-----adb禁用错了系统app导致无法开机 如何保数据无损恢复机型

在刷机维修过程中 。我们会遇到一些由于客户使用adb指令来禁用手机app而导致手机无法开机进入系统的故障机型。通常此类问题机型有好几种解决方法。但如果客户需要保数据来恢复机型。其实操作也是很简单的.还有类似误删除应用导致不开机等等如何保数据。 通过博文了解💝💝�…...

Vue3 实战:基于 mxGraph 与 WebSocket 的动态流程图构建

本文将详细介绍如何在 Vue3 项目中集成 mxGraph 可视化库&#xff0c;并通过 WebSocket 实现画布元素的实时更新。适合有 Vue 基础的前端开发者学习参考。 一、技术栈准备 Vue3&#xff1a;采用 Composition API 开发mxGraph&#xff1a;JavaScript 流程图库&#xff08;版本 …...

Ubuntu AX200 iwlwifi-cc-46.3cfab8da.0.tgz无法下载的解决办法

文章目录 前言一、检查网卡是否被识别二、确认内核模块是否可用1.AX200 wifi 要求内核5.12.检查 iwlwifi.ko 是否存在&#xff1a;3.如果未找到&#xff0c;可能是内核模块未正确生成。尝试安装 linux-modules-extra&#xff1a;4.再次检查 iwlwifi.ko 是否存在&#xff1a;5.确…...

蓝桥杯,利用 Vue.js 构建简易任务管理器

在日常开发中&#xff0c;我们经常需要处理各种任务和计划。一个简单且高效的任务管理器可以帮助我们更好地组织和安排时间。今天&#xff0c;我将向大家展示如何使用 Vue.js 构建一个简易的任务管理器。这个项目不仅能够帮助我们更好地理解 Vue.js 的基本语法和功能&#xff0…...

国际机构Gartner发布2025年网络安全趋势

转自&#xff1a;中国新闻网 中新网北京3月14日电 国际机构高德纳(Gartner)14日发布的消息称&#xff0c;网络安全和风险管理在2025年“面临挑战与机遇并存的局面”&#xff0c;“实现转型和提高弹性”对确保企业在快速变化的数字世界中&#xff0c;实现安全且可持续的创新至关…...

【设计模式】单件模式

七、单件模式 单件(Singleton) 模式也称单例模式/单态模式&#xff0c;是一种创建型模式&#xff0c;用于创建只能产生 一个对象实例 的类。该模式比较特殊&#xff0c;其实现代码中没有用到设计模式中经常提起的抽象概念&#xff0c;而是使用了一种比较特殊的语法结构&#x…...

Elasticsearch + Docker:实现容器化部署指南

Elasticsearch是一款强大的分布式搜索和分析引擎&#xff0c;广泛应用于日志分析、全文检索、实时数据分析等场景。而Docker作为一种轻量级的容器化技术&#xff0c;能够帮助开发者快速部署和管理应用。将Elasticsearch与Docker结合&#xff0c;不仅可以简化部署流程&#xff0…...

win32汇编环境,网络编程入门之十一

;win32汇编环境,网络编程入门之十一 ;在上一教程里&#xff0c;我们学习了如何读取大容量的网页内容&#xff0c;在这一教程里&#xff0c;我们学习一下如何在wininet或winhttp机制中提取网页中的超链接 ;>>>>>>>>>>>>>>>>>…...

穿越之程序员周树人的狂人日记Part3__人机共生纪元

穿越之程序员周树人的狂人日记Part3__人机共生纪元 代码知识点&#xff1a;协程、内存管理、版本控制 故事一【协程陷阱】择偶标准的多核运算 故事二【内存泄漏】中产幻觉的垃圾回收 故事三【版本控制】人设仓库的强制推送 故事四【容器化生存】&#xff1a;员工生存之现状 静夜…...

后端——AOP异步日志

需求分析 在SpringBoot系统中&#xff0c;一般会对访问系统的请求做日志记录的需求&#xff0c;确保系统的安全维护以及查看接口的调用情况&#xff0c;可以使用AOP对controller层的接口进行增强&#xff0c;作日志记录。日志保存在数据库当中&#xff0c;为了避免影响接口的响…...

【C#语言】深入理解C#多线程编程:从基础到高性能实践

文章目录 ⭐前言⭐一、多线程的本质价值&#x1f31f;1、现代计算需求&#x1f31f;2、C#线程演进史 ⭐二、线程实现方案对比&#x1f31f;1、传统线程模型&#x1f31f;2、现代任务模型&#xff08;推荐&#xff09;&#x1f31f;3、异步编程范式 ⭐三、线程安全深度解析&…...

第十四章:模板实例化_《C++ Templates》notes

模板实例化 核心知识点解析多选题设计题关键点总结 核心知识点解析 两阶段查找&#xff08;Two-Phase Lookup&#xff09; 原理&#xff1a; 模板在编译时分两个阶段处理&#xff1a; 第一阶段&#xff08;定义时&#xff09;&#xff1a;检查模板语法和非依赖名称&#xff0…...