在ffmpeg中增加libavformat库所支持格式的媒体文件

来自http://blog.csdn.net/menguio/article/details/6411591

 

如何输出一个libavformat库所支持格式的媒体文件,具体步骤如下:


(1)av_register_all(),初始化 libavcodec库,并注册所有的编解码器和格式。
(2)guess_format(),根据文件名来获取输出文件格式,默认为mpeg。
(3)av_alloc_format_context() 分配输出媒体内容。
  ov->oformat = fmt;
  snprintf( oc->filename, sizeof(oc->filename), “%s”, filename );
(4)add_video_stream()使用默认格式的编解码器来增加一个视频流,并初始化编解码器。
  (4.1)av_new_stream()增加一个新的流到一个媒体文件。
  (4.2)初始化编解码器:
    c = st->codec;
    c->codec_id = codec_id;
    c->codec_type = CODEC_TYPE_VIDEO;
    c->bit_rate = 400000;
    c->width = 352;
    c->height = 288;
    c->time_base.den = STREAM_FRAME_RATE; //每秒25副图像
    c->time_base.num = 1;
    c->gop_size = 12;
    c->pix_fmt = STREAM_PIX_FMT; //默认格式为PIX_FMT_YUV420P
    …… ……
(5)av_set_parameters()设置输出参数,即使没有参数,该函数也必须被调用。
(6)dump_format()输出格式信息,用于调试。
(7)open_video()打开视频编解码器并分配必要的编码缓存。
  (7.1)avcodec_find_encoder()寻找c->codec_id指定的视频编码器。
  (7.2)avcodec_open()打开编码器。
  (7.3)分配视频输出缓存:
    video_outbuf_size = 200000;
    video_outbuf = av_malloc( video_outbuf_size );
  (7.4)picture = alloc_picture()分配原始图像。
    (7.4.1)avcodec_alloc_frame()分配一个AVFrame并设置默认值。
    (7.4.2)size = avpicture_get_size()计算对于给定的图片格式以及宽和高,所需占用多少内存。
    (7.4.3)picture_buf = av_malloc( size )分配所需内存。
    (7.4.4)avpicture_fill()填充AVPicture的域。
  (7.5)可选。如果输出格式不是YUV420P,那么临时的YUV420P格式的图像也是需要的,由此再转换为我们所需的格式,因此需要为临时的YUV420P图像分配缓存:
  tmp_picture = alloc_picture()
说明:tmp_picture,picture,video_outbuf。如果输出格式为YUV420P,则直接通过avcodec_encode_video()函数将picture缓存中的原始图像编码保存到video_outbuf缓存中;如果输出格式不是YUV420P,则需要先通过sws_scale()函数,将YUV420P格式转换为目标格式,此时tmp_picture缓存存放的是YUV420P格式的图像,而picture缓存为转换为目标格式后保存的图像,进而再将 picture缓存中的图像编码保存到video_outbuf缓存中。
(8)url_fopen()打开输出文件,如果需要的话。
(9)av_write_header()写流动头部。
(10)LOOP循环{
    计算当前视频时间video_pts
    是否超时退出循环?
    write_video_frame()视频编码
  }
  (10.1)write_video_frame()
    如果图片不是YUV420P,则需要用 sws_scale()函数先进行格式转换。
    若需要原始图像:
      av_init_packet()初始化一个包的选项域。
      av_write_frame()向输出媒体文件写一个包,该包会包含一个视频帧。
    若需要编码图像:
      avcodec_encode_video() 编码一视频帧。
      av_init_packet()
      av_write_frame()
(11)close_video()关闭每个编解码器。
(12)av_write_trailer()写流的尾部。
(13)释放资源
  av_freep()释放 AVFormatContext下的AVStream->AVCodecContext和AVStream:
    for( i = 0; i < oc->nb_streams; i++ ){
      av_freep( &oc->streams[i]->codec );
      av_freep( &oc->streams[i] );
    }
  url_fclose()关闭输出文件。
  av_free()释放 AVFormatContext。

