SDL和視頻編程
爲了在屏幕上顯示,咱們將使用SDL.SDL是Simple Direct Layer的縮寫。它是一個出色的多媒體庫,適用於多平臺,而且被用在許多工程中。你能夠從它的官方網站的網址http://www.libsdl.org/上來獲得這個庫的源代碼或者若是有可能的話你能夠直接下載開發包到你的操做系統中。按照這個指導,你將須要編譯這個庫。(剩下的幾個指導中也是同樣)數組
SDL庫中有許多種方式來在屏幕上繪製圖形,並且它有一個特殊的方式來在屏幕上顯示圖像――這種方式叫作YUV覆蓋。YUV(從技術上來說並不叫YUV而是叫作YCbCr)是一種相似於RGB方式的存儲原始圖像的格式。粗略的講,Y是亮度份量,U和V是色度份量。(這種格式比RGB複雜的多,由於不少的顏色信息被丟棄了,並且你能夠每2個Y有1個U和1個V)。SDL的YUV覆蓋使用一組原始的YUV數據而且在屏幕上顯示出他們。它能夠容許4種不一樣的YUV格式,可是其中的YV12是最快的一種。還有一個叫作YUV420P的YUV格式,它和YV12是同樣的,除了U和V份量的位置被調換了之外。420意味着它以4:2:0的比例進行了二次抽樣,基本上就意味着1個顏色份量對應着4個亮度份量。因此它的色度信息只有原來的1/4。這是一種節省帶寬的好方式,由於人眼感受不到這種變化。在名稱中的P表示這種格式是平面的――簡單的說就是Y,U和V份量分別在不一樣的數組中。FFMPEG能夠把圖像格式轉換爲YUV420P,可是如今不少視頻流的格式已是YUV420P的了或者能夠被很容易的轉換成YUV420P格式。多線程
因而,咱們如今計劃把指導1中的SaveFrame()函數替換掉,讓它直接輸出咱們的幀到屏幕上去。但一開始咱們必須要先看一下如何使用SDL庫。首先咱們必需先包含SDL庫的頭文件而且初始化它。ide
#include <SDL.h>函數 #include <SDL_thread.h>網站
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {ui fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());spa exit(1);操作系統 }線程 |
SDL_Init()函數告訴了SDL庫,哪些特性咱們將要用到。固然SDL_GetError()是一個用來手工除錯的函數。
建立一個顯示
如今咱們須要在屏幕上的一個地方放上一些東西。在SDL中顯示圖像的基本區域叫作面surface。
SDL_Surface *screen;
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); if(!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } |
這就建立了一個給定高度和寬度的屏幕。下一個選項是屏幕的顏色深度――0表示使用和當前同樣的深度。(這個在OS X系統上不能正常工做,緣由請看源代碼)
如今咱們在屏幕上來建立一個YUV覆蓋以便於咱們輸入視頻上去:
SDL_Overlay *bmp;
bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); |
正如前面咱們所說的,咱們使用YV12來顯示圖像。
顯示圖像
前面那些都是很簡單的。如今咱們須要來顯示圖像。讓咱們看一下是如何來處理完成後的幀的。咱們將原來對RGB處理的方式,而且替換SaveFrame()爲顯示到屏幕上的代碼。爲了顯示到屏幕上,咱們將先創建一個AVPicture結構體而且設置其數據指針和行尺寸來爲咱們的YUV覆蓋服務:
if(frameFinished) { SDL_LockYUVOverlay(bmp);
AVPicture pict; pict.data[0] = bmp->pixels[0]; pict.data[1] = bmp->pixels[2]; pict.data[2] = bmp->pixels[1];
pict.linesize[0] = bmp->pitches[0]; pict.linesize[1] = bmp->pitches[2]; pict.linesize[2] = bmp->pitches[1];
// Convert the image into YUV format that SDL uses img_convert(&pict, PIX_FMT_YUV420P, (AVPicture *)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
SDL_UnlockYUVOverlay(bmp); } |
首先,咱們鎖定這個覆蓋,由於咱們將要去改寫它。這是一個避免之後發生問題的好習慣。正如前面所示的,這個AVPicture結構體有一個數據指針指向一個有4個元素的指針數據。因爲咱們處理的是YUV420P,因此咱們只須要3個通道即只要三組數據。其它的格式可能須要第四個指針來表示alpha通道或者其它參數。行尺寸正如它的名字表示的意義同樣。在YUV覆蓋中相同功能的結構體是像素pixel和程度pitch。(程度pitch是在SDL裏用來表示指定行數據寬度的值)。因此咱們如今作的是讓咱們的覆蓋中的pict.data中的三個指針有一個指向必要的空間的地址。相似的,咱們能夠直接從覆蓋中獲得行尺寸信息。像前面同樣咱們使用img_convert來把格式轉換成PIX_FMT_YUV420P。
繪製圖像
但咱們仍然須要告訴SDL如何來實際顯示咱們給的數據。咱們也會傳遞一個代表電影位置、寬度、高度和縮放大小的矩形參數給SDL的函數。這樣,SDL爲咱們作縮放而且它能夠經過顯卡的幫忙來進行快速縮放。
SDL_Rect rect;
if(frameFinished) {
// Convert the image into YUV format that SDL uses img_convert(&pict, PIX_FMT_YUV420P, (AVPicture *)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
SDL_UnlockYUVOverlay(bmp); rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_DisplayYUVOverlay(bmp, &rect); } |
如今咱們的視頻顯示出來了!
讓咱們再花一點時間來看一下SDL的特性:它的事件驅動系統。SDL被設置成當你在SDL中點擊或者移動鼠標或者向它發送一個信號它都將產生一個事件的驅動方式。若是你的程序想要處理用戶輸入的話,它就會檢測這些事件。你的程序也能夠產生事件而且傳遞給SDL事件系統。當使用SDL進行多線程編程的時候,這至關有用,這方面代碼咱們能夠在指導4中看到。在這個程序中,咱們將在處理完包之後就當即輪詢事件。如今而言,咱們將處理SDL_QUIT事件以便於咱們退出:
SDL_Event event;
av_free_packet(&packet); SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: SDL_Quit(); exit(0); break; default: break; } |
讓咱們去掉舊的冗餘代碼,開始編譯。若是你使用的是Linux或者其變體,使用SDL庫進行編譯的最好方式爲:
gcc -o tutorial02 tutorial02.c -lavutil -lavformat -lavcodec -lz -lm \ `sdl-config --cflags --libs` |
這裏的sdl-config命令會打印出用於gcc編譯的包含正確SDL庫的適當參數。爲了進行編譯,在你本身的平臺你可能須要作的有點不一樣:請查閱一下SDL文檔中關於你的系統的那部分。一旦能夠編譯,就立刻運行它。
當運行這個程序的時候會發生什麼呢?電影簡直跑瘋了!實際上,咱們只是以咱們能從文件中解碼幀的最快速度顯示了全部的電影的幀。如今咱們沒有任何代碼來計算出咱們何時須要顯示電影的幀。最後(在指導5),咱們將花足夠的時間來探討同步問題。但一開始咱們會先忽略這個,由於咱們有更加劇要的事情要處理:音頻!