Ffmpeg 开发笔记(一)

Posted by BY Blog on November 28, 2019

FFMPEG 开发笔记(一)

本文是基于网络资料作出的一系列整理以便于后续研究,文中大部分资料出处来源于 雷霄骅 ,感谢大佬。

一、函数与结构体

1、av_register_all()

该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。 (目前使用ffmpeg4.0以上的版本,这个函数已经废弃)

2、AVStream

AVStream在FFmpeg使用过程中关于编解码至关重要的结构体之一,是对流(Stream)的封装和抽象,描述了视频、音频等流的编码格式等基本流信息。此外也是音频、视频、字母数据流的重要载体。 对于一个典型的mp4格式的媒体源来说,需要经过解封装(解复用)解码然后输出的过程。而解封装从容器中分离出来的流,在FFmpeg中对应的对象就是AVStream。解复用解出来几条AVStream,就会在AVFormateContext中的nb_streams+1(总流数+1),并且将AVStream保存在streams数组中。

typedef struct AVStream {
int index;    /**< 在AVFormatContext中的stream索引 */
/**
* 特定格式的stream id。
* 解码: 由libavformat设定
* 编码: 如果未设置,则由用户通过libavformat设置
*/
int id;
AVCodecContext *codec;
/**
* 这是表示帧时间戳的基本时间单位(以秒为单位)。
* 解码: libavformat设置
* 编码: 可以在avformat_write_header()之前由调用者设置,以向混流器提供关于所需单位时间的
* 提示。在avformat_write_header()中,混流器将用实际用于写入文件的时间戳(根据格式可能与
* 用户提供的时间戳相关或不相关)的单位时间覆盖该字段。
*/
AVRational time_base;
/**
* 解码: 流显示序列中的第一帧pts时间,基于流时间(in stream time base.)。
* 只有当你百分百确定该值就是真实的第一帧的pts时间,才可以设置它
* 该值可能未定义(AV_NOPTS_VALUE).
* @note The ASF header does NOT contain a correct start_time the ASF
* 分流器禁止设置该值。
*/
int64_t start_time;
/**
* 解码: 流时长,基于流时间(in stream time base.)
*      如果一个源文件指定了比特率,而未指定流时长,该值将由比特率和文件大小估算。
* 编码: May be set by the caller before 用户可以在avformat_write_header()调用前设
* 置,提示混流器估算时长
*/
int64_t duration;
int64_t nb_frames;                 ///< 表示该流的已知帧数,或者为0
int disposition; /**< AV_DISPOSITION_* 推荐比特字段 */
enum AVDiscard discard; ///< 选择那些数据包可以被丢掉而不用被分流器分流。
/**
* 采样率(如果未知,该值为0)
* - 编码: 用户设置.
* - 解码: libavformat设置.
*/
AVRational sample_aspect_ratio;
AVDictionary *metadata;//原数据信息
/**
* 平均帧率
* - 分流: 在创建流时或者才函数avformat_find_stream_info()函数中可能被设置。
* - 混流: 可能在avformat_write_header()函数调用前被设置
*/
AVRational avg_frame_rate;
/**
* 对于设置有AV_DISPOSITION_ATTACHED_PIC标志的流, 该数据包会包含该附加图片(专辑图片什么的)
* 解码: libavformat设置, 不能被用户修改。
* 编码: 不使用
*/
AVPacket attached_pic;
AVPacketSideData *side_data;
int            nb_side_data;

/**
* 供用户检测流上发生的时间标志。 事件处理后,用户必须清除标志。 AVSTREAM_EVENT_FLAG_ *的组合。
*/
int event_flags;
/**
* 包含键和值的一系列字符串,用于描述推荐的编码器配置。
* 系列以 ','分割.
* 键和值由'='分割.
*/
char *recommended_encoder_configuration;
/**
* 显示宽高比(如果未知,则为0)
* - 编码: 不使用
* - 解码: libavformat设置, 用于在内部计算显示宽高比。
*/
AVRational display_aspect_ratio;
struct FFFrac *priv_pts;
/**
* libavformat内部使用的不透明字段。 不得以任何方式访问。
*/
AVStreamInternal *internal;
/*
* 与此流关联的编解码器参数。 分别在avformat_new_stream()和avformat_free_context()
* 中由libavformat分配和释放。
*
* - 分流: 由libavformat在流创建时填充或在avformat_find_stream_info()赋值。
* - 混流: 在avformat_write_header()之前由调用者填充
*/
AVCodecParameters *codecpar;
} AVStream;

