SDL_Init
SDL_CreateWindow
SDL_Window
SDL_CreateRenderer
SDL_Renderer
SDL_CreateTexture
SDL_Texture <-- 读取本地YUV文件
SDL_UpdateTexture <-- YUV data <-- 本地文件
SDL_RenderClear
SDL_RenderCopy
SDL_RenderPresent <-- 显示
用ffmpeg提取测试文件
ffmpeg -i test.mp4 -vf format=yuv420p output.yuv
全局
#define TRUE 1
#define FALSE 0
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新时间
#define FF_QUIT_EVENT (SDL_USEREVENT + 2) // 用户自定义事件
#define QUIT_EVENT (SDL_USEREVENT + 3) // 退出事件
// 定义分辨率 YUV 像素分辨率
#define YUV_WIDTH 1920
#define YUV_HEIGHT 1080
// 定义 YUV 格式
#define YUV_FORMAT SDL_PIXELFORMAT_IYUV
int s_thread_exit = FALSE; // 退出标志 = 1 则退出
线程回调函数
int refresh_video_timer(void *data)
{
while (!s_thread_exit)
{
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40); // 40毫秒发一次刷新事件
}
s_thread_exit = FALSE;
// 触发退出事件
SDL_Event event;
event.type = QUIT_EVENT;
SDL_PushEvent(&event);
return 0;
}
播放
int YUV_video_display()
{
SDL_Init(SDL_INIT_VIDEO);
// SDL
SDL_Event event; // 事件
SDL_Rect rect; // 矩形
SDL_Window *window = NULL; // 窗口
SDL_Renderer *renderer = NULL; // 渲染
SDL_Texture *texture = NULL; // 纹理
SDL_Thread *timer_thread = NULL; // 请求刷新线程
uint32_t pixformat = YUV_FORMAT; // YUV420P --> SDL_PIXELFORMAT_IYUV
// 分辨率
// 1.YUV 的分辨率
int video_width = YUV_WIDTH;
int video_height = YUV_HEIGHT;
// 2.显示窗口的分辨率
int win_width = YUV_WIDTH;
int win_height = YUV_HEIGHT;
// YUV 文件句柄
FILE *video_fd = NULL;
const char *yuv_path = "/home/king/ffmpeg_src/lessonCode/ZeroVoiceEducation/01_lesson/yuv420pTest.yuv";
size_t video_buff_len = 0;
uint8_t *video_buf = NULL; // 读取数据后先把放到buffer里面
// 测试文件YUV420P格式
uint32_t y_frame_len = video_width * video_height;
uint32_t u_frame_len = video_width * video_height / 4;
uint32_t v_frame_len = video_width * video_height / 4;
uint32_t yuv_frame_len = y_frame_len + u_frame_len + v_frame_len;
// 创建窗口
window = SDL_CreateWindow("Simplest YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_width, video_height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
if (!window)
{
fprintf(stderr, "SDL: could not create window,err:%s\n", SDL_GetError());
goto _FAIL;
}
// 基于窗口创建渲染器
renderer = SDL_CreateRenderer(window, -1, 0);
// 基于渲染器创建纹理 | SDL_TEXTUREACCESS_STREAMING - 动态显示流的方式
texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, video_width, video_height);
// 分配空间
video_buf = (uint8_t *)malloc(yuv_frame_len);
if (!video_buf)
{
fprintf(stderr, "Failed to alloc yuv frame space!\n");
goto _FAIL;
}
// 打开YUV文件
video_fd = fopen(yuv_path, "rb");
if (!video_fd)
{
fprintf(stderr, "Failed to open yuv file\n");
goto _FAIL;
}
// 创建请求刷新线程
timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);
while (TRUE)
{
// 收取SDL系统里面的事件
SDL_WaitEvent(&event);
if (event.type == REFRESH_EVENT) // 画面刷新事件
{
video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd);
if (video_buff_len <= 0)
{
fprintf(stderr, "Failed to read data from yuv file!\n");
goto _FAIL;
}
// 设置纹理的数据 video_width = 1080 - plane
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
// 显示区域,可以修改w和h进行缩放
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;
// 清除当前显示
SDL_RenderClear(renderer);
// 将纹理的数据拷贝给渲染器
SDL_RenderCopy(renderer, texture, NULL, &rect);
// 显示
SDL_RenderPresent(renderer);
}
else if (event.type == SDL_WINDOWEVENT)
{
// if resize
SDL_GetWindowSize(window, &win_width, &win_height);
printf("SDL_WINDOWEVENT --窗口宽度:%d--, --窗口高度:%d--\n", win_width, win_height);
}
else if (event.type == SDL_QUIT) // SDL 标准退出事件
{
s_thread_exit = TRUE;
}
else if (event.type == QUIT_EVENT) // 自定义退出事件
{
break;
}
}
_FAIL:
s_thread_exit = TRUE; // 保证线程能够退出
// 释放资源
if (timer_thread)
SDL_WaitThread(timer_thread, NULL); // 等待线程退出
if (video_buf)
free(video_buf);
if (video_fd)
fclose(video_fd);
if (texture)
SDL_DestroyTexture(texture);
if (renderer)
SDL_DestroyRenderer(renderer);
if (window)
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
运行结果: