(四)qt中使用ffmpeg播放视频,可暂停恢复

一、在qt中添加ffmpeg库及头文件

INCLUDEPATH += /usr/local/ffmpeg/include
LIBS += -L/usr/local/lib -lavutil -lavcodec -lavformat -lswscale

二、详细代码

FFempegVideoDecode 视频解码类(放入线程中)

ffmpegvideodecode.h

#ifndef FFMPEGVIDEODECODE_H
#define FFMPEGVIDEODECODE_H#include <QObject>
#include <QThread>
#include <QDebug>
#include <QTime>
struct AVCodec;
struct AVCodecContext;
struct AVFrame;
struct AVFormatContext;
struct SwsContext;
struct AVPacket;class FFmpegVideoDecode : public QObject
{Q_OBJECT
public:explicit FFmpegVideoDecode(QObject *parent = nullptr);~FFmpegVideoDecode();bool initFFmpeg(QString fileName);void clear();private:void inputError(int ret, QString funName);signals:void sigToStart();void sigToUpdateImage(const QImage &image);public slots:void onStartPlay(QString name);void onStopPlay();void onFinish();void onUpdateRead();private:AVFormatContext* pFormatCtx = nullptr;AVCodecContext* pCodecCtx = nullptr;AVFrame* pAvFrame = nullptr;AVFrame* pFrameRGB32 = nullptr;AVPacket* packet = nullptr;uint8_t *out_buffer = nullptr;SwsContext *img_convert_ctx = nullptr;int videoIndex;bool is_stop = false;bool is_finish = true;
};#endif // FFMPEGVIDEODECODE_H

ffmpegvideodecode.cpp

