📑 目录
- [📡 1. Qt 网络部分](#📡 1. Qt 网络部分)
- [🎯 1.1 UDP Socket](#🎯 1.1 UDP Socket)
- [📌 1.1.1 核心 API 概览](#📌 1.1.1 核心 API 概览)
- [🖥️ 1.1.2 回显服务器](#🖥️ 1.1.2 回显服务器)
- [💻 1.1.3 回显客户端](#💻 1.1.3 回显客户端)
- [🔗 1.2 TCP Socket](#🔗 1.2 TCP Socket)
- [📌 1.2.1 核心 API 概览](#📌 1.2.1 核心 API 概览)
- [🖥️ 1.2.2 回显服务器](#🖥️ 1.2.2 回显服务器)
- [💻 1.2.3 回显客户端](#💻 1.2.3 回显客户端)
- [🌐 1.3 HTTP Client](#🌐 1.3 HTTP Client)
- [📌 1.3.1 核心 API](#📌 1.3.1 核心 API)
- [🖥️ 1.3.2 代码示例](#🖥️ 1.3.2 代码示例)
- [🧩 1.4 其他模块](#🧩 1.4 其他模块)
- [🎯 1.1 UDP Socket](#🎯 1.1 UDP Socket)
- [🎵 2. Qt 音视频](#🎵 2. Qt 音视频)
- [🔊 2.1 Qt 音频](#🔊 2.1 Qt 音频)
- [📌 2.1.1 核心 API 概览](#📌 2.1.1 核心 API 概览)
- [🖥️ 2.1.2 示例](#🖥️ 2.1.2 示例)
- [🎬 2.2 Qt 视频](#🎬 2.2 Qt 视频)
- [📌 2.2.1 核心 API 概览](#📌 2.2.1 核心 API 概览)
- [🖥️ 2.2.2 示例](#🖥️ 2.2.2 示例)
- [🔊 2.1 Qt 音频](#🔊 2.1 Qt 音频)
- [📝 小总结](#📝 小总结)
- [🎯 经典面试题](#🎯 经典面试题)
📡 1. Qt 网络部分
💡 笔记:网络编程是 Qt 开发中非常核心的部分。实际开发中,HTTP 协议往往比 TCP/UDP 使用更多一些。HTTP 协议本质也就是通过 TCP 实现的,实现一个 HTTP 客户端/服务器,本质就是基于 TCP socket 进行封装。Qt 只是提供了 HTTP 的客户端接口,而没有提供服务器端的接口。
📌写在前面 :使用Qt网络编程的
API,需要先在.pro文件中添加network模块,之前的文章中的Qt的各种组件,各种内容,都是包含在QtCore模块中的 - 默认包含。**此处只需要在
.pro文件中的QT += core gui network上加上network模块即### 🎯 1.1 UDP Socket别#### 📌 1.1.1 核心 API 概览et
📌 1.1.1 核心 API 概览
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议。在 Qt 中,主要通过 QUdpSocket 类来实现 UDP 通信。
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
bind(const QHostAddress&, quint16 port) |
方法 | 绑定指定的地址和端口号 | bind |
writeDatagram(const QNetworkDatagram&) |
方法 | 发送一个 UDP 数据报 | sendto |
readyRead |
信号 | 有数据到达并准备就绪时触发 | 无(类似于 IO 多路复用中的通知机制) |
receiveDatagram() |
方法 | 接收一个 UDP 数据报 | recvfrom |
💡 笔记 :端口号本质就是一个 2 字节的无符号整数,
quint16本质就是一个unsigned short。
QNetworkDatagram 表⽰⼀个 UDP 数据报.
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16) |
构造函数 | 通过 QByteArray、目标 IP 地址、目标端口号构造一个 UDP 数据报,通常用于发送数据时 |
无 |
data() |
方法 | 获取数据报内部持有的数据,返回 QByteArray |
无 |
senderAddress() |
方法 | 获取数据报中包含的对端的 IP 地址 | 无(recvfrom 包含了该功能) |
senderPort() |
方法 | 获取数据报中包含的对端的端口#### 🖥️ 1.1.2 回显服务器该功能) |
🖥️ 1.1.2 回显服务器
1) 创建界面 :包含一个 QListWidget,用于显示收到的数据。

2) 创建 QUdpSocket 并初始化
修改 widget.h,添加 QUdpSocket 指针成员:
cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 创建 socket 成员
QUdpSocket* socket;
};
修改 widget.cpp,实例化 QUdpSocket 并进行初始化:
✏️注意:⼀般来说, 要先连接信号槽, 再绑定端⼝。
如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了。
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1.先设置服务器窗口标题
this->setWindowTitle("服务器");
//2.实例化socket
socket = new QUdpSocket(this);
//3.先连接信号和槽,防止因先绑定端口而没有相应的处理动作导致错过数据
connect(socket,&QUdpSocket::readDatagram,this,&Widget::processRequest);
//4.绑定端口号
bool ret = socket->bind(QHostAddress::Any,8888);
if(!ret)
{
QMessageBox::critical(nullptr,"服务器启动出错",socket->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
3) 实现 processRequest 函数
- 读取请求并解析
- 根据请求计算响应
- 把响应写回到客⼾端
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#include<QNetworkDatagram>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1.先设置服务器窗口标题
this->setWindowTitle("服务器");
//2.实例化socket
socket = new QUdpSocket(this);
//3.先连接信号和槽,防止因先绑定端口而没有相应的处理动作导致错过数据
connect(socket,&QUdpSocket::readyRead,this,&Widget::processRequest);
//4.绑定端口号
bool ret = socket->bind(QHostAddress::Any,8888);
if(!ret)
{
QMessageBox::critical(nullptr,"服务器启动出错",socket->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::processRequest()
{
//1.读取请求
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
//2.根据请求计算响应
const QString& response = process(request);
//3.把响应写回到客户端 - 构建一个响应数据包
QNetworkDatagram responseDatagram(response.toUtf8(),
requestDatagram.senderAddress(),
requestDatagram.senderPort());
socket->writeDatagram(responseDatagram);
//显示打印日志
QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
QString Widget::process(const QString &request)
{
}
4) 实现 process 函数
由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容:
cpp
QString Widget::process(const QString& request)
{
return request;
}
💡 笔记:"根据请求处理响应"是服务器开发中的最核心的步骤。一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的。
此时,服务器程序编写完毕。但是直接运行还看不出效果,还需要搭配客户端来使用。
💻 1.1.3 回显客户端
1) 创建界面 :包含一个 QLineEdit、QPushButton、QListWidget。
- 先使用水平布局把
QLineEdit和QPushButton放好,并设置这两个控件的垂直方向的sizePolicy为Expanding。 - 再使用垂直布局把
QListWidget和上面的水平布局放好。 - 设置垂直布局的
layoutStretch为5, 1。

2) 在 mainwindow.cpp 中,先创建两个全局常量,表示服务器的 IP 和端口
cpp
//先定义两个常量,表示服务器的IP和端口号
const QString& IP = "127.0.0.1";
const quint16 PORT = 8888;
3) 创建 QUdpSocket 成员
修改 mainwindow.h,定义成员:
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include<QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
private:
Ui::MainWindow *ui;
//创建一个socket
QUdpSocket* socket;
//先定义两个常量,表示服务器的IP和端口号
const QString SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8888;
};
#endif // MAINWINDOW_H
修改 mainwindow.cpp,初始化 socket:
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//1.设置窗口名称
this->setWindowTitle("客户端");
//2.实例化socket
socket = new QUdpSocket(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
4) 给发送按钮 slot 函数,实现发送请求
cpp
void MainWindow::on_pushButton_clicked()
{
//1.获取到输入框的内容
const QString& text = ui->lineEdit->text();
//2.构造请求数据
QNetworkDatagram requestDatagram(text.toUtf8(),
QHostAddress(IP),
PORT);
//3.发送请求
socket->writeDatagram(requestDatagram);
//4.消息添加到列表框中
ui->listWidget->addItem("客户端说:" + text);
//5.清空输入框
ui->lineEdit->setText("");
}
5) 再次修改 MainWindow 的构造函数,通过信号槽,来处理服务器的响应
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QLineEdit>
#include<QNetworkDatagram>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//1.设置窗口名称
this->setWindowTitle("客户端");
//2.实例化socket
socket = new QUdpSocket(this);
connect(socket,&QUdpSocket::readyRead,this,[=](){
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem(QString("服务器说:") + response);
});
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
//1.获取到输入框的内容
const QString& text = ui->lineEdit->text();
//2.构造请求数据
QNetworkDatagram requestDatagram(text.toUtf8(),
QHostAddress(IP),
PORT);
//3.发送请求
socket->writeDatagram(requestDatagram);
//4.消息添加到列表框中
ui->listWidget->addItem("客户端说:" + text);
//5.清空输入框
ui->lineEdit->setText("");
}
最终执行效果 :启动多个客户端都可以正常工作。

🔗 1.2 TCP Socket
📌 1.2.1 核心 API 概览
核心类是两个:QTcpServer 和 QTcpSocket。
QTcpServer 用于监听端口,和获取客户端连接。
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
listen(const QHostAddress&, quint16 port) |
方法 | 绑定指定的地址和端口号,并开始监听 | bind 和 listen |
nextPendingConnection() |
方法 | 从系统中获取到一个已经建立好的 tcp 连接。返回一个 QTcpSocket,表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信 |
accept |
newConnection |
信号 | 有新的客户端建立连接好之后触发 | 无(但是类似于 IO 多路复用中的通知机制) |
QTcpSocket 用于客户端和服务器之间的数据交互。
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
readAll() |
方法 | 读取当前接收缓冲区中的所有数据。返回 QByteArray 对象 |
read |
write(const QByteArray&) |
方法 | 把数据写入 socket 中 | write |
deleteLater |
方法 | 暂时把 socket 对象标记为无效。Qt 会在下个事件循环中析构释放该对象 | 无(但是类似于"半自动化的垃圾回收") |
readyRead |
信号 | 有数据到达并准备就绪时触发 | 无(但是类似于 IO 多路复用中的通知机制) |
disconnected |
信号 | 连接断开时触发 | 无(但是类似于 IO 多路复用中的通知机制) |
💡 笔记 :
QByteArray用于表示一个字节数组,可以很方便的和QString进行相互转换。例如:使用QString的构造函数即可把QByteArray转成QString;使用QString的toUtf8函数即可把QString转成QByteArray。读写都是使用QByteArray,与 UDP 不同。
💡 笔记 :事件循环 ,简单理解,可以认为是 Qt 程序内部,带有一个"生物钟"这样的东西,周期性的执行一些逻辑。
deleteLater函数这个操作,不是立即销毁clientSocket,而是告诉 Qt,在下一轮事件循环中,再进行上述的销毁操作。槽函数都是在事件循环中执行的,进入下一轮事件循环,意味着上一轮事件循环肯定结束了,也意味着当前的槽函数肯定是执行完毕了。
🖥️ 1.2.2 回显服务器
1) 创建界面 :包含一个 QListWidget,用于显示收到的数据。
2) 创建 QTcpServer 并初始化
修改 widget.h,添加 QTcpServer 指针成员:
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include<QTcpSocket>
#include <QWidget>
#include<QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void processConnection();
QString process(QString request);
private:
Ui::Widget *ui;
//创建QTcpServer服务器指针
QTcpServer* tcpServer;
};
#endif // WIDGET_H
修改 widget.cpp,实例化 QTcpServer 并进行后续初始化操作:
- 设置窗口标题
- 实例化 TCP server(父元素设为当前控件,会在父元素销毁时被一起销毁)
- 通过信号槽,处理客户端建立的新连接
- 监听端口
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QHostAddress>
#include<QMessageBox>
#include<QListWidget>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1.设置窗口标题
this->setWindowTitle("服务器");
//2.实例化TcpServer
tcpServer = new QTcpServer(this);
//先绑定消息到来时的处理函数
//3.通过信号槽,处理客户端建立的新链接
connect(tcpServer,&QTcpServer::newConnection,this,&Widget::processConnection);
//4.监听端口 - 相当于bind和listen
bool ret = tcpServer->listen(QHostAddress::Any,8888);
if(!ret)
{
//消息对话框QMessageBox
QMessageBox::critical(nullptr,"服务器启动失败!",tcpServer->errorString());
exit(1);
}
}
3) 继续修改 widget.cpp,实现处理连接的具体方法 processConnection
- 获取到新的连接对应的 socket
- 通过信号槽,处理收到请求的情况
- 通过信号槽,处理断开连接的情况
cpp
void Widget::processConnection()
{
//获取到新链接对应的socket
QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
QString log = QString("[") + clientSocket->peerAddress().toString() +
":" + QString::number(clientSocket->peerPort()) + "] 客户端上线!";
ui->listWidget->addItem(log);
//2.通过信号槽,处理到请求的情况 - 缓冲区中的数据就绪触发信号
connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
//a)读取请求
QString request = clientSocket->readAll();
//b)根据请求处理响应
const QString& response = process(request);
//c)将响应写回客户端 - 将字符串转化为QByteArray(字节数组)
clientSocket->write(response.toUtf8());
QString log = QString("[") +clientSocket->peerAddress().toString() + ":"
+QString::number(clientSocket->peerPort()) +"] req:"
+ request +",resp:" + response;
ui->listWidget->addItem(log);
});
//3.通过信号槽,处理连接断开的情况
connect(clientSocket,&QTcpSocket::disconnected,this,[=]()
{
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + clientSocket->peerPort() + "] 客户端已经下线!";
//删除clientSocket - 断开的连接会在下一个时间循环启动时清除掉
clientSocket->deleteLater();
});
}
4) 实现 process 方法,实现根据请求处理响应
由于我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容:
cpp
QString Widget::process(const QString &request)
{
return request;
}
💡 笔记:"根据请求处理响应"是服务器开发中的最核心的步骤。一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的。
💻 1.2.3 回显客户端
1) 创建界面 :包含一个 QLineEdit、QPushButton、QListWidget。
- 先使用水平布局把
QLineEdit和QPushButton放好,并设置这两个控件的垂直方向的sizePolicy为Expanding。 - 再使用垂直布局把
QListWidget和上面的水平布局放好。 - 设置垂直布局的
layoutStretch为5, 1。
2) 创建 QTcpSocket 并实例化
修改 widget.h,创建成员:
cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
// 新增 QTcpSocket
QTcpSocket* socket;
};
修改 widget.cpp,对 QTcpSocket 进行实例化:
- 设置窗口标题
- 实例化 socket 对象(父元素设为当前控件,会在父元素销毁时被一起销毁)
- 和服务器建立连接
- 等待并确认连接是否出错
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//1.设置窗口标题
this->setWindowTitle("客户端");
//2.实例化tcp客户端
tcpClient = new QTcpSocket(this);
//3.和tcp服务器建立连接
tcpClient->connectToHost("127.0.0.1",8888);
//4.等待并确认连接是否出错
if(!tcpClient->waitForConnected())
{
QMessageBox::critical(nullptr,"连接服务器出错!",tcpClient->errorString());
exit(1);
}
}
3) 修改 widget.cpp,给按钮增加点击的 slot 函数,实现发送请求给服务器
cpp
void Widget::on_pushButton_clicked()
{
// 获取输入框的内容
const QString& text = ui->lineEdit->text();
// 清空输入框内容
ui->lineEdit->setText("");
// 把消息显示到界面上
ui->listWidget->addItem(QString("客户端说: ") + text);
// 发送消息给服务器
socket->write(text.toUtf8());
}
4) 修改 widget.cpp 中的 Widget 构造函数,通过信号槽,处理收到的服务器的响应
cpp
// 处理服务器返回的响应.
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});
先启动服务器,再启动客户端(可以启动多个),最终执行效果:由于我们使用信号槽处理同一个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响了。

🌐 1.3 HTTP Client
进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议。
- 通过 HTTP 从服务器获取数据。
- 通过 HTTP 向服务器提交数据。
💡 笔记:实际开发过程中,HTTP 获取到的数据,也不一定是 HTML,更大的可能是客户端和服务器开发约定好交互的数据格式。按照约定的格式,客户端拿到之后,进行解析,并展示到界面上。
📌 1.3.1 核心 API
关键类主要是三个:QNetworkAccessManager、QNetworkRequest、QNetworkReply。
QNetworkAccessManager 提供了 HTTP 的核心操作。
| 方法 | 说明 |
|---|---|
get(const QNetworkRequest&) |
发起一个 HTTP GET 请求。返回 QNetworkReply 对象。 |
post(const QNetworkRequest&, const QByteArray&) |
发起一个 HTTP POST 请求。返回 QNetworkReply 对象。 |
QNetworkRequest 表示一个 HTTP 请求(不含 body)。
如果需要发送一个带有 bod
QNetworkAccessManager的post方法中通过单独的参数来传入 body。
| 方法 | 说明 |
|---|---|
QNetworkRequest(const QUrl&) |
通过 URL 构造一个 HTTP 请求。 |
setHeader(QNetworkRequest::KnownHeaders header, const QVariant& value) |
设置请求头。 |
💡 笔记 :
QVariant表示一个类型可变的值,类似于 C 语言中的void*。
其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值:
| 取值 | 说明 |
|---|---|
ContentTypeHeader |
描述 body 的类型。 |
ContentLengthHeader |
描述 body 的长度。 |
LocationHeader |
用于重定向报文中指定重定向地址。(响应中使用,请求用不到) |
CookieHeader |
设置 cookie。 |
UserAgentHeader |
设置 User-Agent。 |
QNetworkReply 表示一个 HTTP 响应。这个类同时也是 QIODevice 的子类。
| 方法 | 说明 |
|---|---|
error() |
获取出错状态。 |
errorString() |
获取出错原因的文本。 |
readAll() |
读取响应 body。 |
header(QNetworkRequest::KnownHeaders header) |
读取响应指定 header 的值。 |
此外,QNetworkReply 还有一个重要的信号 finished,会在客户端收到完整的响应数据之后触发。
🖥️ 1.3.2 代码示例
给服务器发送一个 GET 请求。
1) 创建界面 :包含一个 QLineEdit、QPushButton。
- 先使用水平布局把
QLineEdit和QPushButton放好,并设置这两个控件的垂直方向的sizePolicy为Expanding。 - 再使用垂直布局把
QPlainTextEdit和上面的水平布局放好。(QPlainTextEdit的readOnly设为true) - 设置垂直布局的
layoutStretch为5, 1。
💡 笔记 :此处建议使用
QPlainTextEdit而不是QTextEdit。主要因为QTextEdit要进行富文本解析,如果得到的 HTTP 响应体积很大,就会导致界面渲染缓慢甚至被卡住。
2) 修改 widget.h,创建 QNetworkAccessManager 属性
cpp
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
// 新增属性
QNetworkAccessManager* manager;
};
3) 修改 widget.cpp,创建实例
cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化属性
manager = new QNetworkAccessManager(this);
}
4) 编写按钮的 slot 函数,实现发送 HTTP 请求功能
cpp
void Widget::on_pushButton_clicked()
{
// 1. 获取到输入框中的 URL, 构造 QUrl 对象
QUrl url(ui->lineEdit->text());
// 2. 构造 HTTP 请求对象
QNetworkRequest request(url);
// 3. 发送 GET 请求
QNetworkReply* response = manager->get(request);
// 4. 通过信号槽来处理响应
connect(response, &QNetworkReply::finished, this, [=]() {
if (response->error() == QNetworkReply::NoError) {
// 响应正确
QString html(response->readAll());
ui->plainTextEdit->setPlainText(html);
// qDebug() << html;
} else {
// 响应出错
ui->plainTextEdit->setPlainText(response->errorString());
}
response->deleteLater();
});
}
执行程序,观察效果。

