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庫的頭文件並且初始化它。
#include <SDL.h> #include <SDL_thread.h>
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); 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),我們將花足夠的時間來探討同步問題。但一開始我們會先忽略這個,因爲我們有更加重要的事情要處理:音頻!
完整代碼如下:
// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard,
// and a tutorial by Martin Bohme ([email protected])
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// Use
//
// gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed,
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial02 myvideofile.mpg
//
// to play the video stream on your screen.
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
//**************************************************************
#include <libavutil/avstring.h>
#include <libswscale/swscale.h>
//*************************************************************
}
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif
#include <stdio.h>
int main(int argc, char *argv[])
{
AVFormatContext *pFormatCtx=avformat_alloc_context();
int i, videoStream;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVFrame *pFrame;
AVPacket packet;
int frameFinished;
float aspect_ratio;
SwsContext *img_convert_ctx;
SDL_Overlay *bmp; // a YUV overlay on that screen so we can input video to it
SDL_Surface *screen; //The basic area for displaying images with SDL is called a surface:
SDL_Rect rect;// variable include the position(x,y)、width and height of the windows
SDL_Event event;// 關閉程序的事件
char * filename="C:\\1.mp4";
// Register all formats and codecs
av_register_all();
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
exit(1);
}
// Open video file
if(avformat_open_input(&pFormatCtx, filename, NULL, NULL)!=0)
return -1; // Couldn't open file
// Retrieve stream information
if(av_find_stream_info(pFormatCtx)<0)
return -1; // Couldn't find stream information
// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, argv[1], 0);
// Find the first video stream
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
if(videoStream==-1)
return -1; // Didn't find a video stream
// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;
// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
fprintf(stderr, "Unsupported codec!\n");
return -1; // Codec not found
}
// Open codec
if(avcodec_open(pCodecCtx, pCodec)<0)
return -1; // Could not open codec
// Allocate video frame
pFrame=avcodec_alloc_frame();
// Make a screen to put our video
#ifndef __DARWIN__
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit(1);
}
// Allocate a place to put our YUV image on that screen
bmp = SDL_CreateYUVOverlay(pCodecCtx->width,pCodecCtx->height,SDL_YV12_OVERLAY,screen);
// Read frames and save first five frames to disk
i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
// Is this a packet from the video stream?
if(packet.stream_index==videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
// Did we get a video frame?
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];//uint8_t *data[AV_NUM_DATA_POINTERS],這裏data是指針數組,
//數組中放的是都是指針
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_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_RGB24,SWS_BICUBIC, NULL, NULL, NULL);
sws_scale(img_convert_ctx,(const uint8_t* const*)pFrame->data,pFrame->linesize,0,pCodecCtx->height,pict.data,pict.linesize);
SDL_UnlockYUVOverlay(bmp);
rect.x = -10;
rect.y = -10;
rect.w = pCodecCtx->width;
rect.h = (pCodecCtx->height)/2;
SDL_DisplayYUVOverlay(bmp, &rect);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
SDL_PollEvent(&event);
switch(event.type)
{
case SDL_QUIT:
SDL_Quit();
exit(0);
break;
default:
break;
}
}
// Free the YUV frame
av_free(pFrame);
// Close the codec
avcodec_close(pCodecCtx);
// Close the video file
av_close_input_file(pFormatCtx);
system("pause");
return 0;
}