Tutorial 02: Outputting to the Screen输出到屏幕

来源:互联网 发布:曼秀雷敦 男士 知乎 编辑:程序博客网 时间:2024/05/18 23:14

 

Tutorial 02: Outputting to the Screen输出到屏幕

Code: tutorial02.c§

* 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. 

很多人对调用YCbCr YUV的转换比较恼怒,一般来说,YUV是一个模拟格式,YCbCr是一个数字的格式。FfmpegSDL在他们的代码和宏里头,都是把YCbCr作为YUV的。

SDL and Video

 

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).

想画到屏幕上,我们打算用SDLSDL代表Simple Direct Layer,,这是一个优秀的库,用于多媒体的,还是跨平台的,而且被应用于多个项目中。

我们可以在官方网站得到库,可以为你的操作系统下载一个开发包。你需要这个库,为你的指南编译代码。

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 overlayYUV (technically not YUV but YCbCr)§ 

SDL可以有很多种方法向屏幕上画图,它特意还有一个用于向屏幕上显示movies的函数,我们叫做YUV overlay YUV (技术上讲YUV 并不是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.YUVRGB都是用来存储原始图像信息的 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.

So our current plan is to replace the SaveFrame() 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:

#include <SDL.h>

#include <SDL_thread.h>

粗略的讲,Y代表的是亮度成分。UV是色彩部分。这笔RGB复杂多了,因为一些色彩信息会被丢弃,你可能仅有1UV sample 针对每2Ysamples。 SDLYUV overlay 采用一数组的raw YUV数据,并且显示他们。它接受4个不同类型的YUV的格式,但是YV12是最快的。还有一个YUV格式,叫做YUV420P,和YV12一样,除了UV数组被交换了之后。420的意思是在4:2:0率下被subsampled§ ,这基本上意味着对于每四个亮度samples,有1颜色,因此,颜色信息是四分的。这有个很好的方式可以保存bandwidth,人眼也不能觉察到改变。字母P代表format是平面的,意味着Y/U/V三个组件是独立的数组。Ffmpeg可以把图像转换为YUV420P,带有添加的bonus,就是说很多视频流已经在那样的format了,或者很容易 转换成那样的格式。

 

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的初始化函数从本质上告诉我们这个库可提供我们使用的特性。SDL_GetError()是个方便的调试函数。

Creating a Display  创建显示

Now we need a place on the screen to put stuff. The basic area for displaying images with SDL is called a surface: 现在,我们需要一个屏幕上的地方可以放stuff,这个基本的地方是使用SDL来显示图形用的,叫做一个surface

SDL_Surface§ *screen;  声明的是个屏幕,就是surface

 

screen = SDL_SetVideoMode§(pCodecCtx->width, pCodecCtx->height, 0, 0);

if(!screen) {

  fprintf(stderr, "SDL: could not set video mode - exiting/n");

  exit(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是一个特殊值,代表一个意味着“和当前的显示一致”。这个功能在OS X还不能工作。

Now we create a YUV overlay on that screen so we can input video 创建YUV overlay 输入视频to it:

现在我们创建一个YUV overlay 在屏幕上,这样我们可以输入视频了。

SDL_Overlay§     *bmp;

 

bmp = SDL_CreateYUVOverlay§(pCodecCtx->width, pCodecCtx->height,

                           SDL_YV12_OVERLAY, screen);

 

As we said before, we are using YV12 to display the image.    使用YV12以显示图像

Displaying the Image显示图像

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 SaveFrame() with our display code. To display the image, we're going to make an AVPicture§ struct and set its data pointers and linesize to our YUV overlay:

很好,足够简单了!我们现在要做的是显示图像。我们沿着这条路走下去,看看我们在哪里完成的帧。我们可以去除所有的用于RGB帧的stuff, 并且我们打算使用自己的显示代码,以取代SaveFrame() 。要显示图像,我们要做一个 AVPicture§结构,并且设置她的数据指针和linesize指向我们的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  

把图像变作SDL使用的YUV格式

    img_convert§(&pict, PIX_FMT_YUV420P,

                    (AVPicture§ *)pFrame, pCodecCtx->pix_fmt, 

            pCodecCtx->width, pCodecCtx->height);

    

    SDL_UnlockYUVOverlay§(bmp);

  }    

 

First, we lock the overlay先锁定overlay  because we are going to be writing to it. This is a good habit to get into so you don't have problems later. The AVPicture§ struct,是指向一个有四个指针的数组的指针 as shown before, has a data pointer that is an array of 4 pointers. Since we are dealing with YUV420P here, we only have 3 channels, and therefore only 3 sets of data. Other formats might have a fourth pointer for an alpha channel or something. linesize is what it sounds like. The analogous相似的结构 structures in our YUV overlay are the pixels and pitches variables. ("pitches" is the term SDL uses to refer to the width of a given line of data.) So what we do is point the three arrays of pict.data at our overlay, so when we write to pict, we're actually writing into our overlay, which of course already has the necessary space allocated. Similarly, we get the linesize information directly from our overlay. We change the conversion format to PIX_FMT_YUV420P, and we use img_convert§ just like before.

首先,锁定overlay。因为我们正在写入overlay。这是个需要养成的很好的习惯,这样以后就不会出现问题。 AVPicture§ 结构,如前所述,有一个数据指针,这个指针指向4个指针的数组。由于我们在这里处理的是YUV420P,我们仅有3channel,因此仅有3组数据。其他的formats 可能有一个第四指针,代表一个alphachannel或者是其他的什么。Linesize就是linesize的意思。我们的YUV overlay中,有相似的结构,他们是pixelspitches 变量(pitchesSDL中的术语,用来指向给定了line的数据的宽度)。这样,我们要做的就是在我们的overlay,指向pict.data 的三个数组.这样,当我们写入pict的时候,我们实际上已经写入了我们了overlay.overlay肯定当然的分配好了必须的空间.

同样,我们可以从overlay中直接得到linesize的信息.我们改变了转换格式到PIX_FMT_YUV420P,像以前一样,我们用img_convert§来做这件事.

Drawing the Image 画图

But we still need to tell SDL to actually show the data we've given it. We also pass this function a rectangle矩形 that says where the movie should go and what width and height it should be scaled to. This way, SDL does the scaling缩放比例 for us, and it can be assisted by your graphics processor for faster scaling:

SDL_Rect§ rect;

但是,我们仍旧需要告诉SDL如何显示我们给她的数据.我们也要传递给这个函数一个矩形,表明这个movie应该在哪里,高度和宽度应该如何被裁剪.这样,SDL为我们做好了缩放比例,她也可以借助于图形处理器以进行更快的缩放.

  if(frameFinished) {  帧完成了

    /* ... code ... */

    // Convert the image into YUV format that SDL uses  要把image变成SDL用的YUV格式

    img_convert§(&pict, PIX_FMT_YUV420P,

                    (AVPicture§ *)pFrame, pCodecCtx->pix_fmt, 

            pCodecCtx->width, pCodecCtx->height);

    

    SDL_UnlockYUVOverlay§(bmp);  解锁YUVOverlay

    rect.x = 0;

    rect.y = 0;

    rect.w = pCodecCtx->width;

    rect.h = pCodecCtx->height;

    SDL_DisplayYUVOverlay§(bmp, &rect);  显示YUVOverlay

  }

 

Now our video is displayed!  现在视频就可以显示了。

Let's take this time to show you another feature of SDL: its event system. 她的event系统 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. 事件机制对于SDL多线程编程非常有用This is especially useful when multithread programming with SDL, which we'll see inTutorial 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的另一个特性展示给你:事件系统.SDL 在你type的时候启动,或者在SDL应用程序之中移动鼠标,或者发送一个信号,这都会产生一个event。你的程序然后就核实这些event,如果她想处理这些输入的话。你的程序也可以自己编写event发送给你SDL事件系统。

SDL_Event§       event;  事件

 

    av_free_packet§(&packet);  释放包

    SDL_PollEvent(&event);  登记包

    switch(event.type) { 切换事件类型

    case SDL_QUIT:   如果SDL退出

      SDL_Quit§();   SDL就退出

      exit(0);

      break;

    default:

      break;

    }

 

And there we go! Get rid of all the old cruft, and you're ready to compile. If you are using Linux or a variant, the best way to compile using the SDL libs is this:

gcc -o tutorial02 tutorial02.c -lavutil -lavformat -lavcodec -lz -lm /

`sdl-config --cflags --libs`

好了,我们继续前进!取出所有的老stuff,你应该准备好去编译了。如果你用linux或者一个变体,最好的使用SDL库的方式如下:

sdl-config just prints out the proper flags for gcc to include the SDL libraries properly. You may need to do something different to get it to compile on your system; please check the SDL documentation for your system. Once it compiles, go ahead and run it.

sdl-config gcc指出合适的标志,以包含SDL库。你需要做一些不同的事情使得SDL可以在你的机子上编译。

What happens when you run this program? The video is going crazy! In fact, we're just displaying all the video frames as fast as we can extract them from the movie file. We don't have any code right now for figuring out when we need to display video. Eventually (in Tutorial 5§), we'll get around to syncing the video. But first we're missing something even more important: sound!

你运行这个程序会发生什么问题呢?视频很疯狂吧!事实上,我们仅仅是想和我们从movie file中图区视频帧的速度一样,尽快的播放所有的视频帧。当我们需要显示视频的时候,现在还没有任何代码确定什么时间去播放视频。最终,在指南5中,我们同步了视频。但是首先,我们不能丢掉更为重要的东西,那就是声音!

>> Playing Sound§

 

 

===============================================================

 

 

指导2:输出到屏幕

 源代码:tutorial2.c

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),我们将花足够的时间来探讨同步问题。但一开始我们会先忽略这个,因为我们有更加重要的事情要处理:音频!

 

 

 

=========================================

 

 

 

 

原创粉丝点击