嵌入式Qt 开发一个音乐播放器
上篇文章:RK3568源码编译与交叉编译环境搭建,进行了OK3568开发板软件开发环境搭建,通过编译RK3568的源码,可以得到Qt开发的交叉编译相关工具。
本篇,就来在搭建好的软件开发中,进行Qt软件的开发测试。由于Qt是支持跨平台的,因此本篇的音乐播放器,先在Windows上编写调试,功能调好后再通过交叉编译,在Linux板子上测试。
在第一篇的开箱介绍中,体验了OK3568板子自带的Qt界面,有视频播放器、音乐播放器等,这些都实现了基本的播放功能,但没有对操作界面做更加丰富的开发,所以,本篇来实现一个界面更加优美,操作更新方便的音乐播放器软件,可以实现音乐列表的显示与选择播放、歌词显示等,先来看下最终的效果:

本篇的Qt代码从野火Linux开发板的例程中移植修改而来,下面分析下程序的代码结构。
1 音乐播放器开发总体结构
整个Qt音乐播放器项目的代码结构如下:
- 主代码中是音乐播放器相关的代码,包括:
- 音乐播放器主界面
- 唱片动画界面:在音乐播放时显示一个唱片转动的动画效果
- 操作按钮界面:实现播放、暂停、继续、上一首、下一首、进度调节,音量调节
- 歌词显示界面:当有对应的歌词时,显示歌词
- 歌词加载与计算
- Ui代码中使用一些Qt的基本功能,包括:
- 一个Qt界面基类
- 滑条功能类
- 图标按钮显示类
- 列表功能类
- Skin中是一些图片资源和字体/皮肤定义
- 最后是编译的中间文件和编译结果存储的目录

