SDL视频显示的流程
实现流程
准备视频文件
准备一个格式为yuv420p
,分辨率为320x240
的yuv
数据,并且将视频文件放入项目构建的目录下:
初始化SDL
初始化SDL
的视频模块
//初始化 SDL
if(SDL_Init(SDL_INIT_VIDEO))
{
fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
创建一个窗口
- 创建一个SDL窗口,用于纹理渲染
- 初始时窗口大小为
YUV视频
分辨率大小
#define YUV_WIDTH 320
#define YUV_HEIGHT 240
SDL_Window *window = NULL;
int win_width = YUV_WIDTH;
int win_height = YUV_WIDTH;
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;
}
设置YUV
缓存
设置每次读取视频帧的缓存大小,这里每次就读取1
帧即可,计算方式为:Y
分量+U
分量+V
分量大小
YUV
格式为yuv420p
Y
分量大小为:Width * Height
U
分量大小为:Width * Height /4
V
分量大小为:Width * Height / 4
yuv420p
格式示例图
代码如下:
uint8_t *video_buf = NULL;
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;
video_buf = (uint8_t*)malloc(yuv_frame_len);
if(!video_buf)
{
fprintf(stderr, "Failed to alloce yuv frame space!\n");
goto _FAIL;
}
创建渲染器
创建渲染器,用于视频纹理的渲染
SDL_Renderer *renderer = NULL;
renderer = SDL_CreateRenderer(window, -1, 0);
创建纹理
根据yuv420p
数据格式
其中:
YUV_FORMAT
为YUV420P
SDL_TEXTUREACCESS_STREAMING
指定纹理的访问方式,STREAMING
模式允许高效更新纹理数据,用于视频渲染
SDL_Texture *texture = NULL;
uint32_t pixformat = YUV_FORMAT; // YUV420P
texture = SDL_CreateTexture(renderer,
pixformat,
SDL_TEXTUREACCESS_STREAMING,
video_width,
video_height);
打开yuv
文件
使用文件操作,二进制读的方式打开视频文件
// 打开YUV文件
video_fd = fopen(yuv_path, "rb");
if( !video_fd )
{
fprintf(stderr, "Failed to open yuv file\n");
goto _FAIL;
}
创建定时器
- 创建一个定时器,用于定时刷新视频帧,控制
fps
- 实际上就是创建一个新的线程,定期唤醒加入自定义的刷新事件
创建定时器
// 创建请求刷新线程
timer_thread = SDL_CreateThread(refresh_video_timer,
NULL,
NULL);
定时器线程函数
每次延时40ms左右,大概25fps
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
#define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件
int s_thread_exit = 0; // 退出标志 = 1则退出
int refresh_video_timer()
{
while (!s_thread_exit)
{
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
s_thread_exit = 0;
//push quit event
SDL_Event event;
event.type = QUIT_EVENT;
SDL_PushEvent(&event);
return 0;
}
开启事件循环
开启事件循环,进行视频渲染操作
等待事件
- 等待事件加入队列中,并阻塞在此
SDL_WaitEvent(&event);
画面刷新事件
- 定时器周期地加入刷新事件
- 读取
yuv
文件,并加入到纹理中 - 设置渲染的矩形窗口为当前窗口的大小以及开始的位置
- 将纹理的数据拷贝到
CPU
端 - 渲染
CPU
端的数据
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 = 320, plane
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
// 显示区域,可以通过修改w和h进行缩放
rect.x = 0;
rect.y = 0;
rect.w = win_width;
rect.h = win_height;
// 清除当前显示
SDL_RenderClear(renderer);
// 将纹理的数据拷贝给渲染器
SDL_RenderCopy(renderer, texture, NULL, &rect);
// 显示
SDL_RenderPresent(renderer);
}
窗口事件
- 接收窗口事件(如窗口移动,窗口大小改变等)
- 暂时不做操作
退出事件
- 包括自定义的退出事件
QUIT_EVENT
- 以及
QUIT_EVENT
,比如(关闭窗口,调用SDL_QUIT
等) - 调用退出事件后,会通知定时器停止操作,并且退出循环,进行内存清理
while (1)
{
// 收取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 = 320, plane
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
// 显示区域,可以通过修改w和h进行缩放
rect.x = 0;
rect.y = 0;
rect.w = win_width;
rect.h = win_height;
// 清除当前显示
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 win_width:%d, win_height:%d\n",win_width,
win_height );
}
else if(event.type == SDL_QUIT) //退出事件
{
s_thread_exit = 1;
}
else if(event.type == QUIT_EVENT)
{
break;
}
}
关闭操作和内存清理
渲染结束或手动关闭窗口后,需要关闭文件和SDL
子系统,并且释放相关内存
_FAIL:
s_thread_exit = 1; // 保证线程能够退出
// 释放资源
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;
}
实现的效果
实现的效果如下:
整体代码
main.c
#include <stdio.h>
#include <string.h>
#include <SDL.h>
//自定义消息类型
#define REFRESH_EVENT (SDL_USEREVENT + 1) // 请求画面刷新事件
#define QUIT_EVENT (SDL_USEREVENT + 2) // 退出事件
//定义分辨率
// YUV像素分辨率
#define YUV_WIDTH 320
#define YUV_HEIGHT 240
//定义YUV格式
#define YUV_FORMAT SDL_PIXELFORMAT_IYUV
int s_thread_exit = 0; // 退出标志 = 1则退出
int refresh_video_timer()
{
while (!s_thread_exit)
{
SDL_Event event;
event.type = REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
s_thread_exit = 0;
//push quit event
SDL_Event event;
event.type = QUIT_EVENT;
SDL_PushEvent(&event);
printf("finish Timer\n");
return 0;
}
#undef main
int main(int argc, char* argv[])
{
//初始化 SDL
if(SDL_Init(SDL_INIT_VIDEO))
{
fprintf( stderr, "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
// 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_WIDTH;
// YUV文件句柄
FILE *video_fd = NULL;
const char *yuv_path = "yuv420p_320x240.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);
// 基于渲染器创建纹理
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 alloce 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 (1)
{
// 收取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 = 320, plane
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
rect.w = win_width;
rect.h = win_height;
// 显示区域,可以通过修改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;
// // 320x240 怎么保持原视频的宽高比例
// rect.w = video_width * w_ratio;
// rect.h = video_height * h_ratio;
// rect.w = video_width * 0.5;
// rect.h = video_height * 0.5;
// 清除当前显示
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 win_width:%d, win_height:%d\n",win_width,
win_height );
}
else if(event.type == SDL_QUIT) //退出事件
{
s_thread_exit = 1;
}
else if(event.type == QUIT_EVENT)
{
break;
}
}
_FAIL:
s_thread_exit = 1; // 保证线程能够退出
// 释放资源
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;
}
更多资料
更多资料参考:https://github.com/0voice