常见变量及其作用

int index; //在AVFormatContext中的索引,这个数字是自动生成的,可以通过这个数字从AVFormatContext::streams表中索引到该流。
int id;//流的标识,依赖于具体的容器格式。解码:由libavformat设置。编码:由用户设置,如果未设置则由libavformat替换。
AVCodecContext *codec;//指向该流对应的AVCodecContext结构,调用avformat_open_input时生成。
AVRational time_base;//这是表示帧时间戳的基本时间单位(以秒为单位)。该流中媒体数据的pts和dts都将以这个时间基准为粒度。
int64_t start_time;//流的起始时间,以流的时间基准为单位。如需设置,100%确保你设置它的值真的是第一帧的pts。
int64_t duration;//解码:流的持续时间。如果源文件未指定持续时间,但指定了比特率,则将根据比特率和文件大小估计该值。
int64_t nb_frames; //此流中的帧数(如果已知)或0。
enum AVDiscard discard;//选择哪些数据包可以随意丢弃,不需要去demux。
AVRational sample_aspect_ratio;//样本长宽比(如果未知,则为0)。
AVDictionary *metadata;//元数据信息。
AVRational avg_frame_rate;//平均帧速率。解封装:可以在创建流时设置为libavformat,也可以在avformat_find_stream_info()中设置。封装:可以由调用者在avformat_write_header()之前设置。
AVPacket attached_pic;//附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
int probe_packets;//编解码器用于probe的包的个数。
int codec_info_nb_frames;//在av_find_stream_info()期间已经解封装的帧数。
int request_probe;//流探测状态,1表示探测完成,0表示没有探测请求,rest 执行探测。
int skip_to_keyframe;//表示应丢弃直到下一个关键帧的所有内容。
int skip_samples;//在从下一个数据包解码的帧开始时要跳过的采样数。
int64_t start_skip_samples;//如果不是0,则应该从流的开始跳过的采样的数目。
int64_t first_discard_sample;//如果不是0,则应该从流中丢弃第一个音频样本。
int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];//内部数据,从pts生成dts。
int64_t last_dts_for_order_check;
uint8_t dts_ordered;
uint8_t dts_misordered;//内部数据,用于分析dts和检测故障mpeg流。
AVRational display_aspect_ratio;//显示宽高比。

3、AVCodecContext

AVCodecContext中很多的参数是编码的时候使用的,而不是解码的时候使用的

enum AVMediaType codec_type  //编解码器的类型(视频,音频...)
struct AVCodec  *codec       //采用的解码器AVCodec(H.264,MPEG2...)
int bit_rate                 //平均比特率
uint8_t *extradata; int extradata_size//针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)
AVRational time_base         //根据该参数,可以把PTS转化为实际的时间(单位为秒s)
int width, height            //如果是视频的话,代表宽和高
int refs                     //运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有)
int sample_rate              //采样率(音频)
int channels                 //声道数(音频)
enum AVSampleFormat sample_fmt//采样格式
int profile                   //型(H.264里面就有,其他编码标准应该也有)
int level                     //级(和profile差不太多)

4、AVCodec

AVCodec是存储编解码器信息的结构体,包含了编解码器的基本信息,例如编解码器的名称,编解码类型(video or audio),以及编解码的参数等。下面列举了常用字段:

 const char *name; //编解码器名字
 const char *long_name; //编解码器全名
 enum AVMediaType type; //编解码器类型
 enum AVCodecID id; //编解码器ID
 const AVRational *supported_framerates; //支持帧率(视频)
 const enum AVPixelFormat *pix_fmts; //支持像素格式(视频)
 const int *supported_samplerates; //支持音频采样率(音频)
 const enum AVSampleFormat *sample_fmts; //支持采样格式(音频)
 const uint64_t *channel_layouts; //支持声道数(音频)
 const AVClass *priv_class; //私有数据

