QT下使用ffmpeg+SDL实现音视频播放器,支持录像截图功能,提供源码分享与下载
前言:
SDL是音视频播放和渲染的一个开源库,主要利用它进行视频渲染和音频播放。
SDL库下载路径:https://github.com/libsdl-org/SDL/releases/tag/release-2.26.3,我使用的是2.26.3版本,大家可以自行选择该版本或其他版本的库。
一、SDL库介绍:
SDL2.lib、SDL2main.lib和SDL2test.lib是SDL库的不同部分和功能。
SDL2.lib:这是SDL库的主要部分,包含了所有常用的SDL功能和函数。它提供了与窗口、渲染、音频、事件处理等相关的功能。
SDL2main.lib:这是用于Windows平台上的SDL2的可执行文件的入口点的库文件。它包含了与Windows系统相关的代码,用于初始化SDL2和设置应用程序的入口点。
SDL2test.lib:这是SDL测试库,包含了一些用于测试和验证SDL功能的测试代码和工具。
通常情况下,您只需要链接SDL2.lib和SDL2main.lib就可以使用SDL库的大部分功能。SDL2test.lib主要是用于SDL开发的测试和验证,一般情况下不需要链接到您的应用程序中。
请注意,如果你在Qt项目中使用了Qt的消息循环(例如使用QApplication::exec()),则应该使用SDL2main.lib而不是SDL2.lib。这是因为SDL2main.lib包含了一个定义了WinMain函数的模块,可以与Qt的消息循环兼容。如果你不使用Qt的消息循环,可以使用SDL2.lib。
音频方面:
SDL提供两种使音频设备取得音频数据方法:
a. push,SDL以特定的频率调用回调函数,在回调函数中取得音频数据(本文采用);
b. pull,用户程序以特定的频率调用SDL_QueueAudio(),向音频设备提供数据。
二、SDL常用函数介绍:
2.1. SDL初始化,初始化音频和视频模块以及时间模块
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
TestNotNull(NULL, SDL_GetError());
}
2.2. 创建一个显示窗口给SDL winid是qt窗口的id,这样SDL窗口就会嵌入到Qt窗口里面
window = SDL_CreateWindowFrom((void *)winId());
SDL_Window* SDL_CreateWindow(const char* A,int B,int C,int D,int E,Uint32 F)
函数说明:创建窗口,成功返回指向SDL_Window的指针,失败返回NULL
2.3.为指定窗口创建渲染器上下文
SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, int index, Uint32 flags)
2.4.将制定区域(srcrect)的纹理数据,拷贝到渲染目标尺寸为(dstrect)的渲染器上下文(renderer)中,为下一步的渲染做准备
int SDL_RenderCopy(SDL_Renderer* renderer, SDL_Texture* texture, const SDL_Rect* srcrect, const SDL_Rect* dstrect)
参数的含义如下。
renderer:渲染目标。
texture:输入纹理。
srcrect:选择输入纹理的一块矩形区域作为输入。设置为NULL的时候整个纹理作为输入。
dstrect:选择渲染目标的一块矩形区域作为输出。设置为NULL的时候整个渲染目标作为输出。
2.5.将渲染器上下文中的数据,渲染到关联窗体上去
void SDL_RenderPresent(SDL_Renderer* renderer)
2.6.此函数可为渲染器上下文创建纹理
SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer,
Uint32 format,
int access,
int w,
int h)
2.7.使用新的像素数据更新给定的纹理矩形。也就是说,可以固定刷新纹理的某一分部区域
图像数据写入显存中
int SDL_UpdateTexture(SDL_Texture* texture,
const SDL_Rect* rect,
const void* pixels,
int pitch)
2.8.清理屏幕
int SDLCALL SDL_RenderClear(SDL_Renderer * renderer);
2.9.线程、锁、条件变量
SDL线程创建:SDL_CreateThread
SDL线程等待:SDL_WaitThead
SDL互斥锁:SDL_CreateMutex/SDL_DestroyMutex
SDL锁定互斥:SDL_LockMutex/SDL_UnlockMutex
SDL条件变量(信号量):SDL_CreateCond/SDL_DestoryCond
SDL条件变量(信号量)等待/通知:SDL_CondWait/SDL_CondSingal
10.读取事件
SDL_PollEvent
从事件队列中,读取事件的常用函数
如果时间队列中有待处理事件,返回1;如果没有可处理事件,则返回0
11.发送事件
int SDL_PushEvent(SDL_Event * event);
12.等待事件
SDL_WaitEvent
13.音频相关
SDL_PauseAudio(0) //恢复音频播放
SDL_PauseAudio(1) //暂停音频播放
SDL_CloseAudio() //关闭音频
三、ffmpeg+SDL实现音视频播放器:
3.1 支持实时流、支持视频文件的音视频播放
3.2 支持录像、支持截图功能
录像:
截图:
3.3 核心代码
cvideoplayer.h
#ifndef CVIDEOPLAYER_H
#define CVIDEOPLAYER_H#include <QObject>
#include <QWidget>#include <QtWidgets>
#include <SDL.h>
#include "commondef.h"
#include <QQueue>
#include "mp4recorder.h"#define MAX_AUDIO_OUT_SIZE 8*1152typedef struct audio_data
{char data[MAX_AUDIO_OUT_SIZE];int size;
}audio_data_t;class cVideoPlayer : public QWidget
{Q_OBJECT
public:cVideoPlayer(QWidget *parent = nullptr, int nWidth = 704, int nHeight = 576);~cVideoPlayer();public:bool loadVideo(const QString &sUrl);void playVideo();void OpenAudio(bool bOpen);void Snapshot();void startRecord(bool bStart);protected:void paintEvent(QPaintEvent *event) override;public:QQueue<audio_data_t*> m_adq;private:bool m_bRun = false;int m_nPlayWay = MEDIA_PLAY_STREAM;SDL_Window *m_pWindow = nullptr;SDL_Renderer *m_pRenderer = nullptr;SDL_Texture *m_pTexture = nullptr;AVFormatContext *m_pFormatCtx = nullptr;AVCodecContext* m_pVideoCodecCxt = nullptr; //视频解码器上下文AVCodecContext* m_pAudioCodecCxt = nullptr; //音频解码器上下文SwrContext *m_pAudioSwrContext = nullptr; //音频重采样上下文AVFrame *m_pFrame = nullptr;AVFrame *m_pYuvFrame = nullptr;AVFrame *m_pPcmFrame = nullptr;struct SwsContext *m_pSwsCtx = nullptr;int m_nBufferSize = 0;uint8_t *m_pDstBuffer = nullptr;int m_nVideoIndex = -1;int m_nAudioIndex = -1;int64_t m_nStartTime;AVPacket m_packet;enum AVCodecID m_CodecId;enum AVCodecID m_AudioCodecId;int m_nSDLWidth;int m_nSDLHeight;//音频SDL_AudioSpec m_audioSpec;bool m_bSupportAudio = false;bool m_bPauseAudio = false;int m_nAudioSampleRate = 8000; //音频采样率int m_nAudioPlaySampleRate = 44100; //音频播放采样率int m_nAudioPlayChannelNum = 1; //音频播放通道数//mp4录像mp4Recorder m_mp4Recorder;bool m_bRecord = false;//截图bool m_bSnapshot = false;SDL_Surface *m_pSurface = nullptr;
};#endif // CVIDEOPLAYER_H
cvideoplayer.cpp
#include "cvideoplayer.h"QMutex g_lock;// 音频处理回调函数。读队列获取音频包,解码,播放
void sdl_audio_callback(void *userdata, uint8_t *stream, int len)
{cVideoPlayer *pPlayer = (cVideoPlayer*)userdata;SDL_memset(stream, 0, len);if(pPlayer && pPlayer->m_adq.size() > 0){QMutexLocker guard(&g_lock);audio_data_t* pAudioData = pPlayer->m_adq.dequeue();if(pAudioData->size == 0)return;len = (len>pAudioData->size?pAudioData->size:len);SDL_MixAudio(stream, (uint8_t *)pAudioData->data, len, SDL_MIX_MAXVOLUME);delete pAudioData;}
}cVideoPlayer::cVideoPlayer(QWidget *parent, int nWidth, int nHeight) : QWidget(parent),m_nSDLWidth(nWidth), m_nSDLHeight(nHeight)
{this->resize(nWidth, nHeight);
}cVideoPlayer::~cVideoPlayer()
{m_bRun = false;MY_DEBUG << "~cVideoPlayer 000";if(nullptr != m_pSurface){SDL_FreeSurface(m_pSurface);m_pSurface = nullptr;}if(nullptr != m_pTexture){SDL_DestroyTexture(m_pTexture);m_pTexture = nullptr;}MY_DEBUG << "~cVideoPlayer 111";if(nullptr != m_pRenderer){SDL_DestroyRenderer(m_pRenderer);m_pRenderer = nullptr;}MY_DEBUG << "~cVideoPlayer 222";if(nullptr != m_pWindow){SDL_DestroyWindow(m_pWindow);m_pWindow = nullptr;}MY_DEBUG << "~cVideoPlayer 333";SDL_CloseAudio();SDL_Quit();MY_DEBUG << "~cVideoPlayer 444";if(nullptr != m_pFrame){av_frame_free(&m_pFrame);m_pFrame = nullptr;}if(nullptr != m_pYuvFrame){av_frame_free(&m_pYuvFrame);m_pYuvFrame = nullptr;}MY_DEBUG << "~cVideoPlayer 555";if(nullptr != m_pVideoCodecCxt){avcodec_close(m_pVideoCodecCxt);//m_pVideoCodecCxt = nullptr; //设置为nullptr会导致崩溃}MY_DEBUG << "~cVideoPlayer 666";if(m_pFormatCtx){avformat_close_input(&m_pFormatCtx);avformat_free_context(m_pFormatCtx);m_pFormatCtx = nullptr;}MY_DEBUG << "~cVideoPlayer 777";if(nullptr != m_pSwsCtx){sws_freeContext(m_pSwsCtx);m_pSwsCtx = nullptr;}MY_DEBUG << "~cVideoPlayer 888";if(nullptr != m_pDstBuffer){av_free(m_pDstBuffer);}MY_DEBUG << "~cVideoPlayer end";
}bool cVideoPlayer::loadVideo(const QString &sUrl)
{avformat_network_init();if(sUrl.contains("rtsp://") || sUrl.contains("http://")) //实时流m_nPlayWay = MEDIA_PLAY_STREAM;elsem_nPlayWay = MEDIA_PLAY_FILE;m_pFormatCtx = avformat_alloc_context();if (avformat_open_input(&m_pFormatCtx, sUrl.toStdString().c_str(), nullptr, nullptr) != 0){MY_DEBUG << "Failed to open video file";return false;}if (avformat_find_stream_info(m_pFormatCtx, nullptr) < 0){MY_DEBUG << "Failed to find stream information";return false;}m_nVideoIndex = av_find_best_stream(m_pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);m_nAudioIndex = av_find_best_stream(m_pFormatCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if (m_nVideoIndex == -1){MY_DEBUG << "Failed to find video stream";return false;}//查找并打开视频解码器m_pVideoCodecCxt = avcodec_alloc_context3(nullptr);if (avcodec_parameters_to_context(m_pVideoCodecCxt, m_pFormatCtx->streams[m_nVideoIndex]->codecpar) != 0){MY_DEBUG << "Failed to copy codec parameters to codec context";return false;}const AVCodec *codec = avcodec_find_decoder(m_pVideoCodecCxt->codec_id);if (codec == nullptr){MY_DEBUG << "Failed to find video decoder";return false;}if (avcodec_open2(m_pVideoCodecCxt, codec, nullptr) < 0){MY_DEBUG << "Failed to open video decoder";return false;}m_CodecId = codec->id;//查找并打开音频解码器if(m_nAudioIndex > 0){//查找音频解码器const AVCodec *pAVCodec = avcodec_find_decoder(m_pFormatCtx->streams[m_nAudioIndex]->codecpar->codec_id);if(!pAVCodec){MY_DEBUG << "audio decoder not found";return false;}m_AudioCodecId = pAVCodec->id;//音频解码器参数配置if (!m_pAudioCodecCxt)m_pAudioCodecCxt = avcodec_alloc_context3(nullptr);if(nullptr == m_pAudioCodecCxt){MY_DEBUG << "avcodec_alloc_context3 error m_pAudioCodecCxt=nullptr";return false;}avcodec_parameters_to_context(m_pAudioCodecCxt, m_pFormatCtx->streams[m_nAudioIndex]->codecpar);//打开音频解码器int nRet = avcodec_open2(m_pAudioCodecCxt, pAVCodec, nullptr);if(nRet < 0){avcodec_close(m_pAudioCodecCxt);MY_DEBUG << "avcodec_open2 error m_pAudioCodecCxt";return false;}//音频重采样初始化if (nullptr == m_pAudioSwrContext){if(m_pAudioCodecCxt->channel_layout <= 0 || m_pAudioCodecCxt->channel_layout > 3)m_pAudioCodecCxt->channel_layout = 1;MY_DEBUG << "m_audioCodecContext->channel_layout:" << m_pAudioCodecCxt->channel_layout;MY_DEBUG << "m_audioCodecContext->channels:" << m_pAudioCodecCxt->channels;m_pAudioSwrContext = swr_alloc_set_opts(0,m_pAudioCodecCxt->channel_layout,AV_SAMPLE_FMT_S16,m_pAudioCodecCxt->sample_rate,av_get_default_channel_layout(m_pAudioCodecCxt->channels),m_pAudioCodecCxt->sample_fmt,m_pAudioCodecCxt->sample_rate,0,0);auto nRet = swr_init(m_pAudioSwrContext);if(nRet < 0){MY_DEBUG << "swr_init error";return false;}}m_nAudioSampleRate = m_pAudioCodecCxt->sample_rate;m_audioSpec.freq = m_nAudioSampleRate; // 采样率m_audioSpec.format = AUDIO_S16SYS; // S表带符号,16是采样深度,SYS表采用系统字节序m_audioSpec.channels = m_pAudioCodecCxt->channels; // 声道数m_audioSpec.silence = 0; // 静音值m_audioSpec.samples = m_pAudioCodecCxt->frame_size; // SDL声音缓冲区尺寸,单位是单声道采样点尺寸x通道数m_audioSpec.callback = sdl_audio_callback; // 回调函数,若为NULL,则应使用SDL_QueueAudio()机制m_audioSpec.userdata = this; // 提供给回调函数的参数//打开音频设备并创建音频处理线程if(SDL_OpenAudio(&m_audioSpec, NULL) < 0){MY_DEBUG << "SDL_OpenAudio failed.";return false;}if(nullptr == m_pPcmFrame)m_pPcmFrame = av_frame_alloc();m_bSupportAudio = true;}av_dump_format(m_pFormatCtx, 0, NULL, 0);m_pFrame = av_frame_alloc();m_pSwsCtx = sws_getContext(m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, m_pVideoCodecCxt->pix_fmt,m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, AV_PIX_FMT_YUV420P,SWS_BICUBIC, nullptr, nullptr, nullptr);m_nBufferSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1);m_pDstBuffer = (unsigned char*)av_malloc(m_nBufferSize);if (!m_pDstBuffer){MY_DEBUG << "av_malloc error";return false;}m_pYuvFrame = av_frame_alloc();int ret = av_image_fill_arrays(m_pYuvFrame->data, m_pYuvFrame->linesize, m_pDstBuffer, AV_PIX_FMT_YUV420P, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height, 1);if(ret <= 0){return false;}//SDLif(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){MY_DEBUG << "SDL_Init failed.";return false;}m_pWindow = SDL_CreateWindowFrom((void *)winId());if(!m_pWindow){MY_DEBUG << "SDL_CreateWindowFrom failed.";return false;}SDL_ShowWindow(m_pWindow);m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, 0);if(!m_pRenderer){MY_DEBUG << "SDL_CreateRenderer failed.";return false;}m_pTexture = SDL_CreateTexture(m_pRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, m_pVideoCodecCxt->width, m_pVideoCodecCxt->height);if(!m_pTexture){MY_DEBUG << "SDL_CreateTexture failed.";return false;}m_pSurface = SDL_CreateRGBSurface(0, m_nSDLWidth, m_nSDLHeight, 24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0);m_bRun = true;m_nStartTime = av_gettime();return true;
}void cVideoPlayer::playVideo()
{SDL_Event event;//启用音频子线程回调处理if(m_bSupportAudio)SDL_PauseAudio(0);while(m_bRun){if(av_read_frame(m_pFormatCtx, &m_packet) >= 0){if(m_bRecord){//MY_DEBUG << "record...";AVPacket* pPkt = av_packet_clone(&m_packet);m_mp4Recorder.saveOneFrame(*pPkt, m_CodecId, m_AudioCodecId);av_packet_free(&pPkt);}if (m_packet.stream_index == m_nVideoIndex){if(m_nPlayWay == MEDIA_PLAY_FILE)//本地文件播放延时处理{AVRational time_base = m_pFormatCtx->streams[m_nVideoIndex]->time_base;AVRational time_base_q = {1, AV_TIME_BASE}; // AV_TIME_BASE_Q;int64_t pts_time = av_rescale_q(m_packet.dts, time_base, time_base_q);//MY_DEBUG << "pts_time:" << pts_time;int64_t now_time = av_gettime() - m_nStartTime;if (pts_time > now_time)av_usleep((pts_time - now_time));}avcodec_send_packet(m_pVideoCodecCxt, &m_packet);while (avcodec_receive_frame(m_pVideoCodecCxt, m_pFrame) == 0){sws_scale(m_pSwsCtx, m_pFrame->data, m_pFrame->linesize, 0, m_pVideoCodecCxt->height,m_pYuvFrame->data, m_pYuvFrame->linesize);SDL_UpdateTexture(m_pTexture, nullptr, m_pYuvFrame->data[0], m_pYuvFrame->linesize[0]);SDL_RenderClear(m_pRenderer);SDL_RenderCopy(m_pRenderer, m_pTexture, nullptr, nullptr);SDL_RenderPresent(m_pRenderer);if(m_bSnapshot){SDL_RenderReadPixels(m_pRenderer, NULL, SDL_PIXELFORMAT_RGB24, m_pSurface->pixels, m_pSurface->pitch);QString sImgePath = QString("%1screenshot.bmp").arg(SNAPSHOT_DEFAULT_PATH);SDL_SaveBMP(m_pSurface, sImgePath.toUtf8().data());m_bSnapshot = false;}SDL_PollEvent(&event);if (event.type == SDL_QUIT){MY_DEBUG << "event.type == SDL_QUIT";m_bRun = false;break;}}}else if(m_packet.stream_index == m_nAudioIndex){int nRet = avcodec_send_packet(m_pAudioCodecCxt, &m_packet);if(nRet != 0){av_packet_unref(&m_packet);continue;}nRet = 0;while(!nRet){nRet = avcodec_receive_frame(m_pAudioCodecCxt, m_pPcmFrame);if(nRet == 0){audio_data_t* pAudioData = new audio_data_t;uint8_t *pData[1];pData[0] = (uint8_t *)pAudioData->data;//获取目标样本数auto nDstNbSamples = av_rescale_rnd(m_pPcmFrame->nb_samples,m_nAudioPlaySampleRate,m_nAudioSampleRate,AV_ROUND_ZERO);//重采样int nLen = swr_convert(m_pAudioSwrContext, pData, nDstNbSamples,(const uint8_t **)m_pPcmFrame->data,m_pPcmFrame->nb_samples);if(nLen <= 0){MY_DEBUG << "swr_convert error";delete pAudioData;continue;}//获取样本保存的缓存大小int nOutsize = av_samples_get_buffer_size(nullptr, m_pAudioCodecCxt->channels,m_pPcmFrame->nb_samples,AV_SAMPLE_FMT_S16,0);pAudioData->size = nOutsize;QMutexLocker guard(&g_lock);if(!m_bPauseAudio)m_adq.enqueue(pAudioData);}}}av_packet_unref(&m_packet);}}
}void cVideoPlayer::OpenAudio(bool bOpen)
{if(bOpen){m_bPauseAudio = false;SDL_PauseAudio(0);}else{m_bPauseAudio = true;SDL_PauseAudio(1);}
}void cVideoPlayer::Snapshot()
{m_bSnapshot = true;
}void cVideoPlayer::startRecord(bool bStart)
{if(bStart){QString sPath = RECORD_DEFAULT_PATH;QDate date = QDate::currentDate();QTime time = QTime::currentTime();QString sRecordPath = QString("%1%2-%3-%4-%5%6%7.mp4").arg(sPath).arg(date.year()). \arg(date.month()).arg(date.day()).arg(time.hour()).arg(time.minute()). \arg(time.second());MY_DEBUG << "sRecordPath:" << sRecordPath;if(nullptr != m_pFormatCtx && m_bRun){m_bRecord = m_mp4Recorder.Init(m_pFormatCtx, m_CodecId, m_AudioCodecId, sRecordPath);}}else{if(m_bRecord){MY_DEBUG << "stopRecord...";m_mp4Recorder.DeInit();m_bRecord = false;}}
}void cVideoPlayer::paintEvent(QPaintEvent *event)
{Q_UNUSED(event);QPainter painter(this);painter.fillRect(rect(), Qt::black);
}
3.4 开发过程问题处理
1)qtmain.lib 无法解析外部符号_main _winmain
这个错误通常是由于Qt和SDL使用了不同的入口函数引起的。Qt使用的是main函数作为入口,而SDL使用的是WinMain函数作为入口。当你在Qt项目中调用SDL函数时,链接器会找不到main或WinMain函数,从而导致链接错误。
解决方法:在main函数上增加#undef main,如下
#undef main
int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;w.show();return a.exec();
}
2)视频没渲染问题
创建好窗口以后需要调用 SDL_ShowWindow(window);显示SDL窗口
3.5 播放器工程下载
https://download.csdn.net/download/linyibin_123/88262235
相关文章:

