模块:
libavcodec - 编码解码器
libavdevice - 输入输出设备的支持
libavfilter - 视音频滤镜支持
libavformat - 视音频等格式的解析
libavutil
- 工具库
libpostproc - 后期效果处理
libswscale -
图像颜色、尺寸转换
1. ffmpga代码简析
1.1 av_log()
av_log()是FFmpeg中输出日志的函数。随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数。一般情况下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*, ...);
(2)它的声明后面有一个av_printf_format(3, 4)。有关这个地方的左右还没有深入研究,网上资料中说它的作用是按照printf()的格式检查av_log()的格式。
av_log()每个字段的含义如下:
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级别。
1.1 int _tmain(int argc, _TCHAR* argv[])
用过C的人都知道每一个C的程序都会有一个main(),但有时看别人写的程序发现主函数不是int main(),而是int _tmain(),而且头文件也不是<iostream.h>而是<stdafx.h>,会困惑吧?首先,这个_tmain()是为了支持unicode所使用的main一个别名而已,既然是别名,应该有宏定义过的,在哪里定义的呢?就在那个让你困惑的<stdafx.h>里,有这么两行
#include <stdio.h>
#include <tchar.h>
我们可以在头文件<tchar.h>里找到_tmain的宏定义
#define _tmain main
所以,经过预编译以后, _tmain就变成main了
//_TCHAR类型是宽字符型字符串,和我们一般常用的字符串不同,它是32位或者更 高的操作系统中所使用的类型.
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等码流数据)
(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/*