从AVCodec中,字段除了基本数据类型,还涉及到了其它结构体和枚举,例如AVMediaType,AVCodecID,AVRational,AVPixelFormat,AVSampleFormat,AVClass等。

AVMediaType媒体类型,是视频,音频,字幕等。

 enum AVMediaType {
     AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
     AVMEDIA_TYPE_VIDEO,
     AVMEDIA_TYPE_AUDIO,
     AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
     AVMEDIA_TYPE_SUBTITLE,
     AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
     AVMEDIA_TYPE_NB
 };

AVCodecID编解码器唯一标识符

AVPixelFormat视频格式,如RGB,YUV等

 enum AVPixelFormat {
  AV_PIX_FMT_NONE = -1,
  AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
  AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
  AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
  AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
  AV_PIX_FMT_YUV422P,   ///< planar YUV 4:2:2, 16bpp, (1 Cr & Cb sample per 2x1 Y samples)
  AV_PIX_FMT_YUV444P,   ///< planar YUV 4:4:4, 24bpp, (1 Cr & Cb sample per 1x1 Y samples)
  AV_PIX_FMT_YUV410P,   ///< planar YUV 4:1:0,  9bpp, (1 Cr & Cb sample per 4x4 Y samples)
  AV_PIX_FMT_YUV411P,   ///< planar YUV 4:1:1, 12bpp, (1 Cr & Cb sample per 4x1 Y samples)
  AV_PIX_FMT_GRAY8,     ///<        Y        ,  8bpp
  AV_PIX_FMT_MONOWHITE, ///<        Y        ,  1bpp, 0 is white, 1 is black, in each byte pixels are ordered from the msb to the lsb
  AV_PIX_FMT_MONOBLACK, ///<        Y        ,  1bpp, 0 is black, 1 is white, in each byte pixels are ordered from the msb to the lsb
  AV_PIX_FMT_PAL8,      ///< 8 bits with AV_PIX_FMT_RGB32 palette
  AV_PIX_FMT_YUVJ420P,  ///< planar YUV 4:2:0, 12bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV420P and setting color_range
  AV_PIX_FMT_YUVJ422P,  ///< planar YUV 4:2:2, 16bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV422P and setting color_range
  AV_PIX_FMT_YUVJ444P,  ///< planar YUV 4:4:4, 24bpp, full scale (JPEG), deprecated in favor of AV_PIX_FMT_YUV444P and setting color_range
 #if FF_API_XVMC
  AV_PIX_FMT_XVMC_MPEG2_MC,///< XVideo Motion Acceleration via common packet passing
  AV_PIX_FMT_XVMC_MPEG2_IDCT,
  AV_PIX_FMT_XVMC = AV_PIX_FMT_XVMC_MPEG2_IDCT,
 #endif /* FF_API_XVMC */
  AV_PIX_FMT_UYVY422,   
  ......
 }

AVSampleFormat采样格式,其中带P结尾的为平面格式。例如对于双通道音频,设左通道为L,右通道为R,如果是平面格式,在内存中的排列为LLLLLL……RRRRRR……,对于非平面格式,内存中排列为LRLRLRLRLR……

5、AVFrame

AVFrame中存储的是经过解码后的原始数据。在解码中,AVFrame是解码器的输出;在编码中,AVFrame是编码器的输入。下图中,“decoded frames”的数据类型就是AVFrame:

 _______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                       _________
                                      |         |
                                      | decoded |
                                      | frames  |
                                      |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|

AVFrame数据结构非常重要,它的成员非常多,导致数据结构定义篇幅很长。下面引用的数据结构定义中省略冗长的注释以及大部分成员,先总体说明AVFrame的用法,然后再将一些重要成员摘录出来单独进行说明:

typedef struct AVFrame {
    uint8_t *data[AV_NUM_DATA_POINTERS];
    int linesize[AV_NUM_DATA_POINTERS];
    uint8_t **extended_data;
    int width, height;
    int nb_samples;
    int format;
    int key_frame;
    enum AVPictureType pict_type;
    AVRational sample_aspect_ratio;
    int64_t pts;
    ......
} AVFrame;

