(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()<fileList.append(s+newFileList[i]);
}
}
ui->listWidget->clear();
for(i=0;ilistWidget->addItem(QFileInfo(fileList.at(i)).fileName());
}
//qDebug()<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();
}
}
void MainWindow::on_listWidget_clicked(const QModelIndex &index)
QString fileName = QString(fileList.at(ui->listWidget->currentRow()));
选择要播放的音频后,获取当前选定的行,重复执行上述播放实现。
直接向后台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()<viocenum;
if(this->playStatus)
{
process->write(QString("volume "+QString::number(position)+" 2\n").toAscii());
this->viocenum = position;
}
else
{
this->viocenum = position;
//qDebug()<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);
}
}