#include "ffmpegvideodecode.h"
extern "C"
{
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <QEventLoop>
#include <QPixmap>
#include <QTimer>/*** @brief      非阻塞延时* @param msec 延时毫秒*/
void  sleepMsec(int msec)
{if(msec <= 0) return;QEventLoop loop;		//定义一个新的事件循环QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数loop.exec();			//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
}FFmpegVideoDecode::FFmpegVideoDecode(QObject *parent) : QObject(parent)
{connect(this,&FFmpegVideoDecode::sigToStart,this,&FFmpegVideoDecode::onUpdateRead);
}FFmpegVideoDecode::~FFmpegVideoDecode()
{clear();
}bool FFmpegVideoDecode::initFFmpeg(QString fileName)
{clear();/** avformat_network_init主要就是初始化win socket和openssl。但是由于我使用linux(ubuntu),未使用openssl支持,所以该函数对我平台没有任何意义。* 执行网络库的全局初始化。这是可选的,不再推荐。此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。如果libavformat链接到这些库的较新版本,或者不使用它们,则不需要调用此函数。否则,您需要在启动使用该函数的任何其他线程之前调用该函数。一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数没有任何用途*.//avformat_network_init();/** int av_dict_set(AVDictionary **pm, const char *key, const char *value, int flags);* AVDictionary是FFmpeg中用于存储元数据的一种数据结构,它基本上是一个简单的哈希表,用于存储字符串类型的键值对。它可以被用来传递多种数据,例如视频解码参数、音频参数、字幕参数等等。* 其中,pm是要添加键值对的AVDictionary对象的指针指针;key是要添加的键名;value是要添加的键值;flags是一些标志位,可选项有:0:默认,如果已经定义一个关键字,则将其删除并替换为新的键值对。AV_DICT_APPEND:如果关键字已经定义,则将其与新的值合并。AV_DICT_DONT_OVERWRITE:如果已经存在键名,则不会执行任何操作。*/AVDictionary* dict = nullptr;//使用 TCP 方式av_dict_set(&dict, "rtsp_transport", "tcp", 0);//设置 接收包间隔最大延迟,微秒av_dict_set(&dict, "max_delay", "200", 0);//在进行网络操作时允许的最大等待时间。5秒av_dict_set(&dict, "timeout", "5000000", 0);//设置阻塞超时,否则可能在流断开时连接发生阻塞,微秒av_dict_set(&dict, "stimeout", "3000000", 0);//设置 find_stream_info 最大时长,微秒//av_dict_set(&dict, "analyzeduration", "1000000", 0);/** avformat_open_input 打开输入媒体流* 参数ps包含一切媒体相关的上下文结构,有它就有了一切,本函数如果打开媒体成功,会返回一个AVFormatContext的实例.参数url媒体文件名或URL.参数fmt是要打开的媒体格式的操作结构,因为是读,所以是inputFormat.此处可以传入一个调用者定义的inputFormat,对应命令行中的 -f xxx段,如果指定了它,在打开文件中就不会探测文件的实际格式了,以它为准了.参数options是对某种格式的一些操作,是为了在命令行中可以对不同的格式传入特殊的操作参数而建的, 为了了解流程,完全可以无视它.*//** avformat_alloc_context用于分配一个AVFormatContext结构体并初始化它*/pFormatCtx = avformat_alloc_context();int ret = avformat_open_input(&pFormatCtx,fileName.toStdString().c_str(),NULL,&dict);// 释放参数字典if(dict)av_dict_free(&dict);if (ret != 0){inputError(ret,"avformat_open_input");clear();return false;}/** 用于找到媒体文件中的流信息,也就是获取视频和音频的相关数据,如编码格式、分辨率、采样率等* 获取多媒体流的信息(视频文件信息),一个视频文件中可能会同时包括视频文件、音频文件、字幕文件等多个媒体流。*/ret = avformat_find_stream_info(pFormatCtx, NULL);if (ret < 0){inputError(ret,"avformat_find_stream_info");clear();return false;}qint64 totalTime = pFormatCtx->duration / (AV_TIME_BASE / 1000); // 计算视频总时长(毫秒)qDebug() << QString("视频总时长:%1 ms,[%2]").arg(totalTime).arg(QTime::fromMSecsSinceStartOfDay(int(totalTime)).toString("HH:mm:ss zzz"));/** 通过AVMediaType枚举查询视频流ID(也可以通过遍历查找)*//** int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);* 查找最佳匹配的媒体流的函数* ic:AVFormatContext指针,表示输入的媒体文件上下文。type:要查找的媒体流类型,可以是音频流、视频流或字幕流等。wanted_stream_nb:期望的媒体流索引号,可以是特定的索引号,也可以是AV_NOPTS_VALUE(-1)表示任意流。related_stream:前一个相关流的索引号,如果没有前一个相关流,则传入-1。decoder_ret:返回解码器指针。flags:查找最佳流的标志位,默认为0。返回值:找到的最佳匹配媒体流的索引号,如果找不到则返回AVERROR_STREAM_NOT_FOUND。*/videoIndex = -1;videoIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);/*for (int i = 0; i < pFormatCtx->nb_streams; ++i){if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {videoIndex = i;break;}}*/if (videoIndex < 0){inputError(videoIndex,"av_find_best_stream");clear();return false;}/** AVCodecParameters 用于保存音视频流的基本参数信息。* 主要成员说明* codec_id:编解码器的ID,取值为枚举类型AVCodecID中的一种。* width:视频帧宽度。* height:视频帧高度。*/AVCodecParameters* parmeter = pFormatCtx->streams[videoIndex]->codecpar;/** avcodec_find_decoder 通过解码器ID获取视频解码器(新版本返回值必须使用const)*/const AVCodec* _pCodec = avcodec_find_decoder(parmeter->codec_id);if (_pCodec == NULL){inputError(AVERROR_DECODER_NOT_FOUND,"avcodec_find_decoder");clear();return false;}/** avcodec_alloc_context3 用于分配一个编解码器上下文(AVCodecContext)结构体* AVCodecContext结构的主要作用是设置编码过程的参数*/pCodecCtx = avcodec_alloc_context3(_pCodec);//初始化一个编解码上下文/** avcodec_parameters_to_context 该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中*/ret = avcodec_parameters_to_context(pCodecCtx, parmeter);if (ret < 0){inputError(ret,"avcodec_parameters_to_context");clear();return false;}/** avcodec_open2 用于打开编解码器并初始化其上下文*/ret = avcodec_open2(pCodecCtx, _pCodec, NULL);if (ret < 0){inputError(ret,"avcodec_open2");clear();return false;}//分配AVFrame并将其字段设置为默认值。pAvFrame = av_frame_alloc();pFrameRGB32 = av_frame_alloc(); //存储解码后转换的RGB数据//保存RGB32/** av_image_get_buffer_size 用于计算图像缓冲区的大小*//** av_image_fill_arrays 用于将图像数据填充到AVFrame结构体中,瓜分malloc分配到的内存* 参数const uint8_t *src之后的逻辑中就没再用了->对分配内存瓜分*/int size = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height,4);out_buffer = (uint8_t *)av_malloc(size);ret = av_image_fill_arrays(pFrameRGB32->data,pFrameRGB32->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height,1);if (ret < 0){inputError(ret,"av_image_fill_arrays");clear();return false;}//分配AVPacket并将其字段设置为默认值。packet = av_packet_alloc();/** av_new_packet 用于分配一个新的AVPacket结构体*/av_new_packet(packet, pCodecCtx->width * pCodecCtx->height);//分配packet的有效载荷并初始化其字段//qDebug() << "***********视频信息**********";//av_dump_format(pFormatCtx, 0, fileName.toStdString().c_str(), 0);//qDebug() << "*****************************";return true;
}void FFmpegVideoDecode::onStartPlay(QString name)
{bool flag = initFFmpeg(name);is_stop = false;if(flag){is_finish = false;Q_EMIT sigToStart();}else{qDebug()<<"初始化ffmpeg失败!";}
}void FFmpegVideoDecode::onStopPlay()
{is_stop = !is_stop;
}void FFmpegVideoDecode::onFinish()
{qDebug()<<"关闭视频";is_finish = true;
}void FFmpegVideoDecode::clear()
{if(pFrameRGB32){av_frame_free(&pFrameRGB32);pFrameRGB32 = nullptr;}if(pAvFrame){av_frame_free(&pAvFrame);pAvFrame = nullptr;}if(pCodecCtx){avcodec_close(pCodecCtx);pCodecCtx = nullptr;}// 因为avformat_flush不会刷新AVIOContext (s->pb)。如果有必要,在调用此函数之前调用avio_flush(s->pb)。if(pFormatCtx && pFormatCtx->pb){avio_flush(pFormatCtx->pb);}if(pFormatCtx){avformat_flush(pFormatCtx);   // 清理读取缓冲avformat_close_input(&pFormatCtx);pFormatCtx = nullptr;}if (img_convert_ctx){sws_freeContext(img_convert_ctx);img_convert_ctx = nullptr;}if(out_buffer){//delete [] out_buffer;av_free(out_buffer);out_buffer = nullptr;}
}void FFmpegVideoDecode::inputError(int ret, QString funName)
{char *error = new char[1024];memset(error, 0, 1024);// 将数组置零av_strerror(ret, error, 1024);qDebug()<<"函数名为:"<<funName<<" 的函数发生错误,错误原因:"<<QString::fromStdString(error)<<" 返回值:"<<ret;delete[] error;error = NULL;
}void FFmpegVideoDecode::onUpdateRead()
{//设置sws_scale转换格式为AV_PIX_FMT_RGB32/** sws_getContext 用于创建一个SWSContext结构体* struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,int dstW, int dstH, enum AVPixelFormat dstFormat,int flags, SwsFilter *srcFilter,SwsFilter *dstFilter, const double *param);* 参数srcW和srcH分别表示源图像的宽度和高度* 参数srcFormat表示源图像的像素格式* 参数dstFormat表示目标图像的像素格式* 参数flags表示转换的标志;* 参数srcFilter和dstFilter分别表示源图像和目标图像的滤波器;* 参数param表示转换的参数。*/img_convert_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);while (!is_finish){//方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态if (is_stop){continue;}//方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
//        while (is_stop && !is_finish)
//        {
//            sleepMsec(200);
//        }/** int av_read_frame(AVFormatContext *s, AVPacket *pkt);用于从输入流中读取一帧数据。* 参数s是一个指向AVFormatContext指针的指针,表示要读取数据的输入流;* 参数pkt是一个指向AVPacket指针的指针,表示要存储读取到的数据的AVPacket结构体。*/if (av_read_frame(pFormatCtx, packet) >= 0){if (videoIndex == packet->stream_index) //此流是视频流{/** int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);用于将AVPacket结构体中的数据发送到解码器。* 参数avctx是一个指向AVCodecContext指针的指针,表示要发送数据的解码器;* 参数avpkt是一个指向AVPacket指针的指针,表示要发送的数据。*//** int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);用于从解码器中接收解码后的帧数据。* 参数avctx是一个指向AVCodecContext指针的指针,表示要接收数据的解码器;* 参数frame是一个指向AVFrame指针的指针,表示要存储接收到的数据的AVFrame结构体。*/int ret = avcodec_send_packet(pCodecCtx, packet);if (ret < 0){inputError(ret,"avcodec_send_packet");continue;}ret = avcodec_receive_frame(pCodecCtx, pAvFrame);if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){//拉流显示视频时一开始报错: 返回值:-11,资源暂时不可用//代表解码器暂时没有数据可读,需要输入更多的AVPacket。//在代码中设定碰到这个错误时直接continue让avcodec_send_packet()对解码器输入更多数据continue;}else if(ret < 0){inputError(ret,"avcodec_receive_frame");continue;}/** sws_scale 用于将图像从一个像素格式转换为另一个像素格式。* 可以在同一个函数里实现:1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理*/sws_scale(img_convert_ctx,(const uint8_t* const*)pAvFrame->data,pAvFrame->linesize,0,pCodecCtx->height,pFrameRGB32->data,pFrameRGB32->linesize);QImage image((uchar*)pFrameRGB32->data[0], pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);Q_EMIT sigToUpdateImage(image);//sleepMsec->非阻塞(点击播放可直接重头播放) QThread::msleep(40)->阻塞//方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态QThread::msleep(40);//方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
//                sleepMsec(40);}/** av_packet_free和av_packet_unref都是FFmpeg库中的函数,用于释放AVPacket结构体中的资源。* av_packet_free函数会释放AVPacket结构体中的所有资源,包括存储数据的缓冲区等。* av_packet_unref函数只会释放AVPacket结构体中的部分资源,例如释放存储数据的缓冲区等,但不会释放AVPacket结构体本身。*/av_packet_unref(packet);}else{qDebug()<<"视频播放到结尾,播放结束";is_finish = true;//break;}}}

VideoPlayer类(视频显示)

videoplayer.h

#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H#include <QWidget>
#include <QThread>
#include <QMutex>
#include "ffmpegvideodecode.h"namespace Ui {
class VideoPlayer;
}class VideoPlayer : public QWidget
{Q_OBJECTpublic:explicit VideoPlayer(QWidget *parent = 0);~VideoPlayer();signals:void sigToStart(QString name);void sigToStop();void sigToFinish();public slots:void onUpdateImage(const QImage &image);protected:void paintEvent(QPaintEvent *event) override;private:Ui::VideoPlayer *ui;FFmpegVideoDecode *ffmpeg_decode;QThread *thread_decode;QPixmap pixmap_video;QMutex m_mutex;
};#endif // VIDEOPLAYER_H

videoplayer.cpp

#include "videoplayer.h"
#include "ui_videoplayer.h"
#include <QPainter>VideoPlayer::VideoPlayer(QWidget *parent) :QWidget(parent),ui(new Ui::VideoPlayer)
{ui->setupUi(this);ffmpeg_decode = new FFmpegVideoDecode;thread_decode = new QThread;ffmpeg_decode->moveToThread(thread_decode);connect(thread_decode,&QThread::finished,ffmpeg_decode,&FFmpegVideoDecode::deleteLater);connect(thread_decode,&QThread::finished,thread_decode,&QThread::deleteLater);connect(ffmpeg_decode,&FFmpegVideoDecode::sigToUpdateImage,this,&VideoPlayer::onUpdateImage);connect(this,&VideoPlayer::sigToStart,ffmpeg_decode,&FFmpegVideoDecode::onStartPlay);connect(this,&VideoPlayer::sigToFinish,ffmpeg_decode,&FFmpegVideoDecode::onFinish,Qt::DirectConnection);//方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay,Qt::DirectConnection);//方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
//    connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay);thread_decode->start();
}VideoPlayer::~VideoPlayer()
{delete ui;Q_EMIT sigToFinish();thread_decode->quit();thread_decode->wait();
}void VideoPlayer::onUpdateImage(const QImage &image)
{m_mutex.lock();pixmap_video = QPixmap::fromImage(image);m_mutex.unlock();update();
}void VideoPlayer::paintEvent(QPaintEvent *event)
{if(!pixmap_video.isNull()){QPainter painter(this);m_mutex.lock();QPixmap pixmap = pixmap_video.scaled(this->size(), Qt::KeepAspectRatio);m_mutex.unlock();int x = (this->width() - pixmap.width()) / 2;int y = (this->height() - pixmap.height()) / 2;painter.drawPixmap(x, y, pixmap);}QWidget::paintEvent(event);
}

Widget类(主界面)

widget.h

#include "videoplayer.h"
#include "ui_videoplayer.h"
#include <QPainter>VideoPlayer::VideoPlayer(QWidget *parent) :QWidget(parent),ui(new Ui::VideoPlayer)
{ui->setupUi(this);ffmpeg_decode = new FFmpegVideoDecode;thread_decode = new QThread;ffmpeg_decode->moveToThread(thread_decode);connect(thread_decode,&QThread::finished,ffmpeg_decode,&FFmpegVideoDecode::deleteLater);connect(thread_decode,&QThread::finished,thread_decode,&QThread::deleteLater);connect(ffmpeg_decode,&FFmpegVideoDecode::sigToUpdateImage,this,&VideoPlayer::onUpdateImage);connect(this,&VideoPlayer::sigToStart,ffmpeg_decode,&FFmpegVideoDecode::onStartPlay);connect(this,&VideoPlayer::sigToFinish,ffmpeg_decode,&FFmpegVideoDecode::onFinish,Qt::DirectConnection);//方法1:由于子线程阻塞式延时读取视频帧,无法修改视频的暂停状态,可通过修改connect的连接方式在主线程中修改暂停状态connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay,Qt::DirectConnection);//方法2:将子线程阻塞式延时修改未非阻塞式,可直接修改视频的暂停状态
//    connect(this,&VideoPlayer::sigToStop,ffmpeg_decode,&FFmpegVideoDecode::onStopPlay);thread_decode->start();
}VideoPlayer::~VideoPlayer()
{delete ui;Q_EMIT sigToFinish();thread_decode->quit();thread_decode->wait();
}void VideoPlayer::onUpdateImage(const QImage &image)
{m_mutex.lock();pixmap_video = QPixmap::fromImage(image);m_mutex.unlock();update();
}void VideoPlayer::paintEvent(QPaintEvent *event)
{if(!pixmap_video.isNull()){QPainter painter(this);m_mutex.lock();QPixmap pixmap = pixmap_video.scaled(this->size(), Qt::KeepAspectRatio);m_mutex.unlock();int x = (this->width() - pixmap.width()) / 2;int y = (this->height() - pixmap.height()) / 2;painter.drawPixmap(x, y, pixmap);}QWidget::paintEvent(event);
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"#include <QFileDialog>Widget::Widget(QWidget *parent) :QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);player = new VideoPlayer(ui->widget_play);ui->horizontalLayout->addWidget(player);
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_start_clicked()
{Q_EMIT player->sigToFinish();Q_EMIT player->sigToStart(ui->lineEdit_path->text());ui->pushButton_stop->setText("暂停");
}void Widget::on_pushButton_stop_clicked()
{Q_EMIT player->sigToStop();if(ui->pushButton_stop->text() == "暂停")ui->pushButton_stop->setText("恢复");else if(ui->pushButton_stop->text() == "恢复")ui->pushButton_stop->setText("暂停");
}void Widget::on_pushButton_select_clicked()
{QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),"/home",tr("Video (*.mp4 *.flv)"));if(fileName.isEmpty()) return;ui->lineEdit_path->setText(fileName);qDebug()<<"选择文件:"<<fileName;
}

