在优麒麟上使用MPV编写自己的播放器

发布时间:2019-09-10 08:00:08 点击次数:782

上一期,小编给大家简要介绍了MPV的配置和使用方法,虽然MPV功能强大,但默认情况下,MPV无GUI图形界面,用户需要通过命令行或者手动修改其配置文件达到配置MPV的目的,这样就给普通用户带来了诸多不便。为此,本文将介绍如何在优麒麟系统上使用Qt编写带UI图形的MPV播放器,使用户对快速定制具有图形界面的MPV播放器有一个大致的了解。安装编程开发依赖包:$ sudo apt install qtb

上一期,小编给大家简要介绍了MPV的配置和使用方法,虽然MPV功能强大,但默认情况下,MPV无GUI图形界面,用户需要通过命令行或者手动修改其配置文件达到配置MPV的目的,这样就给普通用户带来了诸多不便。为此,本文将介绍如何在优麒麟系统上使用Qt编写带UI图形的MPV播放器,使用户对快速定制具有图形界面的MPV播放器有一个大致的了解。

安装编程开发依赖包:

$ sudo apt install qtbase5-dev qt5-qmake qtscript5-dev qttools5-dev-tools

1、QProcess类

为了实现一个既可以在Qt程序中控制MPV,又可以让Qt程序得到MPV的输出信息的播放器,这里重点介绍Qt的QProcess类。QProcess类可用来调用外部程序,并与外部程序进行通信。其把外部程序的进程当作一个有序的I/O设备,通过对I/O设备的读写来完成进程间的通信,即:write()函数实现对进程标准输入的写操作,通过read(),readLine()和getChar()函数实现对标准输出的读操作。

在正常渠道模式下,QProcess的无名管道stdinChannelpipe,stdoutChannelpipe和stderrChannelpipe分别与标准输入、标准输出和标准容错进行绑定,实现与外部程序的通信;而在融合模式下,没有容错管道,此时,标准容错端和标准输出端将共同挂接到子进程的stdoutChannelpipe的写端来实现内外进程的通信,即标准输出和标准容错绑定到同一个管道的写端。

本文介绍通过QProcess类调用MPV,并设置一系列播放参数,如视频驱动、音频驱动、软解/硬解、缓存等。至于Qt图形和MPV视频窗口的关联,则是使用“--wid widget->winId()”进行绑定,通过winId()可以获得一个数字,其中widget是一个QWidget对象,这样将界面上一个窗口的句柄给了MPV,即视频输出定位到了widget窗体部件中(wid为MPV指定了输入窗口,-wid参数只在X11、directX和OpenGL中适用)。本文推荐使用融合模式,代码为:setProcessChannelMode(QProcess::MergedChannels)。

2、Qt对MPV的控制

前面详细介绍了Qt的QProcess类,这里描述下该类是如何设置MPV的参数和启动MPV的,即如何使用给标准输入控制MPV。MPV自动从标准输入中读取信息并执行,该类指令信息都需要以“\n”结尾。管道的读端描述符stdinChannelpipe[0]复制给了标准输入,即标准输入的描述符也为stdinChannelpipe[0],隐藏按照标准输入的描述符去读信息就是到stdinChannelpipe所对应的管道中读取信息。QProcess的start()函数将开启进程,第一个参数即为MPV二进制,第二个参数为给MPV的参数列表,执行start()函数后,将完成内核中管道以及通信环境的建立。QProcess的成员函数write()向stdinChannelpipe[1]端写入信息,比如想让MPV退出,则通过write函数写入“quit \n”即可(write()函数将向stdinChannelpipe[1]端写入命令)。参考代码如下:

QString mpvCmd = "/usr/bin/mpv";  

QStringList args;

args << "-- no-quiet";

args << "--wid=100663330";

args << xxx.avi;

process->start(mpvCmd, args);//启动播放

waitForStarted();

QString cmd = "set pause yes";

process->write(cmd.toLocal8Bit() + "\n");//设置暂停

3、Qt获取MPV的信息

使用QProcess的融合模式,从标准输出和标准容错得到MPV 的信息。绑定QProcess的信号readyReadStandardOutput,当该信号出发时,再通过QProcess的readAllStandardOutput()函数获取信息(当然,这里也可以while循环来判断是否可以读取一行数据:while(process->canReadLine()),如果可以,则读取一行:QString line(process->readLine());),readAllStandardOutput获取的数据类型为QByteArray,对数据进行解析时通过“\n” 和“\r” 切分后一行一行解析,通过对每行的QByteArray数据进行详细解析可以得出MPV的状态信息,并将一些状态在Qt的图形程序上动态展示处理,比如MPV播放成功,则根据播放成功的信息将Qt图形的播放按钮设置为可暂停状态,此时点击该按钮给MPV发送的指令应该是暂停而非启动。另外,可以绑定QProcess的信号finished(int, QProcess::ExitStatus),当该信号被触发时,通过判断QProcess的bytesAvailable()值,如果>0,则通过QProcess的readAllStandardOutput()函数获取信息。

