FFMPEG视音频编解码零基础学习方法
100行代码实现最简单的基于FFMPEG+SDL的视频播放器
本文例子的源码_demuxing_decoding.cpp,修改自源码自带的例子ffmpeg源码/doc/example/demuxing_decoding.c
FFmpeg很庞大,可以参考上面的链接,csdn上雷霄骅的博客,稍微熟悉点后可以看官方自带的例子,ffmpeg-3.3.8/doc/examples,通过例子熟悉FFmpeg的流程和函数.不过看了很多例子都是FFmpeg+SDL的,实际上Android开发者真的需要用SDL吗,作为学习的例子,渲染部分用Android平台自身的功能好了.
这个例子说明了怎样使用ffmpeg进行多媒体文件的解封装,解码成Yuv420p,并保存为文件.然后可以用Yuv播放器播放该文件.
流程图如下,图片来源:
涉及到的结构体说明:
AVFormatContext 详细介绍
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:元数据
AVInputFormat
AVInputFormat存储输入音视频使用的封装格式,每种音视频封装格式都对应一个AVInputFormat 结构。比如它包含的对象:
/**
* A comma separated list of short names for the format. New names
* may be appended with a minor bump.
/
const char name;
/
* Descriptive name for the format, meant to be more human-readable
* than name. You should use the NULL_IF_CONFIG_SMALL() macro
* to define it.
*/
const char *long_name;
输入一个mp4文件,打印它的name和long_name如下:
name:mov,mp4,m4a,3gp,3g2,mj2, long_name:(null)
AVStream 详细介绍
AVStream是存储每一个视频/音频流信息的结构体。
AVStream重要的变量如下所示:
int index:标识该视频/音频流
AVCodecContext *codec:指向该视频/音频流的AVCodecContext(它们是一一对应的关系)
AVRational time_base:时基。通过该值可以把PTS,DTS转化为真正的时间。FFMPEG其他结构体中也有这个字段,但是根据我的经验,只有AVStream中的time_base是可用的。PTS*time_base=真正的时间
int64_t duration:该视频/音频流长度
AVDictionary *metadata:元数据信息
AVRational avg_frame_rate:帧率(注:对视频来说,这个挺重要的)
AVPacket attached_pic:附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面。
AVCodecContext 详细介绍
音视频流中关于编解码器的信息就是被我们叫做"codec context"(编解码器上下文)的东西,这里面包含了流中所使用的关于编解码器的所有信息.关键变量:
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差不太多)
在这里需要注意:AVCodecContext中很多的参数是编码的时候使用的,而不是解码的时候使用的。
AVCodec 详细介绍
AVCodec是存储编解码器信息的结构体,主要的几个变量:
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:支持的声道数(仅音频)
int priv_data_size:私有数据的大小
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)
...(代码太长,略)
AVIOContext 详细介绍
AVIOContext是FFMPEG管理输入输出数据的结构体
AVPacket 详细介绍
AVPacket是存储编码数据的结构体,典型用法是由解封装器填充数据,然后传递给解码器,或者从编码器接收数据,然后传递给封装器.重要的变量:
uint8_t *data:压缩编码的数据。
例如对于H.264来说。1个AVPacket的data通常对应一个NAL。
注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流
因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。
int size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流。
AVFrame 详细介绍
AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表,QP表,运动矢量表等数据。编码的时候也存储了相关的数据。因此在使用FFMPEG进行码流分析的时候,AVFrame是一个很重要的结构体。
主要变量:
uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。
int width, height:视频帧宽和高(1920x1080,1280x720…)
int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个
int format:解码后原始数据类型(YUV420,YUV422,RGB24…)
int key_frame:是否是关键帧
enum AVPictureType pict_type:帧类型(I,B,P…)
AVRational sample_aspect_ratio:宽高比(16:9,4:3…)
int64_t pts:显示时间戳
int coded_picture_number:编码帧序号
int display_picture_number:显示帧序号
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳过宏块表
int16_t (*motion_val[2])[2]:运动矢量表
uint32_t *mb_type:宏块类型表
short *dct_coeff:DCT系数,这个没有提取过
int8_t *ref_index[2]:运动估计参考帧列表(貌似H.264这种比较新的标准才会涉及到多参考帧)
int interlaced_frame:是否是隔行扫描
uint8_t motion_subsample_log2:一个宏块中的运动矢量采样个数,取log的
其他的变量不再一一列举,源代码中都有详细的说明。在这里重点分析一下几个需要一定的理解的变量:
1. data[]
对于packed格式的数据(例如RGB24),会存到data[0]里面。
对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]…(YUV420P中data[0]存Y,data[1]存U,data[2]存V), 具体参见:FFMPEG 实现 YUV,RGB各种图像原始数据之间的转换(swscale)
2.pict_type, 包含以下类型:
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 MPEG4
AV_PICTURE_TYPE_SI, ///< Switching Intra
AV_PICTURE_TYPE_SP, ///< Switching Predicted
AV_PICTURE_TYPE_BI, ///< BI type
};
转:FFMPEG中最关键的结构体之间的关系
下面是函数说明(注意随着ffmpeg的版本不同,流程图上的函数名可能不同了):
/**
* Initialize libavformat and register all the muxers, demuxers and
* protocols. If you do not call this function, then you can select
* exactly which formats you want to support.
*
* @see av_register_input_format()
* @see av_register_output_format()
*/
void av_register_all(void);
注册了所有的文件格式和编解码器的库,
也可以只注册特定的格式和编解码器:av_register_input_format(), av_register_output_format()
/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
读取文件的头部并且把信息保存到第一个参数AVFormatContext结构体中。
最后三个参数用来指定特殊的文件格式,缓冲大小和格式参数,但如果把它们设置为空 NULL 或者 0,将自动检测这些参数。
/**
* Read packets of a media file to get stream information. This
* is useful for file formats with no headers such as MPEG. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
* The logical file position is not changed by this function;
* examined packets may be buffered for later processing.
*
* @param ic media file handle
* @param options If non-NULL, an ic.nb_streams long array of pointers to
* dictionaries, where i-th member contains options for
* codec corresponding to i-th stream.
* On return each dictionary will be filled with options that were not found.
* @return >=0 if OK, AVERROR_xxx on error
*
* @note this function isn't guaranteed to open all the codecs, so
* options being non-empty at return is a perfectly normal behavior.
*
* @todo Let the user decide somehow what information is needed so that
* we do not waste time getting stuff the user does not need.
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
avformat_open_input函数只是检测了文件的头部,这个函数检查在文件中的流的信息
/**
* Find the "best" stream in the file.
* The best stream is determined according to various heuristics as the most
* likely to be what the user expects.
* If the decoder parameter is non-NULL, av_find_best_stream will find the
* default decoder for the stream's codec; streams for which no decoder can
* be found are ignored.
*
* @param ic media file handle
* @param type stream type: video, audio, subtitles, etc.
* @param wanted_stream_nb user-requested stream number,
* or -1 for automatic selection
* @param related_stream try to find a stream related (eg. in the same
* program) to this one, or -1 if none
* @param decoder_ret if non-NULL, returns the decoder for the
* selected stream
* @param flags flags; none are currently defined
* @return the non-negative stream number in case of success,
* AVERROR_STREAM_NOT_FOUND if no stream with the requested type
* could be found,
* AVERROR_DECODER_NOT_FOUND if streams were found but no decoder
* @note If av_find_best_stream returns successfully and decoder_ret is not
* NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec.
*/
int av_find_best_stream(AVFormatContext *ic,
enum AVMediaType type,
int wanted_stream_nb,
int related_stream,
AVCodec **decoder_ret,
int flags);
找到视频流或音频流的索引,其中包含编解码器的信息
/**
* Find a registered decoder with a matching codec ID.
*
* @param id AVCodecID of the requested decoder
* @return A decoder if one was found, NULL otherwise.
*/
AVCodec *avcodec_find_decoder(enum AVCodecID id);
找到编解码器
/**
* Allocate an AVCodecContext and set its fields to default values. The
* resulting struct should be freed with avcodec_free_context().
*
* @param codec if non-NULL, allocate private data and initialize defaults
* for the given codec. It is illegal to then call avcodec_open2()
* with a different codec.
* If NULL, then the codec-specific defaults won't be initialized,
* which may result in suboptimal default settings (this is
* important mainly for encoders, e.g. libx264).
*
* @return An AVCodecContext filled with default values or NULL on failure.
*/
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
流中关于编解码器的信息就是被我们叫做“编解码器上下文”(codec context)的东西。这里面包含了流中所
使用的关于编解码器的所有信息,现在我们有了一个指向它的指针.
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
打开编码器
/**
* 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);
分配一个AVFrame并设为默认值,用来存放帧数据
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
从流中读取一帧完整数据或者多帧完整(ffmpeg内部保证了完整性)
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,
int *got_picture_ptr,
const AVPacket *avpkt);
解码原始流中的一帧数据(输出yuv数据)
/**
* Copy image in src_data to dst_data.
*
* @param dst_linesizes linesizes for the image in dst_data
* @param src_linesizes linesizes for the image in src_data
*/
void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4],
const uint8_t *src_data[4], const int src_linesizes[4],
enum AVPixelFormat pix_fmt, int width, int height);
拷贝图片数据