先看下程序的主要代码实现。
首先是整体的主界面部分,进行各项功能的初始化显示,主要包括:
- 唱片界面初始化
- 歌词界面初始化
- 按钮界面初始化
- 播放列表初始化
void MusicPlayer::InitWidget()
{QtWidgetTitleBar *widgetTitle = new QtWidgetTitleBar(this);widgetTitle->SetScalSize(Skin::m_nScreenWidth, 60);widgetTitle->SetBackground(Qt::transparent);widgetTitle->setFont(QFont(Skin::m_strAppFontNormal));widgetTitle->SetTitle(tr("音乐播放器"), "#ffffff", 24);connect(widgetTitle, SIGNAL(signalBackHome()), this, SIGNAL(signalBackHome()));QHBoxLayout *horLayoutCentor = new QHBoxLayout();horLayoutCentor->setContentsMargins(0, 0, 0, 0);horLayoutCentor->setSpacing(0);//唱片界面m_recorder = new WidgetRecord(this);horLayoutCentor->addWidget(m_recorder, 1);//歌词界面m_lyricWidget = new LyricWidget(this);horLayoutCentor->addWidget(m_lyricWidget, 1);//按钮界面m_playerToolBar = new WidgetToolBar(this);connect(m_playerToolBar, SIGNAL(play()), this, SLOT(SltMusicPlay()));connect(m_playerToolBar, SIGNAL(pause()), this, SLOT(SltMusicPause()));connect(m_playerToolBar, SIGNAL(toolBarClicked(int)), this, SLOT(SltToolbarClicked(int)));QVBoxLayout *verLayoutAll = new QVBoxLayout(this);verLayoutAll->setContentsMargins(0, 0, 0, 0);verLayoutAll->setSpacing(0);verLayoutAll->addWidget(widgetTitle, 1);verLayoutAll->addLayout(horLayoutCentor, 6);verLayoutAll->addWidget(m_playerToolBar);// 播放列表m_widgetMusicList = new MusicPlayListWidget(this);connect(m_widgetMusicList, SIGNAL(signalMediaChanged(QString, QString)), this, SLOT(SltCurrentSongChanged(QString, QString)));m_widgetMusicList->hide();connect(m_playerToolBar, SIGNAL(next()), m_widgetMusicList->playList(), SLOT(next()));connect(m_playerToolBar, SIGNAL(previous()), m_widgetMusicList->playList(), SLOT(previous()));connect(m_playerToolBar, SIGNAL(currentPostionChanged(int)), this, SLOT(SltChangePostion(int)));
}
初始化各个界面后,还要连接对应的槽函数,如点击操作按钮后的处理、切换播放列表中的音乐时的处理等。
2 自定义控件的代码
在介绍音乐播放器代码之前,需要先介绍一些Qt自定义控件的代码,这些代码属于公共代码,写好了之后,不仅音乐播放器可以用,后续开发其它功能,也可以复用这部分代码。
2.1 qtwidgetbase
这个cpp文件中定义了3个类:
2.1.1 QtWidgetBase
窗口基类,该类的一个主要功能是可以向窗口内添加图片形式的按钮,并且通过鼠标事件,给出哪个按钮被按下:
void QtWidgetBase::addBtn(int index, QtPixmapButton *btn)
{m_btns.insert(index, btn);this->update();
}
void QtWidgetBase::mousePressEvent(QMouseEvent *e)
{QRect rect;foreach (QtPixmapButton *btn, m_btns){ScaleRect(rect, btn->rect());if (rect.contains(e->pos())){if (btn->isCheckAble()){btn->setChecked(!btn->isChecked());emit signalBtnClicked(btn->id());}else{btn->setPressed(true);}this->update();break;}}if (rect.contains(e->pos())){this->update();}QWidget::mousePressEvent(e);
}
2.1.2 QtWidgetTitleBar
窗口的标题栏,用于绘制窗口上方的标题,如显示“音乐播放器”这几个字,另外,标题栏右侧,还支持一个应用退出(返回)的图标按钮:
void QtWidgetTitleBar::SetTitle(const QString &title, const QColor &textClr, const int &fontSize)
{this->m_strTitle = title;this->m_colorText = textClr;this->m_nFontSize = fontSize;this->update();
}void QtWidgetTitleBar::paintEvent(QPaintEvent *)
{QPainter painter(this);painter.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);painter.scale(m_scaleX, m_scaleY);QRect rect(0, 0, m_nBaseWidth, m_nBaseHeight);if (m_pixmapBackground.isNull()){painter.fillRect(rect, m_colorBackground);}else{painter.drawPixmap(rect, m_pixmapBackground);}// 绘制文字QFont font = painter.font();font.setPixelSize(m_nFontSize);painter.setFont(font);painter.setPen(m_colorText);painter.drawText(rect, Qt::AlignCenter, m_strTitle);
}
2.1.3 QtAnimationWidget
属性动画移动窗体,该类用于在窗口中实现一些简单的动画效果,如音乐播放器会有一个唱片转动和唱针抬起放下的动作。

该类通过调用Qt的QPropertyAnimation属性动画类,实现相关的功能:
QtAnimationWidget::QtAnimationWidget(QWidget *parent) : QtWidgetBase(parent)
{m_bShow = false;m_animation = new QPropertyAnimation(this, "pos");m_colorBackground = QColor("#ffffff");connect(m_animation, SIGNAL(finished()), this, SLOT(SltAnimationFinished()));connect(m_animation, SIGNAL(finished()), this, SIGNAL(signalAnimationFinished()));
}void QtAnimationWidget::StartAnimation(const QPoint &startPos, const QPoint &endPos, int duration, bool bShow)
{if (m_animation->state() != QPropertyAnimation::Stopped){m_animation->stop();}if (!this->isVisible())this->setVisible(true);m_animation->setDuration(duration);m_animation->setStartValue(startPos);m_animation->setEndValue(endPos);m_bShow = bShow;m_animation->start();
}
QPropertyAnimation类的主要API接口:
- setStartValue:指定动作的初始状态
- setEndValue:指定动作的最终状态
- setDuration:指定动画时长,单位为毫秒
- start:进行动画播放
2.2 qtsliderbar
用于实现自定义滑条控件,分为水平滑条与竖直滑条,音乐播放器的播放进度,需要用到水平滑条,音乐播放器的音量调节,需要用到竖直滑条。滑条的基本形状是一个具有一定粗细的直线代表滑条,上面一个圆形代表滑块,可以是两个颜色不同的同心圆。

