stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux+单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器,集成2个Cortex-A7核和1个Cortex-M4 核,A7核上可以跑Linux操作系统,M4核上可以跑FreeRTOS、RT-Thread等实时操作系统。开发板搭配仿真器、显示屏、摄像头、资源扩展板等丰富的扩展模块,可拓展物联网、人工智能等相关技术学习,还可以拓展丰富的项目实战,非常贴合企业当下开发需求,是一款嵌入式Linux入门进阶必备开发板!
可学习技术:嵌入式Linux应用/系统/驱动开发、ARM裸机开发、Qt界面编程、STM32单片机、FreeRTOS、人工智能机器视觉等。其中ARM Cortex-A7裸机开发课程是华清远见独有特色课程,可关注:https://www.bilibili.com/video/BV1Xe4y1i7vm/,持续更新中。
可实战项目:14个Linux+Qt综合项目案例,8个MP1A物联网拓展项目
项目配套文档及源码,可在下方评论区留言索取~~
1、Linux+Qt综合项目案例:华清远见stm32mp157开发板优势特色部分,包括音乐播放器、智慧家庭、智能工业电表、智能出行助手、智能猫眼、环境监测、智能安防、智能语音识别等10余个项目案例,涉及家居、医疗、农业多种应用方向,在案例中使用了多种物联网和嵌入式技术,包括OT开发、linux应用开发、linux驱动开发、物联网云端接入、MQTT协议、json字符串等知识点。
基于Linux+Qt的音乐播放器项目
开发平台:
华清远见stm32mp157开发板豪华套餐(开发板+仿真器+五寸屏+摄像头+资源扩展板+tf卡+读卡器)
项目功能:
1.批量从本地导入歌曲到歌曲列表中。
2.清空歌曲列表。
3.删除歌曲列表中选中的歌曲。
4.用进度条显示歌曲播放的进度,并显示时间。
5.用“播放/暂停”、“停止”、“上一曲”、“下一曲”四个按钮对歌曲
进行控制。
6.用滑动器对歌曲音量进行控制。
7.右键会弹出菜单,可以对程序进行相应的控制。
8.提取歌曲的歌词,并解析歌词,根据歌曲的播放进度显示歌词。
项目展示:
项目实战:
源码分析
UI 界面设计
使用三个 pushbutton 来添加歌曲、删除歌曲和清空歌曲列表。使用四个
toolbutton 来控制歌曲的播放、停止、上一曲、下一曲。使用 ListWidget 控
件来存放歌曲列表。使用 slider 来控制音量和歌曲进度。
文件配置
因为下面要使用音频播放功能,所以需要在 pro 文件引入
QT += core gui multimedia
歌词类(Lyrics)
这个类通过 readLine 这个函数循环读取每一行的歌词,读取到使用
analysisLyricsFile()函数进行解析,使用正则表达式解析 lrc 歌词文件,
获取歌词和时间。
while((line=file.readLine())>0){
analysisLyricsFile(line);
}
lyrics.h
#ifndef LYRICS_H
#define LYRICS_H
#include <QWidget>
#include <QString>
#include <QMap>
#include <iostream>
#include <QFile>
#include <QDebug>
#include <QRegExp>
#include <QList>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
using namespace std;
class Lyrics
{
private:
QString lyricsPath;
public:
QList<QString> listLyricsText;
QList<int> listLyricsTime;
public:
Lyrics(QString lyricsPath);
Lyrics();
bool readLyricsFile(QString lyricsPath);
bool analysisLyricsFile(QString line);
QList<QString> getListLyricsText() const;
QList<int> getListLyricsTime() const;
};
#endif // LYRICS_H
Lyrics.c
#include "lyrics.h"
QList<QString> Lyrics::getListLyricsText() const{
return listLyricsText;
}
QList<int> Lyrics::getListLyricsTime() const{
return listLyricsTime;
}
Lyrics::Lyrics(QString lyricsPath){
this->lyricsPath = lyricsPath;
}
bool Lyrics::readLyricsFile(QString lyricsPath)
{
QFile file(lyricsPath);
qDebug()<<"lyricsPath = "<<lyricsPath;
if(!file.open(QIODevice::ReadOnly | QIODevice::Text)){
listLyricsText.clear();
listLyricsTime.clear();
return false;
}
QString line="";
while((line=file.readLine())>0){
qDebug()<<line;
analysisLyricsFile(line);
}
return true;
}
bool Lyrics::analysisLyricsFile(QString line)
{
if(line == NULL || line.isEmpty()){
qDebug()<<"this line is empty!";
return false;
}
QRegularExpression
regularExpression("\\[(\\d+)?:(\\d+)?(\\.\\d+)?\\](.*)?");
int index = 0;
QRegularExpressionMatch match;
match = regularExpression.match(line, index);
if(match.hasMatch()) {
int totalTime;
totalTime = match.captured(1).toInt() * 60000 +
match.captured(2).toInt() * 1000; /* 计算该时间点毫
秒数 */
QString currentText
=QString::fromStdString(match.captured(4).toStdString()); /*
获取歌词文本*/
qDebug()<<totalTime;
qDebug()<<currentText;
listLyricsText.push_back(currentText);
listLyricsTime.push_back(totalTime);
return true;
}
return false;
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include "lyrics.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void addSongs();
void playPause();
void playStop();
void playNext();
void playPrevious();
void posChanged(qint64 pos);
void durChanged(qint64 dur);
void setPos(int pos);
void clearList();
void delectSong();
void volumeSilence();
void setVol(int vol);
void hideShow();
void redVolume();
void addVolume();
void setPlayTime();
private:
Ui::Widget *ui;
private:
QMediaPlayer* player;
QMediaPlaylist* playList;
QAction* action_hideShow;
QAction* action_addSongs;
QAction* action_playPause;
QAction* action_stop;
QAction* action_previous;
QAction* action_next;
QAction* action_addVolume;
QAction* action_redVolume;
QAction* action_quit;
Lyrics lyrics;
int lyricsID = 0;
QString playPath;
QString presentTime;
QString totalTime;
protected:
void contextMenuEvent(QContextMenuEvent* );
};
#endif // WIDGET_H
Widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QString>
#include <QStringList>
#include <QMenu>
#define BOARD_TYPE_RGB 0
#define BOARD_TYPE_MIPI 1
static int get_board_type(void)
{
char searchText[]="-mipi050";
int len_searchText;
FILE *file;
char string[128];
int len_string;
int i = 0;
memset(string, 0x0, sizeof(string));
file = fopen("/proc/device-tree/compatible", "r");
if (file == NULL) {
printf("fails to open /proc/device-tree/compatible\n");
return -1;
}
len_searchText = strlen(searchText);
while(fgets(string, sizeof(string), file) != 0)
{
len_string = strlen(string);
for(i = 0 ; i < len_string ; i++) {
if(strncmp(searchText, (string + i), len_searchText)
== 0) {
fclose(file);
return BOARD_TYPE_MIPI;
}
}
}
fclose(file);
return BOARD_TYPE_RGB;
}
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// 适配屏幕大小
int board_type = 0;
board_type = get_board_type();
qDebug()<<board_type;
if(board_type==BOARD_TYPE_RGB)
resize(1024,600);
else if(board_type==BOARD_TYPE_MIPI)
resize(480,852);
this->setAutoFillBackground(true);
QPalette palette;
palette.setBrush(QPalette::Background,QBrush(QPixmap(":/images/in
terface.jpg")));
this->setPalette(palette);
ui->volumeSlider->setRange(0,100);
this->setWindowTitle(tr("hqyj 音乐播放器"));
this->playList=new QMediaPlaylist(this);
playList->setPlaybackMode(QMediaPlaylist::Loop);
this->player=new QMediaPlayer(this);
player->setPlaylist(playList);
player->setVolume(50);
ui->volumeSlider->setValue(50);
ui->songsList->setStyleSheet("backgroundcolor:transparent;color: white");
ui->addSongsButton->setStyleSheet("QPushButton{background:
none;border:none;color: white}");
ui->clearButton->setStyleSheet("QPushButton{background:
none;border:none;color: white}");
ui->delectButton->setStyleSheet("QPushButton{background:
none;border:none;color: white}");
ui->volumeButton->setStyleSheet("QToolButton{borderradius:5px;border-width:0px;}");
connect(ui->volumeSlider,SIGNAL(valueChanged(int)),this,SLOT(setV
ol(int)));
connect(ui->addSongsButton,SIGNAL(clicked()),this,SLOT(addSongs()
));
connect(ui->playPauseButton,SIGNAL(clicked()),this,SLOT(playPause
()));
connect(ui->stoptButton,SIGNAL(clicked()),this,SLOT(playStop()));
connect(ui->previousButton,SIGNAL(clicked()),this,SLOT(playPrevio
us()));
connect(ui->nextButton,SIGNAL(clicked()),this,SLOT(playNext()));
connect(player,SIGNAL(durationChanged(qint64)),this,SLOT(durChang
ed(qint64)));
connect(player,SIGNAL(positionChanged(qint64)),this,SLOT(posChang
ed(qint64)));
connect(ui->processSlider,SIGNAL(valueChanged(int)),this,SLOT(set
Pos(int)));
connect(ui->clearButton,SIGNAL(clicked()),this,SLOT(clearList()));
connect(ui->delectButton,SIGNAL(clicked()),this,SLOT(delectSong()
));
connect(ui->volumeButton,SIGNAL(clicked()),this,SLOT(volumeSilenc
e()));
ui->stoptButton->setStyleSheet("border:2px groove
gray;border-radius:10px;padding:2px 4px;");
ui->playPauseButton->setStyleSheet("border:2px groove
gray;border-radius:10px;padding:2px 4px;");
ui->previousButton->setStyleSheet("border:2px groove
gray;border-radius:10px;padding:2px 4px;");
ui->nextButton->setStyleSheet("border:2px groove
gray;border-radius:10px;padding:2px 4px;");
action_hideShow=new QAction(tr("隐藏/显示窗口部件"),this);
action_addSongs=new QAction(tr("添加本地歌曲"),this);
action_playPause=new QAction(tr("播放/暂停"),this);
action_stop=new QAction(tr("停止"),this);
action_previous=new QAction(tr("上一曲"),this);
action_next=new QAction(tr("下一曲"),this);
action_addVolume=new QAction(tr("音量+(5%)"),this);
action_redVolume=new QAction(tr("音量-(5%)"),this);
action_quit=new QAction(tr("退出"),this);
connect(action_hideShow,SIGNAL(triggered()),this,SLOT(hideShow())
);
connect(action_addSongs,SIGNAL(triggered()),this,SLOT(addSongs())
);
connect(action_playPause,SIGNAL(triggered()),this,SLOT(playPause(
)));
connect(action_stop,SIGNAL(triggered()),this,SLOT(playStop()));
connect(action_previous,SIGNAL(triggered()),this,SLOT(playPreviou
s()));
connect(action_next,SIGNAL(triggered()),this,SLOT(playNext()));
connect(action_addVolume,SIGNAL(triggered()),this,SLOT(addVolume(
)));
connect(action_redVolume,SIGNAL(triggered()),this,SLOT(redVolume(
)));
connect(action_quit,SIGNAL(triggered()),this,SLOT(close()));
}
Widget::~Widget()
{
delete ui;
}
//每当音量进度条改变,会触发这个槽函数重新设置音量大小
void Widget::setVol(int vol)
{
player->setVolume(vol);
}
//选择要添加的所有歌曲,添加到 QStringList 中,遍历 QStringList 中的所
有.MP3 文件
//添加到 playList 中,并且添加到 ui 界面的 QlistWight 中
void Widget::addSongs()
{
QStringList songsList=QFileDialog::getOpenFileNames(this,"
选择文件",QDir::homePath(),"*.mp3");
for(int i=0;i<songsList.size();i++)
{
QString song=songsList.at(i);
qDebug()<<song;
//歌词存放路径
playPath =song.left(song.lastIndexOf("/"));
qDebug()<<playPath;
this->playList->addMedia(QUrl::fromLocalFile(song));
QStringList songL=song.split('/');
song=songL.last();
ui->songsList->addItem(song);
}
}
//播放/暂停
void Widget::playPause()
{
ui->processSlider->setEnabled(true);
if("播放"==ui->playPauseButton->text())
{
int index=ui->songsList->currentRow();
if(-1!=index)
{
//歌曲名字
QString songname
=ui->songsList->item(index)->text().left(ui->songsList->item(inde
x)->text().lastIndexOf("."));
playList->setCurrentIndex(index);
player->play();
ui->playPauseButton->setText("暂停");
ui->playPauseButton->setIcon(QIcon(":/images/pause.png"));
// 读取歌词文件
if(!lyrics.readLyricsFile(playPath+"/"+songname+".lrc")){
ui->label->setText("未检测到歌词文件");
ui->label_2->setText("请检查歌词文件是否存在");
}
lyricsID = 0;
}
}
else
{
player->pause();
ui->playPauseButton->setText("播放");
ui->playPauseButton->setIcon(QIcon(":/images/play.png"));
}
}
//播放/停止
void Widget::playStop()
{
ui->processSlider->setEnabled(false);
this->player->stop();
// 清空歌词
ui->label->clear();
ui->label_2->clear();
lyrics.listLyricsText.clear();
}
//播放上一曲
void Widget::playPrevious()
{
player->stop();
int index=playList->currentIndex();
if(0==index)
index=playList->mediaCount()-1;
else
index--;
playList->setCurrentIndex(index);
ui->songsList->setCurrentRow(index);
// player->play();
ui->playPauseButton->setText("播放");
ui->playPauseButton->setIcon(QIcon(":/images/play.png"));
// 清空歌词
ui->label->clear();
ui->label_2->clear();
lyrics.listLyricsText.clear();
}
//播放下一曲
void Widget::playNext()
{
player->stop();
int index=playList->currentIndex();
if(index==playList->mediaCount()-1)
index=0;
else
index++;
playList->setCurrentIndex(index);
ui->songsList->setCurrentRow(index);
ui->playPauseButton->setText("播放");
ui->playPauseButton->setIcon(QIcon(":/images/play.png"));
// 清空歌词
ui->label->clear();
ui->label_2->clear();
lyrics.listLyricsText.clear();
}
//设置进度条显示范围,并计算歌曲总时间,显示出来
void Widget::durChanged(qint64 dur)
{
ui->processSlider->setRange(0,dur);
totalTime.clear();
dur/=1000;
totalTime+=QString::number(dur/60);
totalTime+=':';
if(dur%60<10)
totalTime+='0';
totalTime+=QString::number(dur%60);
ui->totalTimeLabel->setText(totalTime);
}
//设置进度条的值,并计算当前时间,显示出来
void Widget::posChanged(qint64 pos)
{
presentTime.clear();
ui->processSlider->setValue(pos);
pos/=1000;
presentTime+=QString::number(pos/60);
presentTime+=':';
if(pos%60<10)
presentTime+='0';
presentTime+=QString::number(pos%60);
ui->presentTimeLabel->setText(presentTime);
if(presentTime==totalTime)
{
presentTime.clear();
player->stop();
ui->label->clear();
ui->label_2->clear();
}
//根据播放进度的改变设置播放进度
void Widget::setPos(int pos)
{
player->setPosition(pos);
setPlayTime();
}
//清空播放列表
void Widget::clearList()
{
playList->clear();
ui->songsList->clear();
}
//在播放列表中删除某首歌曲
void Widget::delectSong()
{
int index=ui->songsList->currentRow();
if(-1!=index)
{
ui->songsList->takeItem(index);
playList->removeMedia(index);
}
}
//设置有音量和静音
void Widget::volumeSilence()
{
if(ui->volumeButton->text()==tr("音量"))
{
ui->volumeButton->setText(tr("静音"));
ui->volumeSlider->setValue(0);
ui->volumeButton->setIcon(QIcon(":/images/silence.png"));
}
else
{
ui->volumeButton->setText(tr("音量"));
ui->volumeButton->setIcon(QIcon(":/images/volume.png"));
ui->volumeSlider->setValue(50);
}
}
/*鼠标右击事件
*每次鼠标右击都会显示菜单*/
void Widget::contextMenuEvent(QContextMenuEvent*)
{
QMenu* menu=new QMenu(this);
menu->addAction(action_hideShow);
menu->addAction(action_addSongs);
menu->addSeparator();
menu->addAction(action_playPause);
menu->addAction(action_stop);
menu->addAction(action_previous);
menu->addAction(action_next);
menu->addSeparator();
menu->addAction(action_addVolume);
menu->addAction(action_redVolume);
menu->addSeparator();
menu->addAction(action_quit);
menu->move(cursor().pos());
menu->show();
}
//控件隐藏/显示
void Widget::hideShow()
{
if(ui->songsList->isVisible())
{
ui->addSongsButton->setVisible(false);
ui->clearButton->setVisible(false);
ui->delectButton->setVisible(false);
ui->songsList->setVisible(false);
ui->processSlider->setVisible(false);
ui->presentTimeLabel->setVisible(false);
ui->totalTimeLabel->setVisible(false);
ui->volumeButton->setVisible(false);
ui->volumeSlider->setVisible(false);
}
else
{
ui->addSongsButton->setVisible(true);
ui->clearButton->setVisible(true);
ui->delectButton->setVisible(true);
ui->songsList->setVisible(true);
ui->processSlider->setVisible(true);
ui->presentTimeLabel->setVisible(true);
ui->totalTimeLabel->setVisible(true);
ui->volumeButton->setVisible(true);
ui->volumeSlider->setVisible(true);
}
}
//每触发一次,音量+5%,设置到音量进度条上。
void Widget::addVolume()
{
int vol=ui->volumeSlider->value();
vol+=5;
if(vol>100)
vol=100;
ui->volumeSlider->setValue(vol);
}
//每触发一次,音量-5%,设置到音量进度条上。
void Widget::redVolume()
{
int vol=ui->volumeSlider->value();
vol-=5;
if(vol<0)
vol=0;
ui->volumeSlider->setValue(vol);
}
void Widget::setPlayTime()
{
if(!lyrics.getListLyricsTime().empty()&&player->position()>=lyric
s.getListLyricsTime().at(lyricsID)&&lyricsID<lyrics.getListLyrics
Time().size()-1){
ui->label->setText(lyrics.getListLyricsText().at(lyricsID));
ui->label_2->setText(lyrics.getListLyricsText().at(lyricsID+1));
lyricsID++;
}
}
Widget 类主要成员函数
构造函数
在构造函数中主要实现信号槽的连接,包括各种按钮控件的信号槽连接和
QAction 类对象的信号槽连接,各种控件的样式表的初始化。
这里用到的 QAction 类是提供了一个可以同时出现在菜单和工具条上的抽
象用户界面操作,通过重写鼠标右击事件,实现菜单的显示和消失。
addSongs()
添加歌曲功能。通过 QStringList 类实例化对象 songsList,获取要添加
的歌曲的路径和文件名。遍历 songsList,将列表里的每一首歌曲依次添加到
ui 界面的 listwidget 控件里。在此函数里还获取歌词存放的路径(默认歌词
和歌曲文件存放路径相同)。
playPause()
播放和暂停功能。使用 QMediaPlayer 类实例化 player 对象,
QMediaPlaylist 类实例化 playList 对象;从 ui 界面 listwidget 控件获取歌单
添加到 playList;play 对象从 playList 中选择其中一首歌,调用对象 play 函
数,实现歌曲的播放。调用 pause(),实现歌曲的暂停。
playNext()
下一曲。通过 index =playList->currentIndex()函数获取歌曲列表此时
的歌曲序号,使序号+1 ,重新设置歌曲列表此时选中歌曲序号
playList->setCurrentIndex(index);实现下一曲的播放。上一曲使序号减一就
好了。
durChanged()
void Widget::durChanged(qint64 dur)
{
ui->processSlider->setRange(0,dur);
totalTime.clear();
dur/=1000;
totalTime+=QString::number(dur/60);
totalTime+=':';
if(dur%60<10)
totalTime+='0';
totalTime+=QString::number(dur%60);
ui->totalTimeLabel->setText(totalTime);
}
设置进度条显示范围,并计算歌曲总时间,显示出来。
setPlayTime()
在歌词显示栏里设置读取到的歌词。
void Widget::setPlayTime()
{
if(!lyrics.getListLyricsTime().empty()&&player->position()>=lyric
s.getListLyricsTime().at(lyricsID)&&lyricsID<lyrics.getListLyrics
Time().size()-1){
ui->label->setText(lyrics.getListLyricsText().at(lyricsID));
ui->label_2->setText(lyrics.getListLyricsText().at(lyricsID+1));
lyricsID++;
}
}
contextMenuEvent(QContextMenuEvent*)
每次鼠标右击,都会实例化一个 menu 对象 QMenu* menu=new
QMenu(this);然后向菜单对象添加在构造函数实例化的 QAction 对象,然后显
示菜单。
实验源码
源码路径【1_音乐播放器\实验源码\1_music_player】
注意事项
1.在 ubuntu 上运行时,lrc 歌词文件要使用 utf-8 编码,否则读取歌词时会
出现乱码。
2.向开发板拷贝歌曲和歌词时,歌词需要使用 dos2unix 命令,将 DOS 格式
的文本文件转换为 UNIX 格式。(如果不转换,会有识别不了的字符,因为开发
板的 ubuntu 没有自动转换的功能),然后使用 scp 命令拷贝到开发板。
dos2unix wukong.lrc
sudo scp wukong.mp3 wukong.lrc root@192.168.10.128:~
3.在开发板运行时,需要导入中文字库,否则会因为识别不了中文歌词程
序出错。
将【1_音乐播放器\工具软件\wqy-zenhei-0.9.47-nightlybuild.tar.gz 或 wqyzenhei-0.8.38-1.tar.gz】复制到 ubuntu 下。并使用 scp 命令将文件拷贝到开发板
的 usr/share/fonts 目录下,使用 tar 命令解压后即可。
linux@ubuntu:~$ scp wqy-zenhei-0.8.38-1.tar.gz
root@192.168.10.128:/usr/share/fonts/
4.如果使用 mipi 五寸屏运行此项目,需要进行屏幕旋转以适应屏幕,具体
步骤如下:
在/etc/profile.d/qt-eglfs.sh 添加环境变量如下:
下面变量的 event0 设备需要填实际的触摸屏设备
这里即填 event0
export QT_QPA_EGLFS_ROTATION=90
export QT_QPA_EGLFS_NO_LIBINPUT=1
export
QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/event0:rotate=90