基于Linux系统的QT音乐播放器+MQTT远程控制

 

嵌入式软件实验要求在Linux系统上实现一个音乐播放器,应用到QT的信号与槽机制,方便实现。

 

 

 

一 要求:

(1)采用QT开发技术,在Linux上实现一个多媒体播放器。程序自动读取U盘中的所有多媒体格式文件,生成多媒体播放列表,并自动播放第一首歌曲或视频。具有播放、暂停、停止、快进、快退等功能。可以手动选择文件播放。支持自动顺序播放和循环播放。要求支持mp4、WMV、mp3、wav等常见格式文件。

(2)搭建MQTT服务器,可以通过手机或电脑客户端对多媒体播放器进行远程控制,实现远程播放、停止等控制功能。

 

 

二、思路

       要求的是采用QT技术,做一个播放器。首先我们先创建一个进程,用QProcess指向这个进程,这个进程用来对播放器进行操作,实现播放、暂停、停止、快进、快退等功能,为了实现mqtt技术,需要另开一个进程,打开终端,开启mqtt服务,获取终端接收到的数据。同时我们用windows系统用python写一个简易的客户端,往ubuntu里发布内容,从而控制播放器的播放暂停等功能,下面对各个技术进行详细的介绍:

 

 

 

 

三、播放器的实现步骤

 

1.安装Qt与mplayer

 

(1)安装Qt

首先到官网下载QT安装包,此处安装的是qt-opensource-linux-x64-5.7.0.run,

打开终端,输入“sudo chmod -R 777 qtopensource-linux-x64-5.7.0.run”赋予安装包权限,再开始安装Qt,输入“./qt-opensource-linux-x64-5.7.0.run”,然后根据弹出的窗口,按照提示,一直点击next,等待安装。

 

(2)配置Qt环境

打开终端,输入命令“sudo apt-get install gcc g++”,安装linux下编程的编译器。然后输入命令“sudo apt-get install libqt4-dev”,不然编译时会出现错误“cannot find -lgl”。再输入命令“sudo apt-get install essential”,这是一个编译工具,可以使程序直到头文件和库函数放在哪个位置。进入bin目录,“./qtcreator”打开Qt。

 

(3)在Linux系统上安装mplayer播放器

使用“sudo apt-get install mplayer”命令进行安装mplayer。

 

 2.界面的设置

 

3.添加文件功能的实现

定义一个目录,为待读取目录,然后使用dir的过滤器,通过dir.entryList()函数,递归搜索文件,将定义的几种后缀的音频文件过滤出来,然后将其加到fileList中,并将文件名通过addItem函数放到ui播放列表Widget中,QFileInfo为我们提供了系统无关的文件信息,包括文件的名字和在文件系统中位置,文件的访问权限,是否是目录或符合链接,等等。最后将当前行设置在第0行,开始播放。

nameFilters << "*.mp3" << "*.mp4" << "*.rmvb" << "*.mkv" << "*.avi"  << "*.3gp" << "*.mov";
QStringList newFileList = dir.entryList(nameFilters,QDir::Files|QDir::Readable,QDir::Name);
void MainWindow::addFile()
{
    int i;
    QDir dir("/home/msj/");
    if(dir.exists()){
        QString s = "/home/msj/";
        QStringList nameFilters;
        nameFilters << "*.mp3" << "*.mp4" << "*.rmvb" << "*.mkv" << "*.avi"  << "*.3gp" << "*.mov";
        QStringList newFileList = dir.entryList(nameFilters, QDir::Files|QDir::Readable,QDir::Name);
        qDebug()<<newFileList;
        for(i=0;i<newFileList.count();i++){
            if(!fileList.contains(newFileList[i])){
                this->fileList.append(s+newFileList[i]);
            }
        }
        ui->listWidget->clear();

        for(i=0;i<fileList.size();i++){
            ui->listWidget->addItem(QFileInfo(fileList.at(i)).fileName());
        }

        //qDebug()<<fileList.at(0);
        ui->listWidget->setCurrentRow(0);
        selectFile();
    }
}

 

4、播放实现

使用mplay播放器,应用slave模式,不再截获后台事件,并为其开启一个从进程,在后台运行。并实时监测标准输出。

