嵌入式Qt 开发一个音乐播放器

news2024/11/14 23:55:32

上篇文章: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/bash

mkdir -p build
cd build

export PATH=/home/xxpcb/myTest/OK3568/sourcecode/OK3568-linux-source/buildroot/output/OK3568/host/bin:$PATH

qmake .. && make

然后执行该脚本即可编译:

./my3568build.sh 

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

文件复制到板子,我这里使用的ADB无线传输的方式,比较方便,具体ADB操作演示,可参考上篇文章最后的Qt交叉编译与测试部分

需注意的是,板子里的这个Linux系统,不支持中文的显示,所以我把歌名都先改成了字母后再复制到板子中进行测试。

5 总结

本篇介绍了使用Qt开发一个音乐播放器,首先是一个Qt自定义控件的介绍,包括滑条、图标按钮、列表等,然后使用这些自定义组件,以及Qt的各种功能,实现一个音乐播放器,具有基础的音乐播放、暂停继续、歌曲列表显示,歌曲切换等功能。代码可以在Windows上运行,通过交叉编译,可以在OK-3568这块Linux板子上运行。

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

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

相关文章

package-lock.json,深度内容

前言 看完本文&#xff0c;你将从整体了解依赖版本锁定原理&#xff0c;package-lock.json 或 yarn.lock 的重要性。首先要从最近接连出现两起有关 npm 安装 package.json 中依赖包&#xff0c;由于依赖包版本更新 bug 造成项目出错问题说起。 事件一&#xff1a;新版本依赖包…

JavaScript 二叉树

文章目录前言一、何为 树1.根节点2.外&内部节点3.子树4.深度5.高度二、二叉树 & 二叉搜索树1.二叉搜索树插入值2.遍历二叉搜索树I.中序遍历II.先序遍历III.后序遍历3.查找节点4.移除节点总结前言 同前面说到的散列表结构, 树也是一种非顺序数据结构, 对于存储需要快速…

【浅学Nginx】Nginx安装和基础使用

Nginx安装和基础使用1. Nginx是什么2. Nginx的安装3. Nginx的目录结构4. Nginx的配置文件结构5. Nginx的具体应用5.1 部署静态资源5.2 反向代理5.3 负载均衡1. Nginx是什么 Nginx是一个轻量级的 web服务器 / 反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服…

kettle开发-Day37-SQ索引优化

前言&#xff1a;在上一个生产项目中&#xff0c;有个单表数据超249G了&#xff0c;里面存储的数据时间跨度就1年左右&#xff0c;那为啥会出现这种情况呢&#xff1f;数据来源为&#xff0c;一个生产基地所有电表的每分钟读数&#xff0c;一个基地大概500个电表左右&#xff0…

【C++】---Stack和Queue的用法及其模拟实现

文章目录Stack最小栈栈的弹出压入序列逆波兰表达式求值用栈实现队列模拟实现queue用队列实现栈模拟实现Stack stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。它的使用和之前学习的ve…

KDZD880 智能蓄电池放电测试仪

一、产品概述 智能蓄电池放电测试仪主要用于电信、移动、联通、电力直流行业的后备电源铅酸蓄电池的放电测试&#xff0c;具备蓄电池快速容量测试、在线监测及容量核对测试三大功能于一体的产品&#xff0c;集成化程度高、体积小巧、功能完善。 该设备是针对整组 12V-600V 蓄…

JavaScript高级程序设计读书分享之3章——3.4数据类型

JavaScript高级程序设计(第4版)读书分享笔记记录 适用于刚入门前端的同志 ECMAScript 有 6 种简单数据类型&#xff08;也称为原始类型&#xff09;&#xff1a;Undefined、Null、Boolean、Number、String 和 Symbol&#xff08;es6新增&#xff09;。 还有一种复杂数据类型叫…

vim编辑器和gcc/g++编译器和gdb调试器和make/makefile自动化构建工具的使用

vim的三种模式(其实有好多模式 )&#xff08;1&#xff09;.命令模式&#xff08;2&#xff09;.插入模式&#xff08;3&#xff09;.底行模式vim的基本操作vim的命令模式的基本操作vim的插入模式的基本操作vim的底行模式的基本操作vim的配置gcc和g相关操作&#xff08;1&#…

XCP实战系列介绍11-几个常用的XCP命令解析

