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的用法:
- AVFrame对象必须调用av_frame_alloc()在堆上分配,注意此处指的是AVFrame对象本身,AVFrame对象必须调用av_frame_free()进行销毁。
- AVFrame中包含的数据缓冲区是
- 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()