FFmpeg和SDL教程之二(Outputting to the Screen)

时间:2021-11-13 04:04:23


SDL 和 Video


sdl 是 Simple Direct Layer的缩写,是一个优秀的多媒体库,具有跨平台、并且在许多工程应用的优点。你可以从官网下载库文件,或者下载适合自己操作系统的开发包(如果存在的话)。下载的库文件需要编译。

sdl有很多向屏幕画图的方法,其中有一个就是用来在屏幕上显示电影的:YUV overlay(YUV 技术上该称为 YCbCr, 类似RGB那样来存取Raw image)。简单来说,Y 代表亮度(luma),U 和 V 代表色度。SDL的YUV overlay 是按行读取 YUV数据并显示的。它支持四种不同的YUV格式,但是 YV12 是最快。有另外一种 YUV 格式:YUV420P, 与 YV12 相同,除了 U、V数组被调换。420 代表着依 4:2:0比例来采样。基本来说,就是一个样色采样对应着四个亮度采样。颜色信息只占四分之一。这有利于节省带宽,而且人眼对这个变化并不敏感。P后缀代表 格式是 ”planar“ -- 简单说,就是 Y、U、V分别用三个不同的数组来存放,ffmpeg可以将图像转化为 YUV420P。

我们的计划是 替换教程一中的 SaveFrame()函数,把帧数据显示在屏幕上。但是,首先必须清楚如何使用 SDL Library。第一步:包含库文件、初始化 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_GetError()是一个手动调试函数。



Creating a Display


首先需要在屏幕上开辟一块显示的区域,使用 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);
}

这就可以根据给定的宽、高创建一个屏幕区域。下一步就是屏幕的位深(bit depth),0是一个特殊的值,代表着与当前的显示相同(这在OSX上不行)。

现在在屏幕上创建一个 YUV overlay,因此可以把视频放进去:

SDL_Overlay     *bmp;

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

这里,我们使用 YV12来显示图像。



Displaying the Image


To display the image, we're going to make an AVPicture struct and set its data pointers and linesize to our YUV overlay:

 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);
}

首先,我们锁定 overlay,因为我们将要往里面写数据。AVPicture结构体,是一个包含4个指针的数组。因为我们这里处理的是YUV420P,只有三通道,因此只有三个数据集合。其他格式可能有第四个指针指向 alpha 通道或者其他。linesize is what it sounds like。YUV overlay中类似的结构有:pixels、pitches 变量(”pitches“是SDL中用来指代给定一行数据的宽度的)。因此我们需要将 pict.data 的三个数组指向 overlay。当我们向 pict写数据,实际上是向 overlay 写数据。这当然已经做好了内存空间的分配。同样,我们直接从 overlay 中获取 linesize 信息。 我们把格式转换为 PIX_FMT_YUV420P, 并且像以前那样使用img_convert。


Drawing the Image


我们也需要向这个函数传递一个矩形的参数,SDL 可以利用图像处理器的帮助,实现快速的缩放:

SDL_Rect rect;

if(frameFinished) {
/* ... code ... */
// 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特色:事件系统(Event System)。SDL is set up so that when you type, or move the mouse in the SDL application, or send it a signal, it generates an event。程序于是检测这些事件如果想处理用户输入的话。你的程序也能够创建事件来向SDL 事件系统发送信息。这点在使用 SDL 进行多线程编程的时候非常重要。这里,在处理完 Packet后不断地进行事件检测。现在,开始执行 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;
}

运行后,会看见视频显示的很快。事实上,我们将从电影文件中提取的视频帧几乎同步的显示了出来。