代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "sdlqtrgb.h"
#include <sdl/SDL.h>
#include <QMessageBox>
#include <iostream>
#include <fstream>

#pragma comment(lib, "SDL2.lib")

static int sdl_width = 0;
static int sdl_height = 0;
static SDL_Window* sdl_win = nullptr;
static SDL_Renderer* sdl_render = nullptr;
static SDL_Texture* sdl_texture = nullptr;
static unsigned char* yuv = nullptr;
static int pix_size = 4;
static std::ifstream yuv_file;


SdlQtRgb::SdlQtRgb(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);

// =====(1)=====
yuv_file.open("400_300_25.yuv", std::ios::binary);

if (!yuv_file)
{
QMessageBox::information(this, "", "open yuv failed!");
return;
}

sdl_width = 400;
sdl_height = 300;

this->resize(sdl_width, sdl_height);
ui.label->resize(sdl_width, sdl_height);
ui.label->move(0, 0);

SDL_Init(SDL_INIT_VIDEO);

sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());

sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);

sdl_texture = SDL_CreateTexture(sdl_render,
// =====(二)=====
SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING,
sdl_width,
sdl_height
);

yuv = new unsigned char[sdl_width * sdl_height * pix_size];

startTimer(30);
}

void SdlQtRgb::timerEvent(QTimerEvent *ev)
{
// =====(三)=====
yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);

SDL_UpdateTexture(sdl_texture, NULL, yuv, sdl_width);
SDL_RenderClear(sdl_render);
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = sdl_width;
rect.h = sdl_height;
SDL_RenderCopy(sdl_render, sdl_texture, NULL, &rect);
SDL_RenderPresent(sdl_render);
}

SdlQtRgb::~SdlQtRgb()
{
// 清理和释放SDL所使用的资源
SDL_Quit();
}

(一)

为什么要用binary打开文件?

以二进制模式打开文件是为了确保文件内容按原始字节读取或写入,而不经过任何修改或转换。

在文本模式下(即不使用 std::ios::binary 时),文件的读写操作可能会受到操作系统的影响,特别是在处理不同操作系统的换行符时

(二)

SDL_PIXELFORMAT_IYUV格式?

SDL_PIXELFORMAT_IYUV 是 SDL 库中定义的一种像素格式,表示 YUV420P 格式的图像数据。它是一种压缩的 YUV 格式,通常用于视频和图像处理。下面是对 SDL_PIXELFORMAT_IYUV 的详细介绍:

  1. YUV 颜色空间
  • Y 表示亮度分量(Luminance),它决定了图像的亮度或灰度级别。
  • U 和 V 表示色度分量(Chrominance),它们共同描述了图像的颜色信息。
  • YUV 格式通过分离亮度和色度来减少数据量,这在视频压缩和传输中非常有效。
  1. YUV420P 格式
  • YUV420P 是一种常见的 YUV 格式,其中亮度(Y)分量的分辨率与原图一致,而色度(U 和 V)分量的分辨率是亮度的四分之一。
  • IYUV 和 YV12 都是 YUV420P 的变种,它们的区别在于 U 和 V 分量的存储顺序。
  1. IYUV 格式的结构
  • IYUV 格式中,图像数据按平面(Plane)排列:
    • Y 平面:首先存储所有像素的亮度信息,大小为 width * height 字节。
    • U 平面:接下来存储所有 U 分量数据,大小为 width/2 * height/2 字节。
    • V 平面:最后存储所有 V 分量数据,大小同样为 width/2 * height/2 字节。
    • 存储:YYYYYYYY UUUU VVVV
  • 这意味着对于一个 400x300 的图像,IYUV 格式的数据结构如下:
    • Y 分量:400x300 = 120,000 字节
    • U 分量:200x150 = 30,000 字节
    • V 分量:200x150 = 30,000 字节
    • 总数据量:120,000 + 30,000 + 30,000 = 180,000 字节
  1. 与 YV12 的区别
  • IYUV:在内存中按 Y -> U -> V 的顺序存储。
  • YV12:在内存中按 Y -> V -> U 的顺序存储。
  1. 在 SDL 中的使用
  • SDL_PIXELFORMAT_IYUV 在 SDL 中用于处理、显示和转换视频帧。开发者可以使用 SDL 的渲染函数(如 SDL_UpdateTexture)来显示这种格式的图像。
  • 这种格式被广泛用于视频解码和播放,因为它比 RGB 更节省存储空间和带宽。
  1. 应用场景
  • 视频播放:IYUV 格式常用于视频解码器输出的数据格式,适合直接传递给视频渲染器。
  • 视频传输和存储:由于其数据压缩特性,YUV420 格式在视频编码中很常见,例如在 MPEG、H.264 等视频编码标准中。

(三)

1
yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);

这里$\times 1.5$为什么?

具体解释:

YUV 格式的图像数据通常分为三个平面:

  1. Y 分量(亮度信息,Luminance):每个像素都有一个 Y 分量,因此 Y 平面的大小是图像的宽度乘以高度(sdl_width * sdl_height)。
  2. U 分量(色度信息,Chrominance):U 分量平面通常是 Y 平面大小的四分之一(sdl_width/2 * sdl_height/2),因为 U 分量是 2x2 像素块共享的。
  3. V 分量(色度信息,Chrominance):V 分量平面的大小与 U 分量相同,也是 Y 平面的四分之一。

因此,对于一个 YUV420 格式的图像(比如 IYUV 或 YV12 格式):

  • Y 平面:占用 sdl_width * sdl_height 字节。
  • U 平面:占用 (sdl_width/2) * (sdl_height/2) 字节,即 sdl_width * sdl_height / 4 字节。
  • V 平面:占用的字节数与 U 平面相同,也是 sdl_width * sdl_height / 4 字节。

所以,YUV420 图像的总数据量为:

总数据量=Y平面+U平面+V平面

总数据量$=sdl_width \times sdl_height + \displaystyle\frac{sdl_width \times sdl_height}{4}+\displaystyle\frac{sdl_width \times sdl_height}{4}$

总数据量$=sdl_width \times sdl_height\times (1+\frac{1}{4}+\frac{1}{4})=sdl_width \times sdl_height\times1.5$

因此,乘以 1.5 是为了读取包含整个 YUV 图像帧的所有数据,包括 Y、U、V 三个分量。这确保了从文件中读取到的字节数足够填充一个完整的 YUV 图像帧。


1
2
3
4
5
// 处理yuv数据
SDL_UpdateTexture(sdl_texture, NULL, yuv, sdl_width);

// 处理rgb数据
SDL_UpdateTexture(sdl_texture, NULL, rgb, sdl_width * pix_size);

为什么第四个参数一个需要乘pix_size

SDL_UpdateTexture 函数中,第四个参数表示每一行数据的字节数(通常称为 “pitch” 或 “stride”)。对于不同的像素格式,这个参数的值会有所不同。

  • YUV 数据:通常 YUV 数据的每个像素由多个平面(例如 Y、U、V 平面)表示,每个平面可能有不同的分辨率。第四个参数 sdl_width 直接表示一行像素的宽度,因为每个 Y 分量通常占用一个字节。
  • RGB 数据:RGB 数据通常使用单个平面表示,每个像素由多个字节(例如 3 个字节的 RGB24 或 4 个字节的 RGBA32)表示。因此,第四个参数需要乘以 pix_size,即每个像素的字节数,以表示一行数据的总字节数。