画水平滑条的主要代码如下:
//画水平滑条
void QtSliderBar::drawHorizontalBar(QPainter *painter)
{painter->save();QPen pen(m_colorSlider, m_nSliderSize, Qt::SolidLine, Qt::RoundCap);painter->setPen(pen);int nY = this->height() / 2;int radius = m_nHandleSize / 2;painter->drawLine(radius, nY, this->width() - radius, nY);// 先绘制那个大圆QRect rect(m_nOffset - radius, nY - radius, m_nHandleSize, m_nHandleSize);painter->setPen(Qt::NoPen);painter->setBrush(m_bShowHandleBg ? m_colorHandleBg : m_colorHandle);painter->drawEllipse(rect.left(), rect.top(), m_nHandleSize, m_nHandleSize);QPainterPath path;path.moveTo(radius, nY);path.lineTo((m_nOffset >= m_nHandleSize) ? m_nOffset - radius : radius, nY);path.addEllipse(rect.left() + radius / 2, nY - radius / 2, radius, radius);pen.setColor(m_colorHandle);painter->setPen(pen);painter->setBrush(m_colorHandle);painter->drawPath(path);painter->restore();
}
2.3 qtpixmapbutton
图片按钮,顾名思义就是(小)图片形式的按钮,支持未按下和按下后的两种样式的图片显示。如音乐播放器的这些操作按钮,都是图片形式的按钮

该类在构造函数时,将按钮的id,图片按钮要显示的位置,未按下时和按下时要展示的图片,通过参数传入即可:
QtPixmapButton::QtPixmapButton(int id, QRect rect, QPixmap pixmapNormal, QPixmap pixmapPressed) :m_nId(id),m_rect(rect),m_strText(""),m_pixmapNormal(pixmapNormal),m_pixmapPressed(pixmapPressed),m_bPressed(false),m_bVisible(true),m_bCheckAble(false),m_bChecked(false),m_bEnable(true)
{}QPixmap QtPixmapButton::pixmap()
{return (m_bPressed || m_bChecked) ? m_pixmapPressed : m_pixmapNormal;
}
2.4 qtlistwidget
这个cpp文件中定义了2个类:
- QtListWidgetItem
- QtListWidget
2.4.1 QtListWidgetItem
该类用于表示列表中的每一个项,可以是字符,并且可以带有图片(图标):
class QtListWidgetItem
{
public:QtListWidgetItem(int id, const QStringList &args);QtListWidgetItem(int id, const QString &name);QtListWidgetItem(int id, const QString &name, const QPixmap &icon);QtListWidgetItem(int id, const QString &path, const QString &name, const QPixmap &icon);int m_nId;QString m_strText;QPixmap m_pixmapIcon;QString m_strBaseName;QString m_strPath;QRect m_rect;QStringList m_strMultiParameters;bool m_bPressed;
};
2.4.2 QtListWidget
项目的显示,可以按列显示,也可以按行显示,并且支持鼠标的滑动显示,音乐播放器的音乐列表,就是按列显示的:

