0.引言
本篇文章主要讲解如何使用SDL接口去显示YUV数据,在阅读本文前,可以阅读前面文章,学习和了解SDL的基础知识。文章列表如下:
详解SDL常用技术实战(1)
示例代码的基本功能就是从文件中读取yuv数据,然后通过SDL的接口,去40ms刷新一帧数据。把这个刷新事件放到线程队列,由主线程去读取,然后刷新。本次程序是支持窗口的缩放。捕获窗口大小变化的事件,然后根据窗口大小的变化,按照一定的比例去缩放就可以了。至于这个缩放后显示的位置,在以后再讲。
SDL的wiki链接:
http://wiki.libsdl.org/FrontPage
界面如下:
1.SDL渲染相关的数据结构
在代码定义时,定义了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
2.YUV图像显示流程
通过代码演示如何从YUV文件中读取数据,然后通过SDL的接口去显示在窗口。需要经过如下步骤:
(1)SDL_Init()
初始化SDL,表示一个视频显示窗口,示例代码如下:
SDL_Init(SDL_INIT_VIDEO)
(2)SDL_CreateWindow()
创建窗口,窗口的大小,默认使用给YUV定义的分辨率。示例代码如下:
window = SDL_CreateWindow("Simplest YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, video_width, video_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
(3)SDL_CreateThread()
创建请求刷新线程,实例代码如下:
timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);
(4)SDL_CreateRender()
根据步骤2创建的窗口的基础上,创建一个渲染器,示例代码如下:
renderer = SDL_CreateRenderer(window, -1, 0);
(5)SDL_WaitEvent()
等待事件的发生,主线程在这里主要是捕获图像刷新、窗口大小变化及界面退出事件。示例代码如下:
timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL);
(6)SDL_CreateTexture()
根据步骤4创建的渲染器基础上,创建纹理(需要指定像素格式和分辨率),示例代码如下:
texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, video_width, video_height);
(7)event.type == REFRESH_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!"); goto _FAIL; } // 设置纹理的数据 video_width = 320, 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; // 320x240 怎么保持原视频的宽高比例 //如果窗口的值变化了,这里就会更新 rect.w = video_width * w_ratio; rect.h = video_height * h_ratio; 。。。。。。。。
(8)SDL_UpdateTexture()
把新的像素数据放进来,并更新纹理。示例代码如下:
SDL_UpdateTexture(texture, NULL, video_buf, video_width);
(9)SDL_RenderClear()
在准备刷下一帧时,需要清除当前帧。示例代码如下:
SDL_RenderClear(renderer);
(10)SDL_RenderCopy()
将纹理的数据,拷贝给渲染器,去显示。示例代码如下:
SDL_RenderCopy(renderer, texture, NULL, &rect);
(11)SDL_RenderPresent()
渲染器,更新。示例代码如下:
SDL_RenderPresent(renderer);
(12)event.type == SDL_WINDOWEVENT
窗口变化事件,实时捕获窗口的大小。示例代码如下:
else if(event.type == SDL_WINDOWEVENT) { //If Resize SDL_GetWindowSize(window, &win_width, &win_height); printf("SDL_WINDOWEVENT win_width:%d, win_height:%d",win_width, win_height ); }
(13)event.type == SDL_QUIT和event.type == QUIT_EVENT
这里要捕获系统退出事件和自定义退出事件,示例代码如下:
else if(event.type == SDL_QUIT) //退出事件 { s_thread_exit = 1; } else if(event.type == QUIT_EVENT) { break; }
(14)等待线程退出和资源释放
退出之前一定要释放资源,等待子线程退出,示例代码如下:
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();
3.图像刷新线程
本示例代码创建了一个线程,由这个线程去控制刷新的时间和退出。刷新事件控制在40ms刷新一次。在退出时,由子线程打出一个退出事件给子线程,这样主线程才能正确退出。这背后的逻辑就是UI界面的刷新一般都是主线程在操作。示例代码如下:
int refresh_video_timer(void *data){ 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;}
4.图像显示效果
5.总结
本篇文章通过SDL接口演示了如何显示YUV数据。能够快速掌握SDL显示的基本流程。欢迎关注,收藏,转发,分享。
后期关于项目知识,也会更新在微信公众号“记录世界 from antonio”,欢迎关注