AVFrame的用法:

  1. AVFrame对象必须调用av_frame_alloc()在堆上分配,注意此处指的是AVFrame对象本身,AVFrame对象必须调用av_frame_free()进行销毁。
  2. AVFrame中包含的数据缓冲区是
  3. AVFrame通常只需分配一次,然后可以多次重用,每次重用前应调用av_frame_unref()将frame复位到原始的干净可用的状态。

下面将一些重要的成员摘录出来进行说明:

data

uint8_t *data[AV_NUM_DATA_POINTERS];

存储原始帧数据(未编码的原始图像或音频格式,作为解码器的输出或编码器的输入)。 data是一个指针数组,数组的每一个元素是一个指针,指向视频中图像的某一plane或音频中某一声道的plane。 关于图像plane的详细说明参考“色彩空间与像素格式”,音频plane的详细说明参数“ffplay源码解析6-音频重采样 6.1.1节”。下面简单说明: 对于packet格式,一幅YUV图像的Y、U、V交织存储在一个plane中,形如YUVYUV…,data[0]指向这个plane; 一个双声道的音频帧其左声道L、右声道R交织存储在一个plane中,形如LRLRLR…,data[0]指向这个plane。 对于planar格式,一幅YUV图像有Y、U、V三个plane,data[0]指向Y plane,data[1]指向U plane,data[2]指向V plane; 一个双声道的音频帧有左声道L和右声道R两个plane,data[0]指向L plane,data[1]指向R plane。

linesize

 int linesize[AV_NUM_DATA_POINTERS];

对于视频来说,linesize是每行图像的大小(字节数)。注意有对齐要求。 对于音频来说,linesize是每个plane的大小(字节数)。音频只使用linesize[0]。对于planar音频来说,每个plane的大小必须一样。 linesize可能会因性能上的考虑而填充一些额外的数据,因此linesize可能比实际对应的音视频数据尺寸要大。

extended_data

uint8_t **extended_data;

对于视频来说,直接指向data[]成员。 对于音频来说,packet格式音频只有一个plane,一个音频帧中各个声道的采样点交织存储在此plane中;planar格式音频每个声道一个plane。在多声道planar格式音频中,必须使用extended_data才能访问所有声道,什么意思? 在有效的视频/音频frame中,data和extended_data两个成员都必须设置有效值。

width, height

int width, height;

视频帧宽和高(像素)。

nb_samples

int format;

帧格式。如果是未知格式或未设置,则值为-1。 对于视频帧,此值对应于“enum AVPixelFormat”结构:

enum AVPixelFormat {
    AV_PIX_FMT_NONE = -1,
    AV_PIX_FMT_YUV420P,   ///< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)
    AV_PIX_FMT_YUYV422,   ///< packed YUV 4:2:2, 16bpp, Y0 Cb Y1 Cr
    AV_PIX_FMT_RGB24,     ///< packed RGB 8:8:8, 24bpp, RGBRGB...
    AV_PIX_FMT_BGR24,     ///< packed RGB 8:8:8, 24bpp, BGRBGR...
    ......
}

对于音频帧,此值对应于“enum AVSampleFormat”格式:

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

key_frame

int key_frame;

视频帧是否是关键帧的标识,1->关键帧,0->非关键帧。

pict_type

enum AVPictureType pict_type;

视频帧类型(I、B、P等)。如下:

enum AVPictureType {
    AV_PICTURE_TYPE_NONE = 0, ///< Undefined
    AV_PICTURE_TYPE_I,     ///< Intra
    AV_PICTURE_TYPE_P,     ///< Predicted
    AV_PICTURE_TYPE_B,     ///< Bi-dir predicted
    AV_PICTURE_TYPE_S,     ///< S(GMC)-VOP MPEG-4
    AV_PICTURE_TYPE_SI,    ///< Switching Intra
    AV_PICTURE_TYPE_SP,    ///< Switching Predicted
    AV_PICTURE_TYPE_BI,    ///< BI type
};

sample_aspect_ratio

AVRational sample_aspect_ratio;

视频帧的宽高比。

pts

int64_t pkt_pts;

此frame对应的packet中的显示时间戳。是从对应packet(解码生成此frame)中拷贝PTS得到此值。

pkt_dts

int64_t pkt_dts;

