ffmpeg学习之tutorial_1

      最近对ffmpeg感兴趣, 由于我用的是ffmpeg1.1.3,由于 ffmpeg更新很快,网上的很多例子已经很陈旧了,找到了最新版本的这个tutorial点击打开链接。由于对ffmpeg不熟悉,所以需要不断地查看接口。在参考了http://my.oschina.net/u/555701/blog/56616?p=3#comments的基础上,于是有了如下的解析。与新手共勉。


Tutorial 1: Decoding video frames

视频播放过程

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

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

声明变量

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

 31     AVFormatContext *pFormatCtx = NULL;
 32     int i, videoStream;
 33     AVCodecContext *pCodecCtx = NULL;
 34     AVCodec *pCodec = NULL;
 35     AVFrame *pFrame = NULL;
 36     AVFrame *pFrameRGB = NULL;
 37     AVPacket packet;
 38     int frameFinished;
 39     int numBytes;
 40     uint8_t *buffer = NULL;


  • AVFormatContext:保存需要读入的文件的格式信息,比如流的个数以及流数据等。http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html
  • AVCodecContext:保存了相应流的详细编码信息,比如视频的宽、高,编码类型等。http://ffmpeg.org/doxygen/trunk/structAVCodecContext.html
  • AVCodec:真正的编解码器,其中有编解码需要调用的函数。http://ffmpeg.org/doxygen/trunk/structAVCodec.html
  • AVFrame:用于保存数据帧的数据结构,这里的两个帧分别是保存颜色转换前后的两帧图像。http://ffmpeg.org/doxygen/trunk/structAVFrame.html
  • AVPacket:解析文件时会将音/视频帧读入到packet中。http://ffmpeg.org/doxygen/trunk/structAVPacket.html

打开文件

接下来我们打开一个视频文件。


 49     // Register all formats and codecs,

 50     av_register_all();

av_register_all:
http://ffmpeg.org/doxygen/trunk/group__lavf__core.html#ga917265caec45ef5a0646356ed1a507e3
//Initialize libavformat and register all the muxers, demuxers and protocols.
     
      av_register_all() :  定义在 libavformat 里,调用它用以注册所有支持的文件格式以及编解码器,从其 实现代码里可以看到它会调用 avcodec_register_all,因此之后就可以用所有ffmpeg支持的codec了。

 52     // Open video file
 53     if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
 54         return -1; // Couldn't open file

avformat_open_input:
 http://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga10a404346c646e4ab58f4ed798baca32

Open an input stream and read the header.

The codecs are not opened. The stream must be closed with av_close_input_file().


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

 56     // Retrieve stream information
 57     if(avformat_find_stream_info(pFormatCtx, NULL)<0)
 58         return -1; // Couldn't find stream information
 59
 60     // Dump information about file onto standard error
 61     av_dump_format(pFormatCtx, 0, argv[1], 0);

avformat_find_stream_info:
http://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga10a404346c646e4ab58f4ed798baca32 Read packets of a media file to get stream information.

av_dump_format:
http://ffmpeg.org/doxygen/trunk/group__lavf__misc.html#gae2645941f2dc779c307eb6314fd39f10

avformat_open_input函数只是读文件头,并不会填充流信息,因此我们需要接下来调用avformat_find_stream_info获取文件中的流信息,此函数会读取packet,并确定文件中所有的流信息,设置pFormatCtx->streams指向文件中的流,但此函数并不会改变文件指针,读取的packet会给后面的解码进行处理。
最后调用一个帮助函数av_dump_format,输出文件的信息,也就是我们在使用ffmpeg时能看到的文件详细信息。第二个参数指定输出哪条流的信息,-1表示给ffmpeg自己选择。最后一个参数用于指定dump的是不是输出文件,我们dump的是输入文件,因此一定要是0。

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


 63     // Find the first video stream
 64     videoStream=-1;
 65     for(i=0; i<pFormatCtx->nb_streams; i++)
 66         if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
 67             videoStream=i;
 68             break;
 69         }
 70     if(videoStream==-1)
 71         return -1; // Didn't find a video stream

pFormatCtx:http://ffmpeg.org/doxygen/trunk/structAVFormatContext.html中查看数据结构相关参数

codec_type 的宏定义已经由以前的 CODEC_TYPE_VIDEO 改为 AVMEDIA_TYPE_VIDEO 了。接下来我们通过这条 video stream 的编解码信息打开相应的解码器:

 73     // Get a pointer to the codec context for the video stream
 74     pCodecCtx=pFormatCtx->streams[videoStream]->codec;
 75
 76     // Find the decoder for the video stream
 77     pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
 78     if(pCodec==NULL) {
 79         fprintf(stderr, "Unsupported codec!\n");
 80         return -1; // Codec not found
 81     }
 82     // Open codec
 83     if(avcodec_open2(pCodecCtx, pCodec, &optionsDict)<0)
 84         return -1; // Could not open codec

