模块:
libavcodec - 编码解码器
libavdevice - 输入输出设备的支持
libavfilter - 视音频滤镜支持
libavformat - 视音频等格式的解析
libavutil - 工具库
libpostproc - 后期效果处理
libswscale - 图像颜色、尺寸转换
1. ffmpga代码简析
1.2 源码——通用部分
(1). av_register_all (), avcodec_register_all ()
avcodec_register_all() : 注册 hwaccel,encoder,decoder,parser,bitstream
av_register_all() : 注册 muxer,demuxer,protocol,在所有基于ffmpeg的应用程序中几乎第一个被调用的。只有调用了该函数,才能使用复用器,编码器等
avfilter_register_all() : 注册 滤镜filter
(2).内存分配:av_malloc(),av_realloc(),av_mallocz(),av_calloc(),av_free(),av_freep()
内存操作常见函数位于 libavutil\mem.中:
av_malloc()——简单封装了系统的malloc(),并做错误检查工作;
av_realloc()——简单封装了系统的realloc(),用于对申请的内存大小进行调整;
av_mallocz()——av_mallocz()中调用了av_malloc()之后,又调用memset()将分配的内存设置为0
av_calloc()——简单封装了av_mallocz();
av_freep()——释放申请的内存;
av_freep()——简单封装了av_free(),并且在释放内存之后将目标指针设置为null。
(3).ffmpeg结构体
a) 解协议(http,rtsp,rtmp,mms)
AVIOContext,URLProtocol,URLContext 主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应一个URLProtocol结构。(注 意:FFMPEG中文件也被当做一种协议“file”)
b)解封装(flv,avi,rmvb,mp4)
AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频封装格式都对应一个AVInputFormat 结构。
c) 解码(h264,mpeg2,aac,mp3)
每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
d) 存数据
视频的话,每个结构一般是存一帧;音频可能有好几帧
解码前数据:AVPacket
解码后数据:AVFrame
AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)
AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。
AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)
<1>. AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体。下面看几个主要变量的作用(在这里考虑解码的情况):
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext *pb:输入数据的缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名
int64_t duration:时长(单位:微秒ms,转换为秒需要除以1000000)
int bit_rate:比特率(单位bps,转换为kbps需要除以1000)
AVDictionary *metadata:元数据
<2>. 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的
<3>.AVCodecContext,主要的外部API结构体,这个结构体主要用在编解码过程中,而且,编码过程中用到本结构体的参数比解码用到的参数多
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>.AVIOContext,管理输入输出数据的结构体
unsigned char *buffer:缓存开始位置
int buffer_size:缓存大小(默认32768)
unsigned char *buf_ptr:当前指针读取到的位置
unsigned char *buf_end:缓存结束的位置
void *opaque:URLContext结构体
在解码的情况下,buffer用于存储ffmpeg读入的数据。例如打开一个视频文件的时候,先把数据从硬盘读入buffer,然后在送给解码器用于解码。
其中opaque指向了URLContext。注意,这个结构体并不在FFMPEG提供的头文件中,而是在FFMPEG的源代码中。
<5>.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:私有数据的大小
<6>.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音频文件附带的专辑封面。
<7>. 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所属的视频/音频流。
》》》结构体关系
1). 红色字体的first_protocol first_iformat first_avcodec, 这三个不同的头指针,分别为ffmpeg支持 不同的数据源、不同格式以及不同编码的音视频数据提供了处理的可能。
2). ffplay主要划分为四大部分: 数据源, 解复用, 解码, 显示播放. ffplay使用SDL库进行视频显示和声音播放,至于同步在ffplay中自个做的.
首先, 谈谈数据源相应的数据结构: URLContext, ByteIOContext. 这两个与音视频数据相关, URLProtocol体现在功能函数上. 上图中, URLContext的 void *priv_data 指向了FILE结构体, 其实这是作者的一点点失误,ffmpeg使用的是c的底层io, 没有使用stdio. c底层io是不带缓存的, 所以加上了ByteIOContext为无缓冲io提供缓存, 避免频繁的进行i/o操作.
其次, 解复用对应的数据结构: AVFormatContext中的void *priv_data字段, AVStream, AVStream中void *priv_data结合AVInputeFormat的使用, 从下一层的ByteIOContext的buffer中将音视频数据分离.
最后, 解码对应的数据结构: AVCodecContext, AVCodec体现在功能函数上, 其中AVCodecContext的void *priv_data字段是特定的decodec.
数据源 --> char *buffer --> 解复用 --> AVPacket(音频包, 视频包) --> 解码 --> AVFrame(video) / audio_buffer(audio).
3). first_protocol将file, tcp, udp, rtp...数据当成协议看待, 各自均提供URLProtocol结构体实例以功能函数的形式进行体现. first_iformat将ffmpeg所支持的所有格式串连到一起, first_avcodec将ffmpeg支持的所有编码器串连到一起.
三个指针链表是如何确定相应实例的呢?
a. (first_protocol, argv[1]参数) 参数例如: file://pathname/filename 协议: file, 默认也为file; rtsp://ip_address/filename 协议:rtsp
b. (first_iformat, is->iformat->read_probe())
c. (first_avcodec, is->iformat->read_head())
(4). avio_open2()
该函数用于打开FFmpeg的输入输出文件。avio_open2()的声明位于libavformat\avio.h文件中 int avio_open2(AVIOContext **s, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options)
s:函数调用成功之后创建的AVIOContext结构体。
url:输入输出协议的地址(文件也是一种“广义”的协议,对于文件来说就是文件的路径)。
flags:打开地址的方式。AVIO_FLAG_READ:只读;AVIO_FLAG_WRITE:只写;AVIO_FLAG_READ_WRITE:读写。
(5). avcodec_find_encoder()和avcodec_find_decoder()
查找编码器 和解码器,实质就是遍历AVCodec链表并且获得符合条件的元素,声明位于libavcodec\avcodec.h:
AVCodec *avcodec_find_encoder(enum AVCodecID id);
AVCodec *avcodec_find_decoder(enum AVCodecID id);
(6). avcodec_open2()
初始化一个视音频编解码器的AVCodecContext。avcodec_open2()的声明位于libavcodec\avcodec.h
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
avctx:需要初始化的AVCodecContext;
codec:输入的AVCodec;
options:一些选项。例如使用libx264编码的时候,“preset”,“tune”等都可以通过该参数设置
avcodec_open2() 函数的主要工作:
1)为各种结构体分配内存(通过各种av_malloc()实现)
2)将输入的AVDictionary形式的选项设置到AVCodecContext
3)其他一些零零碎碎的检查,比如说检查编解码器是否处于“实验”阶段
4)如果是编码器,检查输入参数是否符合编码器的要求
5)调用AVCodec的init()初始化具体的解码器。
(7). avcodec_close()
该函数用于关闭编码器。avcodec_close()函数的声明位于libavcodec\avcodec.h
int avcodec_close(AVCodecContext *avctx);
1.3 源码——解码部分
(1). avformat_open_input()
打开媒体的过程开始于avformat_open_input(),完成:
1).输入输出结构体AVIOContext的初始化;
2).输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):
判断文件名的后缀 + 读取文件头的数据进行比对
使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与
FFMPEG连接(非专业用词);
3).剩下的就是调用该URLProtocol的函数进行open,read等操作了
(2). avformat_close_input()
用于打开一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的;
avformat_close_input()的声明位于libavformat\avformat.h: void avformat_close_input(AVFormatContext **s);
函数功能:
1)调用AVInputFormat的read_close()方法关闭输入流
2)调用avformat_free_context()释放AVFormatContext
3)调用avio_close()关闭并且释放AVIOContext
(3). avformat_find_stream_info()
该函数可以读取一部分视音频数据并且获得一些相关的信息。avformat_find_stream_info()的声明位于libavformat\avformat.h:
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options); 函数正常执行后返回值大于等于0;
c:输入的AVFormatContext
options:额外的选项
功能:
该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。下面看一下除了成员变量赋值之外,该函数的几个关键流程:
1).查找解码器:find_decoder()
2).打开解码器:avcodec_open2()
3).读取完整的一帧压缩编码的数据:read_frame_internal() 注:av_read_frame()内部实际上就是调用的read_frame_internal()。
4).解码一些压缩编码数据:try_decode_frame()
(4). av_read_frame()
读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。
先参考了其他人对av_read_frame()的解释,在此做一个参考:通过av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以ts流为例,是读取一个完整的 PES包(一个完整pes包包含若干视频或音频es包),读取完毕后,通过av_parser_parse2(***)分析出视频一帧(或音频若干帧), 返回,下次进入循环的时候,如果上次的数据没有完全取完,则st = s->cur_st;不会是NULL,即再此进入av_parser_parse2(***)流程,而不是下面的 av_read_packet(**)流程,这样就保证了,如果读取一次包含了N帧视频数据(以视频为例),则调用 av_read_frame(***)N次都不会去读数据,而是返回第一次读取的数据,直到全部解析完毕。
注意:av_read_frame - 新版本的ffmpeg用的是av_read_frame,而老版本的是av_read_packet ,区别是av_read_packet读出的是包,它可能是半帧或多帧,不保证帧的完整性。av_read_frame对 av_read_packet进行了封装,使读出的数据总是完整的帧
av_read_frame()的声明位于libavformat\avformat.h: int av_read_frame(AVFormatContext *s, AVPacket *pkt);
s:输入的AVFormatContext
pkt:输出的AVPacket/*
(5). avcodec_decodec_video2()
解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。该函数的声明位于libavcodec\avcodec.h,
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);
功能:
1)对输入的字段进行了一系列的检查工作:例如宽高是否正确,输入是否为视频等等。
2)通过ret = avctx->codec->decode(avctx, picture, got_picture_ptr,&tmp)这句代码,调用了相应AVCodec的decode()函数,完成了解码操作。 AVCodec的decode()方法是一个函数指针,指向了具体解码器的解码函数
3)对得到的AVFrame的一些字段进行了赋值,例如宽高、像素格式等等。
(6). avformat_close_input()
关闭一个AVFormatContext,一般情况下是和avformat_open_input()成对使用的。声明位于libavformat\avformat.h
void avformat_close_input(AVFormatContext **s);
功能:
1)调用AVInputFormat的read_close()方法关闭输入流
2)调用avformat_free_context()释放AVFormatContext
3)调用avio_close()关闭并且释放AVIOContext
1.4 源码——编码部分
(1). avformat_alloc_output_context2()
在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())。 avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体。它的声明位于 libavformat\avformat.h
int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat, const char *format_name, const char *filename);
ctx:函数调用成功之后创建的AVFormatContext结构体。
oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由 FFmpeg猜测输出格式。
PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。
函数执行成功的话,其返回值大于等于0。
(2). avformat_write_header()
av_write_frame()用于写视频数据,avformat_write_header()用于写视频文件头,而av_write_trailer()用于写视频文件尾。
avformat_write_header()的声明位于libavformat\avformat.h ,int avformat_write_header(AVFormatContext *s, AVDictionary **options);
s:用于输出的AVFormatContext;
options:额外的选项; 函数正常执行后返回值等于0。
功能:
1)调用init_muxer()初始化复用器
2)调用AVOutputFormat的write_header()
(3). avcodec_encode_video2()
编码一帧视频数据。avcodec_encode_video2()函数的声明位于libavcodec\avcodec.h
int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame, int *got_packet_ptr);
avctx:编码器的AVCodecContext。
avpkt:编码输出的AVPacket。
frame:编码输入的AVFrame。
got_packet_ptr:成功编码一个AVPacket的时候设置为1。
函数返回0代表编码成功
(4).av_write_frame()
输出一帧视音频数据,它的声明位于libavformat\avformat.h: int av_write_frame(AVFormatContext *s, AVPacket *pkt);
s:用于输出的AVFormatContext。
pkt:等待输出的AVPacket。
函数正常执行后返回值等于0。
(5). av_write_trailer()
av_write_trailer()用于输出文件尾,它的声明位于libavformat\avformat.h: int av_write_trailer(AVFormatContext *s);
它只需要指定一个参数,即用于输出的AVFormatContext。
函数正常执行后返回值等于0。
1.5 源码——其他部分
(1). av_log()日志输出系统
av_log()是FFmpeg中输出日志的函数。一般情况下FFmpeg类库的源代码中是不允许使用printf()这种的函数的,所有的输出一律使用av_log()。
av_log()的声明位于libavutil\log.h: void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);
1)函数最后一个参数是“…”。
在C语言中,在函数参数数量不确定的情况下使用“…”来代表参数。例如printf()的原型定义如下: int printf (const char*, ...);
avcl:指定一个包含AVClass的结构体。
level:log的级别
fmt:和printf()一样。
由此可见,av_log()和printf()的不同主要在于前面多了两个参数。其中第一个参数指定该log所属的结构体,例如 AVFormatContext、AVCodecContext等等。第二个参数指定log的级别,源代码中定义了如下几个级别 AV_LOG_PANIC ,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING,AV_LOG_INFO AV_LOG_VERBOSE,AV_LOG_DEBUG。 每个级别定义的数值代表了严重程度,数值越小代表越严重。默认的级别是AV_LOG_INFO。此外,还有一个级别不输出任何信息,即 AV_LOG_QUIET。当前系统存在着一个“Log级别”。所有严重程度高于该级别的Log信息都会输出出来。例如当前的Log级别是 AV_LOG_WARNING,则会输出AV_LOG_PANIC,AV_LOG_FATAL,AV_LOG_ERROR,AV_LOG_WARNING 级别的信息,而不会输出AV_LOG_INFO级别的信息。可以通过av_log_get_level()获得当前Log的级别,通过另一个函数 av_log_set_level()设置当前的Log级别。
(2). AVClass ,AVOption 结构体成员管理系统
AVOption ——描述结构体中的成员变量,它最主要的作用可以概括为两个字:“赋值”。一个AVOption结构体包含了变量名称,简短的帮助,取值等等信息。
AVClass——和AVOption有关的数据都存储在AVClass结构体中, 一个结构体若要支持AVOption ,第一个成员变量是指向AVclass结构体的指针,
该AVClass中的成员变量option必须指向一个AVOption类型的静态数组。
AVOption:用来设置FFmpeg中变量的值的结构体AVOption的特点就在于它赋值时候的灵活性。AVOption可以使用字符串为任何类型的变量赋值。
统一使用字符串赋值。例如给int型变量qp设定值为20,通过 AVOption需要传递进去一个内容为“20”的字符串。
此外,AVOption中变量的名称也使用字符串来表示。结合上面提到的使用字符串赋值的特性,我们可以发现使用AVOption之后,
传递两个字符串(一个是变量的名称,一个是变量的值)就可以改变系统中变量的值。
AVClass:AVClass最主要的作用就是给结构体(例如AVFormatContext等)增加AVOption功能的支持。换句话说AVClass就是AVOption和目标结构体之间的“桥 梁”。AVClass要求必须声明为目标结构体的第一个量。 AVClass中有一个option数组用于存储目标结构体的所有的AVOption。
举个例子,AVFormatContext结构体,AVClass和AVOption之间
图中AVFormatContext结构体的第一个变量为AVClass类型的指针av_class,它在AVFormatContext结构体初始化的时候,被赋值指向了全局静态变量av_format_context_class结构体(定义位于libavformat\options.c)。而 AVClass类型的av_format_context_class结构体中的option变量指向了全局静态数组 avformat_options(定义位于libavformat\options_table.h。
AVOption的几个成员变量:
name:名称。
help:简短的帮助。
offset:选项相对结构体首部地址的偏移量(这个很重要)。
type:选项的类型。 ( AVOptionType类型的变量, AVOptionType是一个枚举类型)
default_val:选项的默认值,(union类型的变量)
min:选项的最小值。
max:选项的最大值。
flags:一些标记。
unit:该选项所属的逻辑单元,可以为空。
AVClass的几个成员变量:
class_name:AVClass名称。
item_name:函数,获取与AVClass相关联的结构体实例的名称。
option:AVOption类型的数组(最重要)。
version:完成该AVClass的时候的LIBAVUTIL_VERSION。
category:AVClass的类型,是一个类型为AVClassCategory的枚举型变量。
(3). sws_getContext() 初始化一个SwsContext
图像处理(缩放,YUV/RGB格式转换)类库libswsscale:libswscale是一个主要用于处理图片像素数据的类库,可以完成图片像素格式的转换,图片的拉伸等工作
ibswscale常用的函数数量很少,一般情况下就3个:
sws_getContext():初始化一个SwsContext。
sws_scale():处理图像数据。
sws_freeContext():释放一个SwsContext。
其中sws_getContext()也可以用sws_getCachedContext()取代。