文章目录
- [Qt C++ 常见面试题](#Qt C++ 常见面试题)
- 一、自我介绍1111231
- Qt
- [二、 1. Qt Design Studio 有了解吗?](#二、 1. Qt Design Studio 有了解吗?)
- [三、2. Qt 事件循环的理解(背诵,关掉开始)](#三、2. Qt 事件循环的理解(背诵,关掉开始))
-
-
- [Qt 事件循环的核心概念(背诵)](#Qt 事件循环的核心概念(背诵))
- 事件循环的典型流程(理解)
- QEventLoop(背诵)
- 事件处理的顺序(背诵)
-
- [四、元对象系统与 QObject](#四、元对象系统与 QObject)
-
-
- [1. **元对象类 (QMetaObject)**](#1. 元对象类 (QMetaObject))
- [2. **信号与槽 (Signals & Slots)**](#2. 信号与槽 (Signals & Slots))
- [3. **宏和 MOC**](#3. 宏和 MOC)
- [4. **动态属性与 RTTI**](#4. 动态属性与 RTTI)
- [5. **反射机制**](#5. 反射机制)
- 典型的使用方式:,;
- [5. **国际化支持**](#5. 国际化支持)
- [6. **计时器功能**](#6. 计时器功能)
-
- [五、4. Qt 源码有做研究吗?](#五、4. Qt 源码有做研究吗?)
- 六、隐式共享(写时复制)有了解吗?
-
-
- 隐式共享的工作原理
- 具体流程:
- [示例:QString 的隐式共享](#示例:QString 的隐式共享)
- 什么时候使用隐式共享?
- 隐式共享的优势:
- 隐式共享的技术细节
- 参考代码:隐式共享的基本实现逻辑
- 总结
-
- [七、C++ 11 有用到它的哪些特性? STL 标准库](#七、C++ 11 有用到它的哪些特性? STL 标准库)
-
-
- [1. **自动类型推导 (auto)(背诵)**](#1. 自动类型推导 (auto)(背诵))
- [2. **范围循环 (range-based for)(背)**](#2. 范围循环 (range-based for)(背))
- [3. **Lambda 表达式**](#3. Lambda 表达式)
- [4. **智能指针 (std::shared_ptr、std::unique_ptr)**](#4. 智能指针 (std::shared_ptr、std::unique_ptr))
- [6. **线程库 (** **`<thread>`** **)**](#6. 线程库 (
<thread>)) - [7. **constexpr**](#7. constexpr)
- [8. **`nullptr`**](#8.
nullptr) - [9. **统一的初始化列表**](#9. 统一的初始化列表)
- [10. **`std::tuple`** **和** **`std::array`**](#10.
std::tuple和std::array) - [11. **显式默认和删除函数 (** **`= default`** **,** **`= delete`** **)**](#11. 显式默认和删除函数 (
= default,= delete)) - [12. **强类型枚举 (****`enum class`** **)**](#12. 强类型枚举 (****
enum class)) - [13. **变长模板参数**](#13. 变长模板参数)
-
- [八、 lambda 表达式用过吗?(C++11)](#八、 lambda 表达式用过吗?(C++11))
-
-
- [Lambda 表达式的基本语法](#Lambda 表达式的基本语法)
- [示例 1: 基本 Lambda 表达式](#示例 1: 基本 Lambda 表达式)
- [示例 2: 带参数的 Lambda 表达式](#示例 2: 带参数的 Lambda 表达式)
- 捕获外部变量
- [示例 3: 捕获外部变量](#示例 3: 捕获外部变量)
- [示例 4: 按引用捕获](#示例 4: 按引用捕获)
- [Lambda 表达式的高级用法](#Lambda 表达式的高级用法)
-
- [九、 STL](#九、 STL)
- [十、 项目](#十、 项目)
-
- 视频解码。如何降低延时,提高性能的;
-
- [1. 使用硬件加速解码](#1. 使用硬件加速解码)
- [2. 减少缓冲区大小](#2. 减少缓冲区大小)
- [3. 调整线程数](#3. 调整线程数)
- [4. 调整帧率](#4. 调整帧率)
- [5. 使用低复杂度的编码参数](#5. 使用低复杂度的编码参数)
- [6. 优化解码流程](#6. 优化解码流程)
- [7. 使用实时解码](#7. 使用实时解码)
- [十一、 视频性能指标参数](#十一、 视频性能指标参数)
-
-
- [1. **比特率(Bitrate)**](#1. 比特率(Bitrate))
- [2. **帧率(Frame Rate, FPS)**](#2. 帧率(Frame Rate, FPS))
- [3. **分辨率(Resolution)**](#3. 分辨率(Resolution))
- [4. **压缩率(Compression Ratio)**](#4. 压缩率(Compression Ratio))
- [5. **延时(Latency)**](#5. 延时(Latency))
- [6. **丢帧率(Frame Drop Rate)**](#6. 丢帧率(Frame Drop Rate))
- [7. **峰值信噪比(PSNR, Peak Signal-to-Noise Ratio)**](#7. 峰值信噪比(PSNR, Peak Signal-to-Noise Ratio))
- [8. **结构相似性(SSIM, Structural Similarity Index)**](#8. 结构相似性(SSIM, Structural Similarity Index))
- [9. **关键帧间隔(Keyframe Interval, GOP Size)**](#9. 关键帧间隔(Keyframe Interval, GOP Size))
- [10. **CPU/GPU 使用率**](#10. CPU/GPU 使用率)
- [11. **内存占用(Memory Usage)**](#11. 内存占用(Memory Usage))
- [12. **吞吐量(Throughput)**](#12. 吞吐量(Throughput))
- [13. **播放延迟(Playback Delay)**](#13. 播放延迟(Playback Delay))
- [14. **色度抽样(Chroma Subsampling)**](#14. 色度抽样(Chroma Subsampling))
- [15. **编码时间(Encoding Time)**](#15. 编码时间(Encoding Time))
- [16. **视频质量评估(VMAF, Video Multi-Method Assessment Fusion)**](#16. 视频质量评估(VMAF, Video Multi-Method Assessment Fusion))
- 总结
-
- [十二、 编解码熟悉吗?](#十二、 编解码熟悉吗?)
- 环境
- [十三、 Qt 启动速度优化](#十三、 Qt 启动速度优化)
-
-
- [1. **减少动态库加载时间**](#1. 减少动态库加载时间)
- [2. **减小应用程序大小**](#2. 减小应用程序大小)
- [3. **资源优化**](#3. 资源优化)
- [4. **减少UI初始化时间**](#4. 减少UI初始化时间)
- [5. **减少文件I/O**](#5. 减少文件I/O)
- [6. **优化线程使用**](#6. 优化线程使用)
- [7. **避免不必要的启动任务**](#7. 避免不必要的启动任务)
- [8. **编译优化**](#8. 编译优化)
-
- [十四、 有写过动态库吗?](#十四、 有写过动态库吗?)
-
-
- [1. 静态库(Static Library)](#1. 静态库(Static Library))
- [2. 动态库(Dynamic Library)](#2. 动态库(Dynamic Library))
- 总结:
- **对比总结**
- **实际选择时的考虑因素**
- [6. **实际示例:完整的动态库构建和应用加载流程**](#6. 实际示例:完整的动态库构建和应用加载流程)
-
- [步骤 1:编写库文件和头文件](#步骤 1:编写库文件和头文件)
- [步骤 2:编译生成动态库](#步骤 2:编译生成动态库)
- [步骤 3:编写应用程序文件](#步骤 3:编写应用程序文件)
- [步骤 4:编译并链接应用程序](#步骤 4:编译并链接应用程序)
- [步骤 5:运行程序](#步骤 5:运行程序)
- 总结
-
- [十五、 平常有看什么相关书籍吗](#十五、 平常有看什么相关书籍吗)
Qt C++ 常见面试题
一、自我介绍1111231
Qt
二、 1. Qt Design Studio 有了解吗?
官方支持 QML 的可视化编程;
三、2. Qt 事件循环的理解(背诵,关掉开始)
用于管理和调度事件的分发 ,Qt 是一个基于事件驱动的框架 ,通过事件循环机制来处理来自系统的事件 (如鼠标点击、键盘输入、窗口更新等)以及应用程序内部的信号和槽函数调用。
Qt 事件循环的核心概念(背诵)
-
原理
- 本质上无限循环exec ,循环中不断检查事件队列是否有新的事件。有,处理;没有,休眠;
-
信号和槽机制
- 可跨线程工作,在事件循环中,信号会被转换成事件,放入事件队列,然后分发到接收者。
-
主线程与事件循环
- 在一个典型的 GUI 程序中,事件循环通常在主线程中运行。用户界面的更新、事件的处理等都在主线程中完成。对于需要执行耗时操作的任务,通常会放到其他线程中,以避免阻塞事件循环。
事件循环的典型流程(理解)
- 事件循环开始运行,进入 QCoreApplication::exec(),主事件循环;
- 等待事件到来(如鼠标点击、键盘输入、绘图事件、定时器事件等),或者处理定时器、网络等异步事件;
- 当事件到来时,将事件放入事件队列;
- 从事件队列中取出事件,找到事件的接收者(即事件目标对象),将事件分发给该对象处理;
- 如果事件处理函数执行完成,继续监听事件队列,循环继续;
- 当调用 QCoreApplication::quit() 或 exit() 时,退出事件循环,程序终止;
QEventLoop(背诵)
启动一个临时的事件循环,比如在等待某个操作完成时。这时可以使用 QEventLoop 来创建并启动一个局部的事件循环。
cpp
QEventLoop loop;
connect(someObject, &SomeObject::signal, &loop, &QEventLoop::quit);
loop.exec(); // 进入局部事件循环,直到接收到信号后退出
事件处理的顺序(背诵)
Qt 中的事件是异步的,通常根据先进先出 的原则进行处理,但某些情况下事件的优先级可以不同,比如定时器事件通常优先于用户输入事件处理。
四、元对象系统与 QObject
Qt 的元对象系统(Meta-Object System)是 Qt 框架的核心组件之一,它为 C++ 提供了额外的功能,如信号与槽机制、运行时类型识别(RTTI)、属性系统等。这些功能在标准的 C++ 中是没有的,但 Qt 通过元对象系统扩展了这些能力。主要组成部分如下:
1. 元对象类 (QMetaObject)
每个继承自 QObject 的类都会自动包含一个 QMetaObject 对象。QMetaObject 存储类的元信息,包括:
- 类名
- 父类信息
- 信号与槽
- 枚举类型
- 属性
2. 信号与槽 (Signals & Slots)
信号与槽机制是 Qt 的核心功能之一,它允许对象之间进行松耦合的通信。信号和槽是通过元对象系统实现的。QObject 类的子类可以定义信号和槽:
-
信号:当某一事件发生时触发(如按钮被点击)。信号是一个函数声明,但没有实现。
-
槽:用于接收信号并处理相应的任务。槽是一个普通的函数。
信号与槽的连接是通过 connect() 函数实现的,信号发出时,系统会调用所有已连接的槽函数。
3. 宏和 MOC
Qt 的元对象系统依赖于一个名为 Meta-Object Compiler (MOC) 的工具。MOC 负责解析类中的特殊宏(如 Q_OBJECT )并生成额外的代码,使得信号与槽机制和其他元对象功能可以正常工作。
- Q_OBJECT 宏:每个需要元对象系统支持的类都必须包含该宏,这样 MOC 才能生成必要的元信息代码。
- Q_PROPERTY 宏:定义类属性,支持 Qt 的属性系统,如可用于绑定或在 Qt 的编辑器中显示。
- Q_ENUMS 和 Q_FLAGS 宏:为枚举和标志类型提供运行时支持。
4. 动态属性与 RTTI
Qt 提供了一套动态属性系统,通过 setProperty () 和 property () 可以动态地为对象设置和获取属性,这在标准 C++ 中是无法做到的。还支持运行时类型识别 (RTTI),通过 inherits() 或 qobject_cast<>() 可以检测对象类型。
5. 反射机制
Qt 的元对象系统还提供了基本的反射机制。通过 QMetaObject 可以查询类的信号、槽、方法、属性等信息,甚至可以在运行时调用方法(如 QMetaObject::invokeMethod())。
典型的使用方式:,;
cpp
class MyClass : public QObject
{
Q_OBJECT // 必须包含该宏
public:
MyClass(QObject *parent = nullptr) : QObject(parent) {}
signals:
void mySignal(); // 定义信号
public slots:
void mySlot(); // 定义槽
};
总之,Qt 的元对象系统通过 MOC 提供了许多 C++ 标准不支持的特性,使得开发更加灵活、高效,特别是 GUI 应用程序的开发。
不声明这个宏对象可以吗?
5. 国际化支持
QObject 还提供了国际化(I18N)支持的功能,通过 tr() 函数,可以方便地进行字符串的翻译处理。
6. 计时器功能
QObject 支持定时器,通过 startTimer() 和 timerEvent() 等函数,可以在对象中处理定时任务。
五、4. Qt 源码有做研究吗?
无
六、隐式共享(写时复制)有了解吗?
是一种高效的内存管理技术。它用于优化对象的复制行为,使得对象的复制不实际进行,而是通过引用计数来共享数据,只有在修改数据时才执行真实的复制操作。
隐式共享的工作原理
隐式共享的核心思想是:当多个对象共享相同的数据时,它们引用同一块内存区域,而不复制数据。只有当某个对象试图修改共享的数据时,才会创建一份副本,以避免对其他共享该数据的对象产生影响。这种方式结合了浅拷贝 和深拷贝 的优点,既保证了内存使用的高效,又能确保对象的独立性。
具体流程:
-
创建对象:
- 当创建一个隐式共享的 Qt 对象时,内部分配一个数据结构,保存实际的数据内容。
-
拷贝对象:
- 当进行对象拷贝时,新对象不会创建独立的副本,而是指向原对象的数据,并增加数据的引用计数。此时,多个对象共享同一份数据。
-
修改对象:
- 如果某个对象尝试修改共享的数据,Qt 检测到该数据有多个引用,则会为这个对象创建一个新的副本,并对副本进行修改,确保其他对象仍然指向原始数据。这就是"写时复制"的概念。
-
销毁对象:
- 当某个对象被销毁时,它只会减少共享数据的引用计数。只有当引用计数降为零时,Qt 才会真正释放这块内存。
示例:QString 的隐式共享
QString 是一个典型使用隐式共享的类。下面是一个简单的例子:
cpp
QString str1 = "Hello, World!"; // 创建一个字符串
QString str2 = str1; // str2 和 str1 共享同一份数据,没有发生实际拷贝
// 修改 str2
str2.append(" Qt!"); // 此时 str2 会创建一个副本并修改,不会影响 str1
在这个例子中,str1 和 str2 在最初是共享同一块内存的,但当 str2 被修改时,它会创建自己的副本,确保 str1 不会受到影响。
什么时候使用隐式共享?
Qt 的许多类都使用隐式共享来提高性能,尤其是那些存储大量数据的类,比如:
- QString:处理字符串数据。
- QByteArray:处理字节数据。
- QVector:处理动态数组。
- QImage:处理图像数据。
这些类可以频繁地进行对象拷贝,而不担心性能问题,直到发生写操作时才会进行深拷贝。
隐式共享的优势:
- 节省内存:多个对象共享同一份数据,直到某个对象需要修改数据为止,减少了不必要的内存分配。
- 提高性能:在不修改数据的情况下,拷贝对象只涉及引用计数的增加,避免了深拷贝的昂贵操作。
- 线程安全:隐式共享在多数情况下是线程安全的,修改操作会在需要时自动进行拷贝,确保不同线程不会修改相同的数据。
隐式共享的技术细节
Qt 使用一个叫做"数据对象(data object) "的结构来实现隐式共享。在这些类中,数据对象会有一个引用计数器 (通常叫 ref),并且这个结构存储实际的数据。
当一个对象被复制时,引用计数器增加;当某个对象要修改数据时,系统会检测引用计数。如果引用计数大于1,表示有多个对象共享数据,于是触发深拷贝(即真正分配新内存并复制数据)。
参考代码:隐式共享的基本实现逻辑
虽然 Qt 的具体实现细节相对复杂,但以下是隐式共享的基本逻辑示例:
cpp
class SharedData {
public:
int refCount;
int value; // 实际存储的数据
};
class ImplicitShared {
private:
SharedData* data; // 指向共享的数据
public:
ImplicitShared() {
data = new SharedData();
data->refCount = 1;
data->value = 0;
}
// 拷贝构造函数
ImplicitShared(const ImplicitShared& other) {
data = other.data;
++data->refCount; // 增加引用计数
}
// 修改时进行深拷贝
void setValue(int v) {
if (data->refCount > 1) {
--data->refCount; // 减少当前对象的引用计数
data = new SharedData(*data); // 深拷贝数据
data->refCount = 1;
}
data->value = v;
}
int value() const {
return data->value;
}
~ImplicitShared() {
if (--data->refCount == 0) {
delete data; // 只有引用计数为0时才删除数据
}
}
};
```
总结
隐式共享是 Qt 框架优化内存管理和提高性能的重要机制之一,特别是在处理大数据对象时。它通过引用计数和写时复制技术实现了数据共享和拷贝的高效性,使得开发者无需担心频繁拷贝数据对性能的影响。
d、p指针,干嘛用的,好处是什么
七、C++ 11 有用到它的哪些特性? STL 标准库
C++11 是 C++ 标准的一次重要更新,带来了许多新的特性,极大地提升了语言的功能性、效率和代码简洁性。以下是 C++11 的一些关键新特性:
1. 自动类型推导 (auto)(背诵)
- 编译器可以根据变量初始化的值来推断变量的类型,简化了变量声明。
cpp
auto x = 42; // x 的类型为 int
2. 范围循环 (range-based for)(背)
- 允许在容器上简洁地迭代,减少手动使用迭代器的复杂性。
cpp
std::vector<int> vec = {1, 2, 3, 4};
for (int i : vec) {
std::cout << i << std::endl;
}
3. Lambda 表达式
- 允许在代码中定义匿名函数(闭包),使得函数式编程更加方便。
cpp
auto add = [](int x, int y) { return x + y; };
4. 智能指针 (std::shared_ptr、std::unique_ptr)
- 提供了内存管理工具,用于自动管理对象的生命周期,避免手动管理内存泄漏。
cpp
std::unique_ptr<int> ptr = std::make_unique<int>(10);
6. **线程库 (** <thread> )
- 标准库中引入了多线程支持,使得并发编程更加便捷和标准化。
cpp
std::thread t([] { std::cout << "Hello from thread!"; });
t.join();
7. constexpr
constexpr函数允许在编译时进行常量表达式求值,从而提升性能。
cpp
constexpr int square(int x) { return x * x; }
8. nullptr
- 引入了
nullptr关键字,替代了旧的NULL,解决了空指针与整数之间的混淆问题。
cpp
int* p = nullptr;
9. 统一的初始化列表
- C++11 引入了统一的初始化语法,使得数组、结构体等对象的初始化更加简单统一。
cpp
std::vector<int> vec = {1, 2, 3, 4};
10. std::tuple 和 std::array
std::tuple提供了一种能够存储不同类型数据的容器,std::array是一种具有固定大小的数组容器。
cpp
std::tuple<int, double, std::string> t(1, 3.14, "C++11");
11. **显式默认和删除函数 (** = default , = delete )
- 提供了一种机制来显式声明默认的或删除的函数(如构造函数、赋值操作符等)。
cpp
class MyClass {
public:
MyClass() = default;
MyClass(const MyClass&) = delete;
};
12. 强类型枚举 ( enum class )
enum class引入了强类型的枚举,避免了传统枚举中的作用域冲突问题。
cpp
enum class Color { Red, Green, Blue };
Color c = Color::Red;
13. 变长模板参数
- 允许定义接收可变参数数量的模板函数或类。
cpp
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl;
}
这些特性不仅增强了 C++ 的功能性,也让代码的表达更加简洁、灵活和高效。C++11 标志着 C++ 语言现代化的开始,为后续的标准(如 C++14、C++17 等)奠定了基础。
八、 lambda 表达式用过吗?(C++11)
捕获变量有哪几种方式?()局部变量
lambda 表达式是一种匿名函数(没有名称的函数)。它允许你定义一个小的函数,并可以将其作为参数传递给其他函数。Lambda 表达式引入于 C++11
Lambda 表达式的基本语法
cpp
[capture](parameters) -> return_type { body }
- capture: 用于指定 lambda 表达式可以访问哪些外部变量(即在 lambda 之外声明的变量)。
- parameters: 参数列表,类似于普通函数的参数列表。
- return_type: 返回类型(可选的,如果能自动推导出则可省略)。
- body: 函数体,lambda 表达式的实现。
示例 1: 基本 Lambda 表达式
cpp
#include <iostream>
int main() {
auto greet = []() {
std::cout << "Hello, World!" << std::endl;
};
greet(); // 调用 lambda
return 0;
}
输出:
Hello, World!
示例 2: 带参数的 Lambda 表达式
cpp
#include <iostream>
int main() {
auto add = [](int a, int b) -> int {
return a + b;
};
std::cout << "Sum: " << add(3, 4) << std::endl;
return 0;
}
输出:
makefile
Sum: 7
捕获外部变量
Lambda 表达式可以捕获外部作用域中的变量,并在表达式内部使用它们。捕获方式可以是按值捕获(即捕获时的变量值)或按引用捕获(捕获变量的引用,允许在 lambda 内修改外部变量)。
-
a\]: 按值捕获 a 变量。
示例 3: 捕获外部变量
cpp
#include <iostream>
int main() {
int x = 10;
int y = 20;
auto printSum = [=]() {
std::cout << "Sum: " << x + y << std::endl;
};
printSum();
return 0;
}
输出:
makefile
Sum: 30
在这个例子中,lambda 表达式按值捕获了 x 和 y,因此它们可以在 lambda 内部访问。
示例 4: 按引用捕获
cpp
#include <iostream>
int main() {
int x = 10;
auto modifyX = [&]() {
x += 5;
};
modifyX();
std::cout << "Modified x: " << x << std::endl;
return 0;
}
输出:
yaml
Modified x: 15
在这个例子中,x 是按引用捕获的,因此在 lambda 内部修改了它的值。
Lambda 表达式的高级用法
Lambda 表达式常用于算法、回调函数和多线程编程等场景。它们提供了简洁的语法来定义短小的内联函数
九、 STL
底层的数据结构是什么?

实现与原理,泛型编程等?
十、 项目
视频解码。如何降低延时,提高性能的;
A:多线程队列,处理什么业务;
1. 使用硬件加速解码
FFmpeg 支持多种硬件加速解码器,它们能够利用 GPU 或专用的视频解码硬件来加速视频解码,减轻 CPU 负载,进而降低延时。 常见的硬件加速解码器有:
- NVIDIA (NVDEC) : h264_cuvid、hevc_cuvid 等。
- Intel (QSV) : h264_qsv、hevc_qsv 等。
- AMD (AMF) : h264_amf、hevc_amf 等。
- Android mediacode
示例:
bash
ffmpeg -hwaccel cuvid -c:v h264_cuvid -i input.mp4 -f rawvideo -pix_fmt yuv420p output.yuv
2. 减少缓冲区大小
可以通过调整解码缓冲区的大小来降低延时,尤其是在网络流媒体中。
- -fflags nobuffer: 禁用输入缓冲区。
- -flags low_delay: 低延时解码。
示例:
bash
ffmpeg -flags low_delay -fflags nobuffer -i input.mp4 -f rawvideo -pix_fmt yuv420p output.yuv
● 缺点:
- 数据丢失,造成卡顿或丢帧现象
3. 调整线程数
FFmpeg 默认会根据输入文件和系统资源自动设置线程数,但在某些情况下,手动调整线程数可以优化性能。
- -threads N: 设置解码线程数量。
示例:
bash
ffmpeg -threads 4 -i input.mp4 -f rawvideo -pix_fmt yuv420p output.yuv
4. 调整帧率
降低输出帧率可以减少解码器的工作量,从而提高性能。
- -r X: 设置输出帧率为 X 帧每秒。
示例:
bash
ffmpeg -i input.mp4 -r 24 -f rawvideo -pix_fmt yuv420p output.yuv
5. 使用低复杂度的编码参数
在某些情况下,解码的延时和性能还受输入视频编码参数的影响。如果视频文件编码复杂度较高,可以在编码时选择较低复杂度的参数,例如降低比特率、减少关键帧间隔。
6. 优化解码流程
使用 -skip_frame 选项跳过部分帧的解码:
- -skip_frame nokey: 跳过非关键帧。
- -skip_frame bidir: 跳过双向预测帧。
示例:
bash
ffmpeg -skip_frame nokey -i input.mp4 -f rawvideo -pix_fmt yuv420p output.yuv
7. 使用实时解码
-re 选项可以强制 FFmpeg 以实际帧率读取输入,这对于流媒体解码中的延时控制很有用。
bash
ffmpeg -re -i input.mp4 -f rawvideo -pix_fmt yuv420p output.yuv
十一、 视频性能指标参数
延时、清晰度(花屏)
h264,在视频处理中,衡量性能的指标可以从多个角度进行分析,包括编码效率、解码效率、流媒体传输性能、视频质量等。下面是一些常见的性能指标参数,它们能够帮助评估视频处理系统的效率和效果。
1. 比特率(Bitrate)
-
定义:比特率表示每秒钟传输或处理的数据量,通常以 kbps(千比特每秒)或 Mbps(兆比特每秒)为单位。
-
作用:比特率直接影响视频的质量和文件大小。较高的比特率通常意味着更高的画质,但也会增加存储和带宽需求。
-
类型:
- 恒定比特率(CBR) :固定的比特率,适合于流媒体传输,保证流畅性。
- 可变比特率(VBR) :根据画面复杂度动态调整比特率,在保证质量的同时节约带宽。
2. 帧率(Frame Rate, FPS)
- 定义:帧率表示每秒播放的帧数,常见的帧率有 24、30、60 FPS 等。
- 作用:帧率影响视频的流畅度。较高的帧率可以带来更流畅的视觉体验,但也会增加处理负担和文件大小。
3. 分辨率(Resolution)
- 定义:分辨率表示视频的像素尺寸(宽度 x 高度),常见的分辨率有 720p(1280x720)、1080p(1920x1080)、4K(3840x2160)等。
- 作用:较高的分辨率通常意味着更清晰的画质,但也会增加文件大小和处理难度。
4. 压缩率(Compression Ratio)
- 定义:压缩率表示原始视频文件被压缩后的文件大小的比例。
- 作用:压缩率越高,文件越小,但压缩可能会降低视频质量。合适的压缩技术可以在保证画质的同时减小文件大小。
5. 延时(Latency)
- 定义:延时表示从输入视频流到解码、处理和输出的时间延迟,通常以毫秒(ms)为单位。
- 作用:对于直播或实时应用,延时是非常重要的性能指标。低延时确保互动性和流畅性。
6. 丢帧率(Frame Drop Rate)
- 定义:丢帧率表示在解码或播放过程中丢失的帧的百分比。
- 作用:丢帧会导致视频播放的卡顿,丢帧率较高时会严重影响观看体验。
7. 峰值信噪比(PSNR, Peak Signal-to-Noise Ratio)
- 定义:PSNR 是一个衡量视频质量的指标,用来比较原始视频和经过压缩或处理后的视频之间的差异。单位是 dB(分贝)。
- 作用:PSNR 值越高,表示压缩或处理后的视频与原始视频越接近,质量越好。一般来说,30 dB 以上的 PSNR 被认为是可接受的质量。
8. 结构相似性(SSIM, Structural Similarity Index)
- 定义:SSIM 是另一种衡量视频质量的指标,评估视频图像结构的相似度。SSIM 的取值范围是 0 到 1,值越接近 1,质量越好。
- 作用:相比 PSNR,SSIM 更加关注图像的感知质量,即人眼对视频质量的真实感知。
9. 关键帧间隔(Keyframe Interval, GOP Size)
- 定义:关键帧间隔指的是两个关键帧(I 帧)之间的帧数,也称为 GOP(Group of Pictures)大小。
- 作用:较长的关键帧间隔可以提高压缩效率,但可能会增加解码的复杂度。较短的关键帧间隔会增加比特率,但提升视频质量和快速跳转能力。
10. CPU/GPU 使用率
- 定义:处理视频过程中,CPU 或 GPU 的使用率,通常以百分比表示。
- 作用:较高的使用率表示系统资源被充分利用,但过高的使用率可能导致系统过载。优化 CPU/GPU 使用率能够提高解码或编码的效率。
11. 内存占用(Memory Usage)
- 定义:视频处理过程中占用的内存量。
- 作用:较高分辨率和复杂编码的视频处理需要更多的内存,内存占用量较大时,可能导致系统性能下降。
12. 吞吐量(Throughput)
- 定义:单位时间内处理或传输的数据量。
- 作用:吞吐量越高,意味着系统能够在单位时间内处理或传输更多数据,影响整体视频处理速度和效率。
13. 播放延迟(Playback Delay)
- 定义:从视频解码到最终显示在屏幕上的时间延迟。
- 作用:播放延迟直接影响用户的观看体验,特别是在直播或视频会议场景中。
14. 色度抽样(Chroma Subsampling)
- 定义:色度抽样表示色彩信息被压缩的比例。常见的色度抽样格式有 4:4:4、4:2:2 和 4:2:0。
- 作用:较低的色度抽样会压缩更多的色彩信息,导致图像质量下降。色度抽样主要影响视频的色彩质量和带宽需求。
15. 编码时间(Encoding Time)
- 定义:对视频进行编码所需的时间。
- 作用:编码时间影响视频生产的效率,特别是对于批量处理或实时编码场景。通过优化编码参数或使用硬件加速可以减少编码时间。
16. 视频质量评估(VMAF, Video Multi-Method Assessment Fusion)
- 定义:VMAF 是 Netflix 推出的一个视频质量评估指标,它综合了多个视频质量评估方法的结果,给出更贴近用户感知的视频质量分数。
- 作用:VMAF 是一个更加现代和综合的质量评估工具,特别适用于评估压缩视频的感知质量。
总结
以上性能指标涵盖了从编码、解码、传输、播放到用户感知的各个方面。根据不同的应用场景(如直播、视频点播、视频会议等),你可以选择最相关的指标进行优化,以提升整体视频处理的效率和用户体验。
十二、 编解码熟悉吗?
sws,QML,video,
帧率,30帧率
采用的通讯协议是什么?
环境
IDE,环境
- Qt Creator:官方推荐的跨平台 IDE,适合大多数 Qt 开发场景。
- Visual Studio:适合 Windows 开发者,通过插件集成 Qt。
- CLion:JetBrains 的跨平台 IDE,适合使用 CMake 管理项目的开发者。
- Eclipse CDT:通过插件支持 Qt 开发,适合开源项目和插件化的需求。
- KDevelop:开源的 Linux 开发环境,内置 Qt 支持。
- NetBeans:通过插件支持 Qt 开发。
- 命令行工具:适合经验丰富的开发者,手动管理构建和依赖。
- 嵌入式开发工具:支持在嵌入式设备上开发和调试 Qt 应用。
编译器和调试器
十三、 Qt 启动速度优化
在优化 Qt 程序的启动速度时,可以从以下几个方面着手:
1. 减少动态库加载时间
- 静态链接库:将常用的库静态链接到应用程序中,减少在启动时加载动态库的开销。
- 使用 Qt 的插件系统:仅在需要时加载特定的插件,而不是在启动时加载所有插件。
2. 减小应用程序大小
- 精简代码:移除不必要的代码和资源文件,减少可执行文件和加载时间。
- 剥离调试符号:对于发布版本,使用 strip 命令剥离掉可执行文件中的调试符号,减小文件大小。
3. 资源优化
- 延迟加载资源:在程序启动时只加载最必要的资源,其他资源在需要时再加载(例如,图像、字体、配置文件等)。
- 使用更轻量的资源格式:将图像、声音等资源文件转换为更轻量的格式,减少加载时间。
- Qt Resource System (qrc) :将资源文件打包到 Qt 的资源系统中,避免文件系统的 I/O 开销。
4. 减少UI初始化时间
- 按需加载 UI 组件:只初始化和渲染启动时需要的 UI 界面,其他组件等到需要时再加载。
- 简化主界面布局:减少启动时界面的复杂性,避免过多的布局计算和渲染任务。
5. 减少文件I/O
- 减少文件读取:在程序启动时尽量减少对配置文件、资源文件的读取,或者将这些文件预先载入内存。
- 使用缓存:可以将一些常用的配置或资源文件缓存起来,避免每次启动都重新读取。
6. 优化线程使用
- 后台加载非关键任务:将一些非关键的初始化任务放到后台线程中运行,避免阻塞主线程。
- 优化多线程初始化:对于多线程应用,合理分配线程任务,避免线程初始化造成的阻塞。
7. 避免不必要的启动任务
- 减少启动时的依赖:尽量减少启动时调用外部服务或依赖网络请求,避免因为网络或服务延迟影响启动速度。
- 延迟初始化不必要的模块:一些在启动阶段不需要的功能或模块可以延迟到使用时才初始化。
8. 编译优化
- 优化编译选项:使用合适的编译器优化选项,例如 -O2 或 -O3 进行优化编译。
- 启用 LTO(Link Time Optimization) :通过链接时优化(LTO)减少生成代码的冗余,提高程序整体性能。
十四、 有写过动态库吗?
提供了接口,加一个接口
dll更新,不影响程序运行,
1. 静态库(Static Library)
静态库的扩展名通常为 .lib (Windows)或 .a(Linux),在编译阶段,静态库中的所有代码都会被链接到可执行文件中,这意味着生成的可执行文件会包含所有的库代码。
优点:
- 独立性:生成的可执行文件不依赖外部库,拷贝到任何环境下都可以独立运行,不需要额外的库文件。
- 性能更高:因为在运行时不需要加载库,所有代码都已经链接到程序中,因此没有运行时动态链接的开销。
缺点:
- 占用空间大:由于静态库的代码会复制到每一个使用它的可执行文件中,导致可执行文件的体积增大。
- 更新不方便:如果库中的代码更新了,需要重新编译所有依赖这个库的应用程序。
使用方法:
-
编译源代码生成静态库:
bashg++ -c mylib.cpp -o mylib.o ar rcs libmylib.a mylib.o -
链接库:
bashg++ main.cpp -o main -L. -lmylib
2. 动态库(Dynamic Library)
动态库的扩展名通常为 .dll (Windows)、 .so (Linux)或 .dylib(macOS)。与静态库不同,动态库中的代码在运行时才加载到内存中,而不是在编译时。
优点:
- 节省空间:因为库的代码不会被复制到每一个可执行文件中,不同的程序可以共享同一个动态库,节省了存储空间。
- 易于更新:库可以独立更新,不需要重新编译依赖它的应用程序。
缺点:
- 依赖性:运行时必须保证库文件存在并且路径正确,否则程序会崩溃或无法运行。
- 性能略有影响:因为在运行时需要加载库,动态链接会带来轻微的性能开销。
使用方法:
-
编译源代码生成动态库:
bashg++ -fPIC -c mylib.cpp -o mylib.o g++ -shared -o libmylib.so mylib.o -
链接库:
bashg++ main.cpp -o main -L. -lmylib -
运行时指定库路径:
bashLD_LIBRARY_PATH=. ./main
总结:
- 静态库适合那些需要独立分发且不经常更新的程序。
- 动态库则适合需要多个程序共享库功能,并且希望能够独立更新的情况。
对比总结
| 特点 | 静态库 | 动态库 |
|---|---|---|
| 编译和链接时间 | 编译时库代码链接进可执行文件,生成较大体积的独立应用程序。 | 程序编译时只链接库的符号表,运行时动态加载库,生成较小的可执行文件。 |
| 资源使用 | 每个可执行文件包含一份库代码,内存和磁盘使用量较大。 | 不同应用程序可以共享同一个动态库,节省内存和磁盘空间。 |
| 更新和维护 | 必须重新编译应用程序才能更新库,适合稳定的库和长期支持的系统。 | 可以独立更新库,无需重新编译应用程序,适合频繁更新或模块化系统。 |
| 程序性能 | 启动速度较快,没有运行时链接开销。 | 稍微有一些运行时开销,特别是在启动时加载和解析符号。 |
| 适用场景 | 嵌入式系统、独立分发的应用、小型工具或对稳定性有高要求的应用。 | 企业级应用、插件化系统、需要节省资源或频繁更新的应用。 |
实际选择时的考虑因素
- 是否需要库更新:如果你希望在不重新编译程序的情况下更新库,动态库是最佳选择。否则,静态库更容易管理。
- 程序规模和部署方式:对于小型、独立分发的程序,静态库较为合适。而对于大型项目或多个程序共享的库,动态库更优。
- 性能和启动时间:如果程序对启动时间有极高要求,静态库通常更适合。动态库有少量的运行时开销。
- 内存和磁盘空间:如果你希望减少系统资源占用并让多个程序共享同一份库,动态库是最佳选择。
6. 实际示例:完整的动态库构建和应用加载流程
步骤 1:编写库文件和头文件
mymath.h
cpp
#ifndef MYMATH_H
#define MYMATH_H
int add(int a, int b);
int multiply(int a, int b);
#endif
mymath.cpp
cpp
#include "mymath.h"
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
步骤 2:编译生成动态库
bash
g++ -fPIC -c mymath.cpp -o mymath.o # 生成目标文件,启用位置无关代码(-fPIC)
g++ -shared -o libmymath.so mymath.o # 生成动态库 (.so)
步骤 3:编写应用程序文件
main.cpp
cpp
#include "mymath.h"
#include <iostream>
int main() {
int a = 5, b = 3;
std::cout << "Add: " << add(a, b) << std::endl;
std::cout << "Multiply: " << multiply(a, b) << std::endl;
return 0;
}
步骤 4:编译并链接应用程序
bash
g++ -I. main.cpp -o main -L. -lmymath
-I.:指定当前目录下的头文件。-L.:指定库文件路径为当前目录。- -lmymath :链接 libmymath.so。
步骤 5:运行程序
bash
LD_LIBRARY_PATH=. ./main
总结
- 头文件加载 :
mymath.h通过#include语句被应用程序加载,编译器需要在编译时找到该文件。 - 编译时链接 :使用
-I和-L参数来指定头文件和库文件的路径,确保正确链接动态库。 - 运行时加载 :在运行时,系统需要能够找到动态库文件,这可以通过设置
LD_LIBRARY_PATH(Linux/macOS)或使用PATH环境变量(Windows)来完成。
通过这些步骤,你可以正确加载并使用动态库及其头文件。
十五、 平常有看什么相关书籍吗