Qt从0到1之工具篇:实现一个简陋的播放器

示例代码

Qt提供了相当多的工具方便我们进行开发,但其中有部分工具不便于单独讲解,因此本文将通过实现一个简单的播放器来介绍如何使用这些工具。本文将通过两种途径来完成软件的开发,一种使用Qt的图形化(GUI)工具来完成,另一种主要使用命令行(CLI)工具,两种途径不是绝对独立的,完全可以在不同的步骤中使用命令行或图形化工具,这需要看个人的使用习惯。由于本文重点是让大家对Qt的工具在开发过程中的作用有一个初步了解,因此每一步的具体细节将不会展开,大家可以查阅相关文档深入了解。

我们要实现的播放器主要功能是打开并播放选择的视频文件,并提供暂停和停止播放功能,程序的效果如下图所示
播放器效果图
为了实现这个播放器,我们将使用到一个第三方库VLC-Qt,这个库可以用来播放多种视频格式,性能优秀,相对于Qt自带的QMultiMedia模块要强大很多。这个库需要手动编译,我们可以参考官方文档进行编译,Windows和Macos下我们也可以直接下载已编译好的动态库,这里不再赘述。

使用GUI工具

新建工程

  1. 运行Qt Creator,在欢迎界面(Welcome) -> 项目(Projects)中选择新建项目(New Project),进入新建向导。
  2. 选择项目类型为Application -> Qt Widget Application,点击确定,进入Qt Widget Application项目配置。
  3. 输入项目名并选择项目文件夹目录。这里项目命名为QtPlayer-GUI
    对于常用的目录,我们可以选择设为默认项目目录(Use as a default project location),避免重复选择。
  4. 选择构建套件。这里会列出我们在设置中添加的所有可用的构建套件,我们需要根据软件的需求和外部条件来选择合适的套件,如为了更好地兼容性,我们可以选择32位的构建套件。当然我们可以选择多个构建套件,在构建时切换。若我们选错了构建套件,最简单的解决办法是关闭Qt Creator,删除Qt Creator生成的.pro.user文件夹,然后再用Qt Creator打开.pro文件,这样会让我们重新选择构建套件。这里我们需要选择和编译VLC-Qt时一致的构建套件环境,避免链接时出错。
  5. 设置主界面类。这里我们可以选择主界面类的基类类型,设置类名和文件名,并选择是否添加对应的UI文件。这里我们将基类设为QWidget,其他保持保持默认即可。
  6. 信息总览。直接点击确定,我们可以看到,Qt Creator自动帮我们生成了一些基本的代码。

引入第三方库

  1. 右键项目管理窗口中项目名 -> 选择添加库...(Add Library...),弹出库添加向导。
  2. 选择外部库(External Library),点击下一步
  3. 选择VLC-Qt库的库文件和头文件目录,这里我们需要用到VLCQtCoreVLCQtWidgets两个库,我们需要添加两次。

添加完第三方库后,我们可以打开.pro文件,可以看到Qt Creator中自动添加了第三方库的相关配置,无需我们手动编写。

设置UI布局

双击生成的.ui文件,进入内置的Qt Designer界面,我们拖出我们需要的UI界面。如下图所示。
UI布局

编写代码

参考VLC-Qt提供的examples,我们可以很方便地实现相关功能,这部分不是重点,这里仅贴出相关代码,不过多解释。需要注意的是在Windows下,若直接下载的预编译好的VLC-Qt的库,推荐编译release版本,因为当前(2018.08.11)预编译版本(VLC-Qt 1.1.0)依赖Visual Studio 2013VC12)的运行库(???大坑),Debug版本会找不到相关运行库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

#include "VLCQtCore/Instance.h"
#include "VLCQtCore/Media.h"
#include "VLCQtCore/MediaPlayer.h"
#include "VLCQtWidgets/WidgetVideo.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget {
Q_OBJECT

public:
explicit Widget(QWidget *parent = nullptr);
~Widget();

private:
void Open();
void Pause();
void Play();
void Stop();

private:
VlcInstance *instance_;
VlcMedia *media_;
VlcMediaPlayer *player_;
VlcWidgetVideo *video_widget_;

private:
Ui::Widget *ui;
};

#endif // WIDGET_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// widget.cpp
#include "widget.h"
#include "ui_widget.h"

#include <QFileDialog>
#include <QToolButton>

