现在的位置: 首页 > 综合 > 正文

Code Project:创建一个FFMPEG前端

2013年08月08日 ⁄ 综合 ⁄ 共 8003字 ⁄ 字号 评论关闭

命令行没有什么不好。对于我们很多人来说,这是使用Linux的最佳理由之一。可以通过输入内容实现几乎所有功能,而且命令行工具对于它们的运行方式通常能够提供极好的控制。但是命令行并不适合所有人,觉得命令行难以理解和令人生畏的Linux用户数量多得令人吃惊,这或许是完全避免使用Linux的理由之一。尽管如今不愿意使用命令行的用户可以不必再使用它,但这仍然意味着他们将遗漏一些很优秀的实用工具。

Qt正好可以扭转这种局面。它是为你最喜爱的命令行工具创建友好GUI的理想工具。它不需要任何顶级的编程技巧,而且工作量也不大,但在此过程中你可以为讨厌命令行的朋友提供帮助,并且对开源应用程序开发做出自己的贡献。为命令行工具创建GUI是最佳起点之一!

如果你已经完成了我们以前的Qt Creator代码项目,如何创建自己的媒体播放器,就能够更加自如地完成这个任务……

如果说有一种工具非常需要GUI,那就是FFMPEG。FFMPEG是一个十分优秀的命令行应用程序,它可以将视频和电影文件从一种格式转换为另一种格式。同时它的复杂程度也令人吃惊。有好几位开发人员都曾经试过为这个工具创建一个GUI,但是FFMPEG的发展速度让人很难驾驭所有额外的组件和用于各个选项的不断改变的语法。

我们将会构建我们自己的FFMPEG GUI,但本指南的意义并不在于帮助人们了解FFMPEG的非凡之处。本指南将展示为几乎任意命令行工具创建GUI是多么轻松的事情——只要替换几行代码即可,而且结果可能对我们大有好处。

qt_menq_step_01

* 应该只有意志坚定和不怕阅读符号文字的人才会输入“ffmpeg -h”

应用程序设计

我们将假定已经启动并正在运行Creator,而且对于使用开发环境相对比较满意。如果不满足上述条件,请复习我们以前的指南。运行应用程序之后,创建一个新的项目并使用“Qt Gui Application”模板,同时保留其他选项的默认值。

和以前的指南一样,我们的开发工作仍然从GUI开始。点击“.ui”文件,Designer视图随之出现。如果想从空白画布开始,可在视图中删除菜单栏、工具栏和状态栏,方法是在窗口右上方的对象视图中选中它们,右键单击每个组件,然后从出现的菜单中选择删除。

我们应用程序的GUI布局显然依赖于要运行的命令行工具。但对于我们的FFMPEG例子,我们要尽可能尝试保持内容的开放性和可修改性。我们需要一个按钮来添加要转换的源文件,以及一个用于显示此文件位置的文本字段。我们需要一些途径来选择转换过程的最终格式,并显示FFMPEG命令的结果。我们还需要另一个按钮,点击它便可启动整个转换过程。

但是用户界面的主要部分将会被我们选出让用户编辑的FFMPEG选项所占据。我们没有理由向普通用户公开FFMPEG中数以百计的选项,因此这里只会出现最常用和最简单的使用选项。

qt_menq_step_02

* 我们的目标是构建出的布局能够最好地利用空间。这也正是我们选择在另一个页面上隐藏命令行输出的原因。

GUI的构建

从左边的Button小部件列表中拖两个按钮到空白画布上,然后双击每个按钮,修改它们的名称。一个按钮的名称定为“Source”,而另一个用于触发转换过程的按钮则需要取类似于“Go!”的名称。另外还可以使用图标代替文本,或者二者同时出现亦无不可(如果这是一种改进的话)。现在从Inputs列表中添加一个Line Edit小部件,然后点击左下方面板中的“enabled”属性以禁用它。

由于Line Edit小部件是交互式的——即默认情况下,用户可以在里面输入内容——我们想禁用这项功能,而只使用它来显示我们要转换文件的源位置。如果想让用户手动输入或修改文件的名称和位置,可以保留此字段的值为enabled。

