(1)采用QT开发技术,在Linux上实现一个多媒体播放器。程序自动读取U盘中的所有多媒体格式文件,生成多媒体播放列表,并自动播放第一首歌曲或视频。具有播放、暂停、停止、快进、快退等功能。可以手动选择文件播放。支持自动顺序播放和循环播放。要求支持mp4、WMV、mp3、wav等常见格式文件。
(2)搭建MQTT服务器,可以通过手机或电脑客户端对多媒体播放器进行远程控制,实现远程播放、停止等控制功能。
要求的是采用QT技术,做一个播放器。首先我们先创建一个进程,用QProcess指向这个进程,这个进程用来对播放器进行操作,实现播放、暂停、停止、快进、快退等功能,为了实现mqtt技术,需要另开一个进程,打开终端,开启mqtt服务,获取终端接收到的数据。同时我们用windows系统用python写一个简易的客户端,往ubuntu里发布内容,从而控制播放器的播放暂停等功能,下面对各个技术进行详细的介绍:
首先到官网下载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,等待安装。
打开终端,输入命令“sudo apt-get install gcc g++”,安装linux下编程的编译器。然后输入命令“sudo apt-get install libqt4-dev”,不然编译时会出现错误“cannot find -lgl”。再输入命令“sudo apt-get install essential”,这是一个编译工具,可以使程序直到头文件和库函数放在哪个位置。进入bin目录,“./qtcreator”打开Qt。
使用“sudo apt-get install mplayer”命令进行安装mplayer。
定义一个目录,为待读取目录,然后使用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(); } }
使用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")); } } }
播放进度条采用信号与槽机制,定义好超时函数,并将其作为信号,槽函数实现获取当前播放时间,在标准输出中每一秒显示一次,与第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(); } }
选择要播放的音频后,获取当前选定的行,重复执行上述播放实现。
直接向后台mplayer中传入seek命令,“0”是相对定位,即在当前的位置上快进快退1秒。
void MainWindow::on_Back_clicked() { if(this->playStatus) { process->write("seek -1 0\n"); } }
直接设置列表框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(); }
设立一个模式变量circle,0为单一播放,1为循环播放,每次播放完毕后,检测circle的值,判断接下来的播放模式。
使用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()); } }
传入命令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; } }
(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,用这个进程来开启服务,并保持订阅状态,接受客户端的发送信息。
我们采用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包加到项目中。
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(); } }首先我们在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(); } } });窗口如下:
1)首先我们先在Linux系统上打开mqtt服务;sudo service mosquitto start
2)然后开始订阅一个主题的消息:mosquitto_sub -h localhost -t "mqtt" -v
3)运行java程序,发送消息,点击play按钮,发送“play",发送成功
经过了以上步骤,我们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); } }