avcodec_find_decoder:
http://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga19a0ca553277f019dd5b0fec6e1f9dca
Find a registered decoder with a matching codec ID.
avcodec_open2:
http://ffmpeg.org/doxygen/trunk/group__lavc__core.html#ga11f785a188d7d9df71621001465b0f1d
Initialize the AVCodecContext to use the given AVCodec.

分配图像缓存

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

 86     // Allocate video frame
 87     pFrame=avcodec_alloc_frame();
 88
 89     // Allocate an AVFrame structure
 90     pFrameRGB=avcodec_alloc_frame();
 91     if(pFrameRGB==NULL)
 92         return -1;

avcodec_alloc_frame:
http://ffmpeg.org/doxygen/trunk/group__lavc__core.html#gad5f9212dec34c9fff0124171fa684a18

Allocate an AVFrame and set its fields to default values.

The resulting struct must be freed using avcodec_free_frame().


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

 94     // Determine required buffer size and allocate buffer
 95     numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
 96             pCodecCtx->height);
 

avpicture_get_size:

http://ffmpeg.org/doxygen/trunk/group__lavc__picture.html#ga18a08bcb237767ef442fd5d3d1dd2084

Calculate the size in bytes that a picture of the given width and height would occupy if stored in the given picture format.

Always assume a linesize alignment of 1.


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

 97     buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
 98
 99     sws_ctx =
100         sws_getContext
101         (
102          pCodecCtx->width,
103          pCodecCtx->height,
104          pCodecCtx->pix_fmt,
105          pCodecCtx->width,
106          pCodecCtx->height,
107          PIX_FMT_RGB24,
108          SWS_BILINEAR,
109          NULL,
110          NULL,
111          NULL
112         );
113
114     // Assign appropriate parts of buffer to image planes in pFrameRGB
115     // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
116     // of AVPicture
117     avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
118             pCodecCtx->width, pCodecCtx->height);

av_malloc:
http://ffmpeg.org/doxygen/trunk/group__lavu__mem.html#gacbca30ebc510a7e4156d66e7aceb2dc8
Allocate a block of size bytes with alignment suitable for all memory accesses (including vectors if available on the CPU).
sws_getContext:
http://ffmpeg.org/doxygen/trunk/group__lsws.html#gaf360d1a9e0e60f906f74d7d44f9abfdd

Allocate and return an SwsContext.

You need it to perform scaling/conversion operations using sws_scale().

avpicture_fill:
http://ffmpeg.org/doxygen/trunk/group__lavc__picture.html#ga18a08bcb237767ef442fd5d3d1dd2084
Fill in the AVPicture fields, always assume a linesize alignment of 1.

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

获取图像

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

120     // Read frames and save first five frames to disk
121     i=0;
122     while(av_read_frame(pFormatCtx, &packet)>=0) {
123         // Is this a packet from the video stream?
124         if(packet.stream_index==videoStream) {
125             // Decode video frame
126             avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished,
127                     &packet);
128
129             // Did we get a video frame?
130             if(frameFinished) {
131                 // Convert the image from its native format to RGB
132                 sws_scale
133                     (
134                      sws_ctx,
135                      (uint8_t const * const *)pFrame->data,
136                      pFrame->linesize,
137                      0,
138                      pCodecCtx->height,
139                      pFrameRGB->data,
140                      pFrameRGB->linesize
141                     );
142
143                 // Save the frame to disk
144                 if(++i <= 20)
145                 {
146                     SaveFrame(pFrameRGB, pCodecCtx->width,
147                             pCodecCtx->height, i);
148                     if (20 == i)
149                         break;
150                 }
151             }
152         }
153
154         // Free the packet that was allocated by av_read_frame
155         av_free_packet(&packet);
156     }
157
158     // Free the RGB image
159     av_free(buffer);
160     av_free(pFrameRGB);
161
162     // Free the YUV frame
164
165     // Close the codec
166     avcodec_close(pCodecCtx);
167
168     // Close the video file
169     avformat_close_input(&pFormatCtx);
170
171     return 0;
172 }

av_read_frame:
http://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#ga4fdb3084415a82e3810de6ee60e46a61

Return the next frame of a stream. 

This function returns what is stored in the file, and does not validate that what is there are valid frames for the decoder. It will split what is stored in the file into frames and return one for each call. It will not omit invalid data between valid frames so as to give the decoder the maximum information possible for decoding.


avcodec_decode_video2:

http://ffmpeg.org/doxygen/trunk/group__lavc__decoding.html#ga99ee61b6dcffb7817a275d39da58cc74

Decode the video frame of size avpkt->size from avpkt->data into picture. Some decoders may support multiple frames in a singleAVPacket, such decoders would then just decode the first frame.