QT下使用ffmpeg+SDL实现音视频播放器,支持录像截图功能,提供源码分享与下载
前言: SDL是音视频播放和渲染的一个开源库,主要利用它进行视频渲染和音频播放。 SDL库下载路径:https://github.com/libsdl-org/SDL/releases/tag/release-2.26.3,我使用的是2.26.3版本,大家可以自行选择该版本或其他版…...

Microsoft Excel整合Python:数据分析的新纪元
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...

【前端代码规范】
前端代码规范 vue3版本:【Vue&React】版本TS版本:【TS&JS】版本vite版本:【Webpack&Vite】版本Eslint版本:命名规则:【见名识意】项目命名:目录命名:JS/VUE文件CSS/SCSS文件命名:HTML文件命名:…...

postgresql-日期函数
postgresql-日期函数 日期时间函数计算时间间隔获取时间中的信息截断日期/时间创建日期/时间获取系统时间CURRENT_DATE当前事务开始时间 时区转换 日期时间函数 PostgreSQL 提供了以下日期和时间运算的算术运算符。 计算时间间隔 age(timestamp, timestamp)函数用于计算两…...

Android11去掉Setings里的投射菜单条目
Android11去掉【设置】--【已连接的设备】--【连接偏好设置】里的投射菜单条目,具体如下: commit 0c0583e6ddcdea21ec02db291d9a07d90f10aa59 Author: wzh <wzhincartech.com> Date: Wed Jul 21 16:37:13 2021 0800去掉投射菜单Change-Id: Id7f…...

