音视频开发-02-Qt开发基础

121 阅读7分钟

.pro文件的配置

1.1 跨平台配置

总所周知,MacWindows平台存在差异,FFmpeg库在其上的位置也不相同,这样就会导致.pro文件无法跨平台使用。

# windows
INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
 
# mac
INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include

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

# windows
win32:INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
win32:LIBS += -LF:/Dev/ffmpeg-6.0_2/lib \
              -lavcodec \
              -lavdevice \
              -lavfilter \
              -lavformat \
              -lavutil \
              -lpostproc \
              -lswscale \
              -lswresample
 
# mac
macx:INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include
macx:LIBS += -L/opt/homebrew/Cellar/ffmpeg/6.0_2/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample \
            -lavresample
 
# linux
# linux:INCLUDEPATH += ...
# linux:LIBS += ...

实际开发中,针对每个平台的配置可能比较多,可以使用大括号来简化区分对应配置。

# windows
win32 {
    INCLUDEPATH += F:/Dev/ffmpeg-6.0_2/include
    LIBS += -LF:/Dev/ffmpeg-6.0_2/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample
}
 
# mac
# mac {} / mac: {} / macx {} / macx: {}都可以
macx {
    INCLUDEPATH += /opt/homebrew/Cellar/ffmpeg/6.0_2/include
    LIBS += -L/opt/homebrew/Cellar/ffmpeg/6.0_2/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample \
            -lavresample
}

1.2 自定义变量

为了更好的开发和阅读代码,可以将公共的信息抽取成变量,然后通过$${变量}来访问。

# mac
macx {
    // 抽取 FFmpeg 路径
    FFMPEG_PATH = /opt/homebrew/Cellar/ffmpeg/6.0_2
    INCLUDEPATH += $${FFMPEG_PATH}/include
    LIBS += -L$${FFMPEG_PATH}/lib \
            -lavcodec \
            -lavdevice \
            -lavfilter \
            -lavformat \
            -lavutil \
            -lpostproc \
            -lswscale \
            -lswresample \
            -lavresample
}

1.3 读取环境变量

.pro文件中,通过message($$(变量))可以读取系统环境变量的值,通过message($${变量})可以读取用户设置变量的值,修改完.pro文件后,重新编译,在概要信息处就能查看打印结果。

image.png

2 控件的基本使用

我们先简单创建个项目,不使用UI文件,来简单了解下Qt控件的使用。

image.png

2.1 窗口设置

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{

    // 设置窗口标题
    setWindowTitle("主窗口");

    // 设置窗口大小,可以通过拖拽边缘进行自由伸缩
    // resize(400, 400);
    
    // 设置窗口的固定大小,不能拖拽伸缩
    setFixedSize(400, 400);
    
    // 设置窗口位置,以父控件的左上角为坐标原点,没有父控件,就以屏幕左上角为坐标原点
    move(100, 100);
    
}

2.2 添加子控件

#include <QPushButton>

    // 创建按钮,C++中new QPushButton()的小括号可以省略,因为没有传参数
    QPushButton *loginBtn = new QPushButton;
    // 设置按钮的文字
    loginBtn->setText("登录");
    // 设置按钮父控件
    loginBtn->setParent(this);
    // 设置按钮的位置和大小
    loginBtn->move(50, 50);
    loginBtn->resize(100, 50);

    // 创建第二个按钮
    QPushButton *regisBtn = new QPushButton("注册", this);
    regisBtn->move(200, 50);
    regisBtn->resize(100, 50);
  • 此处new出来的Qt控件不需要程序员手动delete,因为Qt内部会自动管理控件的内存:当父控件销毁时,会顺带销毁子控件。

3 信号与槽

3.1 基本使用

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

image.png

上图中的效果是:

  • Object1发出信号signal1,交给Object2的槽slot1、slot2去处理。
    • Object1是信号的发送者,Object2是信号的接收者。
  • Object1发出信号signal2,交给Object4的槽slot1去处理。
    • Object1是信号的发送者,Object4是信号的接收者。
  • Object3发出信号signal1,交给Object4的槽slot3去处理。
    • Object3是信号的发送者,Object4是信号的接收者。
  • 1个信号可以由多个槽进行处理,1个槽可以处理多个信号。

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

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