widget.ui

三、运行

1.显示本地视频

直接选择本地视频文件即可,显示界面如下:

2.显示拉流视频

启动mediamtx流媒体服务器(相关内容看上一篇笔记)

./mediamtx

循环推送本地视频到mediamtx服务器

ffmpeg -re -stream_loop -1 -i /home/li/1.mp4 -c copy -f rtsp rtsp://127.0.0.1:8554/stream

即可通过流地址显示视频,界面显示如下:

 代码下载地址:

https://download.csdn.net/download/m0_67254672/89118955

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/178.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RHCE作业二

一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 2.server主机的IP为&#xff1a; 172.25.254.100 3.server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 4.配置server主机的时间同步服务要求可以被所有人使用 二.设定cli…

Http 请求偶发400错误

1. 背景 生产环境偶发400请求错误&#xff0c;发生概率万分之一&#xff0c;异常信息如下&#xff1a; 1&#xff09; 从异常信息可以看到&#xff0c;skywalking的sw8 header解析失效导致异常信息。 2&#xff09; 0x0d0x0a 作为回车换行符号&#xff0c;没有被正确处理&#…

OpenGL:图元

OpenGL的图元 点 GL_POINTS: 将顶点绘制成单个的点 线 GL_LINES:将顶点用于创建线段,2个点成为一条单独的线段。如果顶点个数是奇数,则忽略最后一个。 顶点:v0, v1, v2, v3, … , vn,线段:v0-v1, v2-v3, v4-v5, … , vn-1 - vn GL_LINE_STRIP:将顶点用于创建线段,…