此frame对应的packet中的解码时间戳。是从对应packet(解码生成此frame)中拷贝DTS得到此值。 如果对应的packet中只有dts而未设置pts,则此值也是此frame的pts。

coded_picture_number

int coded_picture_number;

在编码流中当前图像的序号。

display_picture_number

int display_picture_number;

在显示序列中当前图像的序号。

sample_rate

int sample_rate;

音频采样率。

channel_layout

uint64_t channel_layout;

音频声道布局。每bit代表一个特定的声道,参考channel_layout.h中的定义,一目了然:

buf

 AVBufferRef *buf[AV_NUM_DATA_POINTERS]; 

此帧的数据可以由AVBufferRef管理,AVBufferRef提供AVBuffer引用机制。这里涉及到缓冲区引用计数概念: AVBuffer是FFmpeg中很常用的一种缓冲区,缓冲区使用引用计数(reference-counted)机制。 AVBufferRef则对AVBuffer缓冲区提供了一层封装,最主要的是作引用计数处理,实现了一种安全机制。用户不应直接访问AVBuffer,应通过AVBufferRef来访问AVBuffer,以保证安全。 FFmpeg中很多基础的数据结构都包含了AVBufferRef成员,来间接使用AVBuffer缓冲区。 相关内容参考“FFmpeg数据结构AVBuffer” ????帧的数据缓冲区AVBuffer就是前面的data成员,用户不应直接使用data成员,应通过buf成员间接使用data成员。那extended_data又是做什么的呢????

如果buf[]的所有元素都为NULL,则此帧不会被引用计数。必须连续填充buf[] - 如果buf[i]为非NULL,则对于所有j<i,buf[j]也必须为非NULL。 每个plane最多可以有一个AVBuffer,一个AVBufferRef指针指向一个AVBuffer,一个AVBuffer引用指的就是一个AVBufferRef指针。 对于视频来说,buf[]包含所有AVBufferRef指针。对于具有多于AV_NUM_DATA_POINTERS个声道的planar音频来说,可能buf[]存不下所有的AVBbufferRef指针,多出的AVBufferRef指针存储在extended_buf数组中。

6、与AVFrame相关的函数及其说明

6.1、av_frame_alloc()

/**
 * Allocate an AVFrame and set its fields to default values.  The resulting
 * struct must be freed using av_frame_free().
 *
 * @return An AVFrame filled with default values or NULL on failure.
 *
 * @note this only allocates the AVFrame itself, not the data buffers. Those
 * must be allocated through other means, e.g. with av_frame_get_buffer() or
 * manually.
 */
AVFrame *av_frame_alloc(void);

构造一个frame,对象各成员被设为默认值。 此函数只分配AVFrame对象本身,而不分配AVFrame中的数据缓冲区。

6.2、av_frame_free()

/**
 * Free the frame and any dynamically allocated objects in it,
 * e.g. extended_data. If the frame is reference counted, it will be
 * unreferenced first.
 *
 * @param frame frame to be freed. The pointer will be set to NULL.
 */
void av_frame_free(AVFrame **frame);

释放一个frame。

6.3、av_frame_ref()

/**
 * Set up a new reference to the data described by the source frame.
 *
 * Copy frame properties from src to dst and create a new reference for each
 * AVBufferRef from src.
 *
 * If src is not reference counted, new buffers are allocated and the data is
 * copied.
 *
 * @warning: dst MUST have been either unreferenced with av_frame_unref(dst),
 *           or newly allocated with av_frame_alloc() before calling this
 *           function, or undefined behavior will occur.
 *
 * @return 0 on success, a negative AVERROR on error
 */
int av_frame_ref(AVFrame *dst, const AVFrame *src);

为src中的数据建立一个新的引用。 将src中帧的各属性拷到dst中,并且为src中每个AVBufferRef创建一个新的引用。 如果src未使用引用计数,则dst中会分配新的数据缓冲区,将将src中缓冲区的数据拷贝到dst中的缓冲区。

6.4、av_frame_clone()

/**
 * Create a new frame that references the same data as src.
 *
 * This is a shortcut for av_frame_alloc()+av_frame_ref().
 *
 * @return newly created AVFrame on success, NULL on error.
 */
AVFrame *av_frame_clone(const AVFrame *src);

