ffmpeg的tutorial中文版学习笔记(二)

教程2:输出到屏幕

源代码: tutorial02.c

       视频为了在屏幕上显示,我们将使用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 格式,除了U 和V 分量的位置被调换了以外,它和YV12是一样的。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>
初始化SDL:
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。
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来显示图像。
其中SDL_Overlay结构体介绍如下:
----------------------------------------------------------
SDL_Overlay -- YUV video overlay

Structure Definition

typedef struct{
  Uint32 format;
  int w, h;
  int planes;
  Uint16 *pitches;
  Uint8 **pixels;
  Uint32 hw_overlay:1;
} SDL_Overlay;

Structure Data

formatOverlay format (see below)
w, hWidth and height of overlay
planesNumber of planes in the overlay. Usually either 1 or 3
pitchesAn array of pitches, one for each plane. Pitch is the length of a row in bytes.
pixelsAn array of pointers to the data of each plane. The overlay should be locked before these pointers are used.
hw_overlayThis will be set to 1 if the overlay is hardware accelerated.

Description

A SDL_Overlay is similar to a SDL_Surface except it stores a YUV overlay. All the fields are read only, except for pixels which should be locked before use. The format field stores the format of the overlay which is one of the following:

#define SDL_YV12_OVERLAY  0x32315659  /* Planar mode: Y + V + U */
#define SDL_IYUV_OVERLAY  0x56555949  /* Planar mode: Y + U + V */
#define SDL_YUY2_OVERLAY  0x32595559  /* Packed mode: Y0+U0+Y1+V0 */
#define SDL_UYVY_OVERLAY  0x59565955  /* Packed mode: U0+Y0+V0+Y1 */
#define SDL_YVYU_OVERLAY  0x55595659  /* Packed mode: Y0+V0+Y1+U0 */
----------------------------------------------------------
显示图像

       前面那些都是很简单的。现在我们需要来显示图像。让我们看一下是如何来处理完成后的帧的。我们将原来对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);//现在已经被下面的函数替换
	static struct SwsContext *img_convert_ctx;
 	img_convert_ctx=sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,
 			pCodecCtx->height,PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);
 	if(img_convert_ctx==NULL)
 	{
 	     fprintf(stderr,"Can not initialize the conversion context!\n");
 	     exit(1);
 	}
 	sws_scale(img_convert_ctx,pFrame->data,pFrame->linesize,0,pCodecCtx->height,pict.data,pict.linesize);
 	SDL_UnlockYUVOverlay(bmp);
}
其中:AVPicture结构体如下所示:
/**
 * Picture data structure.
 *
 * Up to four components can be stored into it, the last component is
 * alpha.
 */
typedef struct AVPicture {
    uint8_t *data[AV_NUM_DATA_POINTERS];    ///< pointers to the image data planes
    int linesize[AV_NUM_DATA_POINTERS];     ///< number of bytes per line
} AVPicture;
而:
pict.data[i]为一个指针, bmp->pixels[i]指针所指向的 值初始为0,当经过下面的 Convert the image into YUV format that SDL uses 后就有值了,和pict.data[i]保持一致
bmp->pitches[i]为int类型,当bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen)调用后bmp->pitches[i]就有值了,为视频的宽度

       首先,我们锁定这个覆盖,因为我们将要去改写它。这是一个避免以后发生问题的好习惯。正如前面所示的,这个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);
	static struct SwsContext *img_convert_ctx;
	img_convert_ctx=sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height,PIX_FMT_YUV420P,SWS_BICUBIC,NULL,NULL,NULL);
	if(img_convert_ctx==NULL){
		fprintf(stderr,"Can not initialize the conversion context!\n");
		exit(1);
	}
	sws_scale(img_convert_ctx,(const uint8_t *const)pFrame->data,pFrame->linesize,0,pCodecCtx->height,pict.data,pict.linesize);
	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;
}
          在结束后,应该调用void SDL_FreeYUVOverlay(SDL_Overlay *overlay); 释放Overlay来释放SDL_Overlay结构体
          当运行这个程序的时候会发生什么呢?电影简直跑疯了!实际上,我们只是以我们能从文件 中解码帧的最快速度显示了所有的电影的帧。现在我们没有任何代码来计算出我们什么时 候需要显示电影的帧。最后(在教程5),我们将花足够的时间来探讨同步问题。但一开始 我们会先忽略这个,因为我们有更加重要的事情要处理: 音频!
在我Linux系统下编译的命令:
gcc ./tutorial02.c -o ./tutorial02 -lavutil -lavformat -lavcodec -lswscale  -lz -lm `sdl-config --cflags --libs` -I /home/Jiakun/ffmpeg_build/include/ -L /home/Jiakun/ffmpeg_build/lib/ -I /usr/include/SDL/
源代码:见这里的github

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
FFmpeg 是一个开源的音视频处理工具,具有广泛的应用领域,包括视频编辑、转码、裁剪、合并等功能。它可以通过命令行的方式运行,也可以与其他编程语言结合使用。 FFmpeg 的中文教程包括了基本的安装与配置步骤,以及常用的命令使用示例。首先,我们需要下载 FFmpeg 的压缩包,然后解压到指定目录。接着,配置环境变量,将 FFmpeg 的可执行文件路径添加到系统的 PATH 变量中。 在命令行中,我们可以使用以下命令来进行音视频处理操作。例如,使用"ffmpeg -i input.mp4 output.avi"可以将一个 mp4 格式的视频文件转换为 avi 格式。使用"-i"参数指定输入文件,"-o"参数指定输出文件。 除了仅仅转换格式,FFmpeg 还支持对音视频文件进行剪辑和合并。例如,使用"ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:30 output.mp4"可以从 input.mp4 中截取从第 10 秒开始,时长为 30 秒的视频片段,并保存为 output.mp4。 另外,FFmpeg 还提供了丰富的音视频处理工具,如调整分辨率、音频编码、视频旋转等。通过查阅 FFmpeg 官方文档和在线资源,我们可以深入学习和了解更多关于 FFmpeg 的用法和特性。 总结来说,FFmpeg 是一个功能强大的音视频处理工具,其中文教程详细介绍了安装、配置以及常用命令的使用。通过学习和实践,我们可以利用 FFmpeg 进行各种音视频处理操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值