学习笔记(4月18日)vector底层模拟实现(1)

1.迭代器 vector实际上是由迭代器进行维护的&#xff0c;关于迭代器是什么&#xff0c;为什么要叫这个名字&#xff0c;后面的学习会逐渐了解&#xff0c;现在先将迭代器是作为指针即可。 vector底层有三个迭代器&#xff0c;用来起到容量、数组头、元素个数的作用。 同时为…

基于XML配置bean(一)

文章目录 1.获取bean的两种方式1.通过id获取bean&#xff08;前面用过&#xff09;2.通过类型获取bean&#xff08;单例时使用&#xff09;1.案例2.代码1.beans.xml2.SpringBeanTest.java3.结果 3.注意事项 2.三种基本依赖注入方式1.通过属性配置bean&#xff08;前面用过&…

DDoS攻击趋势分析及防御建议:网络安全新挑战与应对策略

在数字化日益普及的今天&#xff0c;网络安全问题日益凸显。其中&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击以其巨大的破坏力和难以防范的特性&#xff0c;发起简单、效果显著、难以追踪等特点&#xff0c;因此被黑客广泛使用&#xff0c;已经成为网络安全领…

Python(九十四)变量的作用域

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

CSS 设置空格原样显示 white-space:pre-wrap;

CSS 设置空格原样显示 问题描述 html 渲染内容时&#xff0c;对于 空格、回车、Tab 键的 默认处理方式是 &#xff1a; 无论存在多少个连续的空格&#xff0c;都只会保留一个。 结论 由于以上的特性&#xff0c;导致了我们无法直接渲染出原格式的文本。pre 标签 了解一下 &…