创建一个新的frame,新的frame和src使用同一数据缓冲区,缓冲区管理使用引用计数机制。 本函数相当于av_frame_alloc()+av_frame_ref()

6.5、av_frame_unref()

/**
 * Unreference all the buffers referenced by frame and reset the frame fields.
 */
void av_frame_unref(AVFrame *frame);

解除本frame对本frame中所有缓冲区的引用,并复位frame中各成员。

6.6、av_frame_move_ref()

/**
 * Move everything contained in src to dst and reset src.
 *
 * @warning: dst is not unreferenced, but directly overwritten without reading
 *           or deallocating its contents. Call av_frame_unref(dst) manually
 *           before calling this function to ensure that no memory is leaked.
 */
void av_frame_move_ref(AVFrame *dst, AVFrame *src);

将src中所有数据拷贝到dst中,并复位src。 为避免内存泄漏,在调用av_frame_move_ref(dst, src)之前应先调用av_frame_unref(dst)

6.7、av_frame_get_buffer()

/**
 * Allocate new buffer(s) for audio or video data.
 *
 * The following fields must be set on frame before calling this function:
 * - format (pixel format for video, sample format for audio)
 * - width and height for video
 * - nb_samples and channel_layout for audio
 *
 * This function will fill AVFrame.data and AVFrame.buf arrays and, if
 * necessary, allocate and fill AVFrame.extended_data and AVFrame.extended_buf.
 * For planar formats, one buffer will be allocated for each plane.
 *
 * @warning: if frame already has been allocated, calling this function will
 *           leak memory. In addition, undefined behavior can occur in certain
 *           cases.
 *
 * @param frame frame in which to store the new buffers.
 * @param align Required buffer size alignment. If equal to 0, alignment will be
 *              chosen automatically for the current CPU. It is highly
 *              recommended to pass 0 here unless you know what you are doing.
 *
 * @return 0 on success, a negative AVERROR on error.
 */
int av_frame_get_buffer(AVFrame *frame, int align);

为音频或视频数据分配新的缓冲区。 调用本函数前,帧中的如下成员必须先设置好:

  • format (视频像素格式或音频采样格式)
  • width、height(视频画面和宽和高)
  • nb_samples、channel_layout(音频单个声道中的采样点数目和声道布局)

本函数会填充AVFrame.data和AVFrame.buf数组,如果有需要,还会分配和填充AVFrame.extended_data和AVFrame.extended_buf。 对于planar格式,会为每个plane分配一个缓冲区。

6.8、av_frame_copy()

/**
 * Copy the frame data from src to dst.
 *
 * This function does not allocate anything, dst must be already initialized and
 * allocated with the same parameters as src.
 *
 * This function only copies the frame data (i.e. the contents of the data /
 * extended data arrays), not any other properties.
 *
 * @return >= 0 on success, a negative AVERROR on error.
 */
int av_frame_copy(AVFrame *dst, const AVFrame *src);

将src中的帧数据拷贝到dst中。 本函数并不会有任何分配缓冲区的动作,调用此函数前dst必须已经使用了和src同样的参数完成了初始化。 本函数只拷贝帧中的数据缓冲区的内容(data/extended_data数组中的内容),而不涉及帧中任何其他的属性。

7、AVPacket

AVPacket是FFmpeg中很重要的一个数据结构,它保存了解复用(demuxer)之后,解码(decode)之前的数据(仍然是压缩后的数据)和关于这些数据的一些附加的信息,如显示时间戳(pts),解码时间戳(dts),数据时长(duration),所在流媒体的索引(stream_index)等等。

对于视频(Video)来说,AVPacket通常包含一个压缩的Frame;而音频(Audio)则有可能包含多个压缩的Frame。并且,一个packet也有可能是空的,不包含任何压缩数据data,只含有边缘数据side data(side data,容器提供的关于packet的一些附加信息,例如,在编码结束的时候更新一些流的参数,在另外一篇av_read_frame会介绍)AVPacket的大小是公共的ABI(Public ABI)一部分,这样的结构体在FFmpeg很少,由此也可见AVPacket的重要性,它可以被分配在栈空间上(可以使用语句AVPacket pkt;在栈空间定义一个Packet),并且除非libavcodec 和libavformat有很大的改动,不然不会在AVPacket中添加新的字段。

