SDL介绍

SDL(Simple DirectMedia Layer)是一个跨平台的多媒体开发库,广泛用于游戏开发和多媒体应用的创建。它提供了对音频、键盘、鼠标、游戏控制器、图形和窗口的底层访问,从而使开发者能够编写高性能的多媒体应用程序。SDL被许多流行的游戏和软件项目所使用。

SDL的主要特点

  1. 跨平台支持: SDL支持多个操作系统,包括Windows、Mac OS、Linux、iOS和Android等,使得开发者能够编写一次代码,运行在多个平台上。
  2. 简化多媒体处理: SDL提供了对音频、视频、输入设备、计时器等多媒体功能的简化接口,使开发者不需要处理底层系统的细节。
  3. 硬件加速: SDL支持使用OpenGL、Direct3D等图形库进行硬件加速,从而提高图形渲染的性能。
  4. 模块化设计: SDL的功能被划分为多个模块,如视频、音频、事件处理、线程、文件I/O等,开发者可以按需使用。

XSDL.h

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
#pragma once
#include "xvideo_view.h"

struct SDL_Window; // 能够使用SDL_WINDOW指针
struct SDL_Renderer;
struct SDL_Texture;

class XSDL :public XVideoView
{
public:

void Close() override;

bool Init(int w, int h,
Format fmt = RGBA) override;

bool Draw(const unsigned char* data,
int linesize = 0) override;

bool Draw(const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
) override;

bool IsExit() override;

private:
SDL_Window *win_ = nullptr;
SDL_Renderer *render_ = nullptr;
SDL_Texture *texture_ = nullptr;
};

参数介绍

1
2
3
4
private:
SDL_Window *win_ = nullptr; // 表示一个窗口
SDL_Renderer *render_ = nullptr; // 渲染器
SDL_Texture *texture_ = nullptr; // 材质

XSDL.cpp

是XVideoView的派生类

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#include "xsdl.h"
#include <sdl/SDL.h>
#include <thread>
#include <iostream>
#pragma comment(lib, "SDL2.lib")

static bool InitVideo()
{
static bool is_first = true;
static std::mutex mux;
std::unique_lock<std::mutex> sdl_lock(mux);
if (!is_first) return true;
is_first = false;
if (SDL_Init(SDL_INIT_VIDEO))
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");

return true;
}

bool XSDL::IsExit()
{
SDL_Event ev;

SDL_WaitEventTimeout(&ev, 1);

if (ev.type == SDL_QUIT)
return true;
return false;
}

void XSDL::Close()
{
std::unique_lock<std::mutex> sdl_lock(mtx_);

if (texture_)
{
SDL_DestroyTexture(texture_);
texture_ = nullptr;
}

if (render_)
{
SDL_DestroyRenderer(render_);
render_ = nullptr;
}

if (win_)
{
SDL_DestroyWindow(win_);
win_ = nullptr;
}
}


int find_renderer_index(const char* name) {
int count = SDL_GetNumRenderDrivers();
SDL_RendererInfo info;

for (int i = 0; i < count; ++i) {
if (SDL_GetRenderDriverInfo(i, &info) == 0) {
if (strcmp(info.name, name) == 0) {
return i;
}
}
}
return -1;
}