void MainWindow::selectFile()
{
    QString fileName = QString(fileList.at(ui->listWidget->currentRow()));
    if(!fileName.isEmpty()){
        if(this->playStatus)
        {
            playStatus = false;
            process->write("quit\n");//设置Mplayer quit
            process->waitForFinished();
        }

        QStringList args;
        args << "-slave";   //默认情况下,mplayer接受键盘的命令,而"-slave"使其不再接受键盘事件,而是作为后台程序运行
        //接受以“\n”结束的命令控制,这样我们可以在进程中给他发送命令,而不需要操作键盘了.
        args << "-quiet";  //尽可能的不打印播放信息
        args << "-zoom"; //视频居中,四周黑条,全屏播放
        args << "-wid" << QString::number(ui->Frame->winId(),10);
        // "-wid <窗口标识>" 是指让MPlayer依附于那个窗口,
        args << "-vo";
        args << "x11";
        args << fileName;//播放file_name文件
        process = new QProcess(this);
        connect(process, SIGNAL(readyReadStandardOutput()),this, SLOT(back_message_slots()));
        //process有可读取的信息时,发出信号,在槽函数back_message_slots()中读取信息。
        process ->setProcessChannelMode(QProcess::MergedChannels);
        //设置进程渠道的模式为融合模 式,即将标准输出和标准容错绑定到同一个管道的写端
        process -> start("mplayer", args);
        this->playStatus = true;
        this->pauseStatus = false;
    }
}

 

槽函数,将标准输出的内容实时显示在ui界面中 

void MainWindow::back_message_slots()
{
    while(process->canReadLine())
    {
        QString message(process->readLine());
        QStringList message_list = message.split("=");
        if(message_list[0] == "ANS_TIME_POSITION")//从mplayer里读取当前时间
        {
            this->curr_time = message_list[1].toDouble();
            QTime time = int_to_time(curr_time);
            ui->Start_Time->setText(time.toString("hh:mm:ss"));
            ui->PlaySlider->setValue(100 * curr_time / fileLength);
        }
        else if(message_list[0] == "ANS_LENGTH")//总时间
        {
            this->fileLength = message_list[1].toDouble();
            QTime time =  int_to_time(fileLength);
            ui->End_Time->setText(time.toString("hh:mm:ss"));
        }
    }
}

 

5、播放进度条

播放进度条采用信号与槽机制,定义好超时函数,并将其作为信号,槽函数实现获取当前播放时间,在标准输出中每一秒显示一次,与第4条中的实时获取ui播放时间呼应。

connect(timer, SIGNAL(timeout()), this, SLOT(get_time_pos()));
timer->start(1000);
void MainWindow::get_time_pos()
{
    if(this->playStatus && (!this->pauseStatus)){
        process->write("get_time_length\n");
        process->write("get_time_pos\n");//mplayer将时间在标准输出显示
    }
    if(this->curr_time == this->fileLength-1 && ui->End_Time->text()!="00:00:00")
    {
        ui->Start_Time->setText("00:00:00");
        ui->End_Time->setText("00:00:00");
        ui->PlaySlider->setValue(0);
        qApp->processEvents();
        ui->Frame->update();
        circle();
    }
}

 

6、点击信号

 

(1)播放列表的点击

void MainWindow::on_listWidget_clicked(const QModelIndex &index)
QString fileName = QString(fileList.at(ui->listWidget->currentRow()));

选择要播放的音频后,获取当前选定的行,重复执行上述播放实现。 

 

(2)快进快退的点击

直接向后台mplayer中传入seek命令,“0”是相对定位,即在当前的位置上快进快退1秒。

void MainWindow::on_Back_clicked()
{
    if(this->playStatus)
    {
        process->write("seek -1 0\n");
    }
}

 

(3)上一首下一首点击

 直接设置列表框setCurrentRow即可,默认从0 - n-1

void MainWindow::on_Last_clicked()
{
    ui->Frame->update();
    if(ui->listWidget->currentRow()-1 >= 0)
    {
        ui->listWidget->setCurrentRow(ui->listWidget->currentRow()-1);
    }
    else
    {
        ui->listWidget->setCurrentRow(ui->listWidget->count()-1);
    }
    selectFile();
}

 

(4)循环单一模式实现

设立一个模式变量circle,0为单一播放,1为循环播放,每次播放完毕后,检测circle的值,判断接下来的播放模式。

 

(5)进度条move

 使用seek命令,第三个参数“1”是绝对定位,进度条默认为0-100,检测拖动的位置,并在继续运行,需要注意的是需要将QString类型的命令转换成ASCII码,再传入mplayer。

