­

「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視頻倍速播放與慢速播放。

希望本文能對你所有幫助,謝謝!