获取MPV的信息进行解析的难点是对繁杂冗余的信息进行过滤,针对关键字定位信息表示的意思。这里可以使用Qt的对获取的QRegExp来进行匹配。

下述正则表达式可用于分析出正在启动中:

QRegExp rx_playing;

rx_playing.setPattern("^Playing:.*|^\\[ytdl_hook\\].*");

下述正则表达式可可解析出暂停、缓存中等各种状态信息:

QRegExp rx_av;

rx_av.setPattern("^STATUS: ([0-9\\.-]+) / ([0-9\\.-]+) P: (yes|no) B: (yes|no) I: (yes|no) VB: ([0-9\\.-]+) AB: ([0-9\\.-]+)");

4、编程示例

使用Qt Creator创建一个新工程,模板中选择"QApplication -> Qt Widgets Application",以下示例取名为mympvplayer,如下图所示:

优麒麟(Ubuntu Kylin)

优麒麟(Ubuntu Kylin)

优麒麟(Ubuntu Kylin)

至此,一个最基本的UI图形框架已经成功搭建完毕了,编译和运行则会弹出一个最基本的QMainWindow图形界面。接下来我们要做的就是在此基础上丰富播放器的界面和完成最基本的功能逻辑处理,示例支持基本的文件播放(包括拖动文件到播放器进行播放和通过鼠标右键菜单选择文件播放)、暂停、停止、进度控制(包括鼠标拖动进度条进行控制和鼠标滚轮上下滚动控制)、音量调节。下面将对部分代码模块进行简单的说明,具体说明和示例代码请查看:https://github.com/eightplus/examples/tree/master/code/Qt/mympvplayer 或 https://eightplus.github.io/2019/08/19/2019-08-19-qt-mpv-player/。

1)增加播放控制栏

这里在主界面的最下方增加一个播放控制栏区域,包括播放/暂停、停止、进度条、音量控制以及时长显示等,如下图所示:

优麒麟(Ubuntu Kylin)

在实例中,图形界面相关的模块主要有两个,一个是类Mainwindow(mainwindow.cpp和mainwindow.h),另一个是类BottomWidget(bottomwidget.cpp和bottomwidget.h)。Mainwindow是主体,负责各个模块对象的创建,图形和数据的交互处理等。BottomWidget创建了三个按钮(播放/暂停、停止、静音),两个滑动条(播放进度条、音量控制条),一个文字显示区域(视频时长和播放时长)。具体代码见:https://github.com/eightplus/examples/blob/master/code/Qt/mympvplayer/bottomwidget.cpp。

2)增加显示模块类

类VideoWindow(videowindow.cpp和videowindow.h)中包含了一个QWidget对象,通过将QWidget对象的wid传递给MPV播放引擎用来显示视频。示例将视频的显示长宽比固定为了4/3。具体代码见:https://github.com/eightplus/examples/blob/master/code/Qt/mympvplayer/videowindow.cpp。

3)增加进程控制类

类MyProcess(myprocess.cpp和myprocess.h)继承于QProcess,使用上面接收QProcess时提到的QProcess::MergedChannels融合模式。该模块的主要功能有两点:一是负责向MPV发送指令,如播放、暂停等(通过write函数 ,每一条指令需要以"\n"结尾);而是接收MPV反馈的各类数据信息,并通过QRegExp设置正则表达式进行匹配和解析,从而显示一些用户可以识别和理解的信息,并实时根据一些状态信息修改界面状态或更改播放控制等。具体代码见:https://github.com/eightplus/examples/blob/master/code/Qt/mympvplayer/myprocess.cpp。 

4)增加交互类

类Core(core.cpp和core.h)相当于一个管家,负责播放器图形端和控制端的通信和交互。如图形上的指令通过该类调用进程控制类的方法去给MPV发送指令,MPV的信息通过该类给图形端去显示。具体代码见:https://github.com/eightplus/examples/blob/master/code/Qt/mympvplayer/core.cpp。

5)增加其他模块类

上面提交的4个模块,已经将播放器需要的图形显示、数据管理和指令处理描述完毕,接下来还需要增加一些辅助类,让自定义的播放器功能更完善,如配置项类Preferences、视频文件数据结构类MediaData、视频文件各类数据设置类MediaSettings、MPV各类数据获取类InfoReader和辅助功能模块类Utils。

参考文档和项目:

https://github.com/ukui/kylin-video/

https://mpv.io/manual/master/#configuration-files

https://mpv.io/manual/stable/

https://github.com/mpv-player/mpv/blob/master/etc/mpv.conf

https://github.com/mpv-player/mpv/blob/master/etc/input.conf