#include "VLCQtCore/Common.h"

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) {
ui->setupUi(this);
setWindowTitle(tr("QtPlayer"));

ui->open_btn_->setIcon(QIcon(":/open.png"));

ui->pause_btn_->setCheckable(true);
ui->pause_btn_->setChecked(false);
ui->pause_btn_->setIcon(QIcon(":/pause.png"));
ui->pause_btn_->setEnabled(false);

ui->stop_btn_->setIcon(QIcon(":/stop.png"));
ui->stop_btn_->setEnabled(false);

video_widget_ = new VlcWidgetVideo(ui->player_);
video_widget_->resize(ui->player_->size());

instance_ = new VlcInstance(VlcCommon::args(), this);
player_ = new VlcMediaPlayer(instance_);
player_->setVideoWidget(video_widget_);

connect(ui->open_btn_, &QToolButton::clicked, this, &Widget::Open);
connect(ui->pause_btn_, &QToolButton::clicked, [this](bool checked) {
if (checked) {
ui->pause_btn_->setIcon(QIcon(":/play.png"));
Pause();
} else {
ui->pause_btn_->setIcon(QIcon(":/pause.png"));
Play();
}
});
connect(ui->stop_btn_, &QToolButton::clicked, this, &Widget::Stop);
}

Widget::~Widget() {
delete ui;
}

void Widget::Open() {
QString file = QFileDialog::getOpenFileName(this, tr("Open file"),
QDir::homePath(),
tr("Video(*.mp4 *.avi *.mkv)"));
if (file.isEmpty()) {
return;
}

ui->open_btn_->setEnabled(false);
ui->pause_btn_->setEnabled(true);
ui->stop_btn_->setEnabled(true);

media_ = new VlcMedia(file, true, instance_);
player_->open(media_);
}

void Widget::Pause() {
player_->pause();
}

void Widget::Play() {
player_->play();
}

void Widget::Stop() {
player_->stop();
delete media_;
media_ = nullptr;

ui->open_btn_->setEnabled(true);
ui->pause_btn_->setChecked(false);
ui->pause_btn_->setIcon(QIcon(":/pause.png"));
ui->pause_btn_->setEnabled(false);
ui->stop_btn_->setEnabled(false);
}

安装必要文件

编译完成后,点击运行,在Windows平台下我们可能会遇到无法启动,提示找不到运行库的问题。这是因为程序运行时没有找到我们用到的VLC-Qt库的动态库。我们需要手动将两个dll文件复制到可执行文件的运行目录,这时就可以正常运行了。但手动复制文件相对比较麻烦,也不利于后期对外发布,因此我们可以利用qmake提供的安装功能,在构建时将依赖的动态库安装到指定目录。我们在pro文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
DESTDIR = $$PWD/out

vlc_lib.path = $$DESTDIR

win32 {
vlc_lib.files += $$PWD/dev/bin/VLCQtCore.dll
vlc_lib.files += $$PWD/dev/bin/VLCQtWidgets.dll
}

INSTALLS += vlc_lib

添加资源文件

运行后,我们会发现按钮上的图片没有显示,这时因为我们在代码中使用了相对路径,程序会在运行时去加载相对于 当前工作目录的相对路径的文件,由于我们在程序目录下没有放置图片文件,所以程序没有找到对应的图片资源,也就无法显示。我们只需要将图片复制到程序目录即可。但对于程序必要的资源文件,为了避免手动复制和不小心误删,我们可以利用Qt的资源系统来将管理这些文件。添加至资源管理系统中的文件将在编译期间编译至可执行程序中,这样就可以避免找不到文件的问题。

  1. 右键项目->选择添加新文件(Add New...),选择Qt模板下的Qt Resource file,输入文件名,这里我们建立一个用于管理图片资源的资源管理文件,因此命名为image,点击完成。
  2. 我们可以发现项目管理窗口多了一项Resource,我们点击下方的添加(Add)按钮,添加一个前缀(Add Prefix),修改为/
  3. 再点击添加(Add)按钮,选择添加文件(Add Files),将我们需要的图片添加至资源管理系统。
  4. 修改代码中的相对路径,替换为资源管理系统中对应文件的路径。

    1
    2
    3
    4
    ui->open_btn_->setIcon(QIcon(":/open.png"));
    ui->pause_btn_->setIcon(QIcon(":/pause.png"));
    ui->pause_btn_->setIcon(QIcon(":/play.png"));
    ui->stop_btn_->setIcon(QIcon(":/stop.png"));