fnm(Node.js 版本管理器)
fnm是什么? fnm是一款快速简单跨平台的 Node.js 版本管理器,使用 Rust 构建。 fnm怎么使用? 查看node 已安装列表 fnm list node 版本切换 fnm use 版本号 fnm use 16.0.0...

Apipost:为什么是开发者首选的API调试工具
文章目录 前言正文接口调试接口公共参数、环境全局参数的使用快速生成并导出接口文档研发协作接口压测和自动化测试结论 前言 Apipost是一款支持 RESTful API、SOAP API、GraphQL API等多种API类型,支持 HTTPS、WebSocket、gRPC多种通信协议的API调试工具。除此之外…...

Echarts图表坐标轴文字太长,省略显示,鼠标放上显示全部(vue)
注意:记得加上这个,触发事件, triggerEvent: true,重点:下面就是处理函数,在实例化图表的时候使用,传入参数是echarts的实例 // 渲染echartsfirstBarChart() {const that thislet columnar echarts.init…...

C语言控制语句——跳转关键字
循环和switch专属的跳转:break循环专属的跳转:continue无条件跳转:goto break 循环的break说明 某一条件满足时,不再执行循环体中后续重复的代码,并退出循环 需求:一共吃5碗饭, 吃到第3碗吃饱了, 结束吃饭…...