void MainWindow::on_PlaySlider_sliderMoved(int position)
{
    if(this->playStatus)
    {
        QString command = "seek "+ QString::number(position) + " 1\n";
        process->write(command.toAscii());

    }
}

 

(6)声音条move

 传入命令volume position 2,如果第三个参数不为0的话,就把volume的值设置为position。

void MainWindow::on_VioceSlider_sliderMoved(int position)
{
    //position;
     //qDebug()<<this->viocenum;
    if(this->playStatus)
    {
        process->write(QString("volume "+QString::number(position)+" 2\n").toAscii());
        this->viocenum = position;
    }
    else
    {
        this->viocenum = position;
        //qDebug()<<this->viocenum;
    }
}

 

四、MQTT传输实现步骤

 

1、首先安装Linux上安装MQTT服务。

(1)先引入mosquitto仓库并更新,输入命令“sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa”以及“sudo apt-get update”,然后安装mosquitto,键入命令“sudo apt-get install mosquitto”,之后便可以开启MQTT服务了。

(2)开启MQTT服务,“sudo service mosquito start”

(3)查看mosquitto服务状态,sudo service mosquitto status

由于MQTT服务是基于发布/订阅模式的通讯协议,我们的思路是再开一个进程pro,用这个进程来开启服务,并保持订阅状态,接受客户端的发送信息。

2、Windows端搭建MQTT服务客户端

(1)调试前准备

我们采用Java来进行写客户端,首先我们要先导入jar包:mqtt-client-0.4.0

网盘地址:https://pan.baidu.com/s/1lD9e4BEIqlVxcSAWiBwdiA  密码 c3g3 

调试环境:Eclipse+jdk1.8.0

首先New一个project项目(New->Project...->Java Project),并导入jar包,放在lib下面。

 然后右键项目,属性,然后添加mqtt的jar包加到项目中。

(2)MQTT的开发

1)、由于MQTT是基于TCP协议的,所以我们首先定义好IP和端口号。

//tcp://MQTT安装的服务器地址:MQTT定义的端口号
public static final String HOST = "tcp://192.168.244.131:1883";
//定义一个主题
public static final String TOPIC = "mqtt";
//定义MQTT的ID,可以在MQTT服务配置中指定
private static final String clientid = "server11";

2)、建立MQTT连接

public ServerMQTT() throws MqttException {
        // MemoryPersistence设置clientid的保存形式,默认为以内存保存
        client = new MqttClient(HOST, clientid, new MemoryPersistence());
        connect();
    }

 connect()是用来连接服务器的。

3)、发送消息

public static void sendMessage(String msg)throws Exception{
        ServerMQTT server = new ServerMQTT();
        server.message = new MqttMessage();
        server.message.setQos(0);  //保证消息能到达一次
        server.message.setRetained(true);
        String str = msg;
        server.message.setPayload(str.getBytes());
        try{
            publish(server.topic11 , server.message);
            //断开连接
//            server.client.disconnect();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

(3)窗口类的创建

首先我们在main函数里用Jframe类new一个窗口,设置好位置、大小和默认退出方式等。

JFrame frame = new JFrame();
    	frame.setTitle("窗体");
    	frame.setBounds(200, 200, 580, 600);//起始位置 大小
    	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	frame.setVisible(true);

我们添加按钮,并增加监听事件,监听事件为向Mqtt服务器发送数据“play”。

JButton button_play = new JButton("play");
        button_play.setBounds(100, 100, 65, 30);
        button_play.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	try {
					sendMessage("play");
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            }
        });

窗口如下:

(4)连接尝试

1)首先我们先在Linux系统上打开mqtt服务;sudo service mosquitto start

 2)然后开始订阅一个主题的消息:mosquitto_sub -h localhost  -t "mqtt" -v

3)运行java程序,发送消息,点击play按钮,发送“play",发送成功

 

(5)用QT开启服务

经过了以上步骤,我们windows端和Unbutu端可以相互通信了,但是我们肯定不能用终端直接完成,现在我们把linux终端上的内容移植到QT上来。

我们在QT头文件上创建一个进程,用于开启在开启终端,首先在.h文件中声明

QProcess *pro_mqtt;

然后我们在cpp文件中创建一下,然后启动订阅主题为”mqtt“的服务,应用信号与槽,读取终端中的内容,在槽里进行相关操作。(!!!!!注意:我们首先要在终端中开启服务,我们在QT上只是开启了订阅)