该组件的类定义如下:
class QtListWidget : public QtWidgetBase
{Q_OBJECT
public:typedef enum{None,LeftDirection,RightDirection,UpDirection,DownDirection} MoveDirection;Q_PROPERTY(int xPos READ movePos WRITE setMovePos)explicit QtListWidget(QWidget *parent = 0);~QtListWidget();QtListWidgetItem *currentItem();void AddItem(int id, QtListWidgetItem *item);void SetItems(const QMap<int, QtListWidgetItem *> &items);void SetBackground(const QPixmap &pixmap);void SetBackground(const QColor &color);void setItemSize(int size);void setHoriazontal(bool bOk);void setAlignment(Qt::Alignment aligns);void setScaleSize(int w, int h);public slots:void setPrevIndex();void setCurrentIndex(int index);void setNexIndex();signals:void currentItemClicked(QtListWidgetItem *item);void currentIndexClicked(int index);private:void setMovePos(int nValue);int movePos() { return m_nStartPos; }void ClearItems();private:bool m_bPressed;QPoint m_startPos;int m_nStartPos;int m_nMoveEndValue;bool m_bRecovery;protected:QMap<int, QtListWidgetItem *> m_listItems;int m_alignment;QColor m_backgroundColor;QPixmap m_pixmapWallpaper;QColor m_colorText;int m_nStartIndex;int m_nCurrentIndex;int m_nMargin;int m_nSpace;int m_nItemShowCnt;int m_nItemSize;bool m_bHorizontal;QPropertyAnimation *m_animationMove;int m_nDirection;protected:QSize sizeHint() const;void resizeEvent(QResizeEvent *e);void mousePressEvent(QMouseEvent *e);void mouseReleaseEvent(QMouseEvent *);void mouseMoveEvent(QMouseEvent *e);void paintEvent(QPaintEvent *);void drawHorizontalItem(QPainter *painter);void drawVerticalItem(QPainter *painter);virtual void drawItemInfo(QPainter *painter, QtListWidgetItem *item);
};
3 音乐播放器代码
上面介绍了一些公用的自定义组件,下面就是利用这些自定义组件,以及Qt的各种功能,实现一个音乐播放器。
3.1 音乐播放
音乐播放部分,主要有三部分功能:
- 音频解码播放:使用Qt自带的媒体播放器组件QMediaPlayer进行音频播放
- 播放时的按钮操作:上一首、暂停/继续、下一首、播放进度调节、音量调节、音乐列表按钮
- 唱片动画界面:在音乐播放时显示一个唱片转动的动画效果,以及唱针放下抬起的动作

