【ffmpeg 到Qt的图片格式转换】精彩的像素:深入解析 AVFrame 到 QImage 的转换

简介: 【ffmpeg 到Qt的图片格式转换】精彩的像素:深入解析 AVFrame 到 QImage 的转换

1. 引言

1.1 为什么需要 AVFrame 到 QImage 的转换?

音视频处理和图像处理的交叉领域,我们经常需要在不同的库和框架之间转换数据。例如,我们可能使用 FFmpeg(一款著名的开源音视频处理库)来读取和解码视频数据,然后使用 Qt(一款著名的开源图形用户界面库)来显示这些数据。

在 FFmpeg 中,视频帧通常被存储在 AVFrame 结构体中。然而,Qt 并不直接支持 AVFrame,而是使用它自己的 QImage 类来处理图像数据。因此,我们需要一个方法来将 AVFrame 转换为 QImage。

1.2 对象和目标读者介绍

这篇文章的主要目标是深入解析 AVFrame 到 QImage 的转换过程,包括涉及的各种技术概念、数据结构和函数。我们将从 AVFrame 和 QImage 的基本结构和数据布局开始讲起,然后深入到颜色空间转换、像素格式转换、内存管理等话题。在整个过程中,我们将详细解释和展示如何使用 FFmpeg 的 libswscale 库以及 Qt 的 QImage 类来完成这个转换。

我们假设读者已经有了一定的 C++ 编程经验,以及基本的音视频处理和图形用户界面编程知识。我们还会涉及一些 C++11 和后续版本的特性,所以对这些特性有一定理解的读者将能更好地理解文章内容。

在接下来的部分,我们将逐一解析 AVFrame 到 QImage 转换的每个步骤,并给出具体的代码示例和详细的解释。我们希望这篇文章能帮助您更深入地理解这个过程,并在实际编程中更好地应用这些知识。

2. AVFrame 和 QImage:基础理解

在音视频处理领域,AVFrame 和 QImage 是两个常见的数据结构,分别用于存储和操作视频帧和图像。理解这两者的结构和数据布局是我们进行 AVFrame 到 QImage 转换的基础。下面,我们将详细解析这两者的基础结构和数据布局。

2.1 AVFrame 的基础结构和数据布局

AVFrame 是来自 FFmpeg 库的一个关键数据结构,用于表示解码后的原始视频(或音频)帧。

typedef struct AVFrame {
    uint8_t* data[AV_NUM_DATA_POINTERS]; // 数据指针
    int linesize[AV_NUM_DATA_POINTERS];  // 每行数据的大小
    // ... 其他成员变量
} AVFrame;

在 AVFrame 中,data 是一个指针数组,用于存储像素数据或者样本数据。每个指针指向一个数据平面(Data Plane)。对于像素格式为 YUV420 的视频帧,data[0]、data[1]、data[2] 分别对应 Y、U、V 三个平面。对于音频帧,data 数组存储样本数据。

linesize 数组存储每个数据平面每行的字节数。同样,对于像素格式为 YUV420 的视频帧,linesize[0]、linesize[1]、linesize[2] 分别对应 Y、U、V 三个平面每行的字节数。

2.2 QImage 的基础结构和数据布局

QImage 是 Qt 库中的一个关键类,用于处理图像。它支持多种像素格式,如 RGB32、ARGB32、RGB888 等。

class QImage {
public:
    QImage(int width, int height, Format format);
    // ... 其他成员函数
private:
    // ... 数据成员
};

在 QImage 对象中,像素数据是连续存储的。我们可以通过 bits() 函数获取到内部像素数据的指针。像素的排列顺序取决于图像的宽度和像素格式。

QImage img(800, 600, QImage::Format_RGB32);
uchar* ptr = img.bits();

在上述代码中,img.bits() 返回的是一个指向 QImage 内部像素数据的指针。像素数据是按行存储的,每行的字节数可以通过 bytesPerLine() 函数获取。对于 RGB32 格式的图像,每个像素占用 4 字节,所以每行的字节数应该是宽度乘以 4。

不过,需要注意的是,由于内存对齐的原因,每行的字节数可能会比宽度乘以每个像素的字节数稍大。因此,我们在处理像素数据时,应当使用 bytesPerLine() 函数返回的值,而不是直接使用宽度乘以每个像素的字节数。

下表总结了 AVFrame 和 QImage 的一些关键函数:

