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

ffmpeg+QOpenGLWidget显示视频

​一个基于 ‌FFmpeg 4.x‌ 和 QOpenGLWidget的简单视频播放器代码示例,实现视频解码和渲染到 Qt 窗口的功能。
1)ffmpeg库界面,视频解码支持软解和硬解方式。
     硬解后,硬件解码完成需要将数据从GPU复制到CPU。优先采用av_hwframe_map函数,失败后再使用av_hwframe_transfer_data

    av_hwframe_map(frameHW, frame, AV_HWFRAME_MAP_READ);                   // 映射硬件数据帧/*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时,你将获得数据的一致性但可能会有性能上的损失。*/

2)显示帧数据采用QOpenGLWidget。
class FrameOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions{
}
该类支持YUV420P、NV12、RGB帧数据显示。在这种数据之间切换时,调用类中​setFrameDataFormat方法切换。

3)解码后,根据码流类型,更新opengl初始化纹理。以便显示YUV420P、NV12、RGB数据。
码流类型改变后,发送信号出去,UI槽中调用setFrameDataFormat,更新opengl初始化纹理。(因为opengl初始化纹理是非线程安全的,需要在UI或同FrameOpenGLWidget线程中处理。)

4)解码播放时间同步控制。

1. Qt 项目配置(.pro 文件)
QT       += core gui openglgreaterThan(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 += \frameopenglwidget.cpp \main.cpp \mainwindow.cpp \videodecode.cppHEADERS += \YUV420PWidget.h \frameopenglwidget.h \mainwindow.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 += targetDISTFILES += \RESOURCES += \player.qrc
‌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>
}/*
struct AVFormatContext;
struct AVCodecContext;
struct AVRational;
struct AVPacket;
struct AVFrame;
struct AVCodec;
struct SwsContext;
struct AVBufferRef;
*///流类型
enum StreamType
{StreamType_Video        = 0,StreamType_Audio        = 1,StreamType_Text         = 2,
};//格式类型
enum FormatType
{FormatType_RGB24        =   0,FormatType_RGB32        =   1,FormatType_YUV420P      =   2,FormatType_NV12         =   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_READ);                   // 映射硬件数据帧/*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;}if(/*0 && */frameTemp->format == AV_PIX_FMT_YUV420P){//回调流数据(方便渲染)if(funCallbackByStreamDecode && !stopWorkFlag){funCallbackByStreamDecode(StreamType_Video,FormatType_YUV420P,llDecodeTs,llPlayTs,frameTemp->width,frameTemp->height,frameTemp->data, frameTemp->linesize, userDataByStreamDecode);}}else if(frameTemp->format == AV_PIX_FMT_NV12){//回调流数据(方便渲染)if(funCallbackByStreamDecode && !stopWorkFlag){funCallbackByStreamDecode(StreamType_Video,FormatType_NV12,llDecodeTs,llPlayTs,frameTemp->width,frameTemp->height,frameTemp->data, frameTemp->linesize, userDataByStreamDecode);}}else{//其他格式,转换成rgb// 转换颜色空间到 RGB24sws_scale(swsCtx, frameTemp->data, frameTemp->linesize, 0, frameTemp->height, rgbFrame->data, rgbFrame->linesize);//回调流数据(方便渲染)if(funCallbackByStreamDecode && !stopWorkFlag){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);}}qDebug()<<"thread is eixt";
}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. 帧数据显示窗口类

frameopenglwidget.h

#ifndef FRAMEOPENGLWIDGET_H
#define FRAMEOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>//帧数据格式
enum FrameDataFormat
{FrameDataFormat_None         =  -1,FrameDataFormat_RGB24        =   0,FrameDataFormat_RGB32        =   1,FrameDataFormat_YUV420P      =   2,FrameDataFormat_NV12         =   3,
};class FrameOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
public:FrameOpenGLWidget(QWidget *parent = nullptr);virtual ~FrameOpenGLWidget();void  setFrameDataFormat(FrameDataFormat eFrameDataFormat);FrameDataFormat getFrameDataFormat();//更新NV12帧数据void updateNV12Frame(const uint8_t* yData, const uint8_t* uvData, int y_linesize, int uv_linesize, int width, int height);//更新YUV420P数据void updateYUV420PFrame(const uint8_t* yData, const uint8_t* uData, const uint8_t* vData, int y_linesize, int u_linesize,  int v_linesize, int width, int height);//更新RGB24数据void updateRGB24Frame(const uint8_t* rgbData, int width, int height, int linesize);//更新RGB32数据void updateRGB32Frame(const uint8_t* rgbData, int width, int height, int linesize);//更新帧数据void updateFrame(int eFrameDataFormat,  uint8_t **data, int *linesize, int width, int height);protected:void initializeGL() override;               // 初始化glvoid resizeGL(int w, int h) override;       // 窗口尺寸变化void paintGL() override;                    // 刷新显示protected:void initializeGLByNV12Frame();void initTextureByNV12Frame();void releaseTextureByNV12Frame();void paintGLByNV12Frame();void initializeGLByYUV420PFrame();void initTextureByYUV420PFrame();void releaseTextureByYUV420PFrame();void paintGLByYUV420PFrame();void initializeGLByRGBFrame();void initTextureByRGBFrame();void releaseTextureByRGBFrame();void paintGLByRGBFrame();void initializeGLByRGB32Frame();void initTextureByRGB32Frame();void releaseTextureByRGB32Frame();void paintGLByRGB32Frame();private:QOpenGLShaderProgram m_program;GLuint m_yTexture = 0;GLuint m_uvTexture = 0;GLuint m_uTexture = 0;GLuint m_vTexture = 0;GLuint m_rgbTexture = 0;GLuint m_rgb32Texture = 0;uint8_t* m_yData = nullptr;uint8_t* m_uvData = nullptr;uint8_t* m_uData = nullptr;uint8_t* m_vData = nullptr;uint8_t* m_nv12Data = nullptr;uint8_t* m_rgbData = nullptr;uint8_t* m_rgb32Data = nullptr;int m_width = 0;int m_height = 0;QSize  m_size;QSizeF  m_zoomSize;QPointF m_pos;bool m_nFirstUpdateFrame = true;int  m_nInitFrameDataFormat = FrameDataFormat_RGB24;
};#endif // FRAMEOPENGLWIDGET_H