AVPacket中的字段可分为两部分:数据的缓存及管理和数据的属性。

7.1、 关于数据的属性有以下字段:

pts: (int64_t)显示时间,结合AVStream->time_base转换成时间戳
dts: (int64_t)解码时间,结合AVStream->time_base转换成时间戳
size: (int)data的大小
stream_index: (int)packet在stream的index位置
flags: (int)标示,结合AV_PKT_FLAG使用,其中最低为1表示该数据是一个关键帧。
#define AV_PKT_FLAG_KEY    0x0001 //关键帧
#define AV_PKT_FLAG_CORRUPT 0x0002 //损坏的数据
#define AV_PKT_FLAG_DISCARD  0x0004 /丢弃的数据
side_data_elems: (int)边缘数据元数个数
duration: (int64_t)数据的时长,以所属媒体流的时间基准为单位,未知则值为默认值0
pos: (int64_t )数据在流媒体中的位置,未知则值为默认值-1
convergence_duration:该字段已deprecated,不在使用

7.2、 关于数据缓存

AVPacket本身只是个容器,不直接的包含数据,而是通过数据缓存的指针引用数据。AVPacket包含两种数据
uint8_t *data:指向保存压缩数据的指针,这就是AVPacket的实际数据。
AVPacketSideData *side_data:容器提供的一些附加数据
AVBufferRef *buf:用来管理data指针引用的数据缓存,其使用在后面介绍。

7.3、 AVPacket中的内存管理

AVPacket实际上可看作一个容器,它本身并不包含压缩的流媒体数据,而是通过data指针引用数据的缓存空间。所以将Packet作为参数传递的时候,就要根据具体的需求,对data引用的这部分数据缓存空间进行特殊的处理。当从一个Packet去创建另一个Packet的时候,有两种情况:

1)两个Packet的data引用的是同一数据缓存空间,这个时候要注意数据缓存空间的释放问题和修改问题(相当于iOS的retain)

2)两个Packet的data引用不同的数据缓存空间,每个Packet都有数据缓存空间的copy

第二种情况,数据空间的管理比较简单,但是数据实际上有多个copy造成内存空间的浪费。所以要根据具体的需求,来选择到底是两个Packet共享一个数据缓存空间,还是每个Packet拥有自己独立的缓存空间。值得注意的是:对于多个Packet共享同一个缓存空间,FFMPEG使用的引用计数的机制(reference-count)。当有新的Packet引用共享的缓存空间时,就将引用计数+1;当释放了引用共享空间的Packet,就将引用计数-1;引用计数为0时,就释放掉引用的缓存。

8、AVPacket相关函数的使用

操作AVPacket的函数大约有30个,主要分为:AVPacket的创建初始化,AVPacket中的data数据管理(clone,free,copy),AVPacket中的side_data数据管理。

void av_init_packet(AVPacket *pkt);

​ 初始化packet的值为默认值,该函数不会影响data引用的数据缓存空间和size,需要单独处理。

int av_new_packet(AVPacket *pkt, int size);

​ av_init_packet的增强版,不但会初始化字段,还为data分配了存储空间

AVPacket *av_packet_alloc(void);

​ 创建一个AVPacket,将其字段设为默认值(data为空,没有数据缓存空间)。

void av_packet_free(AVPacket **pkt);

​ 释放使用av_packet_alloc创建的AVPacket,如果该Packet有引用计数(packet->buf不为空),则先调用av_packet_unref。

AVPacket *av_packet_clone(const AVPacket *src);

​ 其功能是av_packet_alloc和av_packet_ref

int av_copy_packet(AVPacket *dst, const AVPacket *src);

​ 复制一个新的packet,包括数据缓存

int av_copy_packet_side_data(AVPacket *dst, const AVPacket *src);

​ 初始化一个引用计数的packet,并指定了其数据缓存

int av_grow_packet(AVPacket *pkt, int grow_by);

​ 增大Packet->data指向的数据缓存

void av_shrink_packet(AVPacket *pkt, int size);

​ 减小Packet->data指向的数据缓存

9、AVFormatContext pFormatCtx = avformat_alloc_context();

在使用FFMPEG进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):