类型 函数 描述
AVFrame data 返回一个指向像素数据的指针数组
AVFrame linesize 返回一个每行字节数的数组
QImage bits 返回一个指向像素数据的指针
QImage bytesPerLine 返回每行的字节数

接下来,我们将详细讲述如何将 AVFrame 的数据转换为 QImage 可接受的格式。

3. 数据转换:像素格式和颜色空间

在进行 AVFrame 到 QImage 的转换过程中,一个重要的环节是理解和处理不同的像素格式和颜色空间。这个环节的理解和操作对于图像的正确显示具有至关重要的作用。

3.1 不同像素格式的介绍和比较

在处理图像和视频数据时,常会遇到多种不同的像素格式。像素格式(Pixel Format)定义了每个像素的组成部分以及它们在内存中的排列方式。常见的像素格式包括 RGB24、RGB32、YUV420P、YUV422、YUV444等。

下面是一些常见像素格式的简单对比:

像素格式 颜色空间 颜色分量 每个像素占用的位数 是否包含透明度信息
RGB24 RGB R, G, B 24
RGB32 RGB R, G, B, A 32
YUV420P YUV Y, U, V 12
YUV422 YUV Y, U, V 16
YUV444 YUV Y, U, V 24

例如,RGB24 是一个非常常见的像素格式,它使用 RGB 颜色空间,每个像素由三个颜色分量(红色、绿色、蓝色)组成,每个颜色分量占用 8 位,所以每个像素总共占用 24 位。RGB32 类似,但是它多了一个透明度分量(Alpha),每个像素占用 32 位。

3.2 颜色空间转换的概念和必要性

颜色空间(Color Space)定义了颜色的数学表示方法。常见的颜色空间包括 RGB、YUV、HSV 等。在不同的应用场景中,可能会使用不同的颜色空间。例如,RGB 颜色空间广泛用于计算机图形,而 YUV 颜色空间则广泛用于视频处理。

转换颜色空间的需要通常由以下两个因素驱动:

  1. 不同设备或应用可能需要不同的颜色空间。例如,一台显示器可能需要 RGB 数据,而一个视频编码器可能需要 YUV 数据。
  2. 不同的颜色空间具有不同的特性,这可能会影响处理的效果。例如,YUV 颜色空间可以更有效地压缩颜色信息,而 RGB 颜色空间可以更直接地表示颜色。

在 AVFrame 到 QImage 的转换过程中,我们需要处理颜色空间的转换。这是因为 AVFrame 可以有多种不同的像素格式(因此也有多种不同的颜色空间),而 QImage 则期望的是 RGB32 格式的数据。

转换颜色空间的过程通常涉及一些复杂的数学运算,但幸运的是,我们可以使用 libswscale 库来进行这个转换。这个库提供了强大的 API,可以方便地处理颜色空间转换和其他相关的操作。

在下一章节,我们将深入探讨如何使用 libswscale 进行这种转换。

3. 数据转换:像素格式和颜色空间

在进行 AVFrame 到 QImage 的转换过程中,一个重要的环节是理解和处理不同的像素格式和颜色空间。这个环节的理解和操作对于图像的正确显示具有至关重要的作用。

3.1 不同像素格式的介绍和比较

在处理图像和视频数据时,常会遇到多种不同的像素格式。像素格式(Pixel Format)定义了每个像素的组成部分以及它们在内存中的排列方式。常见的像素格式包括 RGB24、RGB32、YUV420P、YUV422、YUV444等。

下面是一些常见像素格式的简单对比:

像素格式 颜色空间 颜色分量 每个像素占用的位数 是否包含透明度信息
RGB24 RGB R, G, B 24
RGB32 RGB R, G, B, A 32
YUV420P YUV Y, U, V 12
YUV422 YUV Y, U, V 16
YUV444 YUV Y, U, V 24

例如,RGB24 是一个非常常见的像素格式,它使用 RGB 颜色空间,每个像素由三个颜色分量(红色、绿色、蓝色)组成,每个颜色分量占用 8 位,所以每个像素总共占用 24 位。RGB32 类似,但是它多了一个透明度分量(Alpha),每个像素占用 32 位。

3.2 颜色空间转换的概念和必要性

颜色空间(Color Space)定义了颜色的数学表示方法。常见的颜色空间包括 RGB、YUV、HSV 等。在不同的应用场景中,可能会使用不同的颜色空间。例如,RGB 颜色空间广泛用于计算机图形,而 YUV 颜色空间则广泛用于视频处理。

