YUV 文件是一种存储 视频原始数据 的格式,它不进行压缩(或仅轻度压缩),主要用于专业视频处理、图像分析、计算机视觉等领域。以下是关于 YUV 文件的详细说明:
1. YUV 是什么?
- YUV 是一种颜色编码系统,与常见的 RGB(红绿蓝)不同,它将图像数据分成:
- Y(亮度/Luma):决定图像的明暗信息(黑白部分)。
- U & V(色度/Chroma):存储颜色信息(色彩饱和度、色调)。
- 这种分离设计最初是为了兼容黑白与彩色电视信号,现在广泛用于视频压缩(如 H.264、H.265)和数字视频处理。
2. YUV 文件的特点
- 原始数据:通常未经压缩,文件体积较大(例如 1 分钟 1080 p 视频可能占数 GB)。
- 多种子格式:根据 UV 分量的采样方式不同,分为多种子格式:
- YUV420:最常用(如 MP4、H.264 视频的底层存储),色度信息缩减采样以节省空间。
- YUV 444:高质量无缩减(用于专业影视后期)。
- 其他变体:YUV 422、NV 12 等。
- 无标准封装:YUV 文件通常只是纯二进制数据流,需额外说明分辨率、采样格式才能正确解析。
3. YUV 文件的常见用途
- 视频编解码开发:测试编码器/解码器的性能(如 FFmpeg 测试)。
- 计算机视觉:人脸识别、运动检测等算法处理原始视频数据。
- 影视后期:无损编辑或色彩校正时保留最大信息量。
- 学术研究:图像处理论文中常用 YUV 序列作为测试素材。
4. YUV vs. RGB
特性 | YUV | RGB |
---|---|---|
数据分离 | 亮度与色度分离 | 红绿蓝三通道混合 |
压缩效率 | 更高(适合视频编码) | 较低 |
常见用途 | 视频存储、传输 | 图像显示、游戏渲染 |
代码
#include <SDL2/SDL.h>
#include <stdio.h>
#include <string.h>
#define YUM_HEIGHT 240
#define YUM_WIDTH 320
int s_thread_exit = 0;
#define FF_REFRESH_EVENT (SDL_USEREVENT + 1)
#define FF_QUIT_EVENT (SDL_USEREVENT + 2)
int refresh_video_func(void* val) {
while(!s_thread_exit) {
SDL_Event event;
event.type = FF_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
s_thread_exit = 0;
SDL_Event event;
event.type = FF_QUIT_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[]) {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window* window = NULL;
SDL_Renderer* render = NULL;
SDL_Texture* texture = NULL;
SDL_Rect rect;
SDL_Event event;
SDL_Thread* video_thread;
int win_width = YUM_WIDTH;
int win_height = YUM_HEIGHT;
int video_width = YUM_WIDTH;
int video_height = YUM_HEIGHT;
int video_buf_len = 0;
uint32_t y_frame_len = video_height * video_width;
uint32_t u_frame_len = y_frame_len / 4;
uint32_t v_frame_len = y_frame_len / 4;
uint32_t one_frame_len = y_frame_len + u_frame_len + v_frame_len;
window = SDL_CreateWindow("YUV Video", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, win_width, win_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
render = SDL_CreateRenderer(window, -1, 0);
texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, video_width, video_height);
uint8_t* video_buf = (uint8_t*)malloc(one_frame_len);
const char* video_path = "/Users/lenn/Workspace/sdl-learn/sdl-yuv/yuv420p_320x240.yuv";
FILE* video_fd = fopen(video_path, "rb");
if (!video_fd) {
fprintf(stderr, "Open yuv file failed\n");
goto FAILED;
}
video_thread = SDL_CreateThread(refresh_video_func, NULL, NULL);
printf("after create thread\n");
while(1) {
SDL_WaitEvent(&event);
if (event.type == FF_REFRESH_EVENT) {
video_buf_len = fread(video_buf, 1, one_frame_len, video_fd);
if (video_buf_len <= 0) {
goto FAILED;
}
rect.x = 0;
rect.y = 0;
float w_ratio = win_width * 1.0 /video_width;
float h_ratio = win_height * 1.0 /video_height;
rect.w = video_width * w_ratio;
rect.h = video_height * h_ratio;
// rect.w = win_width;
// rect.h = win_height;
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, &rect);
SDL_RenderPresent(render);
}
else if (event.type == SDL_WINDOWEVENT) {
SDL_GetWindowSize(window, &win_width, &win_height);
}
else if (event.type == SDL_QUIT) {
s_thread_exit = 1;
}
else if ( event.type == FF_QUIT_EVENT) {
break;
}
}
FAILED:
s_thread_exit = 1;
free(video_buf);
fclose(video_fd);
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(render);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
解读
计算一帧的大小
uint32_t y_frame_len = video_height * video_width;
uint32_t u_frame_len = y_frame_len / 4;
uint32_t v_frame_len = y_frame_len / 4;
uint32_t one_frame_len = y_frame_len + u_frame_len + v_frame_len;
首先是这一段帧大小计算,如何计算一帧的大小。首先我们的视频格式是 YUV420p
,在这个格式中,四个 Y 分量对应一个 u 和 v 分量,也就是说 Y 和 uv 的采样比是 4:1。那么 Y 分量的计算就是画面的 长*宽
,而 uv 分量就是他的 1/4。一帧的大小就是三个分量的大小相加。
纹理的创建
texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, video_width, video_height);
这里其实是 SDL 定义的宏,用来解析 YUVP 的帧数据。
更新纹理
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
这里的传参可以会比较奇怪,传了一个帧数据和宽度,其实这里是有说法的。
如果第二个参数给 NULL 的话就是更新已经 entire 过的纹理,这个纹理在我们创建的时候就已经初始化过了。然后 pitch
参数就是描述帧数据的宽(row)的。
评论