本文以文档的形式来描述FFmpeg怎么入门,这也是为以后写文档做的一个大题框架格式。
文档介绍
文档目的
整理出开源代码 ffmpeg 的资料,方便公司同事后续使用。
文档范围
较为详细的介绍 ffmpeg 的功能、使用以及二次开发。
读者对象
希望了解 ffmpeg 知识,从事 USM 及 IPTV 的同事。
参考文献
TODO
术语与缩写解释
缩略语/术语 | 全 称 | 说 明 |
---|---|---|
ffmpeg | Fast forword mpeg | 音视频转换器 |
ffplay | Fast forword play | 用 ffmpeg 实现的播放器 |
ffserver | Fast forword server | 用 ffmpeg 实现的 rstp 服务器 |
ffprobe | Fast forword probe | 用来输入分析输入流。 |
FFmpeg 支持能力说明
FFmpeg 介绍及安装
FFmpeg 简介
FFmpeg 是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用 LGPL 或 GPL 许可证(依据你选择的组件)。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 codec 都是从头开发的。
FFmpeg 项目由以下几部分组成:
(1)ffmpeg 视频文件转换命令行工具, 也支持经过实时电视卡抓取和编码成视频文件.
(2)ffserver 基于 HTTP、RTSP 用于实时广播的多媒体服务器. 也支持时间平移
(3)ffplay 用 SDL 和 FFmpeg 库开发的一个简单的媒体播放器
(4)libavcodec 一个包含了所有 FFmpeg 音视频编解码器的库. 为了保证最优性能和高可复用性, 大多数编解码器从头开发的.
(5)libavformat 一个包含了所有的普通音视格式的解析器和产生器的库
FFmpeg 安装
将所有源代码压缩在一个文件夹中,例如 /绝对路径/ffmpeg
。
在终端输入以下指令:
1 | cd /绝对路径/ffmpeg |
至此,ffmpeg 安装编译通过,可以进行对音视频的操作。
ffplay 的编译需要依赖于 SDL 库,所以要想编译成功 ffplay,必须先安装 SDL 库,
安装方法:下载最新版本的 SDL 相应版本的 SDL 源码,编译,即可生成 SDL 库。
1 | 首先下载 SDL 软件包 |
FFmpeg 参数说明
通用选项
1 | -L license |
视频选项
1 | -b bitrate 设置比特率,缺省 200kb/s |
高级视频选项
1 | -g gop_size 设置图像组大小 |
音频选项
1 | -ab bitrate 设置音频码率 |
音频/视频捕获选项
1 | -vd device 设置视频捕获设备。比如/dev/video0 |
高级选项
1 | -map file:stream 设置输入流映射 |
FFmpeg 参数实例
1 | ./ffmpeg -y -i /rootVideoConverter/123.avi -ab 56 -ar 22050 -b 1500 -r 15 -qscale 10 –s 480x350 /root/VideoConverter/234.flv |
FFmpeg 支持能力说明
FFmpeg 对编码解码器的支持
ffmpeg 支持的编解码器种类共有 280 多种,涵盖了几乎所有常见音视频编码格式,能解码几乎所有的音视频,每种音视频编解码器的实现都在 libavcodec 目录下有具体的C 语言实现,具体的支持情况参见:
注:编码器和解码器的名称不是完全匹配的,因此有些编码器没有对应相同名称的解码器,反之,
解码器也一样。即使编码和解码都支持也不一定是完全对应的,例如 h263 解码器对应有 h263p 和 h263 编码器。
FFmpeg 对容器格式的支持
ffmpeg 支持对绝大多数的容器格式的读写操作,共计 190 多种,涵盖了互联网上各种常见媒体格式及日常生活中及专业应用中的各种媒体格式。详细的支持情况参见:
FFmpeg 对过滤器的支持
Filters | 说明 |
---|---|
aformat | Convert the input audio to one of the specified formats. |
anull | Pass the source unchanged to the output. |
aresample | Resample audio data. |
ashowinfo | Show textual information for each audio frame. |
abuffer | Buffer audio frames, and make them accessible to the filterchain. |
anullsrc | Null audio source, never return audio frames. |
abuffersink | Buffer audio frames, and make them available to the end of the filter graph. |
anullsink | Do absolutely nothing with the input audio. |
copy | Copy the input video unchanged to the output. |
crop | Crop the input video to width:height:x:y . |
drawbox | Draw a colored box on the input video. |
fade | Fade in/out input video |
fieldorder | Set the field order. |
fifo | Buffer input images and send them when they are requested. |
format | Convert the input video to one of the specified pixel formats. |
gradfun | Debands video quickly using gradients. |
hflip | Horizontally flip the input video. |
lut | Compute and apply a lookup table to the RGB/YUV input video. |
lutrgb | Compute and apply a lookup table to the RGB input video. |
lutyuv | Compute and apply a lookup table to the YUV input video. |
negate | Negate input video. |
noformat | Force libavfilter not to use any of the specified pixel formats for the input to the next filter. |
null | Pass the source unchanged to the output. |
overlay | Overlay a video source on top of the input. |
pad | Pad input image to width:height[:x:y[:color]] (default x and y: 0, default color: black) . |
pixdesctest | Test pixel format definitions. |
scale | Scale the input video to width:height size and/or convert the image format. |
select | Select frames to pass in output. |
setdar | Set the frame display aspect ratio. |
setpts | Set PTS for the output video frame. |
setsar | Set the pixel sample aspect ratio. |
settb | Set timebase for the output link. |
showinfo | Show textual information for each video frame. |
slicify | Pass the images of input video on to next video filter as multiple slices. |
split | Pass on the input to two outputs. |
transpose | Transpose input video. |
unsharp | Sharpen or blur the input video. |
vflip | Flip the input video vertically. |
buffer | Buffer video frames, and make them accessible to the filterchain. |
color | Provide an uniformly colored input, syntax is: [color[:size[:rate]]] |
movie | Read from a movie source. |
nullsrc | Null video source, never return images. |
rgbtestsrc | Generate RGB test pattern. |
testsrc | Generate test pattern. |
buffersink | Buffer video frames, and make them available to the end of the filter graph. |
nullsink | Do absolutely nothing with the input video. |
FFmpeg 对图像颜色空间的支持
ffmpeg 支持常见的图像颜色空间,并且在 libavswcale 中定义了颜色空间转换的相关函数实现各种颜色模式的互转。具体的支持情况见:
FFmpeg 功能及使用说明
ffplay 对多媒体的支持能力验证
一、视频
3gp 177X144
支持播放,在 windows 下播放正常,但是在 linux 下面偶有 BUG 如果发现画面无法显示而声音可以播放的情况下 可以试着切换全屏或者切换分辨率。
1 | AVI 208X176 支持 |
二、音频
1 | AC3 48KHZ 支持 |
三、图像
1 | PNG 支持 |
FFmpeg 格式转换
第一步:准备媒体
前面已经讲的很清楚了,ffmpeg 如何安装不在赘述。准备好相应的文件,如图 2-1所示。
第二步:启动 ffmpeg
由于做的是格式转换,在 ffserver 上不能直观的看见结果,故我是在 linux 下进行的。打开终端,值得一提的是格式转换需要超级用户才能进行,故在命令行输入:su,<回车>
,输入密码进入超级用户,本例中,以 FFmpeg 将 test.avi 转换为 test.mpg。在命令行中输入:
1 | ./ffmpeg –i test.avi –r 25 –s 720x400 test.mpg |
其中原格式分辨率为 320x240,将转为 720x400,-r 前面已经解释其含义,表示设置帧频为 25。转换成功后如图 2-2 所示,前后两种格式播放效果如图 2-3 所示。相应的,转换为其他格式做相应的变化即可。
同时还可以在转换格式时进行强制的音视频转换,如 –vcodec + 格式
,将会强制将视频按指定格式编码,-acodec +格式
,将会强制按指定格式编码音频信息。在转换中有很多其他参数可以指定,如码率、分辨率、帧率等,具体按照 ffmpeg 的参数说明指定参数即可。但有一条转低不转高的原则需要注意,即品质差的音视频转换不建议转换到品质好的音视频。
再说说如何在转换视频的时候将音频合成到视频中,且覆盖其原来的音频。这个现在摸索出两种方法。
方法一:需要两条命令实现,先在命令行中输入:
1 | ./ffmpeg –i test.avi -an –r 25 test.mpg |
此时将生成一个没有声音的 test.mpg 视频,再在命令行中输入:
1 | ./ffmpeg –i test.mpg –i test.mp3 –r 25 test1.mpg |
此时将会生成一个名为 test1.mpg 的视频。该视频播放时视频为 test.avi 的视频,但音频变为了 test.mp3 的音频了。
方法二:只要一条指令即可实现。在命令行中输入:
1 | ./ffmpeg –i test.avi –i test.mp3 –vcodec copy –acodec copy –r 25 test2.mpg |
此时将会生成一个名为 test2.mpg 的视频,播放时其视频为 test.avi 的视频,音频为 test.mp3。–vcodec copy
为 force video codec(‘copy’ to copy stream)。
有一点需要注意,文件命名不能有空格,否则会导致编译时不能通过。另外,-an
为不能使音频记录。
第三步:播放媒体
播放我们转换的媒体,看看是否满足我们当初的愿望,不出什么差错的话,是完全能够满足我们的要求的。
FFmpeg 视频截图
截取一张 300x200
尺寸大小的格式为 jpg 的一张图片:
1 | ./ffmpeg –i test.avi –y –f image2 –t 0.001 –s 300x200 test.jpg |
要截取指定时间的图片,如 5 秒之后的:
1 | ./ffmpeg –i test.avi –y –f image2 –ss 5 –t 0.001 –s 300x200 test.jpg |
其中,-ss
后的单位为秒,也可写成:-ss 00:00:05
。
把视频的前 30 帧转换为一个动态的 gif 图。需要说明的是,转换成功之后,如果用 ffplay 播放是看不出效果的,建议换成其他图片播放器播放。其转换命令为:
1 | ./ffmpeg –i test.avi –vframes 30 –pix_fmt rgb24–y –f gif test.gif |
也可以从视频中的第 10 秒开始截取后面的 5 秒内容转换为一个无限重播的动态 gif 图。其命令为:
1 | ./ffmpeg –i test.avi –pix_fmt rgb24 –ss 10 –t 5 –y –f gif test.gif |
上面两种动态 gif 都是只播一次,想让其一直播,可再加一个参数:-loop_output 0
。
FFmpeg 屏幕录制
屏幕录制其命令为:
1 | ./ffmpeg -f x11grab -r 25 -s wxga -i :0.0 /tmp/outputFile.mpg |
其他相关参数可自行添加。需要说明的是,各个版本的 ffmpeg 对屏幕录制的命令不一。如果你只想录制一个应用程序窗口或者桌面上的一个固定区域,那么可以指定偏移位置和区域大小。使用 xwininfo -frame
命令可以完成查找上述参数。
注:ffmpeg 的屏幕录制功能只能在 Linux 环境下有效。并且在配置时需要添加 –enable-x11grub
指令,默认关闭。
FFmpeg 音视频采集
把摄像头的实时视频录制下来,存储为文件
1 | ./ffmpeg -f video4linux -s 320x240 -r 10 -i /dev/video0 test.asf |
录音,其命令为:
1 | ./ffmpeg –i /dev/dsp -f oss test.mp3 |
FFmpeg 应用实例
用 FFserver 从文件生成流媒体
一、安装 ffmpeg
在 ubuntu 下,运行 sudo apt-get ffmpeg
安装 ffmpeg,在其他 linux 操作系统下,见 ffmpeg 的编译过程(编译完成后可执行自动安装)。
二、准备预播放的媒体文件
如 test.Mp3,在本文档中,默认放入用户文件夹下得 Music 文件夹内.(直接从设备采集不在本文档叙述范围之内)
三、修改 ffserver 配置信息
ffserver 配置文件为: /etc/ffserver.conf
打开,填写配置信息.配置信息包括三方面:
(1)端口绑定等基本信息,在 ·/etc/ffserver.conf· 中有详细注释,在此不再重复,最终配置信息为:
1 | Port 8090 |
(2)媒体文件配置信息.本信息根据具体的媒体文件类型直接在配置文件中取消注释掉相应文件类型的配置信息,然后填写文件路径即可:
1 | MP3 audio |
四、启动 ffserver
在终端中运行: sudo ffserver -f /etc/ffserver.conf
启动 ffserver.
五、播放流媒体
在浏览器中输入 http://127.0.0.1:8090/test.mp3 即可播放音乐.
在终端中输入 ffplay http://localhost:8090/test.mp3
可播放流媒体.
用 FFserver 从设备生成实时流
一、准备媒体
按照上节步骤安装 ffmpeg,保证摄像头和声卡可用,将从摄像头和声卡获取音视频信息。
二、修改 ffserver 配置信息
ffserver 配置文件为: /etc/ffserver.conf
打开,填写配置信息.配置信息包括三方面:
(1)端口绑定等基本信息,在 /etc/ffserver.conf
中有详细注释,在此不再重复,最终配
置信息为:
1 | Port 8090 |
(2)fend(传冲信息),在文件播放中,基本不用动本配置信息,只需要根据具体情况分配缓冲文件.最终配置信息如下:
1 | <Feed feed1.ffm> |
(3)媒体文件配置信息.本信息根据具体的媒体文件类型直接在配置文件中取消注释掉相应文件类型的配置信息,然后填写文件路径即可:
(中间会有很多很多配置信息,都是关于音视频的,有些配置还不懂,慢慢摸索吧)
1 | <Stream test1.mpg> |
三、启动 FFserver
在终端中运行: sudo ffserver -f /etc/ffserver.conf
启动 ffserver.
四、启动 ffmpeg
本例中,以 ffmpeg 作为实时摄像头采集输入.在命令行中输入:
1 | ./ffmpeg -f video4linux2 -r 25 -i /dev/video0 /tmp/feed1.ffm |
如果有音频设备,则采集音频的命令如下:
1 | ./ffmpeg -f oss -i /dev/dsp -f video4linux2 -r 25 -i /dev/video0 /tmp/feed1.ffm |
(音频格式参数自己配置)
五、播放流媒体
在浏览器中输入 http://127.0.0.1:8090/test1.mpg 即可播放音乐.
在终端中输入 ffplay http://localhost:8090/test.swf
可播放流媒体.
FFmpeg 架构
FFmpeg 文件结构
目录 | 文件 | 简要说明 |
libavformat 主要存放ffmpeg 支持的各种编解码 器的实现及ffmpeg 编解码功能相关的 数据结构定义及函 数定义和声明 | allcodecs.c | 简单的注册类函数 |
avcodec.h | 编解码相关结构体定义和函数原型声明 | |
dsputil.c | 限幅数组初始化 | |
dsputil.h | 限幅数组声明 | |
imgconvert.c | 颜色空间转换相关函数实现 | |
imgconvert_template.h | 颜色空间转换相关结构体定义和函数声明 | |
utils_codec.c | 一些解码相关的工具类函数的实现 | |
mpeg4audio.c | mpeg4 音频编解码器的函数实现 | |
mpeg4audio.h | mpeg4 音频编解码器的函数声明 | |
mpeg4data.h | mpeg4 音视频编解码器的公用的函数声明及数据结构定义 | |
mpeg4video.c | mpeg4 视频编解码器的函数实现 | |
mpeg4video.h | mpeg4 视频编解码器的函数的声明及先关数据结构的定义 | |
mpeg4videodec.c | mpeg4 视频解码器的函数实现 | |
mpeg4videoenc.c | mpeg4 视频编码器的函数实现 | |
libavformat 主要存放ffmpeg支 持的各种媒体格式 MUXER/DEMUXER 和数据流协议的定 义和实现文件以及 ffmpeg解复用相 关的数据结构及 函数定 | allformats.c | 简单注册类函数 |
avformat.h | 文件和媒体格式相关函数声明和数据结构定义 | |
avio.c | 无缓冲 IO 相关函数实现 | |
avio.h | 无缓冲 IO 相关结构定义和函数声明 | |
aviobuf.c | 有缓冲数据 IO 相关函数实现 | |
cutils.c | 简单的字符串操作函数 | |
utils_format.c | 文件和媒体格式相关的工具函数的实现 | |
file.c | 文件 io 相关函数 | |
…… | 其他相关媒体流 IO 的函数和数据结构实现文件。如:rtsp、http 等。 | |
avi.c | AVI 格式的相关函数定西 | |
avi.h | AVI 格式的相关函数声明及数据结构定义 | |
avidec.c | AVI 格式 DEMUXER 相关函数定义 | |
avienc.c | AVI 格式 MUXER 相关函数定义 | |
…… | 其他媒体格式的 muxer/demuxer 相关函数及数据结构定义和声明文件 | |
libavutil 主要存放ffmpeg 工具类函数的定义 | avutil.h | 简单的像素格式宏定义 |
bswap.h | 简单的大小端转换函数的实现 | |
commom.h | 公共的宏定义和简单函数的实现 | |
mathematics.c | 数学运算函数实现 | |
rational.h | 分数相关表示的函数实现 |
I\O 模块分析
概述
ffmpeg 项目的数据 IO 部分主要是在 libavformat 库中实现,某些对于内存的操作部分在 libavutil 库中。数据 IO 是基于文件格式(Format)以及文件传输协议(Protocol)的,与具体的编解码标准无关。
ffmpeg 工程转码时数据 IO 层次关系如图所示:
对于上面的数据 IO 流程,具体可以用下面的例子来说明,我们从一个 http 服务器获取音视频数据,格式是 flv 的,需要通过转码后变成 avi 格式,然后通过 udp 协议进行发布。其过程就如下所示:
- 读入 http 协议数据流,根据 http 协议获取真正的文件数据(去除无关报文信息);
- 根据 flv 格式对数据进行解封装;
- 读取帧进行转码操作;
- 按照目标格式 avi 进行封装;
- 通过 udp 协议发送出去。
相关数据结构介绍
在 libavformat 库中与数据 IO 相关的数据结构主要有 URLProtocol、URLContext、ByteIOContext、AVFormatContext 等,各结构之间的关系如图所示。
1、URLProtocol 结构
表示广义的输入文件,该结构体提供了很多的功能函数,每一种广义的输入文件(如:file、pipe、tcp、rtp 等等)对应着一个 URLProtocol
结构,在 av_register_all()
中将该结构体初始化为一个链表,表头为 avio.c
里的 URLProtocol *first_protocol = NULL;
保存所有支持的输入文件协议,该结构体的定义如下:
1 | typedef struct URLProtocol |
注意到,URLProtocol
是一个链表结构,这是为了协议的统一管理,ffmpeg 项目中将所有的用到的协议都存放在一个全局变量 first_protocol 中,协议的注册是在 av_register_all
中完成的,新添加单个协议可以调用 av_register_protocol2
函数实现。而协议的注册就是将具体的协议对象添加至 first_protocol
链表的末尾。
URLProtocol
在各个具体的文件协议中有一个具体的实例,如在 file 协议中定义为:
1 | URLProtocol ff_file_protocol = { |
2、URLContext 结构
URLContext 提供了与当前打开的具体的文件协议(URL)相关数据的描述,在该结构中定义了指定当前 URL(即 filename 项)所要用到的具体的 URLProtocol,即:提供了一个在 URLprotocol 链表中找到具体项的依据,此外还有一些其它的标志性的信息,如 flags, is_streamed 等。它可以看成某一种协议的载体。其结构定义如下:
1 | typedef struct URLContext |
那么 ffmpeg 依据什么信息初始化 URLContext?然后又是如何初始化 URLContext的呢?
在打开一个 URL 时,全局函数 ffurl_open 会根据 filename 的前缀信息来确定 URL所使用的具体协议,并为该协议分配好资源,再调用 ffurl_connect 函数打开具体协议,即调用协议的 url_open,调用关系如下:
1 | int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, |
浅蓝色部分的函数完成了 URLContext 函数的初始化,URLContext 使 ffmpeg 外所暴露的接口是统一的,而不是对于不同的协议用不同的函数,这也是面向对象思维的体现。在此结构中还有一个值得说的是 priv_data 项,这是结构的一个可扩展项,具体协议可以根据需要添加相应的结构,将指针保存在这就行。
3、AVIOContext 结构
AVIOContext(即:ByteIOContext)是由 URLProtocol 和 URLContext 结构扩展而来,也是 ffmpeg 提供给用户的接口,它将以上两种不带缓冲的读取文件抽象为带缓冲的读取和写入,为用户提供带缓冲的读取和写入操作。数据结构定义如下:
1 | typedef struct |
结构简单的为用户提供读写容易实现的四个操作,read_packet write_packet read_pause read_seek,极大的方便了文件的读取,四个函数在加了缓冲机制后被中转到,URLContext 指向的实际的文件协议读写函数中。
下面给出 0.8 版本中是如何将 AVIOContext 的读写操作中转到实际文件中的。
在 avio_open()函数中调用了 ffio_fdopen()函数完成了对 AVIOContex 的初始化,其调用过程如下:
1 | int avio_open(AVIOContext **s, const char *filename, int flags) |
函数调用完成了对 AVIOContext 的初始化,在初始化的过程中,将AVIOContext 的 read_packet 、 write_packet 、 seek 分别初始化为: ffurl_read ffurl_write ffurl_seek , 而这三个函数又将具体的读写操作中转为:
h->prot->url_read、h->prot->url_write、h->prot->url_seek
,另外两个变量初始化时也被相应的中转,如下:
1 | (*s)->read_pause = (int (*)(void *, int))h->prot->url_read_pause; |
所以,可以简要的描述为:AVIOContext 的接口口是加了缓冲后的 URLProtocol 的函数接口。
在 aviobuf.c 中定义了一系列关于 ByteIOContext 这个结构体的函数,如下
put_xxx 系列:
1 | void put_byte(ByteIOContext *s, int b); |
get_xxx 系列:
1 | int get_buffer(ByteIOContext *s, unsigned char *buf, int size); |
这些 put_xxx 及 get_xxx 函数是用于从缓冲区 buffer 中写入或者读取若干个字节,对于读写整型数据,分别实现了大端和小端字节序的版本。而缓冲区 buffer 中的数据又是从何而来呢,有一个 fill_buffer 的函数,在 fill_buffer 函数中调用了ByteIOContext 结构的 read_packet 接口。在调用 put_xxx 函数时,并没有直接进行真
正写入操作,而是先缓存起来,直到缓存达到最大限制或调用 flush_buffer 函数对缓冲区进行刷新,才使用 write_packet 函数进行写入操作。
Demuxer 和 muxer 模块分析
概述
ffmpeg 的 demuxer 和 muxer 接口分别在 AVInputFormat 和 AVOutputFormat 两个结构体中实现,在 av_register_all()函数中将两个结构分别静态初始化为两个链表,保存在全局变量:first_iformat 和 first_oformat 两个变量中。在 FFmpeg 的文件转换或者打开过程中,首先要做的就是根据传入文件和传出文件的后缀名匹配合适的 demuxer和 muxer,得到合适的信息后保存在 AVFormatContext 中。
相关数据结构介绍
1、AVInputFormat
该结构被称为 demuxer,是音视频文件的一个解封装器,它的定义如下:
1 | typedef struct AVInputFormat |
对于不同的文件格式要实现相应的函数接口,这样每一种格式都有一个对应的demuxer,所有的 demuxer 都保存在全局变量 first_iformat 中。红色表示提供的接口。
2、AVOutputFormat
该结构与 AVInputFormat 类似也是在编译时静态初始化,组织为一个链表结构,提供了多个 muxer 的函数接口。
1 | int (*write_header)(struct AVFormatContext *); |
对于不同的文件格式要实现相应的函数接口,这样每一种格式都有一个对应的 muxer,所有的 muxer 都保存在全局变量 first_oformat 中。
3、AVFormatContext
该结构表示与程序当前运行的文件容器格式使用的上下文,着重于所有文件容器共有的属性,在运行时动态的确定其值,是 AVInputFormat 和 AVOutputFormat 的载体,但同一个结构对象只能使 AVInputFormat 和 AVOutputFormat 中的某一个有效。每一个输入和输出文件,都在
1 | static AVFormatContext *output_files[MAX_FILES] 和 |
定义的指针数组全局变量中有对应的实体。对于输入和输出,因为共用的是同一个结构体,所以需要分别对该结构中如下定义的 iformat 或 oformat 成员赋值。在转码时读写数据是通过 AVFormatContext 结构进行的。定义如下:
1 | typedef struct AVFormatContext |
注释部分的成员是 AVFormatContext 中最为重要的成员变量,这些变量的初始化是ffmpeg 能正常工作的必要条件,那么,AVFormatContext 是如何被初始化的呢?文件的格式是如何被探测到的呢?
首先我们来探讨:
1 | struct AVInputFormat *iformat; //指向具体的 demuxer |
三个成员的初始化。
在 avformat_open_input() 函数中调用了 init_input() 函数,然后用调用了av_probe_input_format()函数实现了对 AVFormatContext 的初始化。其调用关系如下:
1 | int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, |
函数用途是根据传入的 probe data 数据,依次调用每个 demuxer 的 read_probe 接口,来进行该 demuxer 是否和传入的文件内容匹配的判断。与 demuxer 的匹配不同,muxer的匹配是调用 guess_format 函数,根据 main( ) 函数的 argv 里的输出文件后缀名来进行的。至此完成了前三个重要成员的初始化,具体的做法就不在深入分析。
下面分别给出 av_read_frame 函数以及 av_write_frame 函数的基本流程。
1 | int av_read_frame(AVFormatContext *s, AVPacket *pkt); |
由上可见,对 AVFormatContext 的读写操作最终是通过 ByteIOContext 来实现的,这样,AVFormatContext 与 URLContext 就由 ByteIOContext 结构联系到一起了。在AVFormat 结构体中有一个 packet 的缓冲区 raw_packet_buffer,是 AVPackList 的指针类型,av_read_packet 函数将读到的包添加至 raw_packet_buffer 链表末尾。
Decoder/Encoder 模块
概述
编解码模块主要包含的数据结构为:AVCodec、AVCodecContext 每一个解码类型都会有自己的 Codec 静态对像,Codec 的 int priv_data_size 记录该解码器上下文的结构大小,如 MsrleContext 。这些都是编译时确定的,程序运行时通过avcodec_register_all()将所有的解码器注册成一个链表。在 av_open_input_stream()函数中调用 AVInputFormat 的 read_header()中读文件头信息时,会读出数据流的CodecID,即确定了他的解码器 Codec。
在 main()函数中除了解析传入参数并初始化 demuxer 与 muxer 的 parse_options( )函数以外,其他的功能都是在 av_encode( )函数里完成的。在 libavcodec\utils.c 中有如下二个函数 : AVCodec *avcodec_find_encoder(enum CodecID id)
和 AVCodec *avcodec_find_decoder(enum CodecID id)
他们的功能就是根据传入的 CodecID,找到匹配的 encoder 和 decoder。在 av_encode( )函数的开头,首先初始化各个 AVInputStream和 AVOutputStream,然后分别调用上述二个函数,并将匹配上的 encoder 与 decoder 分
别保存在:
1 | AVInputStream->AVStream *st->AVCodecContext *codec->struct AVCodec *codec |
相关数据结构的初始化
AVCodecContext 结构
AVCodecContext 保存 AVCodec 指针和与 codec 相关数据,如 video 的 width、height,audio 的 sample rate 等。
AVCodecContext 中的 codec_type,codec_id 二个变量对于 encoder/decoder 的匹配来说,最为重要。
1 | enum CodecType codec_type; /* see CODEC_TYPE_xxx */ |
如上所示,codec_type 保存的是 CODEC_TYPE_VIDEO,CODEC_TYPE_AUDIO 等媒体类型,codec_id 保存的是 CODEC_ID_FLV1,CODEC_ID_VP6F 等编码方式。
以支持 flv 格式为例,在前述的 av_open_input_file(…… ) 函数中,匹配到正确的 AVInputFormat demuxer 后,通过 av_open_input_stream( )函数中调用 AVInputFormat的 read_header 接口来执行 flvdec.c 中的 flv_read_header( )函数。flv_read_header( )函数内,根据文件头中的数据,创建相应的视频或音频 AVStream,并设置 AVStream 中AVCodecContext 的正确的 codec_type 值。codec_id 值是在解码过程。flv_read_packet( )
函数执行时根据每一个 packet 头中的数据来设置的。
以 avidec 为例 有如下初始化,我们主要知道的就是 code_id 和 code_type 该字段关联具体的解码器,和解码类型(音视频或 subtitle)
1 | if (st->codec->stream_codec_tag == AV_RL32("Axan")) |
其他重要数据结构的初始化
AVStream
AVStream 结构保存与数据流相关的编解码器,数据段等信息。比较重要的有如下二个成员:
1 | AVCodecContext *codec; /**< codec context */ |
其中 codec 指针保存的就是上节所述的 encoder 或 decoder 结构。priv_data 指针保存的是和具体编解码流相关的数据,如下代码所示,在 ASF 的解码过程中,priv_data保存的就是 ASFStream 结构的数据。
1 | AVStream *st; |
AVInputStream/ AVOutputStream
根据输入和输出流的不同,前述的 AVStream 结构都是封装在 AVInputStream 和AVOutputStream 结构中,在 av_encode( )函数中使用。AVInputStream 中还保存的有与时间有关的信息。AVOutputStream 中还保存有与音视频同步等相关的信息。
AVPacket
AVPacket 结构定义如下,其是用于保存读取的 packet 数据。
1 | typedef struct AVPacket |
在 av_encode() 函数中,调用 AVInputFormat 的 (*read_packet)(struct AVFormatContext *, AVPacket *pkt)
接口,读取输入文件的一帧数据保存在当前输入 AVFormatContext 的 AVPacket 成员中。
FFmpeg 裁剪说明
本文对 ffmpeg 进行裁剪采用的是配置所需的接口,不需要的不配置,而不是采用修改源代码的方式。
configure 参数
通用选项
在 linux 下进入终端,找到 ffmpeg 解压位置,输入如下命令:
1 | ./configure –help |
得到 configure 的基本选项参数,其并没有中文解释。
1 | --help 显示此帮助信息|print this message |
基本选项介绍
以下为配置 ffmpeg 的基本选项,其含义如下:
1 | --cache-file=FILE |
configure 会在你的系统上测试存在的特性(或者 bug!)。为了加速随后进行的配置,测试的结果会存储在一个 cache file 里。当 configure 到每个子树里都有 configure 脚本的复杂的源码树时,一个很好的 cache file 的存在会有很大帮助。
1 | --help |
输出帮助信息。即使是有经验的用户也偶尔需要使用使用 --help
选项,因为一个复杂的项目会包含附加的选项。例如,GCC 包里的 configure 脚本就包含了允许你控制是否生成和在 GCC 中使用 GNU 汇编器的选项。
1 | --no-create |
configure 中的一个主要函数会制作输出文件。此选项阻止 configure 生成这个文件。你可以认为这是一种演习(dry run),尽管缓存(cache)仍然被改写了。
1 | --quiet |
当 configure 进行他的测试时,会输出简要的信息来告诉用户正在作什么。这样做是因为 configure 可能会比较慢,没有这种输出的话用户将会被扔在一旁疑惑正在发生什么。使用这两个选项中的任何一个都会把你扔到一旁。(译注:这两句话比较有意思,原文是这样的:If there was no such output, the user would be left wondering what is happening. By using this option, you too can be left wondering!)
1 | --version |
打印用来产生 ‘configure’ 脚本的 Autoconf 的版本号。
1 | --prefix=PEWFIX |
--prefix
是最常用的选项。制作出的 Makefile 会查看随此选项传递的参数,当一个包在安装时可以彻底的重新安置他的结构独立部分。举一个例子,当安装一个包,例如说Emacs,下面的命令将会使 Emacs Lisp file 被安装到”/opt/gnu/share”:
1 | ./configure --prefix=/opt/gnu |
1 | --exec-prefix=EPREFIX |
与 --prefix
选项类似,但是他是用来设置结构倚赖的文件的安装位置。编译好的 emacs 二进制文件就是这样一个问件。如果没有设置这个选项的话,默认使用的选项值将被设为和 --prefix
选项值一样。
1 | --bindir=DIR |
指定二进制文件的安装位置。这里的二进制文件定义为可以被用户直接执行的程序。
1 | --sbindir=DIR |
指定超级二进制文件的安装位置。这是一些通常只能由超级用户执行的程序。
1 | --libexecdir=DIR |
指定可执行支持文件的安装位置。与二进制文件相反,这些文件从来不直接由用户执行,但是可以被上面提到的二进制文件所执行。
1 | --datadir=DIR |
指定通用数据文件的安装位置。
1 | --sysconfdir=DIR |
指定在单个机器上使用的只读数据的安装位置。
1 | --sharedstatedir=DIR |
指定可以在多个机器上共享的可写数据的安装位置。
1 | --localstatedir=DIR |
指定只能单机使用的可写数据的安装位置。
1 | --libdir=DIR |
指定库文件的安装位置。
1 | --includedir=DIR |
指定 C 头文件的安装位置。其他语言如 C++的头文件也可以使用此选项。
1 | --oldincludedir=DIR |
指定为除 GCC 外编译器安装的 C 头文件的安装位置。
1 | --infodir=DIR |
指定 Info 格式文档的安装位置。Info 是被 GNU 工程所使用的文档格式。
1 | --mandir=DIR |
指定手册页的安装位置。
1 | --srcdir=DIR |
这个选项对安装没有作用。他会告诉 configure 源码的位置。一般来说不用指定此选项,因为 configure 脚本一般和源码文件在同一个目录下。
1 | --program-prefix=PREFIX |
指定将被加到所安装程序的名字上的前缀。例如,使用 --program-prefix=g
来 configure一个名为 tar 的程序将会使安装的程序被命名为 gtar。当和其他的安装选项一起使用时,这个选项只有当他被 Makefile.in 文件使用时才会工作。
1 | --program-suffix=SUFFIX |
指定将被加到所安装程序的名字上的后缀。
1 | --program-transform-name=PROGRAM |
这里的 PROGRAM 是一个 sed 脚本。当一个程序被安装时,他的名字将经过 sed -e PROGRAM
来产生安装的名字。
1 | --build=BUILD |
指定软件包安装的系统平台。如果没有指定,默认值将是 --host
选项的值。
1 | --host=HOST |
指定软件运行的系统平台。如果没有指定,将会运行 config.guess 来检测。
1 | --target=GARGET |
指定软件面向(target to)的系统平台。这主要在程序语言工具如编译器和汇编器上下文中起作用。如果没有指定,默认将使用 --host
选项的值。
1 | --disable-FEATURE |
一些软件包可以选择这个选项来提供为大型选项的编译时配置,例如使用 Kerberos认证系统或者一个实验性的编译器最优配置。如果默认是提供这些特性,可以使用 --disable-FEATURE
来禁用它,这里 FEATURE 是特性的名字。例如:
1 | ./configure --disable-gui |
1 | --enable-FEATURE[=ARG] |
相反的,一些软件包可能提供了一些默认被禁止的特性,可以使用 --enable-FEATURE
来起用它。这里 FEATURE 是特性的名字。一个特性可能会接受一个可选的参数。例如:
1 | ./configure --enable-buffers=128 |
--enable-FEATURE=no
与上面提到的 --disable-FEATURE
是同义的。
1 | --with-PACKAGE[=ARG] |
在自由软件社区里,有使用已有软件包和库的优秀传统。当用 configure 来配置一个源码树时,可以提供其他已经安装的软件包的信息。例如,倚赖于 Tcl 和 Tk 的 BLT 器件工具包。要配置 BLT,可能需要给 configure 提供一些关于我们把 Tcl 和 Tk 装的何处的信息:
1 | ./configure --with-tcl=/usr/local --with-tk=/usr/local |
--with-PACKAGE=no
与下面将提到的 --without-PACKAGE
是同义的。
1 | --without-PACKAGE |
有时候你可能不想让你的软件包与系统已有的软件包交互。例如,你可能不想让你的新编译器使用 GNU ld。通过使用这个选项可以做到这一点:
1 | ./configure --without-gnu-ld |
1 | --x-includes=DIR |
这个选项是 --with-PACKAGE
选项的一个特例。在 Autoconf 最初被开发出来时,流行使用 configure 来作为 Imake 的一个变通方法来制作运行于 X 的软件。–x-includes 选项提供了向 configure 脚本指明包含 X11 头文件的目录的方法。
1 | --x-libraries=DIR |
类似的,--x-libraries
选项提供了向 configure 脚本指明包含 X11 库的目录的方法。
FFmpeg 裁剪优化实例
对 ffmpeg 的裁剪优化主要是对 ffplay 的裁剪优化,我们制定的需求是能播放测试文件(视频为 mpeg4 编码、音频为 mp2 编码,且为 AVI 复用),根据需求,找到相应的选项,或禁用或启用,最后的命令如下:
1 | ./configure --disable-yasm --disable-parsers --disable-decoders |
其中针对需求,
--disable-parsers
为禁用所有解析器,--disable-decoders
为禁用所有解码器,--disable-encoders
为禁用所有编码器,--enable-decoder=mpeg4
为启用 mpeg4 的编码器 ,--disable-muxers
为禁用所有复用,--disable-demuxers
为禁用所有解复用,--enable-demuxer=avi
为启用 AVI 复用,--enable-decoder=mp2
为启用 mp2 编码,--disable-protocols
为禁用所有协议,--enable-protocol=file
为启用文件协议,--disable-filters
为禁用所有过滤器,--disable-bsfs
为禁用所有码流过滤器。
通过以上配置之后,编译,安装,就生成了我们要求的 ffplay,其大小为 1.8M(1864012 字节)。此次是在 linux 环境下进行的,在以后的配置中,如果需要其他的什么编码器或什么的,按照选项要求进行配置即可。
裁剪优化前后文件比较
前面已经提到本次裁剪优化的内容。经过裁剪优化之后,对其文件夹进行比较,主要有 3 个地方不同,分别是 config.fate、config.h 和 config.mak。在 config.fate 中,其记录的是配置命令,由于前后两次配置命令不同,故相应内容也不同。在config.h 中,其主要是根据配置命令来改变相应预定义的值,达到裁剪优化之效果。在 config.mak 中,改变的也是配置命令中需要改变的选项。
FFmpeg SDK
FFMpeg 中比较重要的函数以及数据结构如下:
1、数据结构:
(1) AVFormatContext
(2) AVOutputFormat
(3) AVInputFormat
(4) AVCodecContext
(5) AVCodec
(6) AVFrame
(7) AVPacket
(8) AVPicture
(9) AVStream
2、初始化函数:
(1) av_register_all()
(2) avcodec_open()
(3) avcodec_close()
(4) av_open_input_file()
(5) av_find_input_format()
(6) av_find_stream_info()
(7) av_close_input_file()
3、音视频编解码函数:
(1) avcodec_find_decoder()
(2) avcodec_alloc_frame()
(3) avpicture_get_size()
(4) avpicture_fill()
(5) img_convert()
(6) avcodec_alloc_context()
(7) avcodec_decode_video()
(8) av_free_packet()
(9) av_free()
4、文件操作:
(1) avnew_steam()
(2) av_read_frame()
(3) av_write_frame()
(4) dump_format()
5、其他函数:
(1) avpicture_deinterlace()
(2) ImgReSampleContext()
FFmpeg 编译
1 | git clone http://source.ffmpeg.org/git/ffmpeg.git ffmpeg |
注意:在执行各自的 configure 创建编译配置文件时,最好都强制带上 –enable-static 和 –enable-shared 参数以确保生成静态库和动态库。另外因为是在 Mac OS X 环境下编译,因此在各自编译完后,都要执行 sudo make install,安装到默认的 /usr/local 目录下相应位置(Mac OS X 下不推荐 /usr),因此不要在 configure 时指定 –prefix,就用默认的 /usr/local 目录前缀即可。完成编译安装后,FFmpeg 的头文件将会复制到 /usr/local/include 下面相应位置,静态库及动态库会被复制到 /usr/local/lib 目录下,FFmpeg 的可执行程序(ffmpeg、ffprobe、ffserver)会被复制到 /usr/local/bin 目录下,这样 FFmpeg 的开发环境就构建好了。