3.1.1 QMediaPlayer播放音频
这里使用Qt自带的QMediaPlayer组件进行音频的播放,QMediaPlayer播放音频的基本步骤为:
- 创建一个QMediaPlayer
- 连接播放位置变化的槽函数
- 设置播放的音乐文件
- 设置音量
- 开始播放
player = new QMediaPlayer;
connect(player, SIGNAL(positionChanged(qint64)), this, SLOT(positionChanged(qint64)));
player->setMedia(QUrl::fromLocalFile("./Music/test.mp3"));
player->setVolume(50);
player->play();
本项目的音频播放初始化部分:
void MusicPlayer::InitPlayer()
{m_player = new QMediaPlayer(this);m_player->setPlaylist(m_widgetMusicList->playList());connect(m_player, SIGNAL(durationChanged(qint64)), this, SLOT(SltDurationChanged(qint64)));connect(m_player, SIGNAL(positionChanged(qint64)), this, SLOT(SltPostionChanged(qint64)));connect(m_player, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(SltMediaError(QMediaPlayer::Error)));m_volumeSlider = new QtSliderBar(this);m_volumeSlider->SetHorizontal(false);m_volumeSlider->SetValue(100);m_volumeSlider->hide();connect(m_volumeSlider, SIGNAL(currentValueChanged(int)), m_player, SLOT(setVolume(int)));
}
3.1.2 播放操作按钮
音乐播放时,需要用到播放、暂停、继续、上一首、下一首、进度调节、音量调节等操作按钮,这里的对应按钮使用图标显示为对应的按钮,通过加载QPixmap来实现:
void MusicToolBar::InitWidget()
{// 按钮m_btns.insert(0, new QtPixmapButton(0, QRect(12, 4, 60, 60), QPixmap(":/images/music/ic_prev.png"), QPixmap(":/images/music/ic_prev_pre.png")));m_btns.insert(1, new QtPixmapButton(1, QRect(64, 4, 60, 60), QPixmap(":/images/music/ic_play.png"), QPixmap(":/images/music/ic_pause.png")));m_btns.insert(2, new QtPixmapButton(2, QRect(128, 4, 60, 60), QPixmap(":/images/music/ic_next.png"), QPixmap(":/images/music/ic_next_pre.png")));m_btns.insert(3, new QtPixmapButton(3, QRect(680, 4, 60, 60), QPixmap(":/images/music/ic_volume.png"), QPixmap(":/images/music/ic_volume_pre.png")));m_btns.insert(4, new QtPixmapButton(4, QRect(740, 4, 60, 60), QPixmap(":/images/music/ic_list.png"), QPixmap(":/images/music/ic_list_pre.png")));m_btns.value(1)->setCheckAble(true);connect(this, SIGNAL(signalBtnClicked(int)), this, SLOT(SltBtnClicket(int)));// 进度条m_progressBar = new QtSliderBar(this);m_progressBar->SetHorizontal(true);m_progressBar->SetMaxValue(0);m_progressBar->SetValue(0);// 进度条的值改变, 转变为MusicToolBar的信号currentPostionChanged()connect(m_progressBar, SIGNAL(currentValueChanged(int)), this, SIGNAL(currentPostionChanged(int)));
}
3.1.3 播放时的动画界面
为了在播放时能有视觉上的动态效果,这里加了一个唱片转动的动画效果,当音乐播放时,唱针移动到唱片上,唱片开始转动。
MusicRecord::MusicRecord(QWidget *parent) : QtWidgetBase(parent)
{m_nRotateCD = 0;m_nRotateHandle = 0;m_bPlay = false;m_nBaseWidth = 400;m_nBaseHeight = 350;m_handlePixmap = new PixmapItem(QPixmap(":/images/music/ic_handle.png"), this);m_pixmapRecord = QPixmap(":/images/music/ic_blackrecord.png");m_pixmapArtist = QPixmap(":/images/music/ic_cd.png");m_timer = new QTimer(this);m_timer->setInterval(100);connect(m_timer, SIGNAL(timeout()), this, SLOT(SltCicleRun()));m_animationHandle = new QPropertyAnimation(m_handlePixmap, "ratote");m_animationHandle->setDuration(300);m_animationHandle->setEasingCurve(QEasingCurve::Linear);connect(m_animationHandle, SIGNAL(valueChanged(QVariant)), this, SLOT(SltHandleMove(QVariant)));
}
void MusicRecord::Start()
{m_bPlay = true;m_timer->start();m_animationHandle->setStartValue(0);m_animationHandle->setEndValue(30);m_animationHandle->start();this->update();
}void MusicRecord::Stop()
{m_bPlay = false;m_timer->stop();m_animationHandle->setStartValue(30);m_animationHandle->setEndValue(0);m_animationHandle->start();this->update();
}
3.2 播放列表
播放列表功能用来实现音频文件的显示,以及手动指定切换要播放的音乐。