我们还为用户提供了一条选择输出格式的方便途径。我们选择使用一列单选按钮,每个单选按钮代表一种格式。从调色板中拖动Radio Button小部件即可添加这些按钮。这种方法的好处在于,每次只能选择一种格式,而这正是我们需要的行为模式。对于每个单选按钮,还需要将其标签的文本修改为能够反映适合FFMPEG输出的设备。例如,如果选择PSP作为输出设备,将禁用iPhone输出和GP2X输出。
为了最大程度利用可用空间,我们准备使用选项卡小部件来保存余下的参数。就像浏览器中的选项卡式web页面一样,允许用户在一块GUI区域的两种不同视图之间进行切换。我们将使用一个选项卡来保存要公开的主要参数,而另一个选项卡可用于保存来自FFMPEG的原始命令输出。这应该意味着,除非用户切换选项卡,否则不会看到乱七八糟的FFMPEG输出。

从Creator中的Containers列表把选项卡小部件拖到画布上,选中之后,点击属性列表中的“currentTabText”属性,以便修改每个选项卡上显示的文本。我们选择了“Options”和“Output”。

被拖到选项卡小部件中的任意小部件都只显示在当前选项卡上,因此我们给Options选项卡添加了5个组合框和5个标签。对于像FFMEG这样的工具而言,组合框是对用户最友好的选项,因为它们将可用选项限制为只出现有效的选项。它对于程序员意味着额外的工作,但这种额外工作可以为可怜的老用户节省很多时间。借助这5个组合框,我们选择支持如下配置:视频的分辨率、帧率和比特率,以及视频的采样率和压缩比特率。

我们重新命名了每个组合框小部件,以便在源代码中能够更好地区分它们,同时在每个组合框旁边放置一个标签,并使用标签文本对每个选项进行了说明。在组合框下面,我们添加了更多小部件,用于保存目的文件的名称—— 一个按钮用于选择文件,以及一个禁用的线编辑字段用于显示位置。

最后,切换到选项卡小部件的下一个页面,并添加一个TextBrowser小部件。我们将使用这个小部件来显示FFMPEG命令的输出。

间距器

现在,我们的应用程序看起来应该相当复杂了。我们需要对各个小部件进行排列,让它们能够很好地缩放并且看起来间隔均匀。Qt使用一个分组和间距器的系统来创建布局,但了解这个系统的使用原理需要花点功夫。它和DTP应用程序处理对象的方式不同。例如,要在选项页面中创建良好的布局,首先shift选择一个标签及其相关组合框,然后从工具栏选择“Lay Out Horizontally”。这将会把两个小部件锁定在并排的位置。

对其他小部件做同样的事情,然后shift选择用于修改视频设置的分组对,并选择“Lay Out Vertically”。这将把每对小部件都对齐到一列中,对音频设置也要做同样的事情。我们还为音频和视频列添加了标签,并在垂直布局集合中包含了它们。要想正确实现分组选择,过程更加麻烦,但如果犯错我们始终可以进行“Undo”和“Redo”操作。

qt_menq_spacers

最好把间距器看作弹簧,并重新复习一些旧的物理课本。

为了保证我们的GUI不会明显延伸为一个大窗口,我们需要使用“间距器”。它们看起来有点像弹簧,可以从小部件调色板添加垂直或水平的间距器。当在水平或垂直布局中包含一个正确的间距器时,小部件将会与一块可扩展空间组合在一起,这块空间就是弹簧所在的位置。如果应用程序窗口扩展或收缩,这块空间也会根据弹簧长度成比例地伸缩。

这用语言也许很难描绘,但只要动手实践便不难理解。最后的结果是布局系统十分灵活,但学习起来却比较困难。我们使用三个水平弹簧来分开视频和音频选项,以及它们与窗口边界的空间,同时使用一个垂直弹簧来将视频与音频编码选项和目的文件位置分开。间距器还可用于在单选按钮之间安插一点距离。

当我们大体上安排好所需要的一切小部件后,选择“Lay Out in a Grid”将所有小部件绑定到主窗口上。此时仍然能够拖动和添加小部件到布局上,但如果需要做较大的改动,则需要首先打破这种绑定,即选择“Break Layout”。

信号与槽

现在,可以把GUI设计与我们将要在源代码中添加的程序功能联系起来了。这要借助于我们前面提过的Qt的信号与槽机制。一开始,切换到Signals/Slots编辑模式(F4),并且从“Go”按钮拖出一个连接到窗口的背景画布上,用于有效地发送信号给MainWindow类。在出现的窗口中,点击左边的Edit按钮,从而打开Signals/Slots编辑窗口。