frameopenglwidget.cpp

#include "frameopenglwidget.h"FrameOpenGLWidget::FrameOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{m_size.setWidth(1);m_size.setHeight(1);m_zoomSize.setWidth(1);m_zoomSize.setHeight(1);m_pos.setX(0);m_pos.setY(0);m_nInitFrameDataFormat = FrameDataFormat_NV12;
}FrameOpenGLWidget::~FrameOpenGLWidget()
{if(m_yData != nullptr){delete []m_yData;m_yData = nullptr;}if(m_uData != nullptr){delete []m_uData;m_uData = nullptr;}if(m_vData != nullptr){delete []m_vData;m_vData = nullptr;}if(m_uvData != nullptr){delete []m_uvData;m_uvData = nullptr;}if(m_rgbData != nullptr){delete []m_rgbData;m_rgbData = nullptr;}if(m_rgb32Data != nullptr){delete []m_rgb32Data;m_rgb32Data = nullptr;}makeCurrent();switch (m_nInitFrameDataFormat) {case FrameDataFormat_RGB24:releaseTextureByRGBFrame();break;case FrameDataFormat_RGB32:releaseTextureByRGB32Frame();break;case FrameDataFormat_YUV420P:releaseTextureByYUV420PFrame();break;case FrameDataFormat_NV12:releaseTextureByNV12Frame();break;}doneCurrent();
}void  FrameOpenGLWidget::setFrameDataFormat(FrameDataFormat eFrameDataFormat)
{if(m_nInitFrameDataFormat != eFrameDataFormat){makeCurrent();m_nInitFrameDataFormat = eFrameDataFormat;switch (m_nInitFrameDataFormat) {case FrameDataFormat_RGB24:releaseTextureByRGBFrame();// 初始化纹理initTextureByRGBFrame();initializeGLByRGBFrame();break;case FrameDataFormat_RGB32:releaseTextureByRGB32Frame();// 初始化纹理initTextureByRGB32Frame();initializeGLByRGB32Frame();break;case FrameDataFormat_YUV420P:releaseTextureByYUV420PFrame();// 初始化纹理initTextureByYUV420PFrame();initializeGLByYUV420PFrame();break;case FrameDataFormat_NV12:releaseTextureByNV12Frame();// 初始化纹理initTextureByNV12Frame();initializeGLByNV12Frame();break;}doneCurrent();}
}
FrameDataFormat FrameOpenGLWidget::getFrameDataFormat()
{return (FrameDataFormat)m_nInitFrameDataFormat;
}//更新NV12帧数据
void FrameOpenGLWidget::updateNV12Frame(const uint8_t* yData, const uint8_t* uvData, int y_linesize, int uv_linesize, int width, int height)
{if(m_yData == nullptr || m_uvData == nullptr){if(m_yData != nullptr){delete []m_yData;}if(m_uvData != nullptr){delete []m_uvData;}m_yData = new uint8_t[y_linesize*height + 1];m_uvData = new uint8_t[uv_linesize*height/2 + 1];}else{if(width != m_width || height != m_height){if(m_yData != nullptr && m_uvData != nullptr){delete []m_yData;delete []m_uvData;m_yData = new uint8_t[y_linesize*height + 1];m_uvData = new uint8_t[uv_linesize*height/2 + 1];}}}memcpy(m_yData, yData, y_linesize*height);memcpy(m_uvData, uvData, uv_linesize*height/2);m_width = width;m_height = height;m_size.setWidth(width);m_size.setHeight(height);if(m_nFirstUpdateFrame){resizeGL(size().width(), size().height());m_nFirstUpdateFrame = false;}update();
}
//更新YUV420P数据
void FrameOpenGLWidget::updateYUV420PFrame(const uint8_t* yData, const uint8_t* uData, const uint8_t* vData, int y_linesize, int u_linesize,  int v_linesize, int width, int height)
{if(m_yData == nullptr || m_uData == nullptr || m_vData == nullptr){if(m_yData != nullptr){delete []m_yData;}if(m_uData != nullptr){delete []m_uData;}if(m_vData != nullptr){delete []m_vData;}m_yData = new uint8_t[y_linesize*height + 1];m_uData = new uint8_t[u_linesize*height/2 + 1];m_vData = new uint8_t[v_linesize*height/2 + 1];}else{if(width != m_width || height != m_height){if(m_yData != nullptr && m_uData != nullptr && m_vData != nullptr){delete []m_yData;delete []m_uData;delete []m_vData;m_yData = new uint8_t[y_linesize*height + 1];m_uData = new uint8_t[u_linesize*height/2 + 1];m_vData = new uint8_t[v_linesize*height/2 + 1];}}}memcpy(m_yData, yData, y_linesize*height);memcpy(m_uData, uData, u_linesize*height/2);memcpy(m_vData, vData, v_linesize*height/2);m_width = width;m_height = height;m_size.setWidth(width);m_size.setHeight(height);if(m_nFirstUpdateFrame){resizeGL(size().width(), size().height());m_nFirstUpdateFrame = false;}update();
}
//更新RGB24数据
void FrameOpenGLWidget::updateRGB24Frame(const uint8_t* rgbData, int width, int height, int linesize)
{if(m_rgbData == nullptr){m_rgbData = new uint8_t[linesize*height + 1000];}else{if(width != m_width || height != m_height){if(m_rgbData != nullptr){delete []m_rgbData;m_rgbData = new uint8_t[linesize*height + 1000];}}}memcpy(m_rgbData, rgbData, linesize*height);m_width = width;m_height = height;m_size.setWidth(width);m_size.setHeight(height);if(m_nFirstUpdateFrame){resizeGL(size().width(), size().height());m_nFirstUpdateFrame = false;}update();
}
//更新RGB32数据
void FrameOpenGLWidget::updateRGB32Frame(const uint8_t* rgbData, int width, int height, int linesize)
{if(m_rgb32Data == nullptr){m_rgb32Data = new uint8_t[linesize*height + 1000];}else{if(width != m_width || height != m_height){if(m_rgb32Data != nullptr){delete []m_rgb32Data;m_rgb32Data = new uint8_t[linesize*height + 1000];}}}memcpy(m_rgb32Data, rgbData, linesize*height);m_width = width;m_height = height;m_size.setWidth(width);m_size.setHeight(height);if(m_nFirstUpdateFrame){resizeGL(size().width(), size().height());m_nFirstUpdateFrame = false;}update();
}
//更新帧数据
void FrameOpenGLWidget::updateFrame(int eFrameDataFormat,  uint8_t **data, int *linesize, int width, int height)
{switch (eFrameDataFormat) {case FrameDataFormat_RGB24:updateRGB24Frame(data[0], width, height, linesize[0]);break;case FrameDataFormat_RGB32:updateRGB32Frame(data[0], width, height, linesize[0]);break;case FrameDataFormat_YUV420P:updateYUV420PFrame(data[0],data[1],data[2], linesize[0], linesize[1],linesize[2], width,height);break;case FrameDataFormat_NV12:updateNV12Frame(data[0],data[1],linesize[0], linesize[1],width,height);break;}
}void FrameOpenGLWidget::initializeGLByNV12Frame()
{m_program.removeAllShaders();// 编译着色器m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 330 core\n""layout(location = 0) in vec4 vertexIn;\n""layout(location = 1) in vec2 textureIn;\n""out vec2 textureCoord;\n""void main(void)\n""{\n""    gl_Position = vertexIn;\n""    textureCoord = textureIn;\n""}\n");m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 330 core\n""in vec2 textureCoord;\n""out vec4 fragColor;\n""uniform sampler2D tex_y;\n""uniform sampler2D tex_uv;\n""void main()\n""{\n""    float y = texture(tex_y, textureCoord).r;\n""    vec2 uv = texture(tex_uv, textureCoord).rg;\n""    y = 1.1643 * (y - 0.0625);\n""    float u = uv.x - 0.5;\n""    float v = uv.y - 0.5;\n""    fragColor = vec4(\n""        y + 1.5958 * v,\n""        y - 0.39173 * u - 0.81290 * v,\n""        y + 2.017 * u,\n""        1.0\n""    );\n""}\n");m_program.link();
}
void FrameOpenGLWidget::initTextureByNV12Frame()
{// 初始化纹理glGenTextures(1, &m_yTexture);glGenTextures(1, &m_uvTexture);
}
void FrameOpenGLWidget::releaseTextureByNV12Frame()
{glDeleteTextures(1, &m_yTexture);glDeleteTextures(1, &m_uvTexture);
}
void FrameOpenGLWidget::paintGLByNV12Frame()
{if (!m_yData || !m_uvData) return;glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);#if 1glViewport(m_pos.x(), m_pos.y(), m_zoomSize.width(), m_zoomSize.height());  // 设置视图大小实现图片自适应
#endifm_program.bind();// 更新Y纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, m_yTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_width, m_height, 0, GL_RED, GL_UNSIGNED_BYTE, m_yData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 更新UV纹理glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, m_uvTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, m_width/2, m_height/2, 0, GL_RG, GL_UNSIGNED_BYTE, m_uvData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 设置纹理单元m_program.setUniformValue("tex_y", 0);m_program.setUniformValue("tex_uv", 1);// 绘制矩形static const GLfloat vertices[] = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,};static const GLfloat texCoords[] = {0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 0.0f,};glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texCoords);glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);m_program.release();
}void FrameOpenGLWidget::initializeGLByYUV420PFrame()
{m_program.removeAllShaders();// 编译着色器m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 330 core\n""layout(location = 0) in vec4 vertexIn;\n""layout(location = 1) in vec2 textureIn;\n""out vec2 textureCoord;\n""void main() {\n""    gl_Position = vertexIn;\n""    textureCoord = textureIn;\n""}");m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 330 core\n""in vec2 textureCoord;\n""out vec4 fragColor;\n""uniform sampler2D tex_y;\n""uniform sampler2D tex_u;\n""uniform sampler2D tex_v;\n""const mat3 yuv2rgb = mat3(\n""    1.164383, 1.164383, 1.164383,\n"        // Y系数"    0.0,     -0.391762, 2.017232,\n"         // U系数"    -0.812968, 0.0,      0.0);\n"            // V系数"void main() {\n""    float y = texture(tex_y, textureCoord).r;\n""    float u = texture(tex_u, textureCoord).r;\n""    float v = texture(tex_v, textureCoord).r;\n""    y = 1.1643 * (y - 0.0625);\n""    u = u - 0.5;\n""    v = v - 0.5;\n""    fragColor = vec4(\n""        y + 2.017232 * v,\n""        y - 0.391762 * u - 0.812968 * v,\n""        y + 1.164383 * u,\n""        1.0\n""    );\n""}");m_program.link();
}
void FrameOpenGLWidget::initTextureByYUV420PFrame()
{// 初始化三个纹理glGenTextures(1, &m_yTexture);glGenTextures(1, &m_uTexture);glGenTextures(1, &m_vTexture);
}
void FrameOpenGLWidget::releaseTextureByYUV420PFrame()
{glDeleteTextures(1, &m_yTexture);glDeleteTextures(1, &m_uTexture);glDeleteTextures(1, &m_vTexture);
}
void FrameOpenGLWidget::paintGLByYUV420PFrame()
{if (!m_yData || !m_uData || !m_vData) return;glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);#if 1glViewport(m_pos.x(), m_pos.y(), m_zoomSize.width(), m_zoomSize.height());  // 设置视图大小实现图片自适应
#endifm_program.bind();// 更新Y纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, m_yTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_width, m_height, 0,GL_RED, GL_UNSIGNED_BYTE, m_yData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 更新U纹理glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, m_uTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_width/2, m_height/2, 0,GL_RED, GL_UNSIGNED_BYTE, m_uData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 更新V纹理glActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, m_vTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_width/2, m_height/2, 0,GL_RED, GL_UNSIGNED_BYTE, m_vData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);// 设置纹理单元m_program.setUniformValue("tex_y", 0);m_program.setUniformValue("tex_u", 1);m_program.setUniformValue("tex_v", 2);// 绘制矩形static const GLfloat vertices[] = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,};static const GLfloat texCoords[] = {0.0f, 1.0f,1.0f, 1.0f,0.0f, 0.0f,1.0f, 0.0f,};glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texCoords);glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);m_program.release();
}void FrameOpenGLWidget::initializeGLByRGBFrame()
{m_program.removeAllShaders();// 编译着色器m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 330 core\n""layout(location = 0) in vec4 vertexIn;\n""layout(location = 1) in vec2 textureIn;\n""out vec2 textureCoord;\n""void main() {\n""    vec4 adjustedPos = vertexIn;\n""    gl_Position = adjustedPos;\n""    textureCoord = textureIn;\n""}");m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 330 core\n""in vec2 textureCoord;\n""out vec4 fragColor;\n""uniform sampler2D tex_rgb;\n""void main() {\n""    fragColor = texture(tex_rgb, textureCoord);\n""}");m_program.link();}
void FrameOpenGLWidget::initTextureByRGBFrame()
{// 初始化纹理glGenTextures(1, &m_rgbTexture);
}
void FrameOpenGLWidget::releaseTextureByRGBFrame()
{glDeleteTextures(1, &m_rgbTexture);
}
void FrameOpenGLWidget::paintGLByRGBFrame()
{if (!m_rgbData || m_width <= 0 || m_height <= 0) return;glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);#if 1glViewport(m_pos.x(), m_pos.y(), m_zoomSize.width(), m_zoomSize.height());  // 设置视图大小实现图片自适应
#endifm_program.bind();// 设置像素存储对齐(针对RGB24格式)glPixelStorei(GL_UNPACK_ALIGNMENT, 1);// 更新RGB纹理glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, m_rgbTexture);glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0,GL_RGB, GL_UNSIGNED_BYTE, m_rgbData);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);m_program.setUniformValue("tex_rgb", 0);// 顶点和纹理坐标static const GLfloat vertices[] = {-1.0f, -1.0f,1.0f, -1.0f,-1.0f, 1.0f,1.0f, 1.0f,};static const GLfloat texCoords[] = {0.0f, 1.0f,  // 左下1.0f, 1.0f,  // 右下0.0f, 0.0f,  // 左上1.0f, 0.0f   // 右上};glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texCoords);glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);m_program.release();
}void FrameOpenGLWidget::initializeGLByRGB32Frame()
{m_program.removeAllShaders();// 编译着色器m_program.addShaderFromSourceCode(QOpenGLShader::Vertex,"#version 330 core\n""layout(location = 0) in vec4 vertexIn;\n""layout(location = 1) in vec2 textureIn;\n""out vec2 textureCoord;\n""void main() {\n""    vec4 pos = vertexIn;\n""    gl_Position = pos;\n""    textureCoord = textureIn;\n""}");m_program.addShaderFromSourceCode(QOpenGLShader::Fragment,"#version 330 core\n""in vec2 textureCoord;\n""out vec4 fragColor;\n""uniform sampler2D tex_rgb32;\n""void main() {\n""    fragColor = texture(tex, textureCoord);\n""}");m_program.link();
}
void FrameOpenGLWidget::initTextureByRGB32Frame()
{// 初始化纹理glGenTextures(1, &m_rgb32Texture);
}
void FrameOpenGLWidget::releaseTextureByRGB32Frame()
{glDeleteTextures(1, &m_rgb32Texture);
}
void FrameOpenGLWidget::paintGLByRGB32Frame()
{if (!m_rgb32Data || m_width <= 0 || m_height <= 0) return;glClearColor(0.0f, 0.0f, 0.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);#if 1glViewport(m_pos.x(), m_pos.y(), m_zoomSize.width(), m_zoomSize.height());  // 设置视图大小实现图片自适应
#endifm_program.bind();// 设置纹理参数glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, m_rgb32Texture);// 更新纹理数据(根据具体格式选择)glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,   // 内部格式m_width, m_height, 0,GL_BGRA,          // 数据格式(根据实际数据调整)GL_UNSIGNED_BYTE, m_rgb32Data);// 设置纹理参数glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);m_program.setUniformValue("tex_rgb32", 0);// 顶点和纹理坐标static const GLfloat vertices[] = {-1.0f, -1.0f,  // 左下1.0f, -1.0f,  // 右下-1.0f, 1.0f,   // 左上1.0f, 1.0f    // 右上};static const GLfloat texCoords[] = {0.0f, 1.0f,    // 左下1.0f, 1.0f,    // 右下0.0f, 0.0f,    // 左上1.0f, 0.0f     // 右上};// 设置顶点属性glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texCoords);glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// 绘制glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);m_program.release();
}void FrameOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();switch (m_nInitFrameDataFormat) {case FrameDataFormat_RGB24:initializeGLByRGBFrame();// 初始化纹理initTextureByRGBFrame();break;case FrameDataFormat_RGB32:initializeGLByRGB32Frame();// 初始化纹理initTextureByRGB32Frame();break;case FrameDataFormat_YUV420P:initializeGLByYUV420PFrame();// 初始化纹理initTextureByYUV420PFrame();break;case FrameDataFormat_NV12:initializeGLByNV12Frame();// 初始化纹理initTextureByNV12Frame();break;}// 指定颜色缓冲区的清除值(背景色)glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}void FrameOpenGLWidget::resizeGL(int w, int h)
{
#if 1//计算需要显示图片的窗口大小,用于实现长宽等比自适应显示if(m_size.width()  <= 0 || m_size.height() <= 0) return;if((double(w) / h) < (double(m_size.width()) / m_size.height())){m_zoomSize.setWidth(w);m_zoomSize.setHeight(((double(w) / m_size.width()) * m_size.height()));}else{m_zoomSize.setHeight(h);m_zoomSize.setWidth((double(h) / m_size.height()) * m_size.width());}m_pos.setX(double(w - m_zoomSize.width()) / 2);m_pos.setY(double(h - m_zoomSize.height()) / 2);this->update(QRect(0, 0, w, h));
#elseglViewport(0, 0, w, h);
#endif}void FrameOpenGLWidget::paintGL()
{switch (m_nInitFrameDataFormat) {case FrameDataFormat_RGB24:paintGLByRGBFrame();break;case FrameDataFormat_RGB32:paintGLByRGB32Frame();break;case FrameDataFormat_YUV420P:paintGLByYUV420PFrame();break;case FrameDataFormat_NV12:paintGLByNV12Frame();break;}
}
‌4. 主窗口调用代码
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QDebug>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);m_vdVideoDecode.globalInit();m_playImage = this->findChild<FrameOpenGLWidget *>("widget");connect(this, SIGNAL(sgnUpdateFrameFormat(int)), this, SLOT(sltUpdateFrameFormat(int)), Qt::BlockingQueuedConnection);
}MainWindow::~MainWindow()
{delete ui;m_vdVideoDecode.globalUnInit();
}void  MainWindow::sltUpdateFrameFormat(int nFormatType)
{if(m_vdVideoDecode.isClose())return;m_playImage->setFrameDataFormat((FrameDataFormat)nFormatType);
}void MainWindow::on_pushButtonOpenFile_clicked(bool checked)
{bool bIsSupportHardware = ui->checkBoxHW->checkState() == Qt::Checked ? true:false;QString filename = QFileDialog::getOpenFileName(nullptr, "Open Video File");if (!filename.isEmpty()){m_vdVideoDecode.setHWDecoder(bIsSupportHardware);m_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;if(nFormatType !=  pMainWindow->m_playImage->getFrameDataFormat()){//发送信号出去,更新opengl初始化纹理。(因为opengl初始化纹理是非线程安全的,需要在UI或同FrameOpenGLWidget线程中处理。)emit pMainWindow->sgnUpdateFrameFormat(nFormatType);}pMainWindow->m_playImage->updateFrame(nFormatType, pStreamData, linesize, width, height);},this);m_vdVideoDecode.setFileStatusCallback([](int FileStatus, int nErrorCode, void * pUserData){qDebug()<<"file is end";},this);m_vdVideoDecode.open(filename);}
}void MainWindow::on_pushButtonCloseFile_clicked()
{m_vdVideoDecode.close();
}

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

相关文章:

ffmpeg+QOpenGLWidget显示视频

​一个基于 ‌FFmpeg 4.x‌ 和 QOpenGLWidget的简单视频播放器代码示例&#xff0c;实现视频解码和渲染到 Qt 窗口的功能。 1&#xff09;ffmpeg库界面&#xff0c;视频解码支持软解和硬解方式。 硬解后&#xff0c;硬件解码完成需要将数据从GPU复制到CPU。优先采用av_hwf…...

从零开始实现 C++ TinyWebServer 项目总览

文章目录 引言Web Server 概念如何实现高性能 WebServer&#xff1f;基础网络通信I/O 多路复用技术并发处理事件处理模式其他优化策略&#xff08;未实现&#xff09; 主要功能模块BufferLogSqlConnectPoolHttpRequestHttpResponseHttpConnectHeapTimerWebServer 引言 TinyWeb…...

opencv无法读取的图像,利用pil和numpy进行修复

代码总结 这段代码的功能是遍历指定文件夹下的所有图像文件&#xff0c;并修复可能存在的格式问题&#xff0c;然后覆盖原图像。 代码解析 设置输入文件夹路径&#xff08;input_folder&#xff09;。遍历文件夹中的所有文件&#xff0c;筛选出 .jpg、.jpeg、.png、.webp 格式…...

Redis分布式寻址算法

分布式寻址算法是分布式系统中用于确定数据应该存储在哪个节点的算法。这些算法对于实现高效的数据存取、负载均衡和系统扩展性至关重要。以下是几种常见的分布式寻址算法的解释&#xff1a; 1. Hash 算法 原理&#xff1a;通过哈希函数将数据的键&#xff08;Key&#xff09…...

CUDA 学习(1)——GPU 架构

典型 CPU 架构与 GPU 架构对比&#xff1a; 上图中绿色部分是计算单元&#xff0c;GPU 有更多的计算核心&#xff0c;计算能力更强。黄色部分是控制单元&#xff0c;CPU 中有大量的控制单元&#xff0c;现代 CPU 的晶体管越来越复杂&#xff0c;除了计算&#xff0c;还要实现乱…...

5个视角、5等级及10档次:《数字化转型领域 参考架构》国家标准正式出台

近日&#xff0c;国家标准GB/T 45341-2025《数字化转型管理 参考架构》正式出台。这是我国研制的首个数字化转型领域基础架构类国家标准&#xff0c;对数字化转型领域标准化建设具有重大意义。 关注WeChat Subscription Account【智慧城市指北】&#xff0c;回复关键字“20250…...

2.4 隐函数及由参数方程确定的函数求导

1.隐函数求导 1.1 例题 2. 参数方程确定的函数求导...

PROE 与 STL 格式转换:开启 3D 打印及多元应用的大门

在 3D 设计与制造的复杂生态中&#xff0c;将 PROE 格式转换为 STL 格式绝非无端之举&#xff0c;而是有着深厚且多元的现实需求作为支撑。 一、文件格式介绍​ &#xff08;一&#xff09;PROE 格式​ PROE 作为一款参数化设计软件&#xff0c;采用基于特征的参数化建模技术…...

【Bug记录】node-sass安装失败解决方案

【Bug记录】node-sass 安装失败解决办法 前言 很多小伙伴在安装 node-sass 的时候都失败了&#xff0c;主要的原因是 node 版本和项目依赖的 node-sass 版本不匹配。 解决方案 解决方案&#xff1a;把项目中的 node-sass 替换为 sass&#xff0c;其实 node-sass 已被官方弃…...

三、Python高级特性

Python中的高级数据结构&#xff1a;队列、栈、双端队列、堆与优先队列 在Python编程中&#xff0c;高级数据结构为我们提供了强大而灵活的工具来处理各种复杂的数据存储和操作需求。本文将详细介绍队列、栈、双端队列、堆与优先队列这几种常见的高级数据结构及其在Python中的…...

使用 OpenCV 拼接进行图像处理对比:以形态学操作为例

图像处理在计算机视觉中起着至关重要的作用&#xff0c;而 OpenCV 作为一个强大的图像处理库&#xff0c;提供了丰富的函数来实现各类图像处理任务。形态学操作&#xff08;Morphological Operations&#xff09;是其中常用的技术&#xff0c;尤其适用于二值图像的处理。常见的…...

OPENCV数字识别(非手写数字/采用模板匹配)

这篇文章的重点在于 模板匹配 的使用。模板匹配是计算机视觉中的一项基本技术&#xff0c;它通过比对输入图像与模板图像的相似度&#xff0c;来进行目标识别。对于数字识别&#xff0c;特别是标准数字的识别&#xff0c;模板匹配非常有效。 请看效果&#xff1a; 文章结构 …...

【STM32】知识点介绍一:硬件知识

文章目录 一、电源引脚简介二、电平信号三、电路分析 一、电源引脚简介 VCC、GND、VDD和VSS是电子电路中常见的术语&#xff0c;代表着不同的电源引脚或电压。 VCC&#xff08;Voltage at the Common Collector&#xff09;&#xff1a;VCC是指集电极&#xff08;Collector&am…...

基于SpringBoot的电影售票系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…...

记录我的ICME2025论文之旅:困顿与收获

人生第一次中B会&#xff0c;还是在课业繁重的大三上&#xff08;有点说法~&#xff09; “在最黑暗的时刻&#xff0c;总有一束光为你指引前行。” ——记录这段难忘的历程 今年的ICME投稿量创下新高&#xff0c;录取率却跌至20多%&#xff0c;并且首次加入了rebuttal&#xf…...

FreeRTOS学习(九):中断管理

FreeRTOS学习&#xff08;九&#xff09;&#xff1a;中断管理 文章目录 FreeRTOS学习&#xff08;九&#xff09;&#xff1a;中断管理前言一、中断优先级分组设置特点中断优先级分组设置 二、FreeRTOS中断优先级配置PendSV和SysTick中断优先级配置 三、中断相关寄存器PRIMASK…...

07-项目中应提前准备下一阶段计划

在项目当前版本的功能开发任务都完成之后&#xff0c;人就空出来了&#xff0c;通常这个时候&#xff0c;项目负责人还有很多繁琐的工作要做&#xff0c;比如项目内部验收、提交测试申请和版本发布等等。为了给项目成员找事情做&#xff0c;就匆匆忙忙安排下个版本的任务&#…...

【C语言】多进程/多线程

【C语言】多进程/多线程 参考链接多进程/多线程服务器1. 多进程服务器2. 多线程服务器 结语参考链接 参考链接 c 中文网 菜鸟 c 多进程/多线程服务器 多进程和多线程是常用的并发编程技术。它们都允许程序同时执行多个任务&#xff0c;提高了系统的资源利用率和程序的运行效率…...

GaussDB数据库表设计与性能优化实践

GaussDB分布式数据库表设计与性能优化实践 引言 在金融、电信、物联网等大数据场景下&#xff0c;GaussDB作为华为推出的高性能分布式数据库&#xff0c;凭借其创新的架构设计和智能优化能力&#xff0c;已成为企业核心业务系统的重要选择。本文深入探讨GaussDB分布式架构下的…...

全星FMEA软件系统——助力汽车部件行业高效管控质量风险

全星FMEA软件系统——助力汽车部件行业高效管控质量风险 在汽车部件行业&#xff0c;产品质量与可靠性直接影响整车安全性与市场竞争力。如何高效管理失效风险、优化控制计划并确保流程合规性&#xff0c;成为企业核心挑战。 全星FMEA软件系统应运而生&#xff0c;专为汽车部…...

文件I/O--C++的文件操作

一、打开文件&#xff0c;从文件中读取、写入文件 从文件中读取数据&#xff1a; #include<fstream> //fstream File stream:文件流 #include<iostream> //fstream包含了 iostream&#xff0c;所以这句可以省略&#xff0c;现在不能了 using namespace std;i…...

Docker-Compose部署 EasySearch 异常问题排查

近期将原本运行在 macOS 上的 EasySearch、Console 和 Coco-server 等服务迁移至群晖 NAS 平台。在迁移过程中遇到了EasySearch容器无法正常启动或运行中意外终止的问题。本文记录了这些问题的具体表现及解决方案&#xff0c;旨在为后续类似部署提供参考。 基础部署配置 以下…...

秒杀业务优化之从分布式锁到基于消息队列的异步秒杀

一、业务场景介绍 优惠券、门票等限时抢购常常出现在各类应用中&#xff0c;这样的业务一般为了引流宣传而降低利润&#xff0c;所以一旦出现问题将造成较大损失&#xff0c;那么在业务中就要求我们对这类型商品严格限时、限量、每位用户限一次、准确无误的创建订单&#xff0c…...

【CGE】社会核算矩阵构建(一):SAM基本结构

【CGE】社会核算矩阵构建&#xff08;一&#xff09;&#xff1a;SAM基本结构 社会核算矩阵构建&#xff08;一&#xff09;&#xff1a;SAM基本结构一、SAM的概念和基本特点二、SAM的基本结构1.开放经济体的SAM表结构2.SAM表各账户的主要核算内容&#xff08;1&#xff09;社会…...

蓝桥杯算法实战分享:算法进阶之路与实战技巧

引言 蓝桥杯作为国内极具影响力的程序设计竞赛&#xff0c;为众多编程爱好者和专业人才提供了展示自我的舞台。参与蓝桥杯不仅能检验自身编程水平&#xff0c;还能拓宽技术视野&#xff0c;为未来职业发展积累宝贵经验。本文将结合历年真题与参赛经验&#xff0c;全面分享蓝桥…...

自定义minshell

我们在前面已经了解了进程的概念&#xff0c;以及如何进行进程控制。接下来我们就使用这些知识&#xff0c;来自己实现一个shell即命令行解释器&#xff01;&#xff01;&#xff01; 一.打印命令行提示符 我们在使用Linux操作系统时&#xff0c;一登陆就会启动bash进程——命…...

坦克大战(c++)

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

《可爱风格 2048 游戏项目:HTML 实现全解析》

一、引言 在如今的数字化时代&#xff0c;小游戏以其简单易上手、趣味性强的特点深受大家喜爱。2048 游戏作为一款经典的数字合并游戏&#xff0c;拥有庞大的玩家群体。本文将详细介绍一个用单文件 HTML 实现的可爱风格 2048 游戏项目&#xff0c;它不仅具备传统 2048 游戏的基…...

C++ 利用类模板实现一个数组类封装

案例描述&#xff1a; 实现一个通用的数组类&#xff0c;要求如下&#xff1a; 可以对内置数据类型以及自定义数据类型的数据进行存储 将数组中的数据存储到堆区 构造函数中可以传入数组的容量 提供对应的拷贝构造函数以及operator防止浅拷贝问题 提供尾插法和尾删法对数组…...

【AndroidRTC-11】如何理解webrtc的Source、TrackSink

Android-RTC系列软重启&#xff0c;改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性&#xff0c;方便形成肌肉记忆。同时不分种类、不分难易程度&#xff0c;在线征集问题切入点。 问题1&#xff1a;如何理解VideoSource、VideoTrack&VideoSink三者的关系…...

数据类设计_图片类设计之9_图标类设计_C++实战_(前端架构)

前言 学的东西多了,要想办法用出来.C和C是偏向底层的语言,直接与数据打交道.尝试做一些和数据方面相关的内容 引入 前面写了矩阵图形类对象和像素图形类对象,本贴通过一个快捷方式图标类的设计,来继续数据类型设计的一些讨论. 快捷方式图标是这个样子: 属性分析 首先,快捷方式…...

fuse性能关键参数entry_timeout

entry_timeout 是 FUSE&#xff08;Filesystem in Userspace&#xff09;中的一个选项&#xff0c;用于控制目录项缓存的有效期。具体来说&#xff0c;它决定了文件系统在多长时间内缓存目录项&#xff08;如文件名到 inode 的映射&#xff09;&#xff0c;从而影响文件系统的性…...

3. 轴指令(omron 机器自动化控制器)——>MC_ResetFollowingError

机器自动化控制器——第三章 轴指令 13 MC_ResetFollowingError变量▶输入变量▶输出变量▶输入输出变量 功能说明▶指令详情▶时序图▶重启动运动指令▶多重启运动指令▶异常 MC_ResetFollowingError 对指令当前位置和反馈当前位置的偏差进行复位。 指令名称FB/FUN图形表现S…...

Spring Boot项目快速创建-开发流程(笔记)

主要流程&#xff1a; 前端发送网络请求->controller->调用service->操纵mapper->操作数据库->对entity数据对象赋值->返回前端 前期准备&#xff1a; maven、mysql下载好 跟学视频&#xff0c;感谢老师&#xff1a; https://www.bilibili.com/video/BV1gm4…...

[操作系统] 进程间通信:进程池的实现

引言 在学习操作系统时&#xff0c;进程间通信&#xff08;IPC&#xff09;和多进程管理是核心内容之一。进程池是一种常见的模式&#xff0c;通过预先创建一组工作进程来处理任务&#xff0c;避免频繁创建和销毁进程带来的开销。本文将详细剖析一个用 C 实现的进程池代码&…...

信号相关的程序

1、不断打印*换行之后响应信号&#xff0c;然后循环 #include <stdio.h> #include <string.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> static void alrm_handler(int signo) {write(1,"!",1); }int main( in…...

【计算机网络】-计算机网络期末复习题复习资料

一、计算机网络体系结构&#xff08;800字&#xff09; 1. OSI参考模型 七层结构&#xff1a;物理层→数据链路层→网络层→传输层→会话层→表示层→应用层 各层核心功能&#xff1a; 物理层&#xff1a;比特流传输&#xff08;如RJ45、光纤接口&#xff09; 数据链路层&…...

Linux 基础入门操作 第十二章 TINY Web 服务器

1 服务器基础架构 1.1 背景知识 Web 服务器使用 HTTP 协议与客户端&#xff08;即浏览器&#xff09;通信&#xff0c;而 HTTP 协议又基于 TCP/IP 协议。因此我们要做的工作就是利用 Linux 系统提供的 TCP 通信接口来实现 HTTP 协议。 而 Linux 为我们提供了哪些网络编程接口…...

L2-052 吉利矩阵

L2-052 吉利矩阵 - 团体程序设计天梯赛-练习集 这道题打表 打表部分被注释了 n4 [0,0,282, 2008, 10147, 40176, 132724, 381424, 981541, 2309384] n3 [0,0,21, 55, 120, 231, 406, 666, 1035, 1540] n2 [0,0,3, 4, 5, 6, 7, 8, 9, 10] l,n map(int,input().split()) if…...

BKA-CNN-LSTM、CNN-LSTM、LSTM、CNN四模型多变量时序光伏功率预测,附模型研究报告

BKA-CNN-LSTM、CNN-LSTM、LSTM、CNN四模型多变量时序光伏功率预测&#xff0c;附模型研究报告 目录 BKA-CNN-LSTM、CNN-LSTM、LSTM、CNN四模型多变量时序光伏功率预测&#xff0c;附模型研究报告预测效果基本介绍程序设计参考资料 预测效果 基本介绍 BKA-CNN-LSTM、CNN-LSTM、…...

【读书笔记】华为《从偶然到必然》

note 华为的成功并非偶然&#xff0c;而是通过IPD体系、投资组合管理、平台战略等系统性工具&#xff0c;将研发投资转化为可持续的商业竞争力。书中强调的“管理即内部因素”理念&#xff0c;揭示了企业规模扩张与管理能力匹配的深层规律&#xff0c;为高科技企业提供了可借鉴…...

flink广播算子Broadcast

文章目录 一、Broadcast二、代码示例三.或者第二种(只读取一个csv文件到广播内存中)提示:以下是本篇文章正文内容,下面案例可供参考 一、Broadcast 为了关联一个非广播流(keyed 或者 non-keyed)与一个广播流(BroadcastStream),我们可以调用非广播流的方法 connect(),…...

实时图像处理:让你的应用更智能

I. 引言 实时图像处理在现代应用中扮演着重要的角色&#xff0c;它能够使应用更加智能、响应更加迅速。本文将深入探讨实时图像处理的原理、部署过程以及未来的发展趋势&#xff0c;旨在帮助开发者更好地理解如何将实时图像处理应用于他们的项目中。 II. 实时图像处理的基础概…...

深入理解 Linux 基础 IO:从文件操作到缓冲区机制

亲爱的读者朋友们&#x1f603;&#xff0c;此文开启知识盛宴与思想碰撞&#x1f389;。 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 在 Linux 系统中&#xff0c;文件输入输出&#xff08;IO&#xff09;…...

汇编语言高级编程技巧:从基础到进阶

前言 汇编语言作为底层编程语言&#xff0c;直接操作硬件&#xff0c;执行效率高&#xff0c;但编写复杂逻辑时往往显得繁琐。通过使用汇编伪指令和宏&#xff0c;我们可以实现类似于高级语言的结构&#xff0c;如条件判断、循环、结构体和函数等&#xff0c;从而提升代码的可读…...

Android Studio常见问题解决

一、环境配置问题 1. 安装失败 问题描述&#xff1a;在安装过程中&#xff0c;可能会遇到硬件要求不符合、网络问题、安装包损坏、权限不足或安装路径问题等&#xff0c;导致安装失败。 解决方法&#xff1a; 硬件要求&#xff1a;确保设备满足最低硬件要求。 网络问题&…...

【RHCE】LVS-NAT模式负载均衡实验

目录 题目 IP规划 配置IP RS1 RS2 RS3 LVS client 配置RS 配置LVS 安装lvs软件 启动ipvsadm服务 lvs规则匹配 ipvsadm部分选项 客户端测试 总结 题目 使用LVS的 NAT 模式实现 3 台RS的轮询访问&#xff0c;IP地址和主机自己规划。 IP规划 主机IP地址RS1-nat模…...

MacOS下的IntelliJ IDEA突然无法访问本机的虚拟机

今天在开发的过程中&#xff0c;突然遇到一个怪事&#xff0c;之前运行的好好的程序&#xff0c;突然间报无法连接redis服务器&#xff0c;一开始以为是网络问题&#xff0c;在OS的terminal里又是ping 又是telnet的&#xff0c;一切正常&#xff0c;可是程序就是连不上。 挠了半…...

【渗透测试】Fastjson 反序列化漏洞原理(一)

目录 一、Fastjson 是什么二、Fastjson 工作原理三、反序列化漏洞原理1. 反序列化漏洞的定义2. Fastjson 的反序列化机制3. 漏洞成因关注以下几点(1) 动态类型解析(2) 自动调用方法(3) 信任用户输入 4. 漏洞利用过程(1) 寻找可利用的类&#xff08;也称为 "Gadget"&a…...

BM100-K系列开关量输入信号隔离器

1. 产品概述 BM100-K系列开关量输入信号隔离器是一款高性能的信号处理设备&#xff0c;专为工业自动化控制系统设计。该产品支持干接点或NAMUR型接近开关输入&#xff0c;并通过继电器或晶体管实现隔离输出。其核心功能包括输入输出逻辑控制&#xff08;同相/反相可调&#xf…...