#include "mainwindow.h"
#include "QPushButton"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 设置窗口大小
    resize(500, 500);
    QPushButton *btn = new QPushButton;
    btn->setText("关闭");
    btn->setFixedSize(100, 50);
    btn->move(200, 200);
    btn->setParent(this);
    
    // 比如点击按钮,关闭当前窗口
    // btn发出clicked信号,就会调用this的close函数
    // clicked信号为QPushButton父类QAbstractButton自带
    // close槽函数为MainWindow父类的父类QWidget自带
    connect(btn, &QPushButton::clicked, this, &MainWindow::close);
    // 可以通过disconnect断开连接
    //disconnect(btn, &QPushButton::clicked, this, &MainWindow::close);

}

MainWindow::~MainWindow() {}

3.2 自定义信号和槽

Qt中信号的发送者和接收者都必须继承自QObjectQt中的控件最终都是继承自QObject,比如QMainWindowQPushButton等,也就是说这套API的基类就是QObject,就像OC中的NSObject

3.2.1 信号的发送者

自定义一个继承自QObject的信号的发送者类为Sender

  • 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}
{}

3.2.2 信号的接收者

自定义一个继承自QObject的信号的接收者类为Receiver

  • 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()";
}

3.2.3 连接

MainWindow中实例化SenderReceiver的对象,连接信号与槽。

  • 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() {}

3.2.4 参数和返回值

信号与槽都如普通函数一样可以有参数和返回值:

  • 发信号时的参数会传递给槽。
  • 槽的返回值会返回到发信号的位置。
// 自定义信号
signals:
    int exit(int a, int b);
    
// 自定义槽
public slots:
    int handleExit(int a, int b);
    
int Receiver::handleExit(int a, int b)
{
    qDebug() << "Receiver::handleExit()" << a << b;
    return a + b;
}

// 发出信号
int a = emit sender->exit(5, 10);

// 输出 15
qDebug() << a;

⚠️⚠️⚠️⚠️⚠️注意:信号的参数个数必须大于等于槽的参数个数。比如下面的写法是错误的:

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

3.3 连接两个信号

两个信号可以进行连接,比如下图,连接了Object 1Signal 1AObject 2Signal 2A

image.jpeg

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

利用connect函数可以连接两个信号。

connect(sender,
        &Sender::exit,
        sender2,
        &Sender2::exit2);

信号的连接是单向的:

  • sender发出exit信号时,sender2会发出exit2信号。
  • sender2发出exit2信号时,sender并不会发出exit信号。

3.4 Lambda

除了定义槽函数处理信号外,还可以直接使用Lambda函数(匿名函数)来处理信号。类似block闭包

connect(sender, &Sender::exit, [](int a, int b) ->int {
    qDebug() << "Lambda handle exit";
    return a + b;
});
  • []是捕获说明符,()是参数列表,->int是返回类型,如果没有返回类型,则可以省略,{}是函数体部分。

3.5 UI文件

如果是通过UI文件生成的控件,那么连接槽函数的步骤也比较简单。

首先,给按钮设定对象名称,如下图的loginBtnregisBtn

image.png

然后,右键点击登录按钮,选择转到槽

image.png

选择clicked信号,点击确定image.png

至此,Qt Creator就会帮我们自动生成槽函数的声明和实现,当我们点击登录按钮时,就会调用这个函数。

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

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

认真观察Qt Creator生成的槽函数名就会发现其命名规则是:on_控件的对象名称_事件名

那么,我们根据命名规则,为注册按钮编写槽函数。

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

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

运行程序,点击注册按钮,会发现成功调用了MainWindow::on_regisBtn_clicked()函数。 于是可知:UI文件中的控件会自动跟符合命名规则的槽函数建立连接

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

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    // 通过ui->访问ui文件中的2个按钮
    ui->loginBtn->setFixedSize(100,50);
    ui->regisBtn->setFixedSize(100, 50);
}