element-plus中的图标和文字水平对齐

<span><el-icon size"14px"><Delete /></el-icon> <span>删除</span> </span>解决方法&#xff1a;加上vertical-align: middle样式就可以了 <span><el-icon size"14px" style"vertical-align: …

【STM32CubeIDE 1.15.0】汉化包带路径配置过程

一、IDE软件下载 二、汉化版包路径 三、IDE软件板载汉化包 一、IDE软件下载 ST官网IDE下载链接 二、汉化版包路径 https://mirrors.ustc.edu.cn/eclipse/technology/babel/update-site/ 找不到就到.cn后面一级一级进 三、IDE软件板载汉化包 https://mirrors.ustc.edu…

数据库工具解析之 OceanBase 数据库导出工具

背景 大多数的数据库都配备了自己研发的导入导出工具&#xff0c;对于不同的使用者来说&#xff0c;这些工具能够发挥不一样的作用。例如&#xff1a;DBA可以使用导数工具进行逻辑备份恢复&#xff0c;开发者可以使用导数工具完成系统间的数据交换。这篇文章主要是为OceanBase…

​波士顿动力发布全新人形机器人:Atlas

4月16日&#xff0c;波士顿动力&#xff08;Boston Dynamics&#xff09;发布了《再见&#xff0c;液压Atlas》视频&#xff0c;正式宣告其研发的液压驱动双足人形机器人Atlas退役。 在视频的结尾&#xff0c;Atlas深深鞠躬&#xff0c;之后还有一句话“直到我们再次相遇&…