国际化翻译

目前我们的程序界面上显示的都是英文,我们需要将其翻译为中文。这需要用到Qt语言家(Qt Linguist)

  1. pro文件中添加生成语言文件的代码,保存后执行qmake

    1
    TRANSLATIONS += zh_CN.ts
  2. Qt Creator中选择工具(Tools)->外部(External)->语言家(Linguist)->更新翻译(Update Translations(lupdate)),生成ts文件,这里面包括了我们要翻译的字符串。

  3. Qt Linguist打开生成的ts文件,选择要翻译的源语言和目标语言,进行翻译。翻译完成后保存并发布,生成二进制文件.qm文件
  4. main.cpp中添加相关代码,安装中文翻译。

    1
    2
    3
    QTranslator translator;   // 需添加头文件 #include <QTranslator>
    translator.load("./zh_CN.qm");
    a.installTranslator(&translator);

对于翻译文件我们同样存在找不到文件导致翻译失败的问题,我们也可以通过资源管理系统来管理翻译后的文件。

打包发布

由于我们的程序运行需要依赖一系列Qt的库,因此我们在对外发布我们的程序时需要将这些动态库一并发布。但Qt的库很多,我们一个一个找我们程序以来的库比较麻烦,因此我们可以借助Qt的部署工具来寻找依赖的动态库。Windows下工具名为windeployqt,MacOS下叫macdeployqt,linux下官方暂时没有提供工具,但有一个强大的第三方工具linuxdeployqt。下面以Windows下为例进行说明。

  1. 将编译好的可执行程序及依赖的第三方库复制到一个单独的目录
  2. 打开Qt命令行处理程序,进入可执行程序目录,执行命令windeployqt QtPlayer-GUI即可

使用CLI工具(Linux环境)

部分步骤与上文类似,仅作简略介绍

新建工程

  1. 创建目录QtPlayer-CLI并创建main.cppplayer.hplayer.cpp三个文件
  2. 执行qmake -project .生成pro文件
  3. 修改pro文件,引入Qt模块

    1
    QT += core gui widgets

引入第三方库

  1. VLC-Qt的库复制到项目目录下dev目录
  2. 修改pro文件,添加以下代码

    1
    2
    LIBS+= $$PWD/dev/lib64 -lVLCQtCore -lVLCQtWidgets
    INCLUDEPATH += $$PWD/dev/include

编写代码

这里我们将通过代码对控件进行布局。具体代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
// main.cpp
#include "player.h"

#include <QApplication>

