“SDL第八篇”支持倍速与慢放的YUV视频播放器
- 2020 年 4 月 2 日
- 笔记
前言
今天向大家介绍一下如何通过 SDL 实现一个YUV视频播放器。它与上次介绍的音频播放器一样,也是一个简单的不能再简单的播放器了。只不过一个是播放的音频PCM数据,另一个播放的时视频YUV数据。
该播放器不涉及到视频的解复用,解码等工作。我们只需要定时的刷新视频帧就可以了,而且还可以支持视频的倍速与慢放。在下面的列子中我将向你演示,使用 SDL 做这样一个播放器是何等的简单。
实现视频播放的原理
YUV播放器其实比较简单,就是设置一个定时间,每隔一段时间就渲染一帧数据。大家小时候都干过一件事儿,就是在自已的编习本上画几张连续的图,用手一翻就可以看到动画效果。一般情况下,每秒达到 25 帧就可以看到连续的效果,如果是 30帧以上动作就非常平滑。像现在的高清电影一般每秒达到60帧以上。
另外,如果原来每秒25帧的视频,现在你按每秒50帧播放就会起到倍速播放的效果。如果每秒 12帧,就会有慢动作的效果。
理解YUV
在我们开始介绍代码之前,你要先了解一下什么是YUV。YUV与RGB是什么关系呢?大家可以去看我的另一篇文件YUV视频格式详解,或看我在慕课网发布的音视频免费入门课程。
下面我们就来看一下代码吧。
例子
下面这个例子如果看了我前面的几篇文章就会觉得很简单了。都是一些常用的 SDL 的API调用做渲染和事件处理。
在这个例子中唯一需要说明的是,它在主线程中做渲染工作,同时启动了一个子线程做定时间。也就是说每 40ms 子线程就发送一个REFRESH 事件,发送完就去睡40ms。
主线程收到REFRESH事件后,就去做一次纹理渲染。渲染完成后再从文件中读一帧的数据。
如果想做倍速播放,你可以调整一下 delay时间,如果从 40ms 减为 20ms 播放速度就会快一倍。如果40ms调整为 80ms播放速度就会慢一倍。
include <stdio.h> #include <string.h> #include <SDL.h> //event message #define REFRESH_EVENT (SDL_USEREVENT + 1) #define QUIT_EVENT (SDL_USEREVENT + 2) int thread_exit=0; int refresh_video_timer(void *udata){ thread_exit=0; while (!thread_exit) { SDL_Event event; event.type = REFRESH_EVENT; SDL_PushEvent(&event); SDL_Delay(40); } thread_exit=0; //push quit event SDL_Event event; event.type = QUIT_EVENT; SDL_PushEvent(&event); return 0; } int main(int argc, char* argv[]) { FILE *video_fd = NULL; SDL_Event event; SDL_Rect rect; Uint32 pixformat = 0; SDL_Window *win = NULL; SDL_Renderer *renderer = NULL; SDL_Texture *texture = NULL; SDL_Thread *timer_thread = NULL; int w_width = 608, w_height = 368; Uint8 *video_pos = NULL; Uint8 *video_end = NULL; unsigned int remain_len = 0; size_t video_buff_len = 0; size_t blank_space_len = 0; Uint8 *video_buf = NULL; const char *path = "1.yuv"; const unsigned int yuv_frame_len = video_width * video_height * 12 / 8; unsigned int tmp_yuv_frame_len = yuv_frame_len; if (yuv_frame_len & 0xF) { tmp_yuv_frame_len = (yuv_frame_len & 0xFFF0) + 0x10; } //initialize SDL if(SDL_Init(SDL_INIT_VIDEO)) { fprintf( stderr, "Could not initialize SDL - %sn", SDL_GetError()); return -1; } //creat window from SDL win = SDL_CreateWindow("YUV Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w_width, w_height, SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE); if(!win) { fprintf(stderr, "Failed to create window, %sn",SDL_GetError()); goto __FAIL; } renderer = SDL_CreateRenderer(win, -1, 0); //IYUV: Y + U + V (3 planes) //YV12: Y + V + U (3 planes) pixformat= SDL_PIXELFORMAT_IYUV; //create texture for render texture = SDL_CreateTexture(renderer, pixformat, SDL_TEXTUREACCESS_STREAMING, video_width, video_height); //alloc space video_buf = (Uint8*)malloc(tmp_yuv_frame_len); if(!video_buf){ fprintf(stderr, "Failed to alloce yuv frame space!n"); goto __FAIL; } //open yuv file video_fd = fopen(path, "r"); if( !video_fd ){ fprintf(stderr, "Failed to open yuv filen"); goto __FAIL; } if((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){ fprintf(stderr, "Failed to read data from yuv file!n"); goto __FAIL; } //set video positon video_pos = video_buf; /* video_end = video_buf + video_buff_len; blank_space_len = BLOCK_SIZE - video_buff_len; */ timer_thread = SDL_CreateThread(refresh_video_timer, NULL, NULL); do { //Wait SDL_WaitEvent(&event); if(event.type==REFRESH_EVENT){ SDL_UpdateTexture( texture, NULL, video_pos, video_width); //FIX: If window is resize rect.x = 0; rect.y = 0; rect.w = w_width; rect.h = w_height; SDL_RenderClear( renderer ); SDL_RenderCopy( renderer, texture, NULL, &rect); SDL_RenderPresent( renderer ); //read block data if((video_buff_len = fread(video_buf, 1, yuv_frame_len, video_fd)) <= 0){ thread_exit =1; continue; } //memset(video_buf+(video_width*video_height), 0, (video_width*video_height)/2); }else if(event.type==SDL_WINDOWEVENT){ //If Resize SDL_GetWindowSize(win, &w_width, &w_height); }else if(event.type==SDL_QUIT){ thread_exit=1; }else if(event.type==QUIT_EVENT){ break; } }while ( 1 ); __FAIL: if(video_buf){ free(video_buf); } //close file if(video_fd){ fclose(video_fd); } SDL_Quit(); return 0; }
小结
本文件介绍了一个简单的YUV播放器是如何实现的。同时还介绍了如何让YUV视频倍速播放与慢速播放。
希望本文能对你所有帮助,谢谢!