转换颜色空间的需要通常由以下两个因素驱动:

  1. 不同设备或应用可能需要不同的颜色空间。例如,一台显示器可能需要 RGB 数据,而一个视频编码器可能需要 YUV 数据。
  2. 不同的颜色空间具有不同的特性,这可能会影响处理的效果。例如,YUV 颜色空间可以更有效地压缩颜色信息,而 RGB 颜色空间可以更直接地表示颜色。

在 AVFrame 到 QImage 的转换过程中,我们需要处理颜色空间的转换。这是因为 AVFrame 可以有多种不同的像素格式(因此也有多种不同的颜色空间),而 QImage 则期望的是 RGB32 格式的数据。

转换颜色空间的过程通常涉及一些复杂的数学运算,但幸运的是,我们可以使用 libswscale 库来进行这个转换。这个库提供了强大的 API,可以方便地处理颜色空间转换和其他相关的操作。

在下一章节,我们将深入探讨如何使用 libswscale 进行这种转换。

4. libswscale:强大的转换工具

libswscale 是 FFmpeg 项目中的一个库,它提供了一组用于处理图像缩放和像素格式转换的功能。当我们需要将 AVFrame(来自 libavcodec)转换为 QImage(来自 Qt)时,libswscale 是一个非常有用的工具。

4.1 libswscale 简介

libswscale 主要有以下几个关键的函数:

  • sws_getContext:创建一个新的 SwsContext 对象。这个对象包含了转换过程所需的所有信息,包括输入和输出的尺寸和像素格式,以及用于缩放的算法。
  • sws_scale:执行实际的转换。它接受一个 SwsContext 对象和一些关于输入和输出数据的信息,然后将输入的图像转换为输出的格式,并将结果写入输出的数据缓冲区。
  • sws_freeContext:释放一个 SwsContext 对象,回收它占用的内存。

4.2 创建 SwsContext 对象

创建 SwsContext 对象的第一步是调用 sws_getContext 函数。这个函数的原型如下:

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                                  int dstW, int dstH, enum AVPixelFormat dstFormat,
                                  int flags, SwsFilter *srcFilter,
                                  SwsFilter *dstFilter, const double *param);

这个函数需要我们提供输入和输出的尺寸和像素格式,以及一些其他的参数。在我们的例子中,输入的尺寸和像素格式来自 AVFrame,输出的尺寸和像素格式是 QImage 的。我们还需要提供一个标志来指定用于缩放的算法。在这个例子中,我们使用的是 SWS_BILINEAR,这是一种双线性插值算法。

以下是一个使用 sws_getContext 的例子:

SwsContext* sws_ctx = sws_getContext(width, height, static_cast<AVPixelFormat>(frame->format),
                                      width, height, AV_PIX_FMT_RGB32,
                                      SWS_BILINEAR, nullptr, nullptr, nullptr);

如果 sws_getContext 返回 nullptr,那么就说明创建 SwsContext 对象失败了。在这种情况下,我们应该进行错误处理。

if (!sws_ctx) {
    // 错误处理
    return QImage();
}

4.3 使用 sws_scale 进行转换

一旦我们有了一个 SwsContext 对象,我们就可以调用 sws_scale 函数来执行实际的转换了。这个函数的原型如下:

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[], const int srcStride[],
              int srcSliceY, int srcSliceH, uint8_t *const dst[], const int dstStride[]);

这个函数需要我们提供一些关于输入和输出数据的信息,包括数据的指针和行大小。我们可以通过 QImage 的 bitsbytesPerLine 方法来获取这些信息。

以下是一个使用 sws_scale 的例子:

uint8_t* data[1] = { reinterpret_cast<uint8_t*>(img.bits()) };
int linesize[1] = { static_cast<int>(img.bytesPerLine()) };
int ret = sws_scale(sws_ctx, frame->data, frame->linesize, 0, height, data, linesize);

如果 sws_scale 返回的值和输入图像的高度不同,那么就说明转换失败了。在这种情况下,我们应该进行错误处理。

if (ret != height) {
    // 错误处理
    return QImage();
}

在完成了转换之后,我们需要调用 sws_freeContext 来释放 SwsContext 对象。

sws_freeContext(sws_ctx);

以上就是使用 libswscale 进行 AVFrame 到 QImage 转换的基本步骤。接下来,我们将深入探讨 QImage 的数据布局和内存管理,以更好地理解这个过程。

5. QImage:数据布局和内存管理

