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库的头文件而且初始化它。ide
#include <SDL.h>函数 #include <SDL_thread.h>网站
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {ui fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());spa 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),咱们将花足够的时间来探讨同步问题。但一开始咱们会先忽略这个,由于咱们有更加剧要的事情要处理:音频!