bool XSDL::Init(int w, int h, Format fmt)
{
if (w <= 0 || h <= 0) return false;

InitVideo();

std::unique_lock<std::mutex> sdl_lock(mtx_);

width_ = w;
height_ = h;
fmt_ = fmt;

if (texture_)
SDL_DestroyTexture(texture_);

if (render_)
SDL_DestroyRenderer(render_);

if (!win_)
{
if (win_id_)
{
win_ = SDL_CreateWindowFrom(win_id_);
}
else
{
win_ = SDL_CreateWindow("Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
}
}

if (!win_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

// int renderer_index = find_renderer_index("opengl");
// render_ = SDL_CreateRenderer(win_, renderer_index, SDL_RENDERER_ACCELERATED);

render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
if (!render_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;

switch (fmt_)
{
case XVideoView::RGBA:
sdl_fmt = SDL_PIXELFORMAT_RGBA32;
break;
case XVideoView::BGRA:
sdl_fmt = SDL_PIXELFORMAT_BGRA32;
break;
case XVideoView::YUV420P:
sdl_fmt = SDL_PIXELFORMAT_IYUV;
break;
case XVideoView::ARGB:
sdl_fmt = SDL_PIXELFORMAT_ARGB32;
break;
case XVideoView::NV12:
sdl_fmt = SDL_PIXELFORMAT_NV12;
break;
default:
break;
}

texture_ = SDL_CreateTexture(render_,
sdl_fmt,
SDL_TEXTUREACCESS_STREAMING,
w, h
);

if (!texture_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

return true;
}

bool XSDL::Draw(const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
)
{
if (!y || !u || !v) return false;

std::unique_lock<std::mutex> sdl_lock(mtx_);

if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
return false;

auto re = SDL_UpdateYUVTexture(texture_,
NULL,
y, y_pitch,
u, u_pitch,
v, v_pitch
);
if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_RenderClear(render_);

if (scale_w_ <= 0) scale_w_ = width_;
if (scale_h_ <= 0) scale_h_ = height_;

SDL_Rect rect;
SDL_Rect *prect = nullptr;
if (scale_w_ > 0)
{
rect.x = 0;
rect.y = 0;
rect.h = scale_h_;
rect.w = scale_w_;
prect = &rect;
}

re = SDL_RenderCopy(render_, texture_, NULL, prect);
if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_RenderPresent(render_);
return true;
}

bool XSDL::Draw(const unsigned char* data,
int linesize)
{
if (!data) return false;

std::unique_lock<std::mutex> sdl_lock(mtx_);

if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
return false;

if (linesize <= 0)
{
switch (fmt_)
{
case XVideoView::RGBA:
break;
case XVideoView::YUV420P:
linesize = width_;
break;
case XVideoView::ARGB:
linesize = width_ * 4;
break;
default:
break;
}
}
if (linesize <= 0) return false;

auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);

if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_RenderClear(render_);

if (scale_w_ <= 0) scale_w_ = width_;
if (scale_h_ <= 0) scale_h_ = height_;

SDL_Rect rect;
SDL_Rect *prect = nullptr;
if (scale_w_ > 0)
{
rect.x = 0;
rect.y = 0;
rect.h = scale_h_;
rect.w = scale_w_;
prect = &rect;
}

re = SDL_RenderCopy(render_, texture_, NULL, prect);
if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_RenderPresent(render_);
return true;
}

InitVideo()

作用:用于第一次初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// static 作用的函数只在此cpp下存在
static bool InitVideo()
{
static bool is_first = true; // 用于判断是否是第一次初始化
static std::mutex mux;
// 确保在多线程环境中对标准输出的访问是线程安全的。std::unique_lock 会在作用域结束时自动解锁互斥锁。
std::unique_lock<std::mutex> sdl_lock(mux);
if (!is_first) return true;
is_first = false;

// 初始化SDL的视频子系统
if (SDL_Init(SDL_INIT_VIDEO))
{
std::cout << SDL_GetError() << std::endl;
return false;
}

// 设定缩放算法解决锯齿问题,线性差值算法
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");

return true;
}

IsExit()

作用:处理检测用户关闭程序,并返回是否关闭的信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool XSDL::IsExit()
{
// SDL_Event 是一个联合类型,包含了各种 SDL 事件的类型和数据。
// 变量 ev 用于存储从事件队列中获取的事件。
SDL_Event ev;

// SDL_WaitEventTimeout 是 SDL 库中的一个函数,用于等待事件,并将事件存储在 ev 变量中。
// 第二个参数 1 表示等待的超时时间为 1 毫秒。如果在 1 毫秒内没有事件发生,该函数会返回 0(失败),如果有事件发生,则返回 1(成功)。
SDL_WaitEventTimeout(&ev, 1);

// 检查 ev 中的事件类型是否为 SDL_QUIT。
// SDL_QUIT 事件通常在用户请求关闭窗口或结束程序时触发。
if (ev.type == SDL_QUIT)
return true;
return false;
}

Close()

作用:关闭程序后,需要清理指针,防止内存泄漏。

先清理材质、再清理渲染器、最后清理窗口句柄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void XSDL::Close()
{
std::unique_lock<std::mutex> sdl_lock(mtx_);

if (texture_)
{
SDL_DestroyTexture(texture_);
texture_ = nullptr;
}

if (render_)
{
SDL_DestroyRenderer(render_);
render_ = nullptr;
}

if (win_)
{
SDL_DestroyWindow(win_);
win_ = nullptr;
}
}

find_renderer_index()

作用:用于在 SDL 中查找具有指定名称的渲染驱动程序的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int find_renderer_index(const char* name) {
// 返回可用的渲染驱动程序的数量,将结果存储在 count 变量中。
int count = SDL_GetNumRenderDrivers();
SDL_RendererInfo info;

for (int i = 0; i < count; ++i) {
// SDL_GetRenderDriverInfo(i, &info)用于获取索引为 i 的渲染驱动程序的信息,并将其存储在 info 变量中。
// 返回值为 0,表示没有错误。
if (SDL_GetRenderDriverInfo(i, &info) == 0) {
if (strcmp(info.name, name) == 0) {
return i;
}
}
}
return -1;
}

Init()

作用:初始化

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
bool XSDL::Init(int w, int h, Format fmt)
{
if (w <= 0 || h <= 0) return false;

// 初始化SDL 视频库
InitVideo();

// 锁,确保线程安全
std::unique_lock<std::mutex> sdl_lock(mtx_);

width_ = w;
height_ = h;
fmt_ = fmt;

if (texture_) // 由于渲染包含材质,所以要先检查
SDL_DestroyTexture(texture_);

if (render_)
SDL_DestroyRenderer(render_);

//////// 1. 创建窗口
if (!win_)
{
if (win_id_) // 如果存在窗口句柄
{
// 渲染到现有窗口,需要传入窗口句柄的指针
win_ = SDL_CreateWindowFrom(win_id_);
}
else
{
// 新建窗口
win_ = SDL_CreateWindow("Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
w, h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
// "Video" 窗口标题
// SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED 表示由系统决定
// SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE 窗口支持OpenGL渲染和用户可以拖动窗口边框来改变尺寸
}
}

if (!win_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

//////// 2. 创建渲染器


/*
【7.28】情况有变,再使用监控相机的时候,这个可以正常显示画面
int renderer_index = find_renderer_index("opengl");


render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);

×
render_ = SDL_CreateRenderer(win_, renderer_index, SDL_RENDERER_ACCELERATED);


【注!!】
此处:SDL_Renderer * SDLCALL SDL_CreateRenderer(SDL_Window * window,
int index, Uint32 flags);
index需要设置为opengl,才能支持SDL_PIXELFORMAT_NV12
NV12是一种 YUV 颜色空间格式,具体是 YUV 4:2:0 子采样格式之一

查看支持的格式代码

sdl_window *win = sdl_createwindow("hello sdl", 100, 100, 640, 480, sdl_window_shown);
int renderer_index = find_renderer_index("opengl");
sdl_renderer *renderer = sdl_createrenderer(win, renderer_index, sdl_renderer_accelerated | sdl_renderer_presentvsync);
sdl_rendererinfo info;
sdl_getrendererinfo(renderer, &info);
printf("renderer name: %s\n", info.name);
printf("supported texture formats:\n");
for (uint32 i = 0; i < info.num_texture_formats; i++) {
printf(" %s\n", sdl_getpixelformatname(info.texture_formats[i]));
}

return false;

*/

// win_ 关联的窗口
// renderer_index 指定的渲染驱动程序
// SDL_RENDERER_ACCELERATED 使用硬件加速

render_ = SDL_CreateRenderer(win_, -1, SDL_RENDERER_ACCELERATED);
if (!render_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

//////// 3. 创建材质
unsigned int sdl_fmt = SDL_PIXELFORMAT_RGBA8888;

switch (fmt_)
{
case XVideoView::RGBA:
sdl_fmt = SDL_PIXELFORMAT_RGBA32;
break;
case XVideoView::BGRA:
sdl_fmt = SDL_PIXELFORMAT_BGRA32;
break;
case XVideoView::YUV420P:
sdl_fmt = SDL_PIXELFORMAT_IYUV;
break;
case XVideoView::ARGB:
sdl_fmt = SDL_PIXELFORMAT_ARGB32;
break;
case XVideoView::NV12:
sdl_fmt = SDL_PIXELFORMAT_NV12;
break;
default:
break;
}

texture_ = SDL_CreateTexture(render_,
sdl_fmt, // 像素格式
SDL_TEXTUREACCESS_STREAMING, // 频繁修改的渲染(带锁,但是用不到,内部以及有锁了)
w, h // 材质大小
);

if (!texture_)
{
std::cerr << SDL_GetError() << std::endl;
return false;
}

return true;
}

Draw(),专门处理YUV格式

XVideoView::DrawFrame进行数据转发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool XVideoView::DrawFrame(AVFrame* frame)
{
...
switch (frame->format)
{
case AV_PIX_FMT_YUV420P:
return Draw(frame->data[0], frame->linesize[0], // Y
frame->data[1], frame->linesize[1], // U
frame->data[2], frame->linesize[2] // V
);
case AV_PIX_FMT_BGRA:
return Draw(frame->data[0], frame->linesize[0]);
}
...
}

作用:接收 YUV 格式的图像数据,将其更新到纹理,并通过 SDL 渲染到窗口上。

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
// 处理YUV格式
// 其中y_pitch表示 Y 平面的步幅,即每行 Y 分量数据的字节数。
bool XSDL::Draw(const unsigned char* y, int y_pitch,
const unsigned char* u, int u_pitch,
const unsigned char* v, int v_pitch
)
{
if (!y || !u || !v) return false;

std::unique_lock<std::mutex> sdl_lock(mtx_);

// 参数检查
if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
return false;

// 复制到显存,更新 YUV 纹理的数据
auto re = SDL_UpdateYUVTexture(texture_,
NULL,
y, y_pitch,
u, u_pitch,
v, v_pitch
);
if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

// 清空屏幕
// 清空渲染目标是为了确保在开始新的绘制操作之前,渲染目标处于一个干净的状态。
SDL_RenderClear(render_);

// 材质复制到渲染器
if (scale_w_ <= 0) scale_w_ = width_;
if (scale_h_ <= 0) scale_h_ = height_;

SDL_Rect rect;
SDL_Rect *prect = nullptr;
if (scale_w_ > 0) // 用户手动设置缩放
{
rect.x = 0;
rect.y = 0;
rect.h = scale_h_; // 渲染的宽高,可缩放
rect.w = scale_w_;
prect = &rect;
}

re = SDL_RenderCopy(render_, texture_, NULL, prect);
// 使用 SDL_RenderCopy 函数将纹理 texture_ 复制到渲染器 render_。
/*
render_:指向用于渲染的渲染器。
texture_:指向要渲染的纹理。
NULL:表示从纹理中复制整个图像,不限定源矩形区域。
prect:指向 SDL_Rect 结构体的指针,表示目标矩形区域,即将纹理复制到渲染目标的哪一部分。如果 prect 指向一个有效的 SDL_Rect 结构体,则纹理会被拉伸或缩小以适应该矩形。如果 prect 为 NULL,则纹理会被拉伸到整个渲染目标。
*/

if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

// 用于更新窗口的显示内容,即将之前使用渲染器绘制的所有内容呈现到屏幕上
SDL_RenderPresent(render_);
return true;
}

使用到了SDL_UpdateYUVTexture,而下面的Draw函数则是使用SDL_UpdateTexture

Draw(),处理YUV、RGB格式

XVideoView::DrawFrame进行数据转发

作用:接收 RGB 格式的图像数据,将其更新到纹理,并通过 SDL 渲染到窗口上。

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
80
81
82
83
84
85
86
87
88
89
// 处理RGBA RGB格式
bool XSDL::Draw(const unsigned char* data,
int linesize)
{
if (!data) return false;

std::unique_lock<std::mutex> sdl_lock(mtx_);

// 如果任何一个资源未初始化或者宽度和高度小于等于0,则返回false。
if (!texture_ || !render_ || !win_ || width_ <= 0 || height_ <= 0)
return false;

if (linesize <= 0)
{
switch (fmt_)
{
case XVideoView::RGBA:
linesize = width_ * 4;
break;
case XVideoView::YUV420P:
// 每个像素占用的内存布局较为复杂,通常宽度直接决定了linesize。
linesize = width_;
break;
case XVideoView::ARGB:
// 每个像素由4个字节组成,分别对应A、R、G和B四个通道。
linesize = width_ * 4;
break;
default:
break;
}
}
if (linesize <= 0) return false;

auto re = SDL_UpdateTexture(texture_, NULL, data, linesize);
/* 复制内存到显存
源码:
SDL_UpdateTexture(SDL_Texture * texture,
const SDL_Rect * rect,
const void *pixels,
int pitch);
*SDL_Texture texture:
指向要更新的纹理对象的指针。

*const SDL_Rect rect:
指向 SDL_Rect 结构体的指针,定义了要更新的纹理区域。如果传入 NULL,则更新整个纹理。

*const void pixels:
指向包含新像素数据的内存区域的指针。数据格式必须与创建纹理时指定的格式相匹配。

int pitch:
每一行像素数据的字节数。这通常是图像宽度乘以每个像素的字节数。例如,对于 ARGB 格式的图像,
如果宽度是 width_,那么 pitch 就是 width_ * 4。

*/

if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

// 清空屏幕
SDL_RenderClear(render_);

// 材质复制到渲染器
if (scale_w_ <= 0) scale_w_ = width_;
if (scale_h_ <= 0) scale_h_ = height_;

SDL_Rect rect;
SDL_Rect *prect = nullptr;
if (scale_w_ > 0) // 用户手动设置缩放
{
rect.x = 0;
rect.y = 0;
rect.h = scale_h_; // 渲染的宽高,可缩放
rect.w = scale_w_;
prect = &rect;
}

re = SDL_RenderCopy(render_, texture_, NULL, prect);
if (re != 0)
{
std::cout << SDL_GetError() << std::endl;
return false;
}

SDL_RenderPresent(render_);
return true;
}

为什么YUV420P不需要×4?

1
2
3
4
5
6
7
8
9
case XVideoView::RGBA:
linesize = width_ * 4;
break;
case XVideoView::YUV420P:
linesize = width_;
break;
case XVideoView::ARGB:
linesize = width_ * 4;
break;

在 RGBA 格式中,每个像素由 4 个字节表示(R、G、B 和 A 各占 1 个字节),因此 linesizewidth_ × 4

但是在 YUV420P 格式中:

YUV420P 是一种色度子采样格式,意味着 YUV 数据被分为三个独立的平面

  • Y 平面:每个像素只占用 1 个字节(即 Y 分量),因此 linesizewidth_
  • U 和 V 平面:由于色度分量的分辨率被降低到四分之一(色度采样为 4:2:0),每个平面的 linesizewidth_ / 2

因此,对于 YUV420P 格式,linesize 直接等于图像的宽度 width_ 而无需乘以 4。这样可以确保你只处理每个像素的亮度(Y 分量),而不是每个像素的所有颜色信息(RGBA)。

总结一下:YUV420P(YUV420P介绍)一个像素共用Y、U或V,而RGBA每个像素的颜色信息是独立的,四个通道(R、G、B、A)一起定义了像素的颜色和透明度。

使用场景

直接传入YUV数据

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
#include "sdlqtrgb.h"
#include <fstream>
#include <QMessageBox>
#include "xvideo_view.h"

static int sdl_width = 0;
static int sdl_height = 0;
static unsigned char* yuv = nullptr;
static int pix_size = 2;
static std::ifstream yuv_file;
static XVideoView *view = nullptr;

void SDLQtRGB::timerEvent(QTimerEvent* ev)
{
// Y占用一个像素,U占用1/4个像素,V占用1/4个像素
yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
if (view->IsExit())
{
view->Close();
exit(0);
}

view->Draw(yuv);
}

SDLQtRGB::SDLQtRGB(QWidget *parent)
: QWidget(parent)
{
// 打开yuv文件
yuv_file.open("400_300_25.yuv", std::ios::binary);

ui.setupUi(this);

sdl_width = 400;
sdl_height = 300;
this->resize(sdl_width, sdl_height); // 调整主窗口的尺寸
ui.label->resize(sdl_width, sdl_height);

// 创建XSDL
view = XVideoView::Create();

// 设置窗口句柄
set_win_id((void*)ui.label->winId());
// 初始化Init
view->Init(sdl_width, sdl_height,
XVideoView::YUV420P);

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

startTimer(40);
}

// 重载resizeEvent函数,使得放大、缩小屏幕,视频跟着放大、缩小
void SDLQtRGB::resizeEvent(QResizeEvent *ev)
{
ui.label->resize(size());
ui.label->move(0, 0);
view->Scale(width(), height());
}

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

使用AVFrame传入YUV数据

AVFrame 介绍

不单单可以传入YUV数据,也可以传入RGBA等数据

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include "sdlqtrgb.h"
#include <fstream>
#include <iostream>
#include <QMessageBox>
#include "xvideo_view.h"

extern "C"
{
#include <libavcodec/avcodec.h>
}
#pragma comment(lib, "avutil.lib")

static int sdl_width = 0;
static int sdl_height = 0;
static std::ifstream yuv_file;
static XVideoView *view = nullptr;
static AVFrame* frame = nullptr;

void SDLQtRGB::timerEvent(QTimerEvent* ev)
{
// YUV分开读取
// yuv420p存储方式
// 4 * 2
// yyyy yyyy
// u u
// v v
yuv_file.read((char*)frame->data[0], sdl_width * sdl_height); // Y
yuv_file.read((char*)frame->data[1], sdl_width * sdl_height / 4); // U
yuv_file.read((char*)frame->data[2], sdl_width * sdl_height / 4); // V

if (view->IsExit())
{
view->Close();
exit(0);
}

view->DrawFrame(frame);
// view->Draw(yuv);
}

SDLQtRGB::SDLQtRGB(QWidget *parent)
: QWidget(parent)
{
// 打开yuv文件
yuv_file.open("400_300_25.yuv", std::ios::binary);
if (!yuv_file)
{
QMessageBox::information(this, "", "open yuv failed!");
return;
}

ui.setupUi(this);

sdl_width = 400;
sdl_height = 300;
this->resize(sdl_width, sdl_height); // 调整主窗口的尺寸
ui.label->resize(sdl_width, sdl_height);
view = XVideoView::Create();
view->Init(sdl_width, sdl_height,
XVideoView::YUV420P);

view->Close();
view->Init(sdl_width, sdl_height,
XVideoView::YUV420P, (void*)ui.label->winId());

// 生成frame对象空间
frame = av_frame_alloc();
frame->width = sdl_width;
frame->height = sdl_height;
frame->format = AV_PIX_FMT_YUV420P;

// 【注意此处】:理解linesize的作用,不加就会出现花屏
frame->linesize[0] = sdl_width; // Y
frame->linesize[1] = sdl_width / 2; // U
frame->linesize[2] = sdl_width / 2; // V

// 生成存储图像空间 默认32字节对齐
auto re = av_frame_get_buffer(frame, 0);
if (re != 0)
{
char buf[1024] = { 0 };
av_strerror(re, buf, sizeof(buf));
std::cerr << buf << std::endl;
}

startTimer(40);
}

void SDLQtRGB::resizeEvent(QResizeEvent *ev)
{
ui.label->resize(size());
ui.label->move(0, 0);
view->Scale(width(), height());
}

SDLQtRGB::~SDLQtRGB()
{}