在我们的 AVFrame 到 QImage 的转换过程中,理解 QImage 的内部数据布局和内存管理是非常重要的。QImage 是 Qt 的一个关键类,用于操作图像数据。在本章节中,我们将深入探索 QImage 的结构和特性。

5.1 QImage 的内部数据布局

首先,我们需要理解 QImage 的内部数据布局。QImage 在内部使用一个字节数组(byte array)来存储图像数据。这个数组的大小是由图像的尺寸(宽度和高度)以及每个像素的字节数决定的。每个像素的字节数取决于图像的格式。例如,在我们的代码中,我们使用的是 QImage::Format_RGB32 格式,这意味着每个像素由 4 字节(32 位)的数据表示,每个颜色通道(红色、绿色、蓝色和透明度)各占 8 位。

下面是 QImage 内部数据布局的一个示例:

Column 1 Column 2 Column N
Row 1 Pixel Pixel Pixel
Row 2 Pixel Pixel Pixel
Row M Pixel Pixel Pixel

每一个 “Pixel” 在这个表格中代表一个像素的数据,对于 QImage::Format_RGB32 格式,它会包含红色、绿色、蓝色和透明度四个通道的值。

5.2 指针数组:为何需要它?

在我们的转换函数中,我们使用了一个指针数组来表示 QImage 的内部数据。那么,为什么我们需要一个指针数组,而不是一个简单的指针或者直接使用 QByteArray 呢?

答案是,我们需要一个指针数组,因为我们需要向 sws_scale 函数提供一个指针数组。sws_scale 函数是 libswscale 库中的一个函数,用于处理图像的缩放和格式转换。这个函数需要一个指针数组作为参数,因为它可以处理多平面的图像数据。在多平面的图像格式中,每个平面的数据都存储在一个单独的数组中,所以我们需要一个指针数组来表示所有的平面。对于单平面的图像格式,如 RGB32,我们只需要一个指针,但是为了满足 sws_scale 函数的参数要求,我们仍然需要将这个指针放入一个数组中。

5.3 QImage 的内存管理:bits 和 bytesPerLine

在 QImage 中,我们可以使用 bits() 方法来获取指向内部数据的指针,使用 bytesPerLine() 方法来获取每行的字节数。这两个方法在内存管理和数据访问中都非常重要。

bits() 方法返回一个指向 QImage 内部数据的指针。这个指针指向的是 QImage 内部数据的首地址,我们可以通过这个指针来访问和修改 QImage 的像素数据。在我们的转换函数中,我们使用这个指针来让 sws_scale 函数能够直接将转换后的数据写入 QImage 的内部数据。

bytesPerLine() 方法返回 QImage 每行的字节数。这个值通常等于图像的宽度乘以每个像素的字节数。然而,有时为了内存对齐的目的,这个值可能会比实际的字节数大。在我们的转换函数中,我们使用这个值来正确地计算每一行数据的位置,以便 sws_scale 函数能够正确地进行数据转换。

5.3.1 深入理解:为何需要 bytesPerLine?

我们可能会问,既然我们知道了图像的宽度和每个像素的字节数,为什么我们不能直接使用这两个值来计算每行的字节数呢?

答案是,虽然在大多数情况下,每行的字节数确实等于图像的宽度乘以每个像素的字节数,但是在某些情况下,为了满足内存对齐的要求,每行的末尾可能会添加一些填充字节。这些填充字节并不包含任何有用的像素数据,但是它们确实占用了内存空间,所以在计算每行的字节数时,我们必须将它们考虑进去。这就是为什么我们需要 bytesPerLine() 方法,而不能简单地使用 width * bytesPerPixel 来计算每行的字节数。

以下是一个简单的示例,说明了为什么我们需要 bytesPerLine()

#include <QImage>
#include <QDebug>
int main() {
    QImage img(100, 100, QImage::Format_RGB32);
    qDebug() << "Width:" << img.width();
    qDebug() << "Bytes per pixel:" << img.depth() / 8;
    qDebug() << "Bytes per line (calculated):" << img.width() * img.depth() / 8;
    qDebug() << "Bytes per line (actual):" << img.bytesPerLine();
    return 0;
}

在这个示例中,我们创建了一个 100x100 大小的 QImage 对象,每个像素由 4 字节(32 位)的数据表示。然后,我们打印了这个图像的宽度、每个像素的字节数、每行的字节数(通过计算得到的)和每行的实际字节数(通过 bytesPerLine() 方法得到的)。如果您运行这个程序,您会看到,尽管通过计算得到的每行的字节数和实际的每行的字节数在大多数情况下是相同的,但在某些情况下,它们可能会不同。