我们需要添加6个槽:executeCommand()、setSource()、setDestination()、setPSP()、setIPOD() 和 setGP()。点击OK,选择新创建的“executeCommand()”槽,然后连接到我们当前正在编辑的Go按钮的clicked信号。需要对我们刚刚为其创建了槽的其他每个按钮做同样的事情,方法是把它们连接到它们对应的槽。例如,应该把来自PSP单选按钮的“clicked()”信号连接到“setPSP()”。

qt_menq_step_03

我们从GUI创建我们自己的槽,而且后面需要在源代码编辑器中为它们添加代码。

完成这项任务之后,Designer编辑就结束了,保存项目并切换为编辑MainWindow.h。我们需要在文件顶部添加几个头:

#include <QProcess>
#include <QByteArray>
#include <QTextBrowser>
#include <QFileDialog>
#include <QDesktopServices>
#include <QComboBox>

因为我们要对已经在Designer中添加给应用程序的小部件进行操作,所以这些头文件中的大部分都是必不可少的。Qprocess和QbyteArray用于在Qt中运行外部的可执行文件,并抓取命令的输出,但我们很快就会讲到这个函数。现在,我们需要为要编写的槽添加定义。在“public:”部分下面添加如下代码:

private slots:
    void executeCommand();
    void outputCommand();
    void setSource();
    void setDestination();
    void setPSP();
    void setIPOD();
    void setGP();

最后,我们需要在头文件的“private:”部分中添加一个变量。此变量将用于在Qt中处理外部可执行文件(FFMPEG):

QProcess commandProcess;

qt_menq_step_04

* 我们从GUI创建我们自己的槽,而且后面需要在源代码编辑器中为它们添加代码。

编程

现在我们到了最富于技巧性的部分,即给应用程序添加功能。首先,我们要为三种要处理的不同输出格式编写“set”函数。这些函数都将使用与设备相关的参数填充组合框,而我们最终将采用这些参数来编译将创建正确输出的FFMPEG命令行。

当然,在能够给GUI添加值之前,首先针对每种格式要有一个有效的FFMPEG命令。例如,我们已经找到用于转换运行在PSP上的视频的最佳FFMEPG命令是:

ffmpeg -i space.mpg '-vcodec' 'libxvid' -s 320x240 -r 29.97 -b 1500 -acodec libfaac
-ac 2 -ar 24000 -ab 65535 -f psp M4V80113.mp4 -y

我们准备采用这些参数中的一部分,并使它们能够在我们的GUI中进行编辑。根据我们在其他项目中的经验,可以通过“ui”对象运行属于GUI中对象的方法,此对象默认是使用标准Creator模板创建的。例如,“ui->comboResolution->clear()”将在comboResolution组合框上执行清除工作。

Creator集成环境的好处在于,可以使用自动完成功能列出每个对象的可用选项,而不用全靠记忆。下面是我们在setPSP函数中换到GUI中的FFMPEG选项。需要把它添加到MainWindow.cpp文件的底部:

void MainWindow::setPSP()
{
    ui->comboResolution->clear();
    ui->comboFramerate->clear();
    ui->comboBitrate->clear();
    ui->comboSamplerate->clear();
    ui->comboAbitrate->clear();
    ui->comboResolution->addItem("240x320");
    ui->comboResolution->addItem("160x120");
    ui->comboFramerate->addItem("29.97");
    ui->comboBitrate->addItem("1500");
    ui->comboSamplerate->addItem("2400");
    ui->comboAbitrate->addItem("65535");
    ui->lineEdit_2->setText("M4V80113.mp4");
 
}

这段代码的自解释程度相当高。为了节省空间,我们将会给其添加多个选项的惟一组合框是分辨率框,但可以很容易地看到如何添加其他选项。我们还需要为其他两种预设置创建相同模板,并把它们放在“setIPOD”和“setGP”函数槽中。由于我们只选择了一个参数集合让用户编辑,因此需要将这些参数与FFMPEG命令中的其他参数结合起来,而我们准备在处理运行外部FFMPEG命令的函数中做这件事情,这个函数叫做“executeCommand()”。

执行一个命令

void MainWindow::executeCommand()
{
    QStringList args;
 
    args << "-i";
    args << ui->lineEdit->text();
    args << "-y";
    args << "-s"; args << ui->comboResolution->currentText();
    args << "-r"; args << ui->comboFramerate->currentText();
    args << "-b"; args << ui->comboBitrate->currentText();
    args << "-ar"; args << ui->comboSamplerate->currentText();
    args << "-ab"; args << ui->comboBitrate->currentText();
    args << ui->lineEdit_2->text();          
 
    if (ui->radioButton->isChecked()){
        args << "-vcodec"; args << "libxvid";
        args << "-acodec"; args << "libfaac";
        args << "-ac"; args << "2";
        args << "-f"; args << "psp";
    }
    commandProcess.start("ffmpeg", args);
 
}

