【秒懂音视频开发】 QT开发基础

187 阅读4分钟

.pro文件的配置

跨平台配置

之前我们分别在WindowsMac环境的Qt项目中集成了FFmpeg。

可以发现在 .pro文件的配置中,FFmpeg库在Mac、Windows上的位置是有所差异的。这样就会导致 .pro文件无法跨平台使用。

# windows INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include # mac INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include

为了实现跨平台配置,可以在配置前面加上平台标识的前缀,表示这个配置只会在对应的平台生效。

    # windows
    win32:INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include
    win32:LIBS += -LF:/Dev/ffmpeg-4.3.2/lib \
                  -lavcodec \
                  -lavdevice \
                  -lavfilter \
                  -lavformat \
                  -lavutil \
                  -lpostproc \
                  -lswscale \
                  -lswresample
    # mac
    macx:INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include
    macx:LIBS += -L/usr/local/Cellar/ffmpeg/4.3.2/lib \
                -lavcodec \
                -lavdevice \
                -lavfilter \
                -lavformat \
                -lavutil \
                -lpostproc \
                -lswscale \
                -lswresample \
                -lavresample

以后针对每个平台的配置可能会比较多,可以使用大括号来简化。

``` # windows
win32 {
    INCLUDEPATH += F:/Dev/ffmpeg-4.3.2/include
    LIBS += -LF:/Dev/ffmpeg-4.3.2/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample
}

# mac
macx {
    INCLUDEPATH += /usr/local/Cellar/ffmpeg/4.3.2/include
    LIBS += -L/usr/local/Cellar/ffmpeg/4.3.2/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample \
            -lavresample
}

自定义变量

可以将公共的信息抽取成变量,然后使用 $${} 去访问。

# mac
macx {
    FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.3.2
    INCLUDEPATH += $${FFMPEG_HOME}/include
    LIBS += -L$${FFMPEG_HOME}/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample \
            -lavresample
}

读取系统环境变量

也可以通过 $$() 读取系统的环境变量。比如,我的Windows中有个叫做FFMPEG_HOME的环境变量。

# 使用message打印环境变量JAVA_HOME的值 message($$(FFMPEG_HOME))

控件的基本使用

为了更好地学习Qt控件的使用,建议创建项目时先不要生成ui文件。

497279-20210304194032371-721491748.png 打开mainwindow.cpp,在MainWindow的构造函数中编写界面的初始化代码。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) {
    // 设置窗口标题
    setWindowTitle("主窗口");

    // 设置窗口大小
    // 窗口可以通过拖拽边缘进行自由伸缩
//    resize(400, 400);
    
    // 设置窗口的固定大小
    // 窗口不能通过拖拽边缘进行自由伸缩
    setFixedSize(400, 400);

    // 设置窗口的位置
    // 以父控件的左上角为坐标原点
    // 没有父控件,就以屏幕的左上角作为坐标原点
    move(100, 100);
}

Qt坐标系如下图所示。

497279-20210304194034450-1153331879.png

添加子控件

#include <QPushButton>

// 创建按钮
QPushButton *btn = new QPushButton;
// 设置按钮的文字
btn->setText("登录");
// 设置父控件为当前窗口
btn->setParent(this);
// 设置按钮的位置和大小
btn->move(50, 50);
btn->resize(100, 50);

// 创建第2个按钮
new QPushButton("注册", this);

new出来的Qt控件是不需要程序员手动delete的,Qt内部会自动管理内存:当父控件销毁时,会顺带销毁子控件。

信号与槽

基本使用

  • 信号(Signal):比如点击按钮就会发出一个点击信号
  • 槽(Slot):一般也叫槽函数,是用来处理信号的函数
  • 官方文档参考:Signals & Slots

497279-20210304194036232-2086622242.png 上图中的效果是:

  • Object1发出信号signal1,交给Object2的槽slot1、slot2去处理

    • Object1是信号的发送者,Object2是信号的接收者
  • Object1发出信号signal2,交给Object4的槽slot1去处理

    • Object1是信号的发送者,Object4是信号的接收者
  • Object3发出信号signal1,交给Object4的槽slot3去处理

    • Object3是信号的发送者,Object4是信号的接收者
  • 1个信号可以由多个槽进行处理,1个槽可以处理多个信号

通过connect函数可以将信号的发送者信号信号的接收者连接在一起。

connect(信号的发送者, 信号, 信号的接收者, 槽);

// 比如点击按钮,关闭当前窗口
// btn发出clicked信号,就会调用this的close函数
connect(btn, &QPushButton::clicked, this, &MainWindow::close);

// 可以通过disconnect断开连接
disconnect(btn, &QPushButton::clicked, this, &MainWindow::close);

自定义信号与槽

信号的发送者和接收者都必须继承自QObject,Qt中的控件最终都是继承自QObject,比如QMainWindow、QPushButton等。

信号的发送者

  • sender.h

    • Q_OBJECT用以支持自定义信号和槽
    • 自定义的信号需要写在signals: 下面
    • 自定义的信号只需要声明,不需要实现
#ifndef SENDER_H
#define SENDER_H

#include <QObject>

class Sender : public QObject {
    Q_OBJECT
public:
    explicit Sender(QObject *parent = nullptr);

    // 自定义信号
signals:
    void exit();
};

#endif // SENDER_H
  • sender.cpp
#include "sender.h"

Sender::Sender(QObject *parent) : QObject(parent) {

}

信号的接收者

  • receiver.h

    • 自定义的槽建议写在public slots: 下面
#ifndef RECEIVER_H
#define RECEIVER_H

#include <QObject>

class Receiver : public QObject {
    Q_OBJECT
public:
    explicit Receiver(QObject *parent = nullptr);

    // 自定义槽
public slots:
    void handleExit();
};

#endif // RECEIVER_H
  • receiver.cpp
#include "receiver.h"
#include <QDebug>

Receiver::Receiver(QObject *parent) : QObject(parent) {

}

// 实现槽函数,编写处理信号的代码
void Receiver::handleExit() {
    qDebug() << "Receiver::handleExit()";
}

连接

  • mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
};
#endif // MAINWINDOW_H
  • mainwindow.cpp
#include "mainwindow.h"
#include "sender.h"
#include "receiver.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) {
    // 创建对象
    Sender *sender = new Sender;
    Receiver *receiver = new Receiver;

    // 连接
    connect(sender,
            &Sender::exit,
            receiver,
            &Receiver::handleExit);

    // 发出信号
    // 最终会调用Receiver::handleExit函数
    emit sender->exit();
        
    // 销毁对象
    delete sender;
    delete receiver;
}

MainWindow::~MainWindow() {

}

参数和返回值

信号与槽都可以有参数和返回值:

  • 发信号时的参数会传递给槽
  • 槽的返回值会返回到发信号的位置
// 自定义信号
signals:
    int exit(int a, int b);

// 自定义槽
public slots:
    int handleExit(int a, int b);

int Receiver::handleExit(int a, int b) {
    // Receiver::handleExit() 10 20
    qDebug() << "Receiver::handleExit()" << a << b;
    return a + b;
}

// 发出信号
int a = emit sender->exit(10, 20);
// 30
qDebug() << a;

连接2个信号

比如下图,连接了Object 1的Signal 1A和Object 2的Signal 2A

  • 当Object 1发出Signal 1A时,会触发Slot X、Slot Y
  • 当Object 2发出Signal 2A时,只会触发Slot Y,而不会触发Slot X

497279-20210304194037999-1645593613.jpg 可以利用connect函数连接2个信号

  • 当sender发出exit信号时,sender2会发出exit2信号
  • 当sender2发出exit2信号时,sender并不会发出exit信号
connect(sender, &Sender::exit, sender2, &Sender2::exit2);

Lambda

也可以直接使用Lambda处理信号。

connect(sender, &Sender::exit, []() {
    qDebug() << "lambda handle exit";
});

ui文件

如果你的控件是通过ui文件生成的,连接槽函数的步骤会更加简单。

首先建议给按钮们起个有意义的变量名,比如分别叫做:loginButtonregisterButton

497279-20210312120532888-1172297687.png 对着登录按钮右键,选择转为槽

497279-20210312120732642-1761590808.png 选择clicked信号,然后OK

497279-20210312120734734-1927951513.png 此时,Qt Creator已经帮你自动生成了槽函数的声明和实现,当我们点击登录按钮时,就会调用这个函数。

class MainWindow : public QMainWindow {
    Q_OBJECT
private slots:
    // 槽函数的声明
    void on_loginButton_clicked();
};

// 槽函数的实现
void MainWindow::on_loginButton_clicked() {
    qDebug() << "on_loginButton_clicked";
}	

其实,认真观察函数名可以发现一个规律,函数名的命名规则是:on控件的变量名事件名

于是,我们可以尝试编写以下代码。

class MainWindow : public QMainWindow {
    Q_OBJECT
private slots:
    // 槽函数的声明
    void on_registerButton_clicked();
};

// 槽函数的实现
void MainWindow::on_registerButton_clicked() {
    qDebug() << "on_registerButton_clicked";
}	

然后,你点击一下注册按钮,会发现成功调用了MainWindow::on_registerButton_clicked函数。于是得知:ui文件中的控件会自动跟符合命名规则的槽函数建立连接

最后,再提示一个知识点:ui文件中的控件可以在代码中通过ui->变量名访问。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);

    // 通过ui->访问ui文件中的2个按钮
    ui->loginButton->setFixedSize(100, 30);
    ui->registerButton->setFixedSize(100, 30);
}