http://lazybing.github.io/blog/categories/ffmpegyuan-ma-fen-xi/
一、下载FFmpeg的编译源码
进入网站:http://ffmpeg.org/download.html
二、编译源码
执行下述命令:
./configure --prefix=/usr/local/ffmpeg --enable-debug=3 --enable-ffplay
sudo make -j 4
sudo make install
如果编译成功,那么将会在安装路径/user/local/ffmpeg下出现如下子目录
FFMpeg 作为音视频领域的开源工具,它几乎可以实现所有针对音视频的处理。本文详细记录使用 FFMpeg 开发过程中,经常使用到的结构体的含义以及使用场景。
首先,我们从 FFMpeg 主要完成的功能视频编解码开始,编解码的大致流程可以使用如下图表
AVPacket结构体解析
从上面的图中可以看出,解复用器 Demuxer 输出的 packets data 作为解码器 Decoder 的输入;同时也是编码器 Encoder 的输出,复用器 Muxer 的输入。
FFMpeg 中使用 AVpacket 结构定义图中描述的 packet。该结构保存了压缩数据,它由 demuxers 输出,之后作为 decoders 的输入;或作为 encoders 的输出,之后作为 muxers 的输入。
对于视频数据,它只包含一帧压缩数据;对于音频数据,它可能包含多帧压缩数据。
typedef struct AVPacket{AVBufferRef *buf;int64_t pts;int64_t dts;uint8_t *data;int size;int stream_index;int flags;AVPacketSideData *side_data;int side_data_elems;int duration;int64_t pos;int64_t convergence_duration;
}
pts:显示时间戳,它的单位是 AVStream->time_base;如果在文件中没有保存这个值,它被设置为 AV_NOPTS_VALUE。由于图像显示不可能早于图像解压,因此 PTS 必须比 DTS(解码时间戳)大或者相等。某些文件格式中可能会使用 PTS/DTS 表示其他含义,此时时间戳必须转为真正的时间戳才能保存到 AVPacket 结构中。
dts:解码时间戳,它的单位是 AVStream->time_base,表示压缩视频解码的时间,如果文件中没有保存该值,它被设置为 AV_NOPTS_VALUE。
data:指向真正的压缩编码的数据。
size:表示该 AVPacket 结构中 data 字段所指向的压缩数据的大小。
stream_index:标识该 AVPacket 结构所属的视频流或音频流。
duration:该 AVPacket 包以 AVStream->time_base 为单位,所持续的时间,0 表示未知,或者为显示时间戳的差值(next_pts - this pts)。
pos:表示该 AVPacket 数据在媒体中的位置,即字节偏移量。
由上面的图可以看出,该结构主要用于解码器(Decoder)或编码器(Encoder),一般使用方法代码示例如下:
AVPacket avpkt;
av_init_packet(&avpkt);
avpkt.size = ...
avpkt.data = ...
AVFrame结构体解析
上面已经分析了压缩域的表示结构体 AVPacket 结构,与之相对于的,FFMpeg 为之提供的表示像素域的结构体为 AVFrame 结构。从上面的图中也可以看出,Frame 主要作为解码器的输出、编码器的输入。AVFrame 结构主要用来描述未压缩的视频或音频数据,比如视频的 YUV 数据、RGB 数据,音频的 PCM 数据等。
AVFrame 结构体必须使用av_frame_alloc()分配,注意该函数只是分配了 AVFrame 结构本身,它的 data 区域要用其他方式管理;该结构体的释放要用av_frame_free()。
AVFrame 结构体通常只需分配一次,之后即可通过保存不同的数据来重复多次使用,比如一个 AVFrame 结构可以保存从解码器中解码出的多帧数据。此时,就可以使用av_frame_unref()释放任何由 Frame 保存的参考帧并还原回最原始的状态。
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; /* number of audio samples(per channel) described by this frame */int format;int key_frame; /* 1->keyframe, 0->not*/enum AVPictureType pict_type;AVRational sample_aspect_ratio;int64_t pts;int64_t pkt_pts;int64_t pkt_dts;int coded_picture_number;int display_picture_number;int quality;void *opaque; /* for some private data of the user */uint64_t error[AV_NUM_DATA_POINTERS];int repeat_pict;int interlaced_frame;int top_field_first; /* If the content is interlaced, is top field displayed first */int palette_has_changed;int64_t reordered_opaque;int sample_rate; /*Sample rate of the audio data*/uint64_t channel_layout; /*channel layout of the audio data*/AVBufferRef *buf[AV_NUM_DATA_POINTERS];AVBufferRef **extended_buf;int nb_exteneded_buf;AVFrameSideData **side_data;int nb_side_data;int flags;enum AVColorRange color_range;enum AVColorPrimaries color_primaries;enum AVColorTransferCharacteristic color_trc;enum AVColorSpace colorspace;enum AVChromaLocation chroma_location;int64_t best_effort_timestamp;int64_t pkt_pos;int64_t pkt_duration;AVDictionary *metadata;int decode _error_flags;int channels;int pkt_size;AVBufferRef *qp_table_buf;
}
data:指向图片或信道的指针,与初始化时分配的大小可能不同,一些解码器取数据范围超出(0,0)-(width, height),具体请查看avcodec_align_dimensions2()方法。一些过滤器或扫描器读数据时可能会超过 16 字节,所以当它们使用时,必须额外分配 16 字节。对于 packed 格式的数据(例如 RGB24),会存放到 data[0] 里面;对于 planar 格式的数据(例如 YUV420P),则会分开 data[0]/data[1]/data[2](YUV420P 中 data[0] 存放 Y,data[1] 存放 U,data[2] 存放 V)。
linesize:对于视频数据,表示每个图像行的字节大小;对于音频数据,表示每个 Plane 的字节大小,只有linesize[0]可以设置,对于plane 音频,每个信道 channel 必须是相同的。对于视频的 linesize 应为 CPU 的对准要求的倍数,一般为 32。注意 linesize 可大于可用的数据的尺寸,有可能存在由于性能原因额外填充。
width/height:视频的宽高
format:帧格式,-1表示未设置的帧格式。对于视频帧,该值为 enum 类型的 AVPixelFormat,例如 AV_PIX_FMT_YUV420P;对于音频帧,该值为 enum 型的 AVSampleFormat,例如 AV_SAMPLE_FMT_S16。
key_frame:关键帧,1 表示关键帧,0 表示非关键帧。
pict_type:帧图片类型,例如 I/P/B。
sample_aspect_ration:帧像素的宽高比,使用 AVRational 表示。
pts:显示时间戳,单位为 time_base。
pkt_pts:该 PTS 是从 AVPacket 结构中拷贝过来的;与之对应的是pkt_dts。
coded_picture_number/display_picture_number:解码序列号和显示序列号(Display Order/Decoded Order)。
interlaced_frame:表示该帧为 interlace 码流或者为 progressive 码流。
top_field_first:对于 interlace 码流,表示该它是 top first or bottom first。
AVCodec结构体解析
上面分析了像素域的表示结构体 AVFrame 和 压缩域的表示结构体 AVPacket,两者之间的转换是通过编解码器来完成的,FFMpeg 中使用 AVCodec 结构体表示编解码。本小节就来看一下 AVCodec 结构体。
typedef struct AVCodec{const char *name;const char *long_name;enum AVMediaType type;enum AVCodecID id;int capabilities;const AVRational *supported_framerates; ///< array of supported framerates, or NULL if any, array is terminated by {0,0}const enum AVPixelFormat *pix_fmts; ///< array of supported pixel formats, or NULL if unknown, array is terminated by -1const int *supported_samplerates; ///< array of supported audio samplerates, or NULL if unknown, array is terminated by 0const enum AVSampleFormat *sample_fmts; ///< array of supported sample formats, or NULL if unknown, array is terminated by -1const uint64_t *channel_layouts; ///< array of support channel layouts, or NULL if unknown. array is terminated by 0uint8_t max_lowres; ///< maximum value for lowres supported by the decoder, no direct access, use av_codec_get_max_lowres()const AVClass *priv_class; ///< AVClass for the private contextconst AVProfile *profiles; ///< array of recognized profiles, or NULL if unknown, array is terminated by {FF_PROFILE_UNKNOWN}int priv_data_size;struct AVCodec *next;int (*init_thread_copy)(AVCodecContext *);int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);const AVCodecDefault *defaults;void (*init_static_data)(struct AVCodec *codec);int (*init)(AVCodecContext *);int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,int *got_packet_ptr);int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);int (*close)(AVCodecContext *);void (*flush)(AVCodecContext *);int caps_internal;
}
name:具体的 CODEC 的名称的简短描述,比如”HEVC”/”H264”等。
long_name: CODEC 名称的详细描述,比如”HEVC (High Efficiency Video Coding)”。
type:媒体类型的字段,它是 enum 型的,表示视频、音频、字幕等,比如AVMEDIA_TYPE_VIDEO、AVMEIDA_TYPE_AUDIO。
id:唯一标识的 CODEC 类型,比如 AV_CODEC_ID_HEVC。
supported_framerates:支持的视频帧率的数组,以{0,0}作为结束。
pix_fmts:编解码器支持的图像格式的数组,以 -1 作为结束。
profiles:编解码器支持的 Profile,以 HEVC 为例,包含”Main”“Main10”“Main Still Picture”。
每一个编解码器对应一个 AVCodec 结构体,对应一种编解码方式,比如 HEVC、AVC、MPEG2、MPEG4、VP6、VP8、VP9等。以 HEVC 为例,FFMpeg中关于 AVCodec 的定义如下:
AVCodec ff_hevc_decoder = {.name = "hevc",.long_name = NULL_IF_CONFIG_SMALL("HEVC (High Efficiency Video Coding)"),.type = AVMEDIA_TYPE_VIDEO,.id = AV_CODEC_ID_HEVC,.priv_data_size = sizeof(HEVCContext),.priv_class = &hevc_decoder_class,.init = hevc_decode_init,.close = hevc_decode_free,.decode = hevc_decode_frame,.flush = hevc_decode_flush,.update_thread_context = hevc_update_thread_context,.init_thread_copy = hevc_init_thread_copy,.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY |AV_CODEC_CAP_SLICE_THREADS | AV_CODEC_CAP_FRAME_THREADS,.profiles = NULL_IF_CONFIG_SMALL(profiles),
};
各种不同的编解码器,FFMpeg 使用注册函数avcodec_register_all()来完成。
关于 AVCodec 的通常使用方法:首先,根据特定的 ID 找到特定的编解码器;其次,根据特定的编解码器分配出特定的描述编解码上下文的 AVCodecContext 结构体;之后,打开编解码器;最后,调用编解码器进行编解码。示例代码如下:
AVCodec *codec = NULL;
AVCodecContext *ctx = NULL;codec = avcodec_find_decoder(origin_ctx->codec_id);
ctx = avcodec_alloc_context3(codec);
avcodec_open2(ctx, codec, NULL);
...
AVCodecContext结构体解析
AVCodecContext 可能是最复杂的结构体了。因为结构体内的变量非常多,不可能一一分析,而且有的变量是编码时候用到、有的是解码时候用到,此处只对几个常用的变量做简要的记录。
typedef struct AVCodecContext{const AVClass *av_class;int log_level_offset;enum AVMediaType codec_type;const struct AVCodec *codec;enum AVCodecID codec_id;unsigned int codec_tag;void *priv_data;struct AVCodecInternal *internal;void *opaque;int bit_rate;int bit_rate_tolerance;int global_quality;int compression_level;int flags;int flags2;uint8_t *extradata;int extradata_size;AVRational time_base;int ticks_per_frame;int delay;int width, height;int coded_width, coded_height;int gop_size;enum AVPixelFormat pix_fmt;void (*draw_horiz_band)(struct AVCodecContext *s,const AVFrame *src, int offset[AV_NUM_DATA_POINTERS],int y, int type, int height);enum AVPixelFormat (*get_format)(struct AVCodecContext *s, const enum AVPixelFormat * fmt);int max_b_frames;float b_quant_factor;int b_frame_strategy;float b_quant_offset;int has_b_frames;int mpeg_quant; /*decoding: unused*/float i_quant_factor; /*decoding: unused*/float i_quant_offset; /*decoding: unused*/float lumi_masking; /*decoding: unused*/float temporal_cplx_masking; /*decoding: unused*/float spatial_cplx_masking; /*decoding: unused*/float p_masking; /*decoding: unused*/float dark_masking; /*decoding: unused*/int slice_count;int prediction_method; /*decoding: unused*/int *slice_offset;AVRational sample_aspect_ratio;int me_cmp; /*decoding: unused*/int me_sub_cmp; /*decoding: unused*/int mb_cmp; /*decoding: unused*/...
}AVCodecContext;
AVMediaType codec_type:编解码器的类型,如音频、视频、字幕。
AVCdec *codec:采用的解码器 AVCodec。
bit_rate: 平均比特率。
int width, height: 视频的宽高。
int refs: 运动估计参考帧的个数。
int sample_rate: 采样率。
int channels: 声道数。
AVCodecContext 使用 avcodec_alloc_context3 分配,该函数除了分配 AVCodecContext 外,还会初始化默认的字段。分配的内存必须通过 avcodec_free_context 释放。
上面提到的几个结构体 AVCodec/AVCodecContext,联合使用,通常是如下形式:
avcodec_register_all();
...
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if(!codec)exit(1);context = avcodec_alloc_context3(codec);if(avcodec_open2(context, codec, opts) < 0)exit(1);
AVStream结构体解析
AVStream用于存储一个视频流、音频流的结构体。
typedef struct AVStream{int index; //stream index in AVFormatContextint id;AVCodecContext *codec;void *priv_data;AVRational time_base;int64_t start_time;int64_t duration;int64_t nb_frames;int disposition;enum AVDiscard discard;AVRational sample_aspect_ratio;AVDictionary *metadata;AVRational avg_frame_rate;AVPacket attached_pic;AVPacketSideData *side_data;int nb_side_data;int event_flags;int pts_wrap_bits;int64_t first_dts;int64_t cur_dts;int64_t last_IP_pts;int last_IP_duration;int probe_packets;int codec_info_nb_frames;/* av_read_frame() support */enum AVStreamParseType need_parsing;struct AVCodecParserContext *parser;struct AVPacketList *last_in_packet_buffer;AVProbeData probe_data;#define MAX_REORDER_DELAY 16int64_t pts_buffer[MAX_REORDER_DELAY+1];AVIndexEntry *index_entries; /**< Only used if the format does notsupport seeking natively. */int nb_index_entries;unsigned int index_entries_allocated_size;AVRational r_frame_rate;int stream_identifier;int64_t interleaver_chunk_size;int64_t interleaver_chunk_duration;int request_probe;int skip_to_keyframe;int skip_samples;int64_t start_skip_samples;int64_t first_discard_sample;int64_t last_discard_sample;int nb_decoded_frames;int64_t mux_ts_offset;int64_t pts_wrap_reference;int pts_wrap_behavior;int update_initial_durations_done;int64_t pts_reorder_error[MAX_REORDER_DELAY+1];uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];int64_t last_dts_for_order_check;uint8_t dts_ordered;uint8_t dts_misordered;int inject_global_side_data;char *recommended_encoder_configuration;AVRational display_aspect_ratio;struct FFFrac *priv_pts;
}AVStream;
index:标识该视频流、音频流,是 AVFormatContext 中的流索引。
AVCodecContext *codec:指向该视频、音频流的内容。
AVRational time_base:通过该值可以把 PTS/DTS 转化为真正的时间。
int64_t duration:该视频、音频流长度。
AVDictionary *metadata: 元数据信息。
AVRational avg_frame_rate: 帧率。
AVFormatContext 结构体解析
AVFormatContext 是包含码流参数比较多的结构体, 它是 FFmpeg 解封装(flv、mp4、rmvb、avi)功能的结构体。 一般使用 avformat_alloc_context() 来创建该结构体,使用 avformat_free_context 释放该结构体。
typedef struct AVFormatContext {const AVClass *av_class;struct AVInputFormat *iformat;struct AVOutputFormat *oformat;void *priv_data;AVIOContext *pb;int ctx_flags;unsigned int nb_streams;AVStream **streams;char filename[1024];int64_t start_time;int64_t duration;int bit_rate;unsigned int packet_size;int max_delay;int flags;const uint8_t *key;int keylen;unsigned int nb_programs;AVProgram **programs;enum AVCodecID video_codec_id;enum AVCodecID audio_codec_id;enum AVCodecID subtitle_codec_id;unsigned int max_index_size;unsigned int max_picture_buffer;unsigned int nb_chapters;AVChapter **chapters;AVDictionary *metadata;int64_t start_time_realtime;int fps_probe_size;int error_recognition;AVIOInterruptCB interrupt_callback;int64_t max_interleave_delta;int strict_std_compliance;int event_flags;int max_ts_probe;int avoid_negative_ts;int ts_id;int audio_preload;int max_chunk_duration;int max_chunk_size;int use_wallclock_as_timestamps;int avio_flags;enum AVDurationEstimationMethod duration_estimation_method;int64_t skip_initial_bytes;unsigned int correct_ts_overflow;int seek2any;int probe_score;int format_probesize;char *codec_whitelist;char *format_whitelist;AVFormatInternal *internal;int io_repositioned;AVCodec *video_codec;AVCodec *audio_codec;AVCodec *subtitle_codec;AVCodec *data_codec;int metadata_header_padding;void *opaque;av_format_control_message control_message_cb;int64_t output_ts_offset;uint8_t *dump_separator;enum AVCodecID data_codec_id;int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
}
struct AVInputFormat *iformat; 输入数据的封装格式,由avformat_open_input设置,仅仅在Demuxing使用。
struct AVOutputFormat *oformat; 输出数据的封装格式,必须由使用者在avformat_write_header前设置,由Muxing使用。
priv_data,在muxing中,由avformat_write_header设置;在demuxing中,由avformat_open_input设置。
AVIOContext *pb;输入数据的缓存。如果iformat/oformat.flags设置为AVFMT_NOFILE的话,该字段不需要设置。对于Demuxing ,需要在avformat_open_input前设置,或由avformat_open_input设置;对于Muxing,在avformat_write_header前设置。
ctx_flags,码流的信息,表明码流属性的的信号。由libavformat设置,例如AVFMTCTX_NOHEADER。
nb_streams 指AVFormatContext.streams的数量,必须由avformat_new_stream设置,不能由其他代码改动。
AVStream **streams;文件中所有码流的列表,新的码流创建使用avformat_new_stream函数。Demuxing中,码流由avformat_open_input创建。 如果AVFMTCTX_NOHEADER被设置,新的码流可以出现在av_read_frame中。Muxing中,码流在avformat_write_header之前由用户创建。它的释放是由avformat_free_context完成的。
filename,输入或输出的文件名,Demuxing中由avformat_open_input设置,Muxing中在使用avformat_write_header前由调用者设置。
int64_t duration;码流的时长。
bit_rate,比特率。
enum AVCodecID video_codec_id;
AVDictionary *metadata; 元数据,适用于整个文件。 ·
AVIOContext 结构体解析
AVIOContext 是 FFMpeg 管理输入输出数据的结构体。
typedef struct AVIOContext{const AVClass *av_class;unsigned char *buffer; /**< Start of the buffer. */int buffer_size; /**< Maximum buffer size */unsigned char *buf_ptr; /**< Current position in the buffer */unsigned char *buf_end; /**< End of the data */int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);int64_t (*seek)(void *opaque, int64_t offset, int whence);int64_t pos; /**< position in the file of the current buffer */int must_flush; /**< true if the next seek should flush */int eof_reached; /**< true if eof reached */int write_flag; /**< true if open for writing */int max_packet_size;unsigned long checksum;unsigned char *checksum_ptr;unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);int error; /**< contains the error code or 0 if no error happened */int (*read_pause)(void *opaque, int pause);int64_t (*read_seek)(void *opaque, int stream_index,int64_t timestamp, int flags);int seekable;int64_t maxsize;int direct;int64_t bytes_read;int seek_count;int writeout_count;int orig_buffer_size;int short_seek_threshold;
}
unsigned char *buffer:缓存开始位置。
int buffer_size:缓存大小。
unsigned char *buf_ptr:当前指针读取到的位置。
unsigned char *buf_end:缓存结束的位置。
Ffmpeg 中 Demux 这一步是通过 avformat_open_input()这个 api 来做的,这个 api 读出文件的头部信息,并做 demux, 在此之后我们就可以读取媒体文件中的音频和视频流,然后通过 av_read_frame()从音频和视频流中读取出基本数据 流 packet,然后将 packet 送到 avcodec_decode_video2()和相对应的 api 进行解码。
FFMpeg 的 output_example.c 例子分析
该例子讲了如何输出一个 libavformat 库所支持格式的媒体文件。
FFmpeg命令及其用途
FFmpeg是一个功能强大的开源项目,用于处理音视频数据。它支持记录、转换和流化数字音频和视频。
FFmpeg命令行工具提供了丰富的功能,包括但不限于视频格式转换、视频剪辑、音频处理等。以下是一些常用的FFmpeg命令及其用途:
还有很多命令参数,不说了