「SDL第八篇」支持倍速与慢放的YUV视频播放器

前言

今天向大家介绍一下如何通过 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视频倍速播放与慢速播放。

希望本文能对你所有帮助,谢谢!