6. 错误检查和验证

在编程中,错误检查和验证是非常重要的步骤。尤其是在我们进行像素数据的转换时,任何一个小错误都可能导致结果图像的颜色失真或者显示错误。在本章节中,我们将会深入探讨如何进行有效的错误检查,以及如何验证我们的转换结果。

6.1 检查 sws_getContext 和 sws_scale 的返回值

我们在前面的章节中已经介绍过,sws_getContextsws_scale 这两个函数在我们的转换过程中起到了关键的作用。然而,如果这两个函数运行失败,会怎样呢?我们是否有有效的手段来发现这些错误,并且进行适当的处理呢?

答案是肯定的。sws_getContext 函数如果失败,会返回一个 nullptr。而 sws_scale 函数则会返回一个整数,表示处理的行数。如果这个值和输入图像的高度不相等,那么就说明转换过程出现了问题。

让我们来看一段示例代码,展示如何进行这些错误的检查:

QImage img(width, height, QImage::Format_RGB32);
SwsContext* sws_ctx = sws_getContext(width, height, static_cast<AVPixelFormat>(frame->format), width, height, AV_PIX_FMT_RGB32, SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!sws_ctx) {
    // 错误处理:sws_getContext 失败
    std::cerr << "sws_getContext failed." << std::endl;
    return QImage();
}
uint8_t* data[1] = { reinterpret_cast<uint8_t*>(img.bits()) };
int linesize[1] = { static_cast<int>(img.bytesPerLine()) };
int ret = sws_scale(sws_ctx, frame->data, frame->linesize, 0, height, data, linesize);
sws_freeContext(sws_ctx);
if (ret != height) {
    // 错误处理:sws_scale 失败
    std::cerr << "sws_scale failed." << std::endl;
    return QImage();
}

在这段代码中,我们添加了两个错误检查的部分。如果 sws_getContext 返回 nullptr,或者 sws_scale 的返回值与图像的高度不相等,我们就打印一个错误信息,并且返回一个空的 QImage 对象。这样,我们就可以确保在出现错误时,我们的函数不会返回错误的结果,并且还能给出错误的提示信息。

6.2 保存并查看结果图像

虽然我们已经进行了错误检查,但是我们还需要确认转换的结果是否正确。毕竟,没有错误并不意味着结果就一定是我们预期的。那么,我们如何来验证我们的转换结果呢?

一种简单而直观的方法就是将结果图像保存为一个文件,然后用图像查看器来查看它。通过直观的观察,我们可以很容易地发现一些明显的问题,比如颜色失真,或者图像倒置等。

让我们来看一段示例代码,展示如何保存结果图像:

// 将结果图像保存为一个 PNG 文件
img.save("output.png");

在这段代码中,我们使用了 QImage::save 这个方法,将结果图像保存为一个 PNG 文件。然后,我们就可以用任何图像查看器来打开这个文件,查看结果图像的效果。

6.3 结论

在这个章节中,我们讨论了错误检查和验证的重要性,以及如何在我们的代码中进行这些操作。我们首先介绍了如何检查 sws_getContextsws_scale 这两个函数的返回值,来发现可能的错误。然后,我们又介绍了如何通过保存和查看结果图像来验证我们的转换结果。

虽然这些方法看起来很简单,但是它们在实际编程中是非常有效的。通过这些方法,我们不仅可以发现和处理错误,还可以验证我们的代码是否能够得到预期的结果。这就是为什么我们需要在代码中加入这些错误检查和验证的操作。

7. 优化和提高效率

在前面的章节中,我们已经详细介绍了如何从 AVFrame 转换到 QImage,并实现了一个基本的转换函数。然而,编程不仅仅是实现功能,更重要的是要考虑代码的效率和优化。在这一章,我们将利用 C++11 及更高版本的一些特性,探讨如何优化我们的代码,提高运行效率。

7.1 利用 C++11 及更高版本的特性

自 C++11 以来,C++ 引入了许多新特性,使得我们可以更高效、更安全地编写代码。例如,右值引用(rvalue reference)和移动语义(move semantics)可以帮助我们避免不必要的复制,提高代码效率。

我们在之前的章节中讲述的 QImage::scaled 方法就是一个很好的例子。这个方法返回一个新的 QImage 对象,这个对象是原图像缩放到指定大小的结果。在早期的 C++ 版本中,这可能会导致图像数据的深度复制,但是在 C++11 及更高版本中,返回的是新图像的移动版本,避免了复制。