本文框架 1.概述2. 常用命令解析2.1 CONNECT连接(0xFF)2.2 SHORT_UPLOAD 命令(0xF4)2.2 SET_MTA (0xF6)2.3 MOVE命令(0x19)2.4 GET_CAL_PAGE(0xEA)2.5 SET_CAL_PAGE(0xEB)2.6 DOWNLOAD(0xF0)1.概述 在文章《看了就会的XCP协议介绍》中详细介绍了XCP的协议,在《XCP实战系列介绍…

Python面试——装饰器

知识链接&#xff1a; 装饰器 装饰器可调用的对象&#xff0c;其参数是被装饰的函数。装饰器可能会处理被装饰的函数然后把它返回&#xff0c;或者将其替换成另外一个函数或者可调用对象。 装饰器有两大特性&#xff1a; 能把被装饰的函数替换成其他函数&#xff08;在元编程…

面试腾讯测试岗后感想,真的很后悔这5年一直都干的是基础测试....

前两天&#xff0c;我的一个朋友去大厂面试&#xff0c;跟我聊天时说&#xff1a;输的很彻底… 我问她&#xff1a;什么情况&#xff1f;她说&#xff1a;很后悔这5年来一直都干的是功能测试… 相信许多测试人也跟我朋友一样&#xff0c;从事了软件测试很多年&#xff0c;却依…

树莓派用默认账号和密码登录不上怎么办;修改树莓派的密码

目录 一、重置树莓派的默认账号和密码 二、修改树莓派的密码 三、超级用户和普通用户的切换 一、重置树莓派的默认账号和密码 在SD卡中根目录建立文件userconf 在userconf中输入如下内容&#xff1a; pi:$6$/4.VdYgDm7RJ0qM1$FwXCeQgDKkqrOU3RIRuDSKpauAbBvP11msq9X58c8Q…

STM32开发(10)----CubeMX配置基本定时器

CubeMX配置基本定时器前言一、定时器的介绍二、实验过程1.实验材料2.STM32CubeMX配置基本定时器2.代码实现3.编译烧录4.硬件连接5.实验结果总结前言 本章介绍使用STM32CubeMX对基本定时器进行配置的方法&#xff0c;STM32F103高性能系列设备包括基本定时器、高级控制定时器、通…

JavaEE-HTTP协议(一)

目录什么是HTTP协议&#xff1f;协议格式如何看到HTTP的报文格式&#xff1f;HTTP请求HTTP响应URLURL encode/decode什么是HTTP协议&#xff1f; 计算机网络&#xff0c;核心概念&#xff0c;网络协议 网络协议种类非常多&#xff0c;其中一些耳熟能详的&#xff0c;IP,TCP,UD…

shell命令行并行神器 - parallel

shell命令行并行神奇 - parallel 概述 GNU parallel 是一个 shell 工具&#xff0c;用于使用一台或多台计算机并行执行作业。作业可以是单个命令或必须为输入中的每一行运行的小脚本。典型的输入是文件列表、主机列表、用户列表、URL 列表或表列表。作业也可以是从管道读取的…

98年的确实卷,公司新来的卷王,我们这帮老油条真干不过.....

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;前段时间我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 …

电脑麦克风没声音怎么办?这3招就可以解决!

最近有用户在使用电脑麦克风进行视频录制时&#xff0c;发现麦克风没有声音。这是什么原因&#xff1f;电脑麦克风没有声音怎么办&#xff1f;关于解决方案&#xff0c;我专门整理了三种方法来帮你们&#xff0c;一起来看看吧&#xff01; 操作环境&#xff1a; 演示机型&#…

在TitanIDE中使用ChatGPT辅助科研开发

作者&#xff1a;行云创新CEO 马洪喜 命题&#xff1a;太空望远镜拍摄的照片处理 假设&#xff1a;我是图形科学家&#xff0c;但不是特别懂Python 先上传一张银河系照片&#xff0c;目的是把彩色转成灰度&#xff1a; 然后我不会啊&#xff0c; 问问chatGPT 彩色图片转灰度…

电话号码的字母组合-力扣17-java

一、题目描述给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。示例 1&#xff1a;输入&#xff1a;digits "23"输出…

Android 一体机研发之修改系统设置————自动锁屏

Android 一体机研发之修改系统设置————屏幕亮度 Android 一体机研发之修改系统设置————声音 Android 一体机研发之修改系统设置————自动锁屏 修改系统设置系列篇章马上开张了&#xff01; 本章将为大家细节讲解自动锁屏。 自动锁屏功能&#xff0c;这个可以根据…