B1098 岩洞施工

solution #include<iostream> using namespace std; int main(){int n, x, top 1000, down 0;//管道水平放入>顶部最低点和底部最高点之间的距离就是能够承担的最大宽度scanf("%d", &n);for(int i 0; i < n; i){scanf("%d", &x);i…

3D模型处理的多进程并行【Python】

今天我们将讨论如何使用 Python 多进程来处理大量3D数据。 我将讲述一些可能在手册中找到的一般信息&#xff0c;并分享我发现的一些小技巧&#xff0c;例如将 tqdm 与多处理 imap 结合使用以及并行处理存档。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生…

AI论文速读 | 2024[VLDB]TFB:全面与公正的时间序列预测方法基准测试研究

论文标题&#xff1a;TFB: Towards Comprehensive and Fair Benchmarking of Time Series Forecasting Methods 作者&#xff1a;Xiangfei Qiu ; Jilin Hu&#xff08;胡吉林&#xff09; ; Lekui Zhou ; Xingjian Wu ; Junyang Du ; Buang Zhang ; Chenjuan Guo&#xff08;郭…

【软件】如何下载谷歌安装包?

1、访问谷歌浏览器官网&#xff1a;https://www.google.cn/chrome/index.html 2、在浏览器地址栏最后添加?standalone1&#xff0c;按回车&#xff0c;重新加载页面。页面和之前的一样&#xff0c;点击下载 完整地址&#xff1a;https://www.google.cn/chrome/index.html?…