if (outputWidth > 0 && outputHeight > 0) {
    QImage scaledImg = img.scaled(outputWidth, outputHeight);
    scaledImg.save(filename);
}

在上面的代码中,img.scaled(outputWidth, outputHeight) 生成了一个新的 QImage 对象,这个对象是 img 的缩放版本。这个新对象被赋值给 scaledImg,但是这里实际上发生的是移动赋值,而不是复制。这就是 C++11 的移动语义在实践中的应用。

另一个值得注意的 C++11 特性是自动类型推导(auto)。这可以使我们的代码更简洁,同时避免类型错误。例如,我们可以用 auto 来接收 sws_getContext 的返回值:

auto sws_ctx = sws_getContext(width, height, static_cast<AVPixelFormat>(frame->format), width, height, AV_PIX_FMT_RGB32, SWS_BILINEAR, nullptr, nullptr, nullptr);

使用 auto 关键字,编译器会自动推导 sws_ctx 的类型,我们不再需要手动指定。

7.2 使用 QImage::scaled 方法进行缩放

如果我们需要改变图像的大小,可以使用 QImage::scaled 方法。这个方法会创建一个新的 QImage 对象,这个对象是原图像缩放到指定大小的结果。我们可以在保存图像之前使用这个方法,以便生成特定大小的图像。

QImage scaledImg = img.scaled(outputWidth, outputHeight);
scaledImg.save(filename);

在上面的代码中,img.scaled(outputWidth, outputHeight) 生成了一个新的 QImage 对象,这个对象是 img 的缩放版本。然后,我们保存这个新的图像,而不是原来的图像。

7.3 使用 PTS 创建唯一文件名

在保存每一帧为图像时,我们可以使用 Presentation Timestamps(PTS,呈现时间戳)来创建唯一的文件名。这样,我们可以知道每个图像文件对应的是视频中的哪一帧。

在我们的代码中,我们可以使用 QString::arg 方法来插入 PTS:

QString filename = QString("output_%1.png").arg(frame->pts);

在上面的代码中,%1 是一个占位符,arg(frame->pts) 会将 frame->pts 的值插入到这个占位符的位置,生成最终的文件名。

使用 PTS 作为文件名的一部分,可以让我们更容易地分析和调试视频处理的结果,这是一个很好的实践。

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
1月前
|
算法 数据处理 开发者
FFmpeg库的使用与深度解析:解码音频流流程
FFmpeg库的使用与深度解析:解码音频流流程
36 0
|
1月前
|
存储 传感器 安全
【串口通信】使用C++和Qt设计和实现串口协议解析器(二)
【串口通信】使用C++和Qt设计和实现串口协议解析器
53 0
|
1月前
|
存储 开发框架 算法
【串口通信】使用C++和Qt设计和实现串口协议解析器(一)
【串口通信】使用C++和Qt设计和实现串口协议解析器
107 0
|
1月前
|
算法 Unix Linux
Linux与Qt线程优先级的对应关系:一次全面解析
Linux与Qt线程优先级的对应关系:一次全面解析
23 0
|
1月前
|
编解码 算法 vr&ar
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换(二)
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换
30 1
|
1月前
|
存储 编解码 算法
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换(一)
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换
63 1
|
24天前
|
编译器 C语言
Qt使用MSVC编译错误: LNK2019: 无法解析的外部符号
Qt使用MSVC编译错误: LNK2019: 无法解析的外部符号
13 1
|
1月前
|
Java 程序员 API
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
【深入探究 Qt 线程】一文详细解析Qt线程的内部原理与实现策略
77 0
|
1月前
|
存储 缓存
【Qt 全局属性相关】 Qt 应用程序行为的全局属性 Qt::ApplicationAttribute枚举解析
【Qt 全局属性相关】 Qt 应用程序行为的全局属性 Qt::ApplicationAttribute枚举解析
22 0
|
1月前
|
网络协议 C++
C++ Qt开发:QTcpSocket网络通信组件
`QTcpSocket`和`QTcpServer`是Qt中用于实现基于TCP(Transmission Control Protocol)通信的两个关键类。TCP是一种面向连接的协议,它提供可靠的、双向的、面向字节流的通信。这两个类允许Qt应用程序在网络上建立客户端和服务器之间的连接。Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用`QTcpSocket`组件实现基于TCP的网络通信功能。
38 8
C++ Qt开发:QTcpSocket网络通信组件

推荐镜像

更多