Qt C++ 软件开发工程师面试题

文章目录

  • [Qt C++ 常见面试题](#Qt C++ 常见面试题)
  • 一、自我介绍1111231
  • Qt
  • [二、 1. Qt Design Studio 有了解吗?](#二、 1. Qt Design Studio 有了解吗?)
  • [三、2. Qt 事件循环的理解(背诵,关掉开始)](#三、2. Qt 事件循环的理解(背诵,关掉开始))
  • [四、元对象系统与 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 源码有做研究吗?)
  • 六、隐式共享(写时复制)有了解吗?
  • [七、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 事件循环的核心概念(背诵)

  1. 原理

    • 本质上无限循环exec ,循环中不断检查事件队列是否有新的事件。有,处理;没有,休眠;
  2. 信号和槽机制

    • 可跨线程工作,在事件循环中,信号会被转换成事件,放入事件队列,然后分发到接收者。
  3. 主线程与事件循环

    • 在一个典型的 GUI 程序中,事件循环通常在主线程中运行。用户界面的更新、事件的处理等都在主线程中完成。对于需要执行耗时操作的任务,通常会放到其他线程中,以避免阻塞事件循环。

事件循环的典型流程(理解)

  1. 事件循环开始运行,进入 QCoreApplication::exec(),主事件循环;
  2. 等待事件到来(如鼠标点击、键盘输入、绘图事件、定时器事件等),或者处理定时器、网络等异步事件;
  3. 当事件到来时,将事件放入事件队列;
  4. 从事件队列中取出事件,找到事件的接收者(即事件目标对象),将事件分发给该对象处理;
  5. 如果事件处理函数执行完成,继续监听事件队列,循环继续;
  6. 当调用 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 源码有做研究吗?

六、隐式共享(写时复制)有了解吗?

是一种高效的内存管理技术。它用于优化对象的复制行为,使得对象的复制不实际进行,而是通过引用计数来共享数据,只有在修改数据时才执行真实的复制操作。

隐式共享的工作原理

隐式共享的核心思想是:当多个对象共享相同的数据时,它们引用同一块内存区域,而不复制数据。只有当某个对象试图修改共享的数据时,才会创建一份副本,以避免对其他共享该数据的对象产生影响。这种方式结合了浅拷贝深拷贝 的优点,既保证了内存使用的高效,又能确保对象的独立性

具体流程:

  1. 创建对象

    • 当创建一个隐式共享的 Qt 对象时,内部分配一个数据结构,保存实际的数据内容。
  2. 拷贝对象

    • 当进行对象拷贝时,新对象不会创建独立的副本,而是指向原对象的数据,并增加数据的引用计数。此时,多个对象共享同一份数据。
  3. 修改对象

    • 如果某个对象尝试修改共享的数据,Qt 检测到该数据有多个引用,则会为这个对象创建一个新的副本,并对副本进行修改,确保其他对象仍然指向原始数据。这就是"写时复制"的概念。
  4. 销毁对象

    • 当某个对象被销毁时,它只会减少共享数据的引用计数。只有当引用计数降为零时,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:处理图像数据。

这些类可以频繁地进行对象拷贝,而不担心性能问题,直到发生写操作时才会进行深拷贝。

隐式共享的优势:

  1. 节省内存:多个对象共享同一份数据,直到某个对象需要修改数据为止,减少了不必要的内存分配。
  2. 提高性能:在不修改数据的情况下,拷贝对象只涉及引用计数的增加,避免了深拷贝的昂贵操作。
  3. 线程安全:隐式共享在多数情况下是线程安全的,修改操作会在需要时自动进行拷贝,确保不同线程不会修改相同的数据。

隐式共享的技术细节

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

● 缺点:

  1. 数据丢失,造成卡顿或丢帧现象

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),在编译阶段,静态库中的所有代码都会被链接到可执行文件中,这意味着生成的可执行文件会包含所有的库代码。

优点:
  • 独立性:生成的可执行文件不依赖外部库,拷贝到任何环境下都可以独立运行,不需要额外的库文件。
  • 性能更高:因为在运行时不需要加载库,所有代码都已经链接到程序中,因此没有运行时动态链接的开销。
缺点:
  • 占用空间大:由于静态库的代码会复制到每一个使用它的可执行文件中,导致可执行文件的体积增大。
  • 更新不方便:如果库中的代码更新了,需要重新编译所有依赖这个库的应用程序。
使用方法:
  1. 编译源代码生成静态库:

    bash 复制代码
    g++ -c mylib.cpp -o mylib.o
    ar rcs libmylib.a mylib.o
  2. 链接库:

    bash 复制代码
    g++ main.cpp -o main -L. -lmylib

2. 动态库(Dynamic Library)

动态库的扩展名通常为 .dll (Windows)、 .so (Linux)或 .dylib(macOS)。与静态库不同,动态库中的代码在运行时才加载到内存中,而不是在编译时。

优点:
  • 节省空间:因为库的代码不会被复制到每一个可执行文件中,不同的程序可以共享同一个动态库,节省了存储空间。
  • 易于更新:库可以独立更新,不需要重新编译依赖它的应用程序。
缺点:
  • 依赖性:运行时必须保证库文件存在并且路径正确,否则程序会崩溃或无法运行。
  • 性能略有影响:因为在运行时需要加载库,动态链接会带来轻微的性能开销。
使用方法:
  1. 编译源代码生成动态库:

    bash 复制代码
    g++ -fPIC -c mylib.cpp -o mylib.o
    g++ -shared -o libmylib.so mylib.o
  2. 链接库:

    bash 复制代码
    g++ main.cpp -o main -L. -lmylib
  3. 运行时指定库路径:

    bash 复制代码
    LD_LIBRARY_PATH=. ./main

总结:

  • 静态库适合那些需要独立分发且不经常更新的程序。
  • 动态库则适合需要多个程序共享库功能,并且希望能够独立更新的情况。

对比总结

特点 静态库 动态库
编译和链接时间 编译时库代码链接进可执行文件,生成较大体积的独立应用程序。 程序编译时只链接库的符号表,运行时动态加载库,生成较小的可执行文件。
资源使用 每个可执行文件包含一份库代码,内存和磁盘使用量较大。 不同应用程序可以共享同一个动态库,节省内存和磁盘空间。
更新和维护 必须重新编译应用程序才能更新库,适合稳定的库和长期支持的系统。 可以独立更新库,无需重新编译应用程序,适合频繁更新或模块化系统。
程序性能 启动速度较快,没有运行时链接开销。 稍微有一些运行时开销,特别是在启动时加载和解析符号。
适用场景 嵌入式系统、独立分发的应用、小型工具或对稳定性有高要求的应用。 企业级应用、插件化系统、需要节省资源或频繁更新的应用。

实际选择时的考虑因素

  1. 是否需要库更新:如果你希望在不重新编译程序的情况下更新库,动态库是最佳选择。否则,静态库更容易管理。
  2. 程序规模和部署方式:对于小型、独立分发的程序,静态库较为合适。而对于大型项目或多个程序共享的库,动态库更优。
  3. 性能和启动时间:如果程序对启动时间有极高要求,静态库通常更适合。动态库有少量的运行时开销。
  4. 内存和磁盘空间:如果你希望减少系统资源占用并让多个程序共享同一份库,动态库是最佳选择。

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

总结

  1. 头文件加载mymath.h 通过 #include 语句被应用程序加载,编译器需要在编译时找到该文件。
  2. 编译时链接 :使用 -I-L 参数来指定头文件和库文件的路径,确保正确链接动态库。
  3. 运行时加载 :在运行时,系统需要能够找到动态库文件,这可以通过设置 LD_LIBRARY_PATH(Linux/macOS)或使用 PATH 环境变量(Windows)来完成。

通过这些步骤,你可以正确加载并使用动态库及其头文件。

十五、 平常有看什么相关书籍吗

相关推荐
bitbitDown2 小时前
从零打造一个 Vite 脚手架工具:比你想象的简单多了
前端·javascript·面试
沐怡旸2 小时前
【穿越Effective C++】条款16:成对使用new和delete时要采用相同形式——内存管理的精确匹配原则
c++·面试
z20348315203 小时前
我与C++的故事
开发语言·c++·c++40周年
异步的告白3 小时前
C语言-数据结构-1-动态数组
c语言·数据结构·c++
ceclar1235 小时前
C++线程操作
c++
矢心5 小时前
setTimeout 和 setInterval:看似简单,但你不知道的使用误区
前端·javascript·面试
2301_807997385 小时前
代码随想录-day30
数据结构·c++·算法·leetcode
咔咔咔的6 小时前
3607. 电网维护
c++
拉不动的猪7 小时前
关于scoped样式隔离原理和失效情况&&常见样式隔离方案
前端·javascript·面试