Chinaunix首页 | 论坛 | 博客
  • 博客访问: 544448
  • 博文数量: 102
  • 博客积分: 2146
  • 博客等级: 大尉
  • 技术积分: 1146
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-09 17:32
文章分类

全部博文(102)

文章存档

2015年(14)

2014年(24)

2013年(5)

2012年(30)

2011年(16)

2010年(13)

分类: 嵌入式

2014-04-02 17:14:20

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

        在最简单的情况下,其实处理Video和Audio的步骤是非常简单的:
1:open video_stream从video.avi中
2:从video_stream中读取packet到frame里面
3:如果frame不完整就goto到第2步继续
4:对frame做些处理
5:跳到第2步重复
        packet包含了要被解码成原始数据帧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会读取媒体文件的头并且把这些信息保存到AVFormatContext结构体中,最后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的所有信息。用pCodecCtx 指向这个信息位置。然后下面就利用pCodecCtx 来找到这个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帧转换成的RGB帧:
  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,用来存放媒体文件的中即将要被转换的原始数据,我们用avpicture_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_malloc函数其实只是封装了malloc,并做了一些内存对齐操作,对于内存的操作,防止内存溢出以及释放,都跟malloc一样,需要我们自己来做。
        下面将pFrameRGBbuffer关联起来,这里的AVPicture 结构是AVFrame 结构的子集,AVPicture 结构与AVFrame 结构的开始部分一模一样:
  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中。然后用解码器pCodecCtxpacket.data中解码出原始数据帧并存放到pFrame中。参数frameFinished判断转换的结果。如果转换完成,调用img_convert函数把原始数据帧pFrame转换成我们要的RGB帧pFrameRGB,并调用SaveFrame函数把RGB帧保存成一个个的PPM图片(这里我们只保存了Video Stream的前15张图片,可以根据个人需要修改)。while循环最后会调用av_free_packet函数清除av_read_frame函数中读入packet的数据,然后循环继续读packet,继续解码,继续转换,继续保存成PPM图片,直到读完整个媒体文件。注意这里自己准备一个pSwsCtx结构,这个结构比较灵活,可以对即将要生成的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. }
        SaveFrame函数如下,先新建一个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]就是每行数据开头的地址。width是每行像素个数,width*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_free函数对应上面的av_malloc函数。

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


 


        










阅读(8517) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~