发送 POST 请求代码也是类似,使用 manager->post() 即可。此处不再演示。
🧩 1.4 其他模块
Qt 中还提供了 FTP、DNS、SSL 等网络相关的组件工具,感兴趣的读者可以自行查看Qt中的参考手册或者借助于AI进行学习。
🎵 2. Qt 音视频
🔊 2.1 Qt 音频
在 Qt 中,音频主要是通过 QSound 类来实现。但是需要注意的是 QSound 类只支持播放 wav 格式的音频文件。也就是说如果想要添加音频效果,那么首先需要将非 wav 格式的音频文件转换为 wav 格式。
💡 笔记 :使用
QSound类时,需要添加模块:multimedia。
📌 2.1.1 核心 API 概览
| 方法 | 说明 |
|---|---|
play() |
开始或继续播放当前源。 |
🖥️ 2.1.2 示例
cpp
/********************************* SoundTest.pro *********************************/
QT += core gui multimedia //添加音频模块
cpp
/********************************* widget.cpp *********************************/
#include "widget.h"
#include "ui_widget.h"
#include <QSound> //添加音频头文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//实例化对象
QSound *sound = new QSound(":/sound.wav",this);
connect(ui->btn,&QPushButton::clicked,[=](){
sound->play(); //播放
});
}
Widget::~Widget()
{
delete ui;
}
🎬 2.2 Qt 视频
在 Qt 中,视频播放的功能主要是通过 QMediaPlayer 类和 QVideoWidget 类来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets。
📌 2.2.1 核心 API 概览
| 方法 | 说明 |
|---|---|
setMedia() |
设置当前媒体源。 |
setVideoOutput() |
将 QVideoWidget 视频输出附加到媒体播放器。如果媒体播放器已经附加了视频输出,将更换一个新的。 |
🖥️ 2.2.2 示例
首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块。
cpp
/************************player.pro*******************************/
QT += core ui
# 添加视频模块
QT += multimedia
QT += multimediawidgets
/********************************* widget.h *********************************/
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QHBoxLayout> //水平布局
#include <QVBoxLayout> //垂直布局
#include <QVideoWidget> //显示视频
#include <QMediaPlayer> //播放声音
#include <QPushButton> //按钮
#include <QStyle> //设置图标
#include <QFileDialog> //选择文件/文件夹
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void chooseVideo();
private:
QMediaPlayer *mediaPlayer;
QVideoWidget *videoWidget;
QVBoxLayout *vbox;
//创建两个按钮:选择视频按钮和开播放按钮
QPushButton *chooseBtn,*playBtn;
};
#endif // WIDGET_H
cpp
/********************************* widget.cpp *********************************/
#include "widget.h"
#include <QMediaPlayer>
#include <QSlider>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//对象实例化
mediaPlayer = new QMediaPlayer(this);
videoWidget = new QVideoWidget(this);
//设置播放画面的窗口
videoWidget->setMinimumSize(600,600);
//实例化窗口布局---垂直布局
this->vbox = new QVBoxLayout(this);
this->setLayout(this->vbox);
//实例化选择视频按钮
chooseBtn = new QPushButton("选择视频",this);
//实例化播放按钮
playBtn = new QPushButton(this);
//设置图标代替文件
playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
//实例化一个水平布局,将以上控件放入水平布局中
QHBoxLayout *hbox = new QHBoxLayout;
//添加控件
hbox->addWidget(chooseBtn);
hbox->addWidget(playBtn);
//将播放窗口和水平布局都添加到垂直布局中
vbox->addWidget(videoWidget);
//布局中添加布局
vbox->addLayout(hbox);
//将选择视频对应的按钮和槽函数进行关联
connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo);
}
void Widget::chooseVideo()
{
//选择视频,返回一个播放视频的名字
QString name = QFileDialog::getSaveFileName(this,"选择视频",".","WMV(*.wmv)");
//设置媒体声音
mediaPlayer->setMedia(QUrl(name));
//输出视频画面
mediaPlayer->setVideoOutput(videoWidget);
//播放
mediaPlayer->play();
}
Widget::~Widget()
{
}
📝 小总结
本章围绕 Qt 网络编程与音视频模块展开,核心要点如下:
- 事件机制 :事件是信号槽的底层机制,信号槽是事件的进一步封装。绝大多数交互通过信号槽完成,特殊场景需要重写事件处理函数(如
enterEvent、mousePressEvent、keyPressEvent等)。 - 事件分发器与事件过滤器 :事件分发器通过重写
bool event(QEvent* e)实现拦截;事件过滤器则是在事件分发之前做更高级的拦截,通过installEventFilter安装、重写eventFilter实现。 - 文件操作 :
QFile负责文件读写,QFileInfo获取文件信息,QSaveFile安全保存文件,QTemporaryFile创建临时文件。 - 多线程 :通过继承
QThread并重写run()实现,启动线程调用start(),通过信号与主线程通信。 - UDP 通信 :无连接协议,使用
QUdpSocket,通过bind绑定端口、writeDatagram发送、receiveDatagram接收。 - TCP 通信 :面向连接的可靠协议,服务器用
QTcpServer监听、nextPendingConnection获取连接;客户端用QTcpSocket连接服务器,通过readyRead信号读取数据。 - HTTP 通信 :基于 TCP 封装,Qt 提供客户端接口(
QNetworkAccessManager、QNetworkRequest、QNetworkReply),不提供服务器端接口。 - 音视频 :
QSound播放 wav 音频;QMediaPlayer+QVideoWidget播放视频。
🎯 经典面试题
1. 信号槽与事件的区别是什么?
信号槽是事件的进一步封装,事件是信号槽的底层机制。用户操作会产生事件,Qt 将操作系统的事件机制封装后进一步封装为信号槽。绝大多数用户交互通过信号槽完成,特殊情况下需要重写事件处理函数。
2. 事件分发器和事件过滤器有什么区别?
事件分发器通过重写 bool event(QEvent* e) 函数实现,所有事件都会进入该函数,根据事件类型分发到对应的事件处理函数,返回 true 表示拦截。事件过滤器则是在事件分发之前做更高级的拦截,通过 installEventFilter 安装,重写 eventFilter 函数实现,适合多个组件统一拦截的场景。
3. UDP 和 TCP 在 Qt 中的使用有何不同?
UDP 是无连接协议,使用 QUdpSocket,通过 bind 绑定端口、writeDatagram 发送数据报、receiveDatagram 接收数据报。TCP 是面向连接的可靠协议,服务器使用 QTcpServer 监听端口、nextPendingConnection 获取连接;客户端使用 QTcpSocket 的 connectToHost 建立连接,读写都使用 QByteArray。
4. deleteLater() 的作用是什么?为什么使用它?
deleteLater() 不是立即销毁对象,而是将对象标记为无效,Qt 会在下一轮事件循环中析构释放该对象。这样可以避免在槽函数执行过程中直接删除对象导致的问题,确保当前槽函数执行完毕后再销毁。
5. Qt 中如何实现多线程?
自定义一个类继承 QThread,重写 run() 函数作为线程处理函数,通过 start() 启动线程(不能直接调用 run()),线程执行结束后通过信号通知主线程,最后关闭线程。