int main(int argc, char *argv[]) {
QApplication a(argc, argv);

Player w;
w.show();

return a.exec();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// player.h
/* Copyright (©) 2018 xyz1001 All rights reserved.
* Author: xyz1001 <zgzf1001@gmail.com>
*
* -*- coding: uft-8 -*-
*/

#ifndef PLAYER_H_LDRNQIXJ
#define PLAYER_H_LDRNQIXJ

#include <QToolButton>
#include <QWidget>

#include <VLCQtCore/Instance.h>
#include <VLCQtCore/Media.h>
#include <VLCQtCore/MediaPlayer.h>
#include <VLCQtWidgets/WidgetVideo.h>

class Player : public QWidget {
Q_OBJECT

public:
explicit Player(QWidget *parent = nullptr);

private:
void Open();
void Pause();
void Play();
void Stop();

private:
void InitButtons();

private:
VlcInstance *instance_;
VlcMedia *media_;
VlcMediaPlayer *player_;
VlcWidgetVideo *video_widget_;

private:
QToolButton *open_btn_;
QToolButton *pause_btn_;
QToolButton *stop_btn_;
};

#endif // PLAYER_H_LDRNQIXJ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// player.cpp
/* Copyright (©) 2018 xyz1001 All rights reserved.
* Author: xyz1001 <zgzf1001@gmail.com>
*
* -*- coding: uft-8 -*-
*/

#include "player.h"

#include <QFileDialog>

#include "VLCQtCore/Common.h"

Player::Player(QWidget *parent) : QWidget(parent) {
setWindowTitle(tr("QtPlayer"));
resize(800, 520);

InitButtons();

video_widget_ = new VlcWidgetVideo(this);
video_widget_->setGeometry(0, 0, 800, 450);

instance_ = new VlcInstance(VlcCommon::args(), this);
player_ = new VlcMediaPlayer(instance_);
player_->setVideoWidget(video_widget_);

connect(open_btn_, &QToolButton::clicked, this, &Player::Open);
connect(pause_btn_, &QToolButton::clicked, [this](bool checked) {
if (checked) {
pause_btn_->setIcon(QIcon("./play.png"));
Pause();
} else {
pause_btn_->setIcon(QIcon("./pause.png"));
Play();
}
});
connect(stop_btn_, &QToolButton::clicked, this, &Player::Stop);
}

void Player::Open() {
QString file = QFileDialog::getOpenFileName(this, tr("Open file"),
QDir::homePath(),
tr("Video(*.mp4 *.avi *.mkv)"));
if (file.isEmpty()) {
return;
}

open_btn_->setEnabled(false);
pause_btn_->setEnabled(true);
stop_btn_->setEnabled(true);

media_ = new VlcMedia(file, true, instance_);
player_->open(media_);
}

void Player::Pause() {
player_->pause();
}

void Player::Play() {
player_->play();
}

void Player::Stop() {
player_->stop();
delete media_;
media_ = nullptr;

open_btn_->setEnabled(true);
pause_btn_->setChecked(false);
pause_btn_->setIcon(QIcon("./pause.png"));
pause_btn_->setEnabled(false);
stop_btn_->setEnabled(false);
}

void Player::InitButtons() {
open_btn_ = new QToolButton(this);
open_btn_->setGeometry(300, 460, 50, 50);
open_btn_->setIcon(QIcon("./open.png"));

pause_btn_ = new QToolButton(this);
pause_btn_->setGeometry(375, 460, 50, 50);
pause_btn_->setCheckable(true);
pause_btn_->setChecked(false);
pause_btn_->setIcon(QIcon("./pause.png"));
pause_btn_->setEnabled(false);

stop_btn_ = new QToolButton(this);
stop_btn_->setGeometry(450, 460, 50, 50);
stop_btn_->setIcon(QIcon("./stop.png"));
stop_btn_->setEnabled(false);
}

编译

创建build目录并进入,执行qmake .. && make,生成可执行文件。

配置动态库路径

编译完成后我们执行编译出的可执行程序,发现无法运行,提示缺少VLC-Qt的运行库,我们需要设置运行库路径,在build目录下执行命令

1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../dev/lib64

添加资源文件

和使用GUI时情况一样,运行后按钮并没有图片,我们可以使用Qt提供的rcc命令来添加资源管理系统。

  1. 创建image文件夹,将图片文件放入,便于管理
  2. 执行命令rcc --project . -o image.qrc,生成资源管理文件
  3. 修改pro文件,将生成的资源管理文件添加至RESOURCES变量中

    1
    RESOURCES += $$PWD/image/image.qrc
  4. 修改代码中的图片路径,替换为qrc文件中的路径

    1
    2
    3
    4
    ui->open_btn_->setIcon(QIcon(":./open.png"));
    ui->pause_btn_->setIcon(QIcon(":./pause.png"));
    ui->pause_btn_->setIcon(QIcon(":./play.png"));
    ui->stop_btn_->setIcon(QIcon(":./stop.png"));

国际化翻译

  1. QtPlayer-CLI.pro文件中添加TRANSLATIONS += zh_CN.ts
  2. 执行lupdate ./QtPlayer-CLI.pro命令,生成ts文件
  3. 如果我们熟悉ts文件格式,可以手动去修改ts文件,这里我们还是调用Qt语言家(linguist)进行翻译。执行命令linguist zh_CN.ts,后续步骤同使用GUI工具中的国际化翻译的3,4步

打包发布

linux下,我们可以使用第三方工具linuxdeployqt来进行打包,该工具可以帮助我们生成Appimage文件格式。具体用法可参考项目的README。

文章目录
  1. 1. 使用GUI工具
    1. 1.1. 新建工程
    2. 1.2. 引入第三方库
    3. 1.3. 设置UI布局
    4. 1.4. 编写代码
    5. 1.5. 安装必要文件
    6. 1.6. 添加资源文件
    7. 1.7. 国际化翻译
    8. 1.8. 打包发布
  2. 2. 使用CLI工具(Linux环境)
    1. 2.1. 新建工程
    2. 2.2. 引入第三方库
    3. 2.3. 编写代码
    4. 2.4. 编译
    5. 2.5. 配置动态库路径
    6. 2.6. 添加资源文件
    7. 2.7. 国际化翻译
    8. 2.8. 打包发布
|