pro_mqtt = new QProcess(this);
pro_mqtt->start("bash");             //启动终端
pro_mqtt->waitForStarted();        //等待启动完成
//sudo service mosquitto start;
pro_mqtt->write("mosquitto_sub -h localhost -t \"mqtt\" -v\n");               //向终端写入命令
connect(pro_mqtt , SIGNAL(readyReadStandardOutput()) , this , SLOT(get_readoutput()));

槽函数(注意要提前在.h文件中声明):

void MainWindow::get_readoutput()
{
    QString str = pro_mqtt->readAllStandardOutput().data();
    if(str.contains("play",Qt::CaseSensitive))
    {
        on_Pause_clicked();//如果接收到play字符串,触发开始暂停的点击事件。
    }
    //。。。。。。等等,不多做累述
}

 至此,Java端与Linux通信完成!

 

 

 

 

 

 

当然也可以用python,Java大同小异,之后抽空再更新。

未完待续......

附录:JAVA端源码:

package mqtt;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.HttpURLConnection;

import javax.swing.JButton;
import javax.swing.JFrame;

import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
 * Title:Server 这是发送消息的服务端
 * Description: 服务器向多个客户端推送主题,即不同客户端可向服务器订阅相同主题
 * @author Unclue_liu
 */
public class ServerMQTT extends JFrame{
 
    //tcp://MQTT安装的服务器地址:MQTT定义的端口号
    public static final String HOST = "tcp://192.168.244.131:1883";
    //定义一个主题
    public static final String TOPIC = "mqtt";
    //定义MQTT的ID,可以在MQTT服务配置中指定
    private static final String clientid = "server11";
 
    private MqttClient client;
    private static MqttTopic topic11;

    private static MqttMessage message;
 
    /**
     * 构造函数
     * @throws MqttException
     */
    public ServerMQTT() throws MqttException {
        // MemoryPersistence设置clientid的保存形式,默认为以内存保存
        client = new MqttClient(HOST, clientid, new MemoryPersistence());
        connect();
    }
 
    /**
     *  用来连接服务器
     */
    private void connect() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(false);
//        options.setUserName(userName);
//        options.setPassword(passWord.toCharArray());
        // 设置超时时间
        options.setConnectionTimeout(10);
        // 设置会话心跳时间
        options.setKeepAliveInterval(20);
        try {
            //client.setCallback(new PushCallback());
            client.connect(options);
            topic11 = client.getTopic(TOPIC);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     *
     * @param topic
     * @param message
     * @throws MqttPersistenceException
     * @throws MqttException
     */
    public static void publish(MqttTopic topic , MqttMessage message) throws MqttPersistenceException,
            MqttException {
        MqttDeliveryToken token = topic.publish(message);
 
        token.waitForCompletion();
        System.out.println("message is published completely! "
                + token.isComplete());
    }
 
 
    public static void sendMessage(String msg)throws Exception{
        ServerMQTT server = new ServerMQTT();
        server.message = new MqttMessage();
        server.message.setQos(0);  //保证消息能到达一次
        server.message.setRetained(true);
        String str = msg;
        server.message.setPayload(str.getBytes());
        try{
            publish(server.topic11 , server.message);
            //断开连接
//            server.client.disconnect();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
    /**
     *  启动入口
     * @param args
     * @throws MqttException
     */
    public static void main(String[] args) throws Exception {

    	new ServerMQTT();
    	JFrame frame = new JFrame();
    	frame.setTitle("窗体");
    	frame.setBounds(200, 200, 550, 400);//起始位置 大小
    	frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(null);
    	frame.setVisible(true);
        JButton button_play = new JButton("play");
        button_play.setBounds(100, 100, 65, 30);
        button_play.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	try {
					sendMessage("play");
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            }
        });
        //上一首
        JButton button_last = new JButton("last");
        button_last.setBounds(200, 100, 65, 30);
        button_last.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	try {
					sendMessage("last");
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            }
        });
        //下一首
        JButton button_next = new JButton("next");
        button_next.setBounds(300, 100, 65, 30);
        button_next.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	try {
					sendMessage("next");
				} catch (Exception e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
            }
        });
        // 向frame中添加一个按钮

        frame.add(button_play);
        frame.add(button_last);
        frame.add(button_next);
    	
        
    }

}
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值