sws_scale:
http://ffmpeg.org/doxygen/trunk/group__lsws.html#gae531c9754c9205d90ad6800015046d74

Scale the image slice in srcSlice and put the resulting scaled slice in the image in dst.

A slice is a sequence of consecutive rows in an image.

Slices have to be provided in sequential order, either in top-bottom or bottom-top order. If slices are provided in non-sequential order the behavior of the function is undefined.


avformat_close_input:
http://ffmpeg.org/doxygen/trunk/group__lavf__decoding.html#gae804b99aec044690162b8b9b110236a4

Close an opened input AVFormatContext.

Free it and all its contents and set *s to NULL.


av_read_frame 从文件中读取一个packet,对于视频来说一个packet里面包含一帧图像数据,音频可能包含多个帧(当音频帧长度固定时),读到这一帧后,如果是视频帧,则使用avcodec_decode_video2 对packet中的帧进行解码,有时候解码器并不能从一个packet中解码得到一帧图像数据(比如在需要其他参考帧的情况下),因此会设置 frameFinished,如果已经得到下一帧图像则设置 frameFinished 非零,否则为零。所以这里我们判断 frameFinished 是否为零来确定 pFrame 中是否已经得到解码的图像。注意在每次处理完后需要调用av_free_packet 释放读取的packet。

解码得到图像后,很有可能不是我们想要的 RGB24 格式,因此需要使用 swscale 来做转换,调用 sws_getCachedContext 得到转换上下文,使用 sws_scale 将图形从解码后的格式转换为 RGB24,最后将前50帧写人 ppm 文件。最后释放图像以及关闭文件。



   8 void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
   9     FILE *pFile;
 10     char szFilename[32];
 11     int y;
 12
 13     // Open file
 14     sprintf(szFilename, "frame%d.ppm", iFrame);
 15     pFile=fopen(szFilename, "wb");
 16     if(pFile==NULL)
 17         return;
 18
 19     // Write header
 20     fprintf(pFile, "P6\n%d %d\n255\n", width, height);
 21
 22     // Write pixel data
 23     for(y=0; y<height; y++)
 24         fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
 25
 26     // Close file
 27     fclose(pFile);
 28 }


    附注:

  接口网址:http://ffmpeg.org/doxygen/trunk/index.html

   数据结构接口:http://ffmpeg.org/doxygen/trunk/annotated.html


这个是http://my.oschina.net/u/555701/blog/56616?p=3#comments里的另一种接口。由于sws_getContext将被放弃,所以更推荐用下面的方法。具体参考前面链接。
sws_getCachedContext:
http://ffmpeg.org/doxygen/trunk/group__lsws.html#gadffa09f208a3eba7fa3a6b1f74ab77f7

Check if context can be reused, otherwise reallocate a new one.

If context is NULL, just calls sws_getContext() to get a new context. Otherwise, checks if the parameters are the ones already saved in context. If that is the case, returns the current context. Otherwise, frees context and gets a new context with the new parameters.

Be warned that srcFilter and dstFilter are not checked, they are assumed to remain the same.


总结:

av_register_all();//初始化ffmpeg库,如果系统里面的ffmpeg没配置好这里会出错 avformat_open_input(); avformat_find_stream_info();//查找文件的流信息 av_dump_format();//dump只是个调试函数,输出文件的音、视频流的基本信息了,帧率、分辨率、音频采样等等 for(...);//遍历文件的各个流,找到第一个视频流,并记录该流的编码信息 sws_getContext();//根据编码信息设置渲染格式 avcodec_find_decoder();//在库里面查找支持该格式的解码器 avcodec_open2();//打开解码器 pFrame=avcodec_alloc_frame();//分配一个帧指针,指向解码后的原始帧 pFrameRGB=avcodec_alloc_frame();//分配一个帧指针,指向存放转换成RGB后的帧 avpicture_fill(pFrameRGB);//给pFrameRGB帧加上分配的内存; while true{      av_read_frame();//读取一个帧(到最后帧则break)      avcodec_decode_video2();//解码该帧      sws_getCachedContext()sws_scale();//把该帧转换(渲染)成RGB      SaveFrame();//对前5帧保存成ppm图形文件(这个是自定义函数,非API)      av_free_packet();//释放本次读取的帧内存 }

  av_free(buffer);   av_free(pFrameRGB);   av_free(pFrame);   avcodec_close(pCodecCtx);   avformat_close_input(&pFormatCtx);

 libavcodec:CODEC其实是Coder/Decoder的缩写,也就是编码解码器;

libavdevice:对输出输入设备的支持;

libavformat:对音频视频格式的解析

libavutil:集项工具;

libpostproc:后期效果处理;

libswscale:视频场景比例缩放、色彩映射转换;




你可能感兴趣的:(linux,ffmpeg)