「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視頻倍速播放與慢速播放。
希望本文能對你所有幫助,謝謝!