FFMPEG and SDL Tutorial 1

Tutorial 1 Making Screencaps(tutorial01.c)

概述

电影文件有几个基本的组成部分。首先,文件本身被称为一个容器,容器的类型决定了在文件中的信息的范围。容器的例子有AVIQuickTime。接下来,你有一堆的信息流,例如,你通常有一个音频流和视频流。 (一个“流”只是一个为“在时间上可用的连续的数据元素”而想象的词。)在一个流中的数据元素被称为帧(frame。每个数据流进行编码由不同类型的解码器。该编解码器定义的实际数据是如何编码和解码 - 因而得名编解码器。例如DivX的编解码器和MP3编解码器。然后从流中读取数据包。数据包(packet)是包含数据位的数据,我们可以将数据位解码为最终可以为我们的应用程序操作的原始帧。就我们而言,每个数据包包含完整的帧,或者音频里的多个帧。

在非常基本的水平下,处理视频和音频流是很容易的:

10 OPEN video_stream FROM video.avi

20 READ packet FROM video_stream INTO frame

30 IF frame NOT COMPLETE GOTO 20

40 DO SOMETHING WITH frame

50 GOTO 20

ffmpeg处理多媒体就像这个程序一样非常简单,尽管一些程序会有非常复杂的”DO SOMETHING ”步骤。所以在本教程中,我们会有打开文件、从视频帧中读取数据并写入文件和我们的“DO SOMETHING“步骤——将帧写入PPM文件。

打开文件

首先,我们先看一下怎样打开一个文件。对于ffmpeg,我们必须先初始化库(请注意,有些系统可能使用<ffmpeg/avcodec.h><ffmpeg/avformat.h>代替)。

#include <avcodec.h>

#include <avformat.h>

------

int main(int argc,charg *argv[])

{

   av_register_all();

这个带库的注册对所有文件格式与编解码器都适用,使他们能够与时自动使用相应的格式文件/编解码器打开。请注意,你只需要在main函数中调用av_register_all()一次。如果你喜欢,它可能只是某些个人注册的文件格式和编解码器,但通常是没有理由你就必须这样做。

现在,我们可以真正打开一个文件:

AVFormatContext *pFormatCtx;

//Open video file

if(av_open_input_file(&pFormatCtx,argv[1],NULL,0,NULL) != 0)

  return -1;//Couldn’t open file

我们从第一个参数获取文件名。这个函数读取文件头,并存储有关文件格式的信息到AVFormatContext数据结构。后面三个参数用来指定文件格式、缓冲区大小和格式选项,但是被设置为Null0时,libavformat会自动检测。

这个函数只查看文件头,所有下一步我们需要检测文件中的流信息:

//Retrieve stream information

if(av_find_stream_info(pFormatCtx)<0)

  return -1;//Couldn’t find stream information

次函数用恰当的信息填充pFormatCtx->streams,我们引入一个方便的调试函数来告诉我们里面发生了什么:

//Dump information about file onto standard error

dump_format(pFormatCtx,0,arg[1],0);

现在pFormatCtx->stream只是一个大小为pFormatCtx->nb_streams的数组指针,让我们跟踪它直到我们找到视频流:

int i;

AVCodeContext *pCodecCtx;

// Find the first video stream

videoStream = -1;

for(i=0;i<pFormatCtx->nb_streams;i++)

  if(pFormatCtx->stream[i]->codec->codec_type == CODEC_TYPE_VIDEO) {

    videoStream = i ;

    break;

}

if(videoStream ==-1)

  return -1;//Didn’t find a video stream

 

//Get a pointer to the codec context for the video stream

pCodecCtx = pFormatCtx->streams[videoStream]->codec;

关于编解码器的流信息在我们所谓的codec context中。这个容器中所有关于流正在应用的编解码器的信息,现在我们有个指针指向它。但是我们仍然要找到编解码器并打开它:

AVCodec *pCodec;

//Find the decoder for the video stream

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if(pCodec = NULL) {

  fprintf(stderr,”Unsuoported codec!/n”);

  return -1; //Codec not found

 }

//Open codec

if(avcodec_open(pCodecCtx,pCodec)<0)

 return -1;//Could not open codec

你们当中有些人可能还记得,旧教程,有两个其他部分的代码:加入CODEC_FLAG_TRUNCATEDpCodecCtx ->flags和增添纠正不正确帧速率的介绍。这两个补丁已不在ffplay.c了,所以我要当他们是没有必要了。还有一个差异指出,因为我们删除该代码:pCodecCtx -> time_base现在拥有帧速率信息。 time_base是一个结构具有分子和分母(AVRational)。我们代表的是分数的帧速率,因为许多编解码器已非整数的帧速率(比如NTSC制式的29.97fps)。

 

存储数据

现在,我们需要一个地方实际地存储帧:

AVFrame *pFrame;

// Allocate video frame

pFrame = avcodec_alloc_frame();

由于我们计划输出PPM文件,这是24RGB存储的,我们将不得不将我们的帧从原始格式转换为RGB格式。 ffmpeg的会为我们做这些转换。对于大多数项目(包括我们)我们将要转换我们的初始帧,到特定的格式。现在,让我们分配一个帧转换:

//Allocate an AVFrame structure

pFrameRGB = avcodec_alloc_frarme();

if(pFrameRGB == NULL)

  return -1;

即使我们为帧分配了空间,当我们转换时仍然需要一个空间存放原始数据。我们用avpicture_qet_size来获得我们需要的大小,并手动分配空间:

uint8_t *buffer;

int numBytes;

//Determine required buffer size and allocate buffer

numBytes = avpicture_qet_size(PIX_FMT_RGB24,pCodecCtx->width,pCodecCtx->height);

buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

av_mallocffmpegmalloc,它只是对malloc进行了简单的包装以确保内存地址对齐。而它并不能保证内存泄露、双字节释放(double freeing)或其他malloc问题。

现在我们用avpicture_fill将帧与我们刚分配的内存联系起来。关于AVPicture:该AVPicture结构是AVFrame结构的一个子集——在AVFrame结构开始的地方与AVPicture结构相同。

// Assign appropriate parts of buffer to image planes in pFrameRGB

// Note that pFrameRGB is an AVFrame,but AVFrame is a superset of AVPicture

avpicture_fill((AVPicture*)pFrameRGB,buffer,PIX_FMT_RGB24,pCodeCtx->width,pCodecCtx->height);

现在我们已经为读入流数据做好了准备。

读数据

我们将要做的是从整个视频流中读取数据包、解码成我们的帧,并且一旦一帧完成我们就转换并存储它。

int frameFinished;

AVPacket packet;

i=0;

while(av_read_frame(pFormatCtx,&packet)>=0)

{

  // Is this a packet from the video stream?

  if(packet.stream_index == videoStream)

    {

       //Decode video frame

       avcodec_decode_video(pCodecCtx,pFrame,&frameFinished,packet.data,packet.size

  }

    //did we get a video frame?

    if(frameFinished)

      {

         //Convert the image from its native format to RGB

       imq_convert((AVPicture *)pFrameRGB,PIX_FMT_RGB24,AVPicture*pFrame,

          pCodecCtx->pix_fmt,pCodecCtx->width,pCodecCtx->height);

     //Save the frame to disk

     if(++I <= 5)

         SaveFrame(pFrameRGB,pCodecCtx->width,pCodecCtx->height,i);

   }

  }

  // Free the packet that was allocated by av_read_frame

 av_free_packet(&packet);

}

这个过程,我们再来走一遍,非常简单:av_read_fraem()读取数据包并把它存在AVPacket结构中。需要指出的是,我们仅需分配数据包的结构——ffmpeg为我们分配了由packet.data指针指向的内部数据,后面将会被av_free_packet()释放。avcodec_decode_video()将会为我们将数据包转换为帧。然而,我们也许没有我们所需要的解码数据包成帧后的所有信息,所以avcodec_decode_video()在下一个帧到来时设置frameFinished。最后,我们利用ing_convert()将原始帧(pCodecCtx->pix_fmt)转化为RGB。记住,你可以投AVFrame指针一AVPicture指针。最后,我们传递帧的高度和宽度给SaveFrame函数。

    现在我们需要做的是让SaveFrame函数以PPM格式写入文件中RGB信息。

void SaveFrame(AVFrame *pFrame,int width,int height,int iFrame)

{

  FILE *pFile;

  char szFilename[32];

  int y;

  // Open file

  sprintf(szFilename,”frame%d.ppm”,iFrame);

  pFile = fopen(szFilename,”wb”);

  if(pFile == NULL)

    return;

  // Write header

  fprintf(pFile,”P6/n%d %d/n255/n”,width,height);

  //Write pixel data

  for(y = 0;y < height;y++)

    fwrite(pFrame->data[0] + y*pFrame->linesize[0],1,width*3,pFile);

  //Close file

  fclose(pFile);

}

我们做一个标准的文件打开,然后写入RGB数据。我们写一次读取一行。一个PPM的文件只是一个含有长字符串的RGB信息的文件。如果你了解HTML的颜色,它会像铺设出每个像素首尾相连地如#000000000000 ....将是一个红色的屏幕。 (这是存储在二进制,没有分离)头信息表明图像的宽和高,以及RGB值最大尺寸。

现在,我们回到主函数。一旦我们从视频流中读取完成,我们要清扫一切:

// Free the RGB image

av_free(buffer);

av_free(pFrameRGB);

// Free the YUV frame

av_free(pFrame);

// Close the codec

avcodec_close(pCodecCtx);

//Close the video file

av_close_input_file(pFormatCtx);

return 0;

 

 

:关于数据包的说明:技术上说,一个数据包可以包含部分帧或者其他数据位,但是FFMPEG的解析器保证我们得到的数据包要么是完整的要么的具有多帧的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值