ffmpeg和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库的头文件并且初始化它。

[cpp]  view plain copy
  1. #include <SDL.h>   
  2.   
  3. #include <SDL_thread.h>   
  4.   
  5. if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {   
  6.   
  7. fprintf(stderr, "Could not initialize SDL - %s/n", SDL_GetError());   
  8.   
  9. exit(1);   
  10.   
  11. }   

SDL_Init()函数告诉�?span lang="EN-US">SDL库,哪些特性我们将要用到。当�?span lang="EN-US">SDL_GetError()是一个用来手工除错的函数�?span lang="EN-US">

创建一个显�?span lang="EN-US">

现在我们需要在屏幕上的一个地方放上一些东西。在SDL中显示图像的基本区域叫做surface�?/span>

 

[c-sharp]  view plain copy
  1. SDL_Surface *screen;   
  2. screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);   
  3. if(!screen)   
  4. {   
  5. fprintf(stderr, "SDL: could not set video mode - exiting/n");  
  6. exit(1);   
  7. }   

这就创建了一个给定高度和宽度的屏幕。下一个选项是屏幕的颜色深度―�?span lang="EN-US">0表示使用和当前一样的深度。(这个�?span lang="EN-US">OS X系统上不能正常工作,原因请看源代码)

现在我们在屏幕上来创建一�?span lang="EN-US">YUV覆盖以便于我们输入视频上去:

SDL_Overlay *bmp;

bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen);

正如前面我们所说的,我们使�?span lang="EN-US">YV12来显示图像�?span lang="EN-US">

显示图像

前面那些都是很简单的。现在我们需要来显示图像。让我们看一下是如何来处理完成后的帧的。我们将原来�?span lang="EN-US">RGB处理的方式,并且替换 SaveFrame() 为显示到屏幕上的代码。为了显示到屏幕上,我们将先建立一�?span lang="EN-US">AVPicture结构体并且设置其数据指针和行尺寸来为我们�?span lang="EN-US">YUV覆盖服务�?/span>

 

[cpp]  view plain copy
  1. if(frameFinished)   
  2.   
  3. {   
  4.   
  5. SDL_LockYUVOverlay(bmp);   
  6.   
  7. AVPicture pict;   
  8.   
  9. pict.data[0] = bmp->pixels[0];   
  10.   
  11. pict.data[1] = bmp->pixels[2];   
  12.   
  13. pict.data[2] = bmp->pixels[1];   
  14.   
  15. pict.linesize[0] = bmp->pitches[0];   
  16.   
  17. pict.linesize[1] = bmp->pitches[2];   
  18.   
  19. pict.linesize[2] = bmp->pitches[1];   
  20.   
  21. // Convert the image into YUV format that SDL uses   
  22.   
  23. img_convert(&pict, PIX_FMT_YUV420P, (AVPicture *)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);   
  24.   
  25. SDL_UnlockYUVOverlay(bmp);   
  26.   
  27. }   

首先,我们锁定这个覆盖,因为我们将要去改写它。这是一个避免以后发生问题的好习惯。正如前面所示的,这�?span lang="EN-US">AVPicture结构体有一个数据指针指向一个有4个元素的指针数据。由于我们处理的�?span lang="EN-US">YUV420P,所以我们只需�?span lang="EN-US">3个通道即只要三组数据。其它的格式可能需要第四个指针来表�?span lang="EN-US">alpha通道或者其它参数。行尺寸正如它的名字表示的意义一样。在YUV覆盖中相同功能的结构体是像素pixel和程�?span lang="EN-US">pitch。(程度pitch是在SDL里用来表示指定行数据宽度的值)。所以我们现在做的是让我们的覆盖中的pict.data中的三个指针有一个指向必要的空间的地址。类似的,我们可以直接从覆盖中得到行尺寸信息。像前面一样我们使�?span lang="EN-US">img_convert来把格式转换�?span lang="EN-US">PIX_FMT_YUV420P�?/span>

绘制图像

但我们仍然需要告�?span lang="EN-US">SDL如何来实际显示我们给的数据。我们也会传递一个表明电影位置、宽度、高度和缩放大小的矩形参数给SDL的函数。这样,SDL为我们做缩放并且它可以通过显卡的帮忙来进行快速缩放�?/span>

 

[cpp]  view plain copy
  1. SDL_Rect rect;   
  2. if(frameFinished) {   
  3. // Convert the image into YUV format that SDL uses   
  4. img_convert(&pict, PIX_FMT_YUV420P, (AVPicture *)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);   
  5. SDL_UnlockYUVOverlay(bmp);   
  6. rect.x = 0;   
  7. rect.y = 0;   
  8. rect.w = pCodecCtx->width;   
  9. rect.h = pCodecCtx->height;   
  10. SDL_DisplayYUVOverlay(bmp, &rect);   
  11. }   

让我们再花一点时间来看一�?span lang="EN-US">SDL的特性:它的事件驱动系统�?span lang="EN-US">SDL被设置成当你�?span lang="EN-US">SDL中点击或者移动鼠标或者向它发送一个信号它都将产生一个事件的驱动方式。如果你的程序想要处理用户输入的话,它就会检测这些事件。你的程序也可以产生事件并且传递给SDL事件系统。当使用SDL进行多线程编程的时候,这相当有用,这方面代码我们可以在指导4中看到。在这个程序中,我们将在处理完包以后就立即轮询事件。现在而言,我们将处理SDL_QUIT事件以便于我们退出:

 

[c-sharp]  view plain copy
  1. SDL_Event event;   
  2. av_free_packet(&packet);   
  3. SDL_PollEvent(&event);   
  4. switch(event.type)   
  5. {   
  6. case SDL_QUIT:   
  7. SDL_Quit();   
  8. exit(0);   
  9. break;   
  10. default:   
  11. break;  
  12. }   

让我们去掉旧的冗余代码,开始编译。如果你使用的是Linux或者其变体,使�?span lang="EN-US">SDL库进行编译的最好方式为�?/span>

gcc -o tutorial02 tutorial02.c -lavutil -lavformat -lavcodec -lz -lm / sdl-config --cflags --libs`

这里�?span lang="EN-US">sdl-config命令会打印出用于gcc编译的包含正�?span lang="EN-US">SDL库的适当参数。为了进行编译,在你自己的平台你可能需要做的有点不同:请查阅一�?span lang="EN-US">SDL文档中关于你的系统的那部分。一旦可以编译,就马上运行它�?span lang="EN-US">

当运行这个程序的时候会发生什么呢?电影简直跑疯了!实际上,我们只是以我们能从文件中解码帧的最快速度显示了所有的电影的帧。现在我们没有任何代码来计算出我们什么时候需要显示电影的帧。最后(在指�?span lang="EN-US">5),我们将花足够的时间来探讨同步问题。但一开始我们会先忽略这个,因为我们有更加重要的事情要处理:音频�?/span>


img_convert不存在?好吧,最新版本的确删除了这个函数,这里我们再实现一遍就ok了

int img_convert(AVPicture *dst, int dst_pix_fmt,
const AVPicture *src, int src_pix_fmt,
int src_width, int src_height)
{
int w;
int h;
SwsContext *pSwsCtx;


w = src_width;
h = src_height;
pSwsCtx = sws_getContext(w, h, (PixelFormat)src_pix_fmt, 
w, h, (PixelFormat)dst_pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);


sws_scale(pSwsCtx, src->data, src->linesize,
0, h, dst->data, dst->linesize);


//这里释放掉pSwsCtx的内存




return 0;
}

你可能感兴趣的:(视频,ffmpeg,音频,sdl)