struct AVInputFormat *iformat  //输入数据的封装格式
AVIOContext *pb                //输入数据的缓存
unsigned int nb_streams        //视音频流的个数
AVStream **streams             //视音频流
char filename[1024]            //文件名
int64_t duration               //时长(单位:微秒us,转换为秒需要除以1000000)
int bit_rate                   //比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata         //元数据

视频的时长可以转换成HH:MM:SS的形式,示例代码如下:

AVFormatContext *pFormatCtx;
CString timelong;
...
//duration是以微秒为单位
//转换成hh:mm:ss形式
int tns, thh, tmm, tss;
tns  = (pFormatCtx->duration)/1000000;
thh  = tns / 3600;
tmm  = (tns % 3600) / 60;
tss  = (tns % 60);
timelong.Format("%02d:%02d:%02d",thh,tmm,tss)

视频的原数据(metadata)信息可以通过AVDictionary获取。元数据存储在AVDictionaryEntry结构体中,如下所示 :

typedef struct AVDictionaryEntry {
    char *key;
    char *value;
} AVDictionaryEntry;

每一条元数据分为key和value两个属性。在ffmpeg中通过av_dict_get()函数获得视频的原数据。下列代码显示了获取元数据并存入meta字符串变量的过程,注意每一条key和value之间有一个”\t:”,value之后有一个”\r\n”

//MetaData------------------------------------------------------------
//从AVDictionary获得
//需要用到AVDictionaryEntry对象
//CString author,copyright,description;
CString meta=NULL,key,value;
AVDictionaryEntry *m = NULL;
//不用一个一个找出来
/*	m=av_dict_get(pFormatCtx->metadata,"author",m,0);
author.Format("作者:%s",m->value);
m=av_dict_get(pFormatCtx->metadata,"copyright",m,0);
copyright.Format("版权:%s",m->value);
m=av_dict_get(pFormatCtx->metadata,"description",m,0);
description.Format("描述:%s",m->value);
*/
//使用循环读出
//(需要读取的数据,字段名称,前一条字段(循环时使用),参数)
while(m=av_dict_get(pFormatCtx->metadata,"",m,AV_DICT_IGNORE_SUFFIX)){
	key.Format(m->key);
	value.Format(m->value);
	meta+=key+"\t:"+value+"\r\n" ;
}

avformat_alloc_context()为分配内存的函数

 /**
 * Allocate an AVFormatContext. 
 * avformat_free_context() can be used to free the context and everything
 * allocated by the framework within it.
 */
 / **
 *分配一个AVFormatContext
 * avformat_free_context()可用于释放上下文和所有内容
 *由框架在其中分配。
 * /

10、avformat_open_input(&pFormatCtx, filepath, NULL, NULL)

该函数用于打开一个输入的封装器。在调用该函数之前,须确保av_register_all()和avformat_network_init()已调用。返回值等 0 代表读取成功,否则视频信息读取失败。

//参数说明:
AVFormatContext ps         //格式化的上下文。要注意,如果传入的是一个AVFormatContext*的指针,则该空间须自己手动清理,若传入的指针为空,则FFmpeg会内部自己创建。
const char *url,           //传入的地址。支持http,RTSP,以及普通的本地文件。地址最终会存入到AVFormatContext结构体当中。
AVInputFormat *fmt,        //指定输入的封装格式。一般传NULL,由FFmpeg自行探测。
AVDictionary options,      //其它参数设置。它是一个字典,用于参数传递,不传则写NULL。参见:**libavformat/options_table.h,其中包含了它支持的参数设置。

11、avformat_find_stream_info(pFormatCtx, NULL)

查找格式和索引。有些早期格式它的索引并没有放到头当中,需要你到后面探测,就会用到此函数。 该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformat\avformat.h 简单解释一下它的参数的含义:

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
//ic:          输入的AVFormatContext。
//options:     额外的选项,目前没有深入研究过。

该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。我们大致浏览一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程。

1.查找解码器:                   find_decoder()
2.打开解码器:                   avcodec_open2()
3.读取完整的一帧压缩编码的数据:     read_frame_internal()
注:av_read_frame()内部实际上就是调用的read_frame_internal()。
4.解码一些压缩编码数据:           try_decode_frame()