apiexample.c例子教我们如何去利用ffmpeg 库中的api函数来自己编写编解码程序。
(1)首先,main函数中一开始会去调用avcodec_init()函数,该函数的作用是初始化libavcodec,而我们在使用avcodec库时,该函数必须被调用。
  (2)avcodec_register_all()函数,注册所有的编解码器(codecs),解析器(parsers)以及码流过滤器(bitstream filters)。当然我们也可以使用个别的注册函数来注册我们所要支持的格式。
  (3)video_encode_example()函数用于视频编码,由图可知,所有的编码工作都在该函数内完成。
  (4)avcodec_find_encoder()函数用于查找一个与codec ID相匹配的已注册的编码器。
  (5)avcodec_alloc_context() 函数用于分配一个AVCodecContext并设置默认值,如果失败返回NULL,并可用av_free()进行释放。
  (6)avcodec_alloc_frame()函数用于分配一个AVFrame并设置默认值,如果失败返回NULL,并可用av_free()进行释放。
  (7)设置参数:
    设置采样参数,即比特率。
    c->bit_rate = 400000;
    设置分辨率,必须是2的倍数。
    c->width = 352;
    c->height = 288;
    设置帧率。
    c->time_base = (AVRational){1,25}; 该帧率为25,其实timebase = 1/framerate,花括号内分别为分子和分母。
    设置GOP大小。
    c->gop_size = 10; 该值表示每10帧会插入一个I帧(intra frame)。
    设置B帧最大数。
    c->max_b_frames = 1; 该值表示在两个非B帧之间,所允许插入的B帧的最大帧数。
    设置像素格式。
    c->pix_fmt = PIX_FMT_YUV420P; 该值将像素格式设置为YUV420P。
  (8)avcodec_open()函数用给定的 AVCodec来初始化AVCodecContext。
  (9)接着是打开文件,f = fopen( filename, “wb” );
(10)分配图像和输出缓存。
    申请100KB左右的内存作为输出缓存。
outbuf_size = 100000;
    outbuf = malloc( outbuf_size );
    根据帧的大小来确定YUV420所占内存大小,一个像素,RGB格式占用3个字节,而YUV420格式只占用两个字节。YUV420格式是指,每个像素都保留一个Y(亮度)分量,而在水平方向上,不是每行都取U和V分量,而是一行只取U分量,则其接着一行就只取V分量,以此重复,所以420不是指没有V,而是指一行采样只取U,另一行采样只取V。在取U和 V时,每两个Y之间取一个U或V。但从4x4矩阵列来看,每4个矩阵点Y区域中,只有一个U和V,所以它们的比值是4:1。所以对于一个像素,RGB需要 8 * 3 = 24位,即占3个字节;而YUV420P,8 + 8/4 + 8/4 = 12位,即占2个字节,其中8指Y分量,8/4指U和V分量。
    size = c->width * c->height;
    picture_buf = malloc( (size * 3) / 2 );
    picture->data[0] = picture_buf;
    picture->data[1] = picture->data[0] + size;
    picture->data[2] = picture->data[1] + size / 4;
    picture->linesize[0] = c->width;
    picture->linesize[1] = c->width / 2;
    picture->linesize[2] = c->width / 2;
    其中,data[0]存放Y,data[1] 存放U,data[2]存放V【FixMe】。linesize[0]表示Y分量的宽度,linesize[1]表示U分量的宽度,linesize[2]表示V分量的宽度。
  (11)编码一秒钟的视频,帧率为25,所以需要循环 25次,每次编码一帧。
  (11.1)准备一幅伪图像,即自己自定义往里面塞数据。
     for(y=0;y<c->height;y++){
for(x=0;x<c->width;x++){

picture->data[0][y*picture->linesize[0]+x]=x+y+i*3;
}
}
for(y=0;y<c->height/2;y++){
for(x=0;x<c->width/2;x++){
picture->data[1][y*picture->linesize[1]+x]=128+y+i*2;
picture->data[2][y*picture->linesize[2]+x]=64+x+i*5;
}
}
  (11.2)avcodec_encode_video()从picture中编码一帧视频数据,并存入到outbuf中,而期间所使用的编码器为c。
  (11.3)将编码完的数据写入到文件里。
  (12)对延时的帧数据进行编码。因为像MPEG4 中,I帧、P帧和B帧之间存在一定的延时【FixMe】。同样是avcodec_encode_video(),然后写入文件。
  (13)添加结束代码,使其成为一个真正的mpeg文件。
    outbuf[0] = 0x00;
    outbuf[1] = 0x00;
    outbuf[2] = 0x01;
    outbuf[3] = 0xb7;
    fwrite( outbuf, 1, 4, f );
  这个结束代码表示什么???
  (14)释放资源。
    fclose(f);
    free(picture_buf);
    free(outbuf);
    avcodec_close(c);
    av_free(c);
    av_free(picture);
