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

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实现音视频播放器,支持录像截图功能,提供源码分享与下载

前言&#xff1a; SDL是音视频播放和渲染的一个开源库&#xff0c;主要利用它进行视频渲染和音频播放。 SDL库下载路径&#xff1a;https://github.com/libsdl-org/SDL/releases/tag/release-2.26.3&#xff0c;我使用的是2.26.3版本&#xff0c;大家可以自行选择该版本或其他版…...

Microsoft Excel整合Python:数据分析的新纪元

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…...

【前端代码规范】

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

postgresql-日期函数

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

Android11去掉Setings里的投射菜单条目

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

fnm(Node.js 版本管理器)

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

Apipost:为什么是开发者首选的API调试工具

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

Echarts图表坐标轴文字太长,省略显示,鼠标放上显示全部(vue)

注意&#xff1a;记得加上这个&#xff0c;触发事件&#xff0c; triggerEvent: true,重点&#xff1a;下面就是处理函数&#xff0c;在实例化图表的时候使用&#xff0c;传入参数是echarts的实例 // 渲染echartsfirstBarChart() {const that thislet columnar echarts.init…...

C语言控制语句——跳转关键字

循环和switch专属的跳转&#xff1a;break循环专属的跳转&#xff1a;continue无条件跳转&#xff1a;goto break 循环的break说明 某一条件满足时&#xff0c;不再执行循环体中后续重复的代码&#xff0c;并退出循环 需求&#xff1a;一共吃5碗饭, 吃到第3碗吃饱了, 结束吃饭…...

C#,《小白学程序》第五课:队列(Queue)

日常生活中常见的排队&#xff0c;软件怎么体现呢&#xff1f; 排队的基本原则是&#xff1a;先到先得&#xff0c;先到先吃&#xff0c;先进先出 1 文本格式 /// <summary> /// 《小白学程序》第五课&#xff1a;队列&#xff08;Queue&#xff09; /// 日常生活中常见…...

【【萌新的STM32学习25--- USART寄存器的介绍】】

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

SpringBootWeb案例 Part 5

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

【ES6】Promise.race的用法

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

PMP - 敏捷 3355

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

Mapbox-gl 关闭所有Popup,以及关闭按钮出现黑色边框bug

1.官方示例 var popup new mapboxgl.Popup().addTo(map);popup.remove(); 很明显&#xff0c;需要记录popup对象&#xff0c;管理起来比较麻烦。 2.本人采用div的方式关闭所有的popup&#xff0c;在map对象上新增加方法 map.closePopupmapView.popupClear function(){$(&q…...

RE:从零开始的车载Android HMI(四) - 收音机刻度尺

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

评估安全 Wi-Fi 接入:Cisco ISE、Aruba、Portnox 和 Foxpass

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

java 泛型作为方法的返回值的封装

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

ASP.NET Core 中基于 Minimal APIs 的Web API

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

Unity ProBuilder SetUVs 不起作用

ProBuilder SetUVs 不起作用 &#x1f41f; 需要设置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(…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

Nuxt.js 中的路由配置详解

Nuxt.js 通过其内置的路由系统简化了应用的路由配置&#xff0c;使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程

鸿蒙电脑版操作系统来了&#xff0c;很多小伙伴想体验鸿蒙电脑版操作系统&#xff0c;可惜&#xff0c;鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机&#xff0c;来体验大家心心念念的鸿蒙系统啦&#xff01;注意&#xff1a;虚拟…...

深度解析:etcd 在 Milvus 向量数据库中的关键作用

目录 &#x1f680; 深度解析&#xff1a;etcd 在 Milvus 向量数据库中的关键作用 &#x1f4a1; 什么是 etcd&#xff1f; &#x1f9e0; Milvus 架构简介 &#x1f4e6; etcd 在 Milvus 中的核心作用 &#x1f527; 实际工作流程示意 ⚠️ 如果 etcd 出现问题会怎样&am…...

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...

自定义线程池1.2

自定义线程池 1.2 1. 简介 上次我们实现了 1.1 版本&#xff0c;将线程池中的线程数量交给使用者决定&#xff0c;并且将线程的创建延迟到任务提交的时候&#xff0c;在本文中我们将对这个版本进行如下的优化&#xff1a; 在新建线程时交给线程一个任务。让线程在某种情况下…...

使用ch340继电器完成随机断电测试

前言 如图所示是市面上常见的OTA压测继电器&#xff0c;通过ch340串口模块完成对继电器的分路控制&#xff0c;这里我编写了一个脚本方便对4路继电器的控制&#xff0c;可以设置开启时间&#xff0c;关闭时间&#xff0c;复位等功能 软件界面 在设备管理器查看串口号后&…...