2024年第十六届“华中杯”(A题)大学生数学建模挑战赛| 物理建模,多目标优化| 数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看华中杯 (A题&#xff09;&#xff01; CS团队倾…

Java面试八股之Iterator和ListIterator的区别是什么

Iterator和ListIterator的区别是什么 这道题也是考查我们对迭代器相关的接口的了解程度&#xff0c;从代码中我们可以看出后者是前者的子接口&#xff0c;在此基础上做了一些增强&#xff0c;并且只用于List集合类型。 定义与基本概念 Iterator&#xff1a; 定义&#xff1a…

虚拟人多元化互动玩法,助力各领域发布会/直播活动“玩转”营销新高度

在数字新科技推动下&#xff0c;各地方文旅、品牌纷纷在发布会、展会、行业峰会论坛、推广直播等场景中&#xff0c;融入虚拟人IP&#xff0c;将虚拟人IP作为虚拟主播、虚拟主持人、虚拟嘉宾、虚拟推荐官、AI数字迎宾员、AI播报员等多重身份&#xff0c;与观众实时互动交流&…

【动态规划】C++解决斐波那契模型题目(三步问题、爬楼梯、解码方法...)

1. 前言 - 介绍动态规划算法 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09; 是一种解决复杂问题的算法设计技术&#xff0c;通常用于解决具有重叠子问题和最优子结构性质的问题。它将问题分解成较小的子问题&#xff0c;通过解决这些子问题并保存其…