为什么需要两个库文件 libavformat 和 libavcodec :许多视频文件格式(AVI就是一个最好的例子)实际上并没有明确指出应该使用哪种编码来解析音频和视频数据;它们只是定义了音频流和视频流(或者,有可 能是多个音频视频流)如何被绑定在一个文件里面。这就是为什么有时候,当你打开了一个AVI文件时,你只能听到声音,却不能看到图象--因为你的系统没有 安装合适的视频解码器。所以, libavformat 用来处理解析视频文件并将包含在其中的流分离出来, 而libavcodec 则处理原始音频和视频流的解码。

打开视频文件:
首先第一件事情--让我们来看看怎样打开一个视频文件并从中得到流。我们要做的第 一件事情就是初始化libavformat/libavcodec:
av_register_all();
这一步注册库中含有的 所有可用的文件格式和编码器,这样当打开一个文件时,它们才能够自动选择相应的文件格式和编码器。要注意你只需调用一次 av_register_all(),所以,尽可能的在你的初始代码中使用它。如果你愿意,你可以仅仅注册个人的文件格式和编码,不过,通常你不得不这么 做却没有什么原因。
下一步,打开文件:
AVFormatContext *pFormatCtx;
const char      *filename="myvideo.mpg";
// 打开视频文件
if(av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)!=0)
  handle_error(); // 不能打开此文件
最后三个参数描述了文件格式,缓冲区大小(size)和格式参数;我们通过简单地指明NULL或0告诉 libavformat 去自动探测文件格式并且使用默认的缓冲区大小。请在你的程序中用合适的出错处理函数替换掉handle_error()。
下 一步,我们需要取出包含在文件中的流信息:
// 取出流信息
if(av_find_stream_info(pFormatCtx)<0)
  handle_error(); // 不能够找到流信息
这一步会用有效的信息把 AVFormatContext 的流域(streams field)填满。作为一个可调试的诊断,我们会将这些信息全盘输出到标准错误输出中,不过你在一个应用程序的产品中并不用这么做:
dump_format(pFormatCtx, 0, filename, false);
就像在引言中提到的那样,我们仅仅处理视频流,而不是音频流。为了让这件事情更容易理解,我 们只简单使用我们发现的第一种视频流:
int            i, videoStream;
AVCodecContext *pCodecCtx;
//  寻找 第一个视频流
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
  if(pFormatCtx->streams->codec.codec_type==CODEC_TYPE_VIDEO)
  {
      videoStream=i;
      break;
  }
if(videoStream==-1)
  handle_error(); // Didn't find a video stream

// 得到视频流编码上下文的指针
pCodecCtx=&pFormatCtx->streams[videoStream]->codec;

好 了,我们已经得到了一个指向视频流的称之为上下文的指针。但是我们仍然需要找到真正的编码器打开它。

AVCodec *pCodec;

//  寻 找视频流的解码器
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
  handle_error(); // 找不到解码器

// 通知解码器我们能够处理截断的bit流--ie,
// bit流帧边界可以在包中
if(pCodec->capabilities & CODEC_CAP_TRUNCATED)
  pCodecCtx->flags|=CODEC_FLAG_TRUNCATED;

// 打开解码器
if(avcodec_open(pCodecCtx, pCodec)<0)
  handle_error(); // 打不开解码器

(那么什么是 “截断bit流”?好的,就像一会我们看到的,视频流中的数据是被分割放入包中的。因为每个视频帧的数据的大小是可变的,那么两帧之间的边界就不一定刚好 是包的边界。这里,我们告知解码器我们可以处理bit流。)


存储在 AVCodecContext结构中的一个重要的信息就是视频帧速率。为了允许非整数的帧速率(比如 NTSC的  29.97 帧),速率以分数的形式存储,分子在 pCodecCtx->frame_rate,分母在 pCodecCtx->frame_rate_base 中。在用不同的视频文件测试库时,我注意到一些编码器(很显然ASF)似乎并不能正确的给予赋值( frame_rate_base 用1代替1000)。下面给出修复补丁:

//  加入这句话来纠正某些编码器产生的帧速错误
if(pCodecCtx->frame_rate>1000 && pCodecCtx->frame_rate_base==1)
  pCodecCtx->frame_rate_base=1000;

注意即使将来这个bug解决了,留下这几句话也并没有什么坏 处。视频不可能拥有超过1000fps的帧速。

只剩下一件事情要做了:给视频帧分配空间以便存储解码后的图片:

