ffmpeg将多媒体文件的Video Stream每帧画面保存为PPM格式图片

       

转自:http://blog.chinaunix.net/uid-20846214-id-4193590.html

注:本文参考http://dranger.com/ffmpeg/tutorial01.html,但是这篇比较老旧了,文中用的最新版的FFmpeg,很多API都跟老版的不同,请大家注意。


        在最简单的情况下,其实处理Video和Audio的步骤是非常简单的:
1:open video_stream从video.avi中
2:从 video_stream中读取packet到frame里面
3:如果frame不完整就goto到第2步继续
4:对frame做些处理
5:跳到第2步重复
        pac ket包含了要被解码成原始数据帧frame的数据块。每个packet都包含了完整的frames。

        这章,我们会打开一个媒体文件,从文件里面读取Video Stream,然后把帧frame写入一个PPM文件,PPM(Portable Pixelmap)文件是一种linux图片格式,它很简单,只包含格式,图像宽高,bit数等信息以及图像数据。
    
一:打开文件
        要用FFmpeg库中的支持,必须包含它的头文件:
  1. #include <avcodec.h>
  2. #include <avformat.h>
  3. ...
  4. ...
  5. int main(int argc, char **argv)
  6. {
  7.     ...
  8.     av_register_all();
  9.     ...
  10. }
        这里调用的av_register_all会注册所有FFmpeg库支持的文件格式和codec,并且 av_ register_ all只调用一次。当一个文件被打开的时候,FFmpeg会自动找到对应的codec。

        接下来打开媒体文件:

  1. AVFormatContext *pFormatCtx;

  2. // Open video file
  3. if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
  4.   return -1; // Couldn't open file        
        这里调用 avformat_open_input 函数打开媒体文件的,媒体文件名由main函数参数argv传递进来。
         avformat_open_input 会读取媒体文件的头并且把这些信息保存到 AVFormatCo ntext结构体中,最后2个参数是用来指定文件格式,buffer大小和格式参数,设置成NULL的话,libavformat库会自动去探测它们。

        接下来我们需要Check Out这个文件的stream信息:
  1. // Retrieve stream information
  2. if (av_find_stream_info(pFormatCtx0)
  3.   return -1; // Couldn't find stream information
        然后调用一个用来debug的函数 dump_ format,会在屏幕上打印一些该媒体文件的信息:
  1. // Dump information about file onto standard error
  2. av_dump_format(pFormatCtx, 0, argv[1], 0);
        到此,该媒体的Video流会在一个数组中, pFormatCtx - > streams是这个数组的指针,数组大小是 pFormatCtx - > nb_streams,下面来从数组中找到Video流数据:
  1. int i;
  2. AVCodecContext *pCodecCtx;

  3. // Find the first video stream
  4. videoStream -1;
  5. for (i=0; i<pFormatCtx->nb_streams; i++) {
  6.   if (pFormatCtx->streams[i]->codec->codec_type =CODEC_TYPE_VIDEO) {
  7.     videoStream i;
  8.     break;
  9.   }
  10. }
  11. if(videoStream =-1)
  12.   return -1; // Didn't find a video stream

  13. // Get a pointer to the codec context for the video stream
  14. pCodecCtx pFormatCtx->streams[videoStream]->codec;
        这里我们得到了这个流使用的codec的所有信息。用 pCod ecC tx 指向这个信息位置。然后下面就利用 pCod ecC tx 来找到这个codec并打开它:

  1. AVCodec *pCodec;

  2. // Find the decoder for the video stream
  3. pCodec avcodec_find_decoder(pCodecCtx->codec_id);
  4. if (pCodec =NULL) {
  5.   fprintf(stderr, "Unsupported codec!\n");
  6.   return -1; // Codec not found
  7. }
  8. // Open codec
  9. if(avcodec_open2(pCodecCtx, pCodec, NULL0)
  10.   return -1; // Could not open codec

二:存储数据
        现在我们需要一个地方来存放从媒体文件中解码出的原始数据帧frame:
  1. AVFrame *pFrame;

  2. // Allocate video frame
  3. pFrame avcodec_alloc_frame();
         还需要一个地方来存 从这个pFrame帧转换成的 R G B 帧:
  1. // Allocate an AVFrame structure
  2. pFrameRGB avcodec_alloc_frame();
  3. if(pFrameRGB =NULL)
  4.   return -1;
        然后把每个RGB帧画面输出到PPM文件中,PPM文件格式用24-bit RGB保存。现在要做的就是把帧frame从当前格式转换成RGB。当然FFmpeg会帮我们完成这个任务,大多数情况下,我们想把帧frame转换成指定格式。
        别急,还需要一个内存buffer,用来存放媒体文件的中即将要被转换的原始数据,我们用 avpi cture_get_size函数来获取需要的size:
  1. uint8_t *buffer;
  2. int numBytes;
  3. // Determine required buffer size and allocate buffer
  4. numBytes avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
  5.                             pCodecCtx->height);
  6. buffer (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
        这里用的 av_ma lloc函数其实只是封装了malloc,并做了一些内存对齐操作,对于内存的操作,防止内存溢出以及释放,都跟malloc一样,需要我们自己来做。
        下面将 pFr a meRGBbuf fer关联起来,这里的 AVPic ture 结构是 AVFr ame 结构的子集, AVPic ture 结构与 AVFr ame 结构的开始部分一模一样:
  1. // Assign appropriate parts of buffer to image planes in pFrameRGB
  2. // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
  3. // of AVPicture
  4. avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
  5.                 pCodecCtx->width, pCodecCtx->height);
        到此都准备好了,下面开始读流:
三:读数据
        下面通过函数 av _read_frame读取Video Stream到packet中。然后用解码器 pCodecC txpacket . data中解码出原始数据帧并存放到 pF r ame中。参数 frameFin ished判断转换的结果。如果转换完成,调用 img _co n vert函数把原始数据帧 pFram e转换成我们要的RGB帧 pFrameRG B,并调用 Sav eF rame函数把RGB帧保存成一个个的PPM图片(这里我们只保存了Video Stream的前15张图片,可以根据个人需要修改)。while循环最后会调用 av_fre e_ packet函数清除 av_r ead_ frame函数中读入packet的数据,然后循环继续读packet,继续解码,继续转换,继续保存成PPM图片,直到读完整个媒体文件。注意这里自己准备一个 p SwsC tx结构,这个结构比较灵活,可以对即将要生成的PPM图片进行操作配置,如反转图片。
  1. int frameFinished;
  2. AVPacket packet;

  3. i=0;
  4. while(av_read_frame(pFormatCtx, &packet)>=0) {
  5.   // Is this a packet from the video stream?
  6.   if(packet.stream_index==videoStream) {
  7.     // Decode video frame pCodecCtx, pFrame, &frameFinished, &packet
  8.     avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
  9.     
  10.     // Did we get a video frame?
  11.     if(frameFinished) {
  12.     // Convert the image from its native format to RGB
  13.       sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, 
                pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
  14.     
  15.       // Save the frame to disk
  16.       if(++i<=15)
  17.         SaveFrame(pFrameRGB, pCodecCtx->width,
  18.                   pCodecCtx->height, i);
  19.     }
  20.   }
  21.     
  22.   // Free the packet that was allocated by av_read_frame
  23.   av_free_packet(&packet);
  24. }
        S aveF rame函数如下,先新建一个ppm文件,先把文件头写进去,然后把数据写进去,
        ppm的第一部分由三行ASCII码组成:
            第一行是P2 or P3 or P6,我们写的P6
            第二行是图像的大小,先是列像素数,后是行像素数,中间有一个空格,我们写的%d %d
            第三行是一个介于1和65535之间的整数,而且必须是文本的,用来表示每一个像素的一个分量用几个比特表示,我们写的255,即8bit表示一个像素分量,那一个像素就是24-bit了。
        三行之后是图像的纯数据流,从左到右,从上到下。我们这里写数据部分, pFrame - > data [ 0 ]是数据头,y是目前写入的行数, pFrame - > linesize [ 0 ]是每行的字节数, pFrame - > data [ 0 ] + y * pFrame - > linesize [ 0 ]就是每行数据开头的地址。 wi d th是每行像素个数, wi d th * 3就是每行要写的数据个数,以像素分量为单位。
  1. void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  2.   FILE *pFile;
  3.   char szFilename[32];
  4.   int y;
  5.   
  6.   // Open file
  7.   sprintf(szFilename, "frame%d.ppm", iFrame);
  8.   pFile=fopen(szFilename, "wb");
  9.   if(pFile==NULL)
  10.     return;
  11.   
  12.   // Write header
  13.   fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  14.   
  15.   // Write pixel data
  16.   for(y=0; y<height; y++)
  17.     fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  18.   
  19.   // Close file
  20.   fclose(pFile);
  21. }
        到此,该做些收尾工作了,释放内存,如果是在main函数中,最后记得返回:
  1. // Free the RGB image
  2. av_free(buffer);
  3. av_free(pFrameRGB);

  4. // Free the YUV frame
  5. av_free(pFrame);

  6. // Close the codec
  7. avcodec_close(pCodecCtx);

  8. // Close the video file
  9. av_close_input_file(pFormatCtx);

  10. return 0;
        这里的 av_fr ee函数对应上面的 av_ malloc函数。

        OK,到此完成了!编译,运行,会发现当前目录下有15长PPM图片:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值