3.2.1 读取音乐文件
这里使用Qt的QMediaPlaylist组件来实现音乐列表的管理。QMediaPlaylist是一个列表,它可以保存媒体文件,包括媒体路径等信息,它具有着列表的性质,比如添加删除插入等,但它能做的,比单纯的储存要多得多。设置播放顺序,对播放的控制,保存到本地,从本地读取,都可以很方便地实现。
void MusicPlayListWidget::ScanDirMedias(const QString &path)
{QDir dir(path);if (!dir.exists())return;dir.setFilter(QDir::Dirs | QDir::Files);dir.setSorting(QDir::DirsFirst);QFileInfoList list = dir.entryInfoList();int index = m_mapItems.size();for (int i = 0; i < list.size(); i++){QFileInfo fileInfo = list.at(i);if (fileInfo.fileName() == "." || fileInfo.fileName() == ".."){continue;}if (fileInfo.isDir()){ScanDirMedias(fileInfo.filePath());}else if (fileInfo.suffix() == "mp3"){QString strName = fileInfo.baseName().toLocal8Bit().constData();QString strPath = fileInfo.absoluteFilePath().toLocal8Bit().constData();m_playList->addMedia(QUrl::fromLocalFile(strPath));m_mapItems.insert(index, new QtListWidgetItem(index, strPath, strName, QPixmap()));index++;}}
}
3.2.2 音乐列表界面
音乐列表的具体界面实现,主要是在对应的矩形位置,显示各个音乐文件的名称:
MusicPlayListWidget::MusicPlayListWidget(QWidget *parent) : QtAnimationWidget(parent)
{this->setAttribute(Qt::WA_TranslucentBackground);m_colorBackground = QColor("#5362b5");m_nBaseWidth = 405;m_nBaseHeight = 350;m_nYPos = 60;m_listWidget = new PlayListWidget(this);QVBoxLayout *verLayout = new QVBoxLayout(this);verLayout->setContentsMargins(0, 10, 0, 10);verLayout->setSpacing(0);verLayout->addStretch(1);verLayout->addWidget(m_listWidget, 9);// 播放列表(创建一个QMediaPlaylist))m_playList = new QMediaPlaylist(this);m_playList->setPlaybackMode(QMediaPlaylist::Loop);connect(m_playList, SIGNAL(currentIndexChanged(int)), this, SLOT(SltCurrMediaChanged(int)));connect(m_listWidget, SIGNAL(currentIndexClicked(int)), m_playList, SLOT(setCurrentIndex(int)));
}
3.3 歌词显示
歌词显示功能,需要在音乐播放的时候,能根据播放的进度,显示对应的歌词。

3.3.1 歌词显示界面
歌词界面的具体界面实现,主要是在对应的矩形位置,显示当前音乐播放位置前后的音乐歌词,并高亮当前播放的那句歌词:
void LyricWidget::drawLyricLines(QPainter *painter)
{painter->save();QTextOption option;option.setWrapMode(QTextOption::WordWrap);option.setAlignment(Qt::AlignCenter);QRect rect(0, 0, m_nBaseWidth, 52);int index = 0;for (int i = 0; i < m_nShowCount; i++){rect = QRect(rect.left(), rect.bottom(), rect.width(), LYRIC_LINE_HEIGHT);index = m_nStartIndex + i;QString strText = index < m_strLyricLines.size() ? m_strLyricLines.at(index) : "";painter->setPen(index == m_nCurrentIndex ? Qt::white : Qt::black);painter->drawText(rect, strText, option);}painter->restore();
}
3.3.2 歌词显示时间计算
通过下面这个函数,计算当前音乐播放的位置:
void LyricWidget::SetLyricPostion(int postion)
{if (!m_bLyricLoad)return;m_lyricFactory->findIndex(postion);
}
4 交叉编译与测试
如果是在Windows上编写的程序,并且支持Windows上运行,在Qt程序编写好后,可以先在Windows查看运行效果。
通过交叉编译,来测试音乐播放器在OK3568-C板子上的播放效果。
为了方便每次的编译,我这里编写一个my3568build.sh的编译脚本:
#! /bin/bashmkdir -p build
cd buildexport PATH=/home/xxpcb/myTest/OK3568/sourcecode/OK3568-linux-source/buildroot/output/OK3568/host/bin:$PATHqmake .. && make
然后执行该脚本即可编译:
./my3568build.sh