AVFrame *pFrame;

pFrame=avcodec_alloc_frame();

就这样,现在我们开始解码这些视频。

解 码视频帧
就像我前面提到过的,视频文件包含数个音频和视频流,并且他们各个独自被分开存储在固定大小的包里。我们要做的就是使用 libavformat依次读取这些包,过滤掉所有那些视频流中我们不感兴趣的部分,并把它们交给 libavcodec 进行解码处理。在做这件事情时,我们要注意这样一个事实,两帧之间的边界也可以在包的中间部分。
听起来很复杂?幸运的是,我们在一个例程中封装了 整个过程,它仅仅返回下一帧:

bool GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
  int videoStream, AVFrame *pFrame)
{
  static AVPacket packet;
  static int      bytesRemaining=0;
  static uint8_t  *rawData;
  static bool     fFirstTime=true;
  Int bytesDecoded;
  Int frameFinished;

//  我 们第一次调用时,将 packet.data 设置为NULL指明它不用释放了
  if(fFirstTime)
  {
      fFirstTime=false;
      packet.data=NULL;
  }

// 解码直到成功解码完整的一帧
  while(true)
  {
       //  除非解码完毕,否则一直在当前包中工作
      while(bytesRemaining > 0)
      {
      //  解码下一块数 据
          bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame,
              &frameFinished, rawData, bytesRemaining);

              // 出错了?
          if(bytesDecoded < 0)
          {
              fprintf(stderr, "Error while decoding frame/n");
              return false;
          }

          bytesRemaining-=bytesDecoded;
          rawData+=bytesDecoded;

              // 我们完成当前帧了吗?接着我们返回
          if(frameFinished)
              return true;
      }

      // 读取下一包,跳过所有不属于这个流的包
      do
      {
          // 释放旧的包
          if(packet.data!=NULL)
              av_free_packet(&packet);

          // 读取新的包
          if(av_read_packet(pFormatCtx, &packet)<0)
              goto loop_exit;
      } while(packet.stream_index!=videoStream);

      bytesRemaining=packet.size;
      rawData=packet.data;
  }

loop_exit:

      // 解码最后一帧的余下部分
  bytesDecoded=avcodec_decode_video(pCodecCtx, pFrame, &frameFinished,
      rawData, bytesRemaining);

      // 释放最后一个包
  if(packet.data!=NULL)
      av_free_packet(&packet);

  return frameFinished!=0;
}

现在,我们要做的就是在一个循环中,调用 GetNextFrame () 直到它返回false。还有一处需要注意:大多数编码器返回 YUV 420 格式的图片(一个亮度和两个色度通道,色度通道只占亮度通道空间分辨率的一半(译者注:此句原句为the chrominance channels samples at half the spatial resolution of the luminance channel))。看你打算如何对视频数据处理,或许你打算将它转换至RGB格式。(注意,尽管,如果你只是打算显示视频数据,那大可不必要这么做;查 看一下 X11 的 Xvideo 扩展,它可以在硬件层进行 YUV到RGB 转换。)幸运的是, libavcodec 提供给我们了一个转换例程 img_convert ,它可以像转换其他图象进行 YUV 和 RGB之间的转换。这样解码视频的循环就变成这样:

while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, pFrame))
{
  img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame,
      pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

  // 处理视频帧(存盘等等)
  DoSomethingWithTheImage(pFrameRGB);
}

RGB 图象pFrameRGB (AVFrame *类型)的空间分配如下:

AVFrame *pFrameRGB;
int     numBytes;
uint8_t *buffer;

// 分配一个AVFrame 结构的空间
pFrameRGB=avcodec_alloc_frame();
if(pFrameRGB==NULL)
  handle_error();

// 确认所需缓冲区大小并且分配缓冲区空间
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
  pCodecCtx->height);
buffer=new uint8_t[numBytes];

// 在pFrameRGB中给图象位面赋予合适的缓冲区
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
  pCodecCtx->width, pCodecCtx->height);

清除
好了,我们已经处理了我们的视 频,现在需要做的就是清除我们自己的东西:
// 释放 RGB 图象
delete [] buffer;
av_free(pFrameRGB);

// 释放YUV 帧
av_free(pFrame);

// 关闭解码器(codec)
avcodec_close(pCodecCtx);

// 关闭视频文件
av_close_input_file(pFormatCtx);

你可能感兴趣的:(Stream,video,null,存储,Parameters,Codec)