下面是对于以上代码块作用的解释。在Qt中运行外部命令的关键是一个叫做“Qprocess”的类。在步骤三结束时,我们在头文件中使用这个类创建了我们自己的对象,现在正是时候使用它来执行FFMPEG。使用“commandProcess”变量时,我们只要使用两个变量运行“start”即可——命令本身和我们的参数列表。

我们使用“QstringList”来快速构造这个参数列表,首先从我们GUI中的小部件,然后是包含在有条件的“if”语句中的PSP特定参数(ui->radioPSP->isChecked)。需要为其他目的设备添加更多参数,才能让转换过程开始工作。

qt_menq_step_05

* 来自FFPMEG命令的输出将显示在我们的主应用程序的输出选项卡中。

在用户点击我们在GUI中创建的“Go”按钮时,将执行这个函数,而且由于我们早先配置好的信号和槽,这个过程也是自动的。但是我们还需要捕捉该过程的输出,以便在文本视图中显示出来,同时给用户提供一些可视的反馈。令人高兴的是,由于信号与槽的神奇魔力,Qprocess可以不太费力地完全执行这种操作。

在MainWindow::MainWindow初始化例行程序中,我们需要在Qprocess输出信号和我们将用于把输出转换为文本以便于显示的“outputCommand”槽之间手动创建连接。在“ui->setupUi”行前面添加如下两行:

connect (&commandProcess, SIGNAL(readyReadStandardOutput()),this, SLOT(outputCommand()));
connect (&commandProcess, SIGNAL(readyReadStandardError()),this, SLOT(outputCommand()));

正如我们看到的那样,从Qprocess发出的有两种类型的输出信号,而且我们将来自这两种信号的输出都发送给同一个函数“outputCommand”,现在需要把这个函数添加到源代码中:

void MainWindow::outputCommand()
{
    QByteArray cmdoutput = commandProcess.readAllStandardOutput();
    QString txtoutput = cmdoutput;
    ui->textBrowser->append(txtoutput);
    cmdoutput = commandProcess.readAllStandardError();
    txtoutput = cmdoutput;
    ui->textBrowser->append(txtoutput);
 
}

这是当Qt从运行“FFMPEG”的Qprocess检测输出时执行的函数。这有点繁复,因为我们不能假定命令的输出是文本,而且输出数据有两种流形式——一种用于来自命令的标准输出,而另一种用于错误输出。我们的安全做法是在使用Qt的优秀转换例行程序将这些数据转换为一个文本字符串之前,将流中的二进制数据复制到一个原始字节数组中。接着把这些数据添加到文本视图中,而且我们对于命令的错误输出重复这个过程。没有什么捷径可以同时抓取到这两种流。

最后,在完成我们的应用程序之前,最后一个步骤是添加两个槽,用于处理源和目的文件位置。这两个槽几乎是完全相同的。下面给出了处理目的文件位置的槽函数:

void MainWindow::setDestination()
{
    QString file = QFileDialog::getSaveFileName (this, tr("Select Destination"),
         QDesktopServices::storageLocation(QDesktopServices::MoviesLocation));
    ui->lineEdit_2->setText(file);
}

第一行创建了一个Qt文件请求器,自动指向系统默认的电影位置,而因为我们已经使用了“getSaveFileName”,用户将被询问一个不一定存在的文件的名称。这与“setSource”完全相反:

void MainWindow::setSource()
{
     QString file = QFileDialog::getOpenFileName(this, tr("Select Source File"),
         QDesktopServices::storageLocation(QDesktopServices::MoviesLocation));
    ui->lineEdit->setText(file);
 
}

输入这最后两个函数后,我们应用程序的源代码就已经完成了,应该有一个可用的FFMPEG GUI,可以自定义它以使用所需的任意参数。编译并运行就可以了。还应该看到,修改这些代码以使用其他命令行工具是多么轻松的事情。

qt_menq_final_pic

下载此项目的代码:qt_menq.tar

借助基于Qt的前端,用户就不必深入研究FFMPEG的命令行用法了。

抱歉!评论已关闭.