将编译成功的可执行文件MusicPlayer复制到OK3568-C板子中,然后在其同目录下创建一个music文件夹,里面放入若干个mp3音乐文件和对应的歌词文件,然后就可以测试了:

文件复制到板子,我这里使用的ADB无线传输的方式,比较方便,具体ADB操作演示,可参考上篇文章最后的Qt交叉编译与测试部分
需注意的是,板子里的这个Linux系统,不支持中文的显示,所以我把歌名都先改成了字母后再复制到板子中进行测试。
5 总结
本篇介绍了使用Qt开发一个音乐播放器,首先是一个Qt自定义控件的介绍,包括滑条、图标按钮、列表等,然后使用这些自定义组件,以及Qt的各种功能,实现一个音乐播放器,具有基础的音乐播放、暂停继续、歌曲列表显示,歌曲切换等功能。代码可以在Windows上运行,通过交叉编译,可以在OK-3568这块Linux板子上运行。
相关文章:
嵌入式Qt 开发一个音乐播放器
上篇文章:RK3568源码编译与交叉编译环境搭建,进行了OK3568开发板软件开发环境搭建,通过编译RK3568的源码,可以得到Qt开发的交叉编译相关工具。 本篇,就来在搭建好的软件开发中,进行Qt软件的开发测试。由于…...
2023秋招万得集团AI算法岗面经分享
本专栏分享 计算机小伙伴秋招春招找工作的面试经验和面试的详情知识点 专栏首页:秋招算法类面经分享 主要分享计算机算法类在面试互联网公司时候一些真实的经验 2022年 11.22下午AI算法岗面试 (1)一面35min 1、自我介绍 2、科研:长文本MRC...
RoI Transformer论文翻译详解
Learning RoI Transformer for Oriented Object Detection in Aerial Images 0.摘要 航空图像中的目标检测是计算机视觉中一个活跃而又具有挑战性的任务,因为它具有鸟瞰视角、高度复杂的背景和变化的物体外观。特别是在航空图像中检测密集的目标时,基于…...
Prometheus 自动发现监控AWS EC2实例
本文章简述对接自动发现AWS云EC2实例 前提环境: PromethuesGrafanaAWS IAM权限 涉及参考文档: AWS EC2Grafana 通用监控模板 一、IAM 用户创建 1、创建Prometheus 策略 策略规则: {"Version": "2012-10-17",&quo…...
从recat源码角度看setState流程
setState setState() 将对组件 state 的更改排入队列批量推迟更新,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。其实setState实际上不是异步,只是代码执行顺序不同,有了异步的感觉。 使用方法 setState(stateChange | u…...
【Java|golang】1234. 替换子串得到平衡字符串---双指针
有一个只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符,且长度为 n 的字符串。 假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。 给你一个这样的字符串 s,请通过「替换一个子串」的方式,…...
自监督表征学习方法——BYOL(Bootstrap Your Own Latent)
自监督表征学习方法——BYOL(Bootstrap Your Own Latent) 参考文献:《Bootstrap Your Own Latent A New Approach to Self-Supervised Learning》 1.前言背景 学习良好的图像表示是计算机视觉中的一个关键挑战,因为它允许对下游任务进行有效的训练。许…...
均衡负载集群(LBC)-1
均衡负载集群(LBC) 客户–>通过Internet—>负载调度器—>n台真实服务器 负载调度器: 软件:LVS;Nginx;Haproxy硬件:F5; LVS架构: 使用到C/S(B/S…...
WebSocket
关于WebSocket: WebSocket 协议在2008年诞生,2011年成为国际标准。现在所有浏览器都已经支持了。 WebSocket 的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话…...
GA-PEG-GA,Glutaric Acid-PEG-Glutaric Acid,戊二酸-聚乙二醇-戊二酸供应
英文名称:Glutaric Acid-PEG-Glutaric Acid,GA-PEG-GA 中文名称:戊二酸-聚乙二醇-戊二酸 GA-PEG-GA是一种线性双功能PEG羧酸试剂。PEG和羧基COOH之间存在C4酯键。PEG羧酸可用于与氨基反应,与NHS和DCC、EDC等肽偶联试剂反应。 P…...
使用sqlmap + burpsuite sql工具注入拿flag
使用sqlmap burpsuite sql工具注入拿flag 记录一下自己重新开始学习web安全之路③。 目标网站:http://mashang.eicp.vip:1651/7WOY59OBj74nTwKzs3aftsh1MDELK2cG/ 首先判断网站是否存在SQL注入漏洞 1.找交互点 发现只有url这一个交互点,搜索框和登录…...
替代AG9300|替代NCS8823|CS5260 Type-C转VGA视频转换方案
替代AG9300|替代NCS8823|CS5260 Type-C转VGA视频转换方案 CS5260是一款是一款实现USB TYPE-C到VGA视频转换的单片机解决方案转换器。CS5260支持USB Type-C显示端口交替模式,CS5260可以将视频和音频流从USB Type-C接口传输到VGA端口。在CS5260芯片中,显示…...
乐鑫特权隔离机制的 OTA 固件升级
固件空中升级 (OTA, Over-The-Air) 是任何联网设备的重要功能之一,支持开发人员通过远程更新固件,以发布新功能或修复错误。乐鑫特权隔离框架中包含两类应用程序:受保护的应用程序 (protected_app) 和用户应用程序 (user_app) ,这…...
C++数据结构 —— 二叉搜索树
目录 1.二叉搜索树的基本概念 1.1二叉搜索树的基本特征 2.二叉搜索树的实现 2.1数据的插入(迭代实现) 2.2数据的搜索(迭代实现) 2.3中序遍历(递归实现) 2.4数据的删除(迭代实现) 2.5数据的搜索(递归实现) 2.6数据的插入(递归实现) 2.7数据的删除(递归实现) 2.8类的完…...
Maven面试题及答案
1、Maven有哪些优点和缺点 优点: 1、简化项目依赖管理 2、方便与持续集成工具(Jenkins)整合 3、有助于多模块项目开发,比如一个模块开发好后发布到仓库,依赖该模块时可以直接从远程仓库更新,不用自己手动去编译 4、有很多插件&am…...
WebRTC系列-Qos系列之接收放RTX处理
文章目录 1. RTX详解1.1 RTX包头解析1.2 RTX包中的OSN2. RTX在WebRTC中处理2.1 组包2.2 解包2.3 发送及接收处理流程2.3.1 发送流程2.3.2 rtx标记的设置流程2.3.3 解析流程2.3.4 RTX解包在上一篇 WebRTC系列-Qos系列之接收NACK文章中分析了接收到nack后解析的主要流程。在WebR…...
国内能否炒伦敦金,2023国际十大正规伦敦金交易平台排名
在目前的投资市场环境中,现货黄金是一种屡见不鲜的投资选择,它依靠国际化的投资环境,成为了世界范围内投资者的重要选择对象。进行现货黄金投资,人们除了要认识市场发展基本现状之外,更要做好基本面和技术面分析工作&a…...
react路由 - react-router-dom
react路由 现代的前端应用大多都是 SPA(单页应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而…...
01-RTOS
对于裸机而言,对于RTOS而言即:对于裸机,打游戏意味着不能回消息 回消息意味着不能打游戏对于RTOS 打游戏和裸机的切换只需要一个时间片节拍 1ms 从宏观来看 就是同时进行的两件事(但要在这两件事情的优先级一样的情况下࿰…...
信息安全管理
信息安全管理信息安全管理信息安全风险管理信息安全管理体系应急响应与灾难恢复应急响应概况信息系统灾难修复灾难恢复相关技术信息安全管理 管理概念:组织、协调、控制的活动,核心过程的管理控制 管理对象和组成:包括人员在内相关资产&…...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
