To draw to the screen, we’re going to use SDL. SDL stands for Simple Direct Layer, and is an excellent library for multimedia, is cross-platform, and is used in several projects. You can get the library at the official website or you can download the development package for your operating system if there is one. You’ll need the libraries to compile the code for this tutorial (and for the rest of them, too).
为了在屏幕上显示,我们将使用 SDL。 SDL 是 Simple Direct Layer 的缩写。它是一个出色的跨平台多媒体库,被用在许多工程中。你可以从它的官网 http://www.libsdl.org/上来得到这个库的源代码或者直接下载开发包到你的操作系统中。在这个教程中需要你编译这个库。(剩下的几个教程也是一样)。
SDL has many methods for drawing images to the screen, and it has one in particular that is meant for displaying movies on the screen - what it calls a YUV overlay. YUV (technically not YUV but YCbCr) *** A note: **There is a great deal of annoyance from some people at the convention of calling “YCbCr”,“YUV”. Generally speaking, YUV is an analog format and YCbCr is a digital format. ffmpeg and SDL both refer to YCbCr as YUV in their code and macros. is a way of storing raw image data like RGB. Roughly speaking, Y is the
brightness (or “luma”) component, and U and V are the color components. (It’s more complicated than RGB because some of the color information is discarded, and you might have only 1 U and V sample for every 2 Y samples.) SDL’s YUV overlay takes in a raw array of YUV data and displays it. It accepts 4 different kinds of YUV formats, but YV12 is the fastest. There is another YUV
format called YUV420P that is the same as YV12, except the U and V arrays are switched. The 420 means it is subsampled at a ratio of 4:2:0, basically meaning there is 1 color sample for every 4 luma samples, so the color information is quartered. This is a good way of saving bandwidth, as the human eye does not percieve this change. The “P” in the name means that the format is “planar” – simply meaning that the Y, U, and V components are in separate arrays. ffmpeg can convert images to YUV420P, with the added bonus that many video streams are in that format already, or are easily converted to that format.
SDL 库中有许多种方式来在屏幕上绘制图形,而且它有一个特殊的方式来在屏幕上显示图像——这种方式叫做 YUV overlay。 YUV(从技术上来讲并不叫 YUV 而是叫做 YCbCr)其实是一种类似于 RGB 方式的存储原始图像的格式 ,很多人被YUV/YCbCr搞得很困惑。通常来讲,YUV是模拟格式,YCbCr是数字格式,ffmpeg和SDL对两者不加区别。 粗略的讲, Y 是亮度分量(luma), U 和 V 是色度分量。(这种格式比 RGB 复杂的多,因为一些颜色信息被丢弃了,而且可以每两个 Y 采样点,只有一个 U 和一个 V 采样点)。 SDL 的 YUV ovelay使用一组原始的
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 格式。
So our current plan is to replace the ppm_save()
function from Tutorial 1, and instead output our frame to the screen. But first we have to start by seeing how to use the SDL Library. First we have to include the libraries and initalize SDL:
于是,我们现在计划把教程 1 中的 ppm_save() 函数替换掉,让它直接输出我们的帧到屏幕上去。但一开始我们必需要先看一下如何使用 SDL 库。首先我们必需先包含 SDL 库的头文件并且初始化它。
#include <SDL2/SDL.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()
essentially tells the library what features we’re going to use. SDL_GetError()
, of course, is a handy debugging function.
SDL_Init() 函数告诉了 SDL 库,哪些特性我们将要用到。当然 SDL_GetError() 是一个用来调试出错的函数。
Now we need a place on the screen to put stuff. The basic area for displaying images with SDL is called a SDL_Window:
现在我们需要在屏幕上的一个地方放上一些东西。在 SDL 中显示图像的基本区域叫做 SDL_Window。
//SDL 2.0 Support for multiple windows
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
screen_w, screen_h, SDL_WINDOW_OPENGL);
if(!screen) {
printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
return -1;
}
This sets up a screen with the given width and height. The next option is the bit depth of the screen - 0 is a special value that means “same as the current display”. (This does not work on OS X; see source.)
这就创建了一个给定高度和宽度的屏幕。下一个选项是屏幕的颜色深度——0 表示使用和当前一样的深度。现在我们在屏幕上来创建一个 SDL_Texture以便于我们输入视频上去:
sdlRenderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,
video_dec_ctx->width, video_dec_ctx->height);
我们使用 YUV420p 来显示图像。
Well that was simple enough! Now we just need to display the image. Let’s go all the way down to where we had our finished frame. We can get rid of all that stuff we had for the RGB frame, and we’re going to replace the ppm_save()
with our display code.
前面那些都是很简单的。现在我们需要来显示图像。让我们看一下是如何来处理完成后的帧的。我们将去掉原来对 RGB 处理的方式,并且替换 ppm_save() 为显示到屏幕上的代码。
sws_scale(sws_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0,
video_dec_ctx->height, video_dst_data, video_dst_linesize);
SDL_UpdateYUVTexture(sdlTexture, &sdlRect,
video_dst_data[0], video_dst_linesize[0],
video_dst_data[1], video_dst_linesize[1],
video_dst_data[2], video_dst_linesize[2]);
SDL_RenderClear( sdlRenderer );
SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, &sdlRect);
SDL_RenderPresent( sdlRenderer );
Now our video is displayed! 现在我们的视频显示出来了!
Let’s take this time to show you another feature of SDL: its 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. Your program then checks for these events if it wants to handle user input. Your program can also make up events to send the SDL event system. This is especially useful when multithread programming with SDL, which we’ll see in Tutorial 4. In our program, we’re going to poll for events right after we finish processing a
packet. For now, we’re just going to handle the SDL_QUIT
event so we can exit:
让我们再花一点时间来看一下 SDL 另一个特性:它的事件(event)驱动系统。 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;
}
And there we go! Get rid of all the old cruft, and you’re ready to compile.
大功告成!让我们去掉旧的冗余代码,开始编译。
g++ -std=c++14 -o tuturial03 tutorial03.cpp -I/INCLUDE_PATH -L/LIB_PATH -lavutil -lavformat -lavcodec -lswscale -lswresample -lavdevice -lz -lavutil -lm -lpthread -ldl