Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4050311
  • 博文数量: 366
  • 博客积分: 9916
  • 博客等级: 中将
  • 技术积分: 7195
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-29 23:27
个人简介

简单!

文章分类

全部博文(366)

文章存档

2013年(51)

2012年(269)

2011年(46)

分类: LINUX

2013-01-10 22:41:50

简介

       视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为(container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中一般会封装有视频和音频轨,也称为视频流(stream)和音频流,播放视频文件的第一步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流以及字幕(如果有的话),解析的数据读到包 (packet)中,每个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图像显示到屏幕上,将声音输出到声卡,最终就是我们看到的视频。

       FFmpeg的API就是根据这个过程设计的,因此使用FFmpeg来处理视频文件的方法非常直观简单。下面就一步一步介绍从视频文件中解码出图片的过程。



声明变量


首先定义整个过程中需要使用到的变量:

  1. AVFormatContext *pFormatCtx = NULL;
  2. AVCodecContext *pCodecCtx = NULL;
  3. AVCodec *pCodec = NULL;
  4. AVFrame *pFrame = NULL;
  5. AVFrame *pFrameRGB = NULL;
  6. AVPacket packet;
  7. AVDictionary *optionsDict = NULL;
  8. struct SwsContext *sws_ctx = NULL;

:保存需要读入文件的格式信息,比如流的个数以及流数据等

:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等

:真正的编解码器,其中有编解码需要调用的函数

:用于保存数据帧的数据结构,这里的两个帧分别是保存转换前后的两帧图像

:解析文件时会将音/视频帧读入到packet中



打开文件


  1. av_register_all();

定义在 里,调用它用以注册所有支持的文件格式以及编解码器,因此之后就可以用所有ffmpeg支持的codec了。当然,我们也可以指定某一文件格式和编解码器,但似乎没有必要这样做。


  1. if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
  2.     return -1;

使用新的API 来打开一个文件,第一个参数是一个AVFormatContext指针变量的地址,它会根据打开的文件信息填充AVFormatContext,需要注意的是,此处的pFormatContext必须为NULL或由分配得到,否则此函数调用会出问题。第二个参数是打开的文件名,通过argv[1]指定,也就是命令行的第一个参数。后两个参数分别用于指定特定的输入格式()以及指定文件打开额外参数的结构,这里均使用NULL。


  1. if(avformat_find_stream_info(pFormatCtx, NULL)<0)
  2.     return -1;
  3.   
  4. av_dump_format(pFormatCtx, -1, argv[1], 0);
函数只是读文件头,并不会填充流信息,因此需要调用获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。

最后调用一个帮助函数,输出文件的信息,也就是在使用ffmpeg时能看到的文件详细信息。第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。

现在 pFormatCtx->streams 中已经有所有流了,因此现在我们遍历它找到第一条视频流。

  1. videoStream=-1;
  2. for(i=0; i<pFormatCtx->nb_streams; i++)
  3.     if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
  4.       videoStream=i;
  5.       break;
  6.     }

  7. if(videoStream==-1)
  8.     return -1;
codec_type 的宏定义已经由以前的 CODEC_TYPE_VIDEO 改为 AVMEDIA_TYPE_VIDEO 了。

接下来我们通过这条 video stream 的编解码信息打开相应的解码器。

  1. pCodecCtx=pFormatCtx->streams[videoStream]->codec;
  2.   
  3.   pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
  4.   if(pCodec==NULL) {
  5.     fprintf(stderr, "Unsupported codec!\n");
  6.     return -1;
  7.   }

if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)
    return -1;



分配图像缓存


接下来我们准备给即将解码的图片分配内存空间。

  1. pFrame=avcodec_alloc_frame();
  2. if(pFrame==NULL)
  3.     return -1;

  4. pFrameRGB=avcodec_alloc_frame();
  5. if(pFrameRGB==NULL)
  6.     return -1;

调用 分配帧,因为最后我们会将图像写成 24-bits RGB 的 PPM 文件,因此这里需要两个 AVFrame,pFrame用于存储解码后的数据,pFrameRGB用于存储转换后的数据:


  1. numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
  2.              pCodecCtx->height);

这里调用 ,根据 pCodecCtx 中原始图像的宽高计算 RGB24 格式的图像需要占用的空间大小,这是为了之后给 pFrameRGB 分配空间。

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

  2.   sws_ctx =
  3.     sws_getContext
  4.     (
  5.         pCodecCtx->width,
  6.         pCodecCtx->height,
  7.         pCodecCtx->pix_fmt,
  8.         pCodecCtx->width,
  9.         pCodecCtx->height,
  10.         PIX_FMT_RGB24,
  11.         SWS_BILINEAR,
  12.         NULL,
  13.         NULL,
  14.         NULL
  15.     );

  16.   avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
  17.          pCodecCtx->width, pCodecCtx->height);

接着上面的,首先是用 分配上面计算大小的内存空间,然后调用 将 pFrameRGB 跟 buffer 指向的内存关联起来。调用 得到转换上下文。



获取图像


一切准备好就可以开始从文件中读取视频帧并解码得到图像了。

  1.   i=0;
  2.   while(av_read_frame(pFormatCtx, &packet)>=0) {
  3.   
  4.     if(packet.stream_index==videoStream) {

  5.       avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished,
  6.              &packet);
  7.       
  8.       if(frameFinished) {
  9.   
  10.         sws_scale
  11.         (
  12.             sws_ctx,
  13.             (uint8_t const * const *)pFrame->data,
  14.             pFrame->linesize,
  15.             0,
  16.             pCodecCtx->height,
  17.             pFrameRGB->data,
  18.             pFrameRGB->linesize
  19.         );
  20.     
  21.     if(++i<=5)
  22.      SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height,
  23.          i);
  24.       }
  25.     }
  26.     
  27.     av_free_packet(&packet);
  28.   }
从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用 对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置 frameFinished,如果已经得到下一帧图像则设置 frameFinished 非零,否则为零。所以这里我们判断 frameFinished 是否为零来确定 pFrame 中是否已经得到解码的图像。注意在每次处理完后需要调用 释放读取的packet。 解码得到图像后,很有可能不是我们想要的 RGB24 格式,因此需要使用 来做转换,使用 将图形从解码后的格式转换为 RGB24。最后将前5帧写人 ppm 文件。

  1. void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  2.   FILE *pFile;
  3.   char szFilename[32];
  4.   int y;
  5.   
  6.   sprintf(szFilename, "frame%d.ppm", iFrame);
  7.   pFile=fopen(szFilename, "wb");
  8.   if(pFile==NULL)
  9.     return;
  10.   
  11.   fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  12.   
  13.   for(y=0; y<height; y++)
  14.     fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);

  15.   fclose(pFile);
  16. }



释放资源


释放图像以及关闭文件。

  1.   av_free(buffer);
  2.   av_free(pFrameRGB);
  3.   
  4.   av_free(pFrame);
  5.   
  6.   avcodec_close(pCodecCtx);

  7.   avformat_close_input(&pFormatCtx);



注:本文根据教程An ffmpeg and SDL Tutorial()书写,基于现有的ffmpeg版本。后续若有标题以Tutorial开头的文章均参考该教程。
阅读(2771) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~