C#,《小白学程序》第五课:队列(Queue)
日常生活中常见的排队,软件怎么体现呢? 排队的基本原则是:先到先得,先到先吃,先进先出 1 文本格式 /// <summary> /// 《小白学程序》第五课:队列(Queue) /// 日常生活中常见…...

【【萌新的STM32学习25--- USART寄存器的介绍】】
萌新的STM32学习25- USART寄存器的介绍 STM32–USART寄存器介绍(F1) 控制寄存器1 (CR1) 位13: 使能USART UE 0: USART分频器和输出被禁止 1: USART模块使能 位12 : 配置8个数据位…...

SpringBootWeb案例 Part 5
4. 配置文件 员工管理的增删改查功能我们已开发完成,但在我们所开发的程序中还一些小问题,下面我们就来分析一下当前案例中存在的问题以及如何优化解决。 4.1 参数配置化 在我们之前编写的程序中进行文件上传时,需要调用AliOSSUtils工具类&…...

【ES6】Promise.race的用法
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。 const p Promise.race([p1, p2, p3]);上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值&#…...

PMP - 敏捷 3355
三个核心 产品负责人 负责最大化投资回报(ROI),通过确定产品特性,把他们翻译成一个有优先级的列表 为下一个 sprint 决定在这个列表中哪些应该优先级最高,并且不断调整优先级以及调整这个列表 职责是定义需求、定义…...

Mapbox-gl 关闭所有Popup,以及关闭按钮出现黑色边框bug
1.官方示例 var popup new mapboxgl.Popup().addTo(map);popup.remove(); 很明显,需要记录popup对象,管理起来比较麻烦。 2.本人采用div的方式关闭所有的popup,在map对象上新增加方法 map.closePopupmapView.popupClear function(){$(&q…...

RE:从零开始的车载Android HMI(四) - 收音机刻度尺
最近比较忙,研究复杂的东西需要大量集中的时间,但是又抽不出来,就写点简单的东西吧。车载应用开发中有一个几乎避不开的自定义View,就是收音机的刻度条。本篇文章我们来研究如何绘制一个收音机的刻度尺。 本系列文章的目的是在讲…...

评估安全 Wi-Fi 接入:Cisco ISE、Aruba、Portnox 和 Foxpass
在当今不断变化的数字环境中,对 Wi-Fi 网络进行强大访问控制的需求从未像现在这样重要。各组织一直在寻找能够为其用户提供无缝而安全的体验的解决方案。 在本博客中,我们将深入探讨保护 Wi-Fi(和有线)网络的四种领先解决方案——…...

java 泛型作为方法的返回值的封装
问题背景 业务需要,经常需要http方式调用某服务,然后某服务返回特定类型的返回内容。 类似 String resStr xxxHttpClient.post() ,然后它返回一个字符串,你还需要反序列化成某种格式的。 返回值可以反序列化成的形式如下&#…...

ASP.NET Core 中基于 Minimal APIs 的Web API
基于 Minimal APIs 的Web API Minimal APIs 是ASP.NET Core中快速构建 REST API 的方式,可以用最少的代码构建全功能的REST API。比如下面三行代码: var app WebApplication.Create(args); app.MapGet("/", () > "Hello World!&quo…...

Unity ProBuilder SetUVs 不起作用
ProBuilder SetUVs 不起作用 🐟 需要设置face.manulUV true public static void Set01UV(this ProBuilderMesh mesh){foreach (var face in mesh.faces){face.manualUV true;//设置为手动uv}var vertices mesh.GetVertices().Select(v > v.position).ToArray(…...

c#接口(interface)
概述: 在C#中,接口是一种定义了一组相关方法、属性和事件的规范。接口可以被类或结构体实现,以提供一种方式来定义类之间的契约或协议。 接口定义了一组成员,这些成员没有具体的实现。实现接口的类必须提供这些成员的具体实现。…...

SSH远程连接macOS服务器:通过cpolar内网穿透技术实现远程访问的设置方法
文章目录 前言1. macOS打开远程登录2. 局域网内测试ssh远程3. 公网ssh远程连接macOS3.1 macOS安装配置cpolar3.2 获取ssh隧道公网地址3.3 测试公网ssh远程连接macOS 4. 配置公网固定TCP地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址 5. 使用固定TCP端口地址ssh远程 …...

【C++】Visual Studio EditorConfig 格式设置
【C】Visual Studio EditorConfig 格式设置 文章目录 【C】Visual Studio EditorConfig 格式设置I - EditorConfig1.1 - 通用设置indent_styleindent_sizetab_widthend_of_linecharsettrim_trailing_whitespaceinsert_final_newline II - Visual Studio 特定键值缩进设置cpp_in…...

服务器单机大规模数据存储方案
大规模数据存储都需要解决三个核心问题: 1.数据存储容量的问题,既然大数据要解决的是数据 PB 计的数据计算问题,而一般的服务器磁盘容量通常 1~2TB,那么如何存储这么大规模的数据呢? 2.数据读写速度的问题&…...

ElasticSearch-集成ik分词器
本文已收录于专栏 《中间件合集》 目录 背景介绍版本选择优势说明集成过程1.下载安装包2.解压安装包3.重启ElasticSearch服务3.1通过ps -ef | grep elastic查看正在启动的es进程号3.2使用kill -9 xxx 杀死进程3.3使用 ./elasticsearch 启动es服务 分词测试细粒度分词方式分词请…...

c++版opencv求二值图的质心
代码 #include <iostream> #include <opencv2/core.hpp> #include <opencv2/highgui.hpp> #include <opencv2/imgproc.hpp>int main(int argc, char* argv[]) {cv::Mat input_image cv::imread("Untitled.png", cv::IMREAD_GRAYSCALE);cv:…...

6、深入解析Kotlin类与对象:构造、伴生、单例全面剖析
前言 本篇文章将带您了解Kotlin编程中的重要概念:类及构造函数、访问修饰符、伴生对象和单例模式。就像搭积木一样,我们会逐步揭开这些概念的面纱,让您轻松理解它们的作用和用法。无论您是编程新手还是有经验的开发者,本文都将为…...

【开源ESP32谷歌恐龙小游戏】【游戏演示和介绍】LVGL ST7789 适用于Arduino
【源码及教程地址-持续更新】 ESP32 C3 LVGL 迷你小电视 Google谷歌恐龙小游戏 1.9寸LCD显示屏开发板 ST7789 适用于Arduino开发板,教程,资料,程序,代码,PDF手册 【开源 & ESP32谷歌恐龙小游戏】【游戏演示和介绍】LVGL ST7789 适用于Arduin...

openCV实战-系列教程7:轮廓检测2与模板匹配(轮廓检测/轮廓特征/轮廓近似/轮廓边界矩阵/轮廓边界圆/模版匹配)、原理解析、源码解读
🧡💛💚💙💜OpenCV实战系列总目录 打印一个图片可以做出一个函数: def cv_show(img,name):cv2.imshow(name,img)cv2.waitKey()cv2.destroyAllWindows() 1、轮廓特征与近似 1.1 轮廓特征 前面我们计算了…...

cs231n_1_IntroToConv
参考的视频来自如下链接https://www.bilibili.com/video/BV1Ed4y1b7bm/ 参考笔记如下https://blog.csdn.net/TeFuirnever/article/details/89059673 x.1 CV历史 生物快速发展于5.4亿年前,那时的化石显示生物进化出了视觉,视觉使得生物多样性大爆炸。 …...