1 FFmpeg从入门到精通-FFmpeg简介
2 FFmpeg从入门到精通-FFmpeg工具使用基础
3 FFmpeg从入门到精通-FFmpeg转封装
4 FFmpeg从入门到精通-FFmpeg转码
5 FFmpeg从入门到精通-FFmpeg流媒体
6 FFmpeg从入门到精通-FFmpeg滤镜使用
7 FFmpeg从入门到精通-FFmpeg中Linux设备操作
8 FFmpeg从入门到精通-FFmpeg接口libavformat的使用
9 FFmpeg从入门到精通-FFmpeg接口libavcodec的使用
10 FFmpeg从入门到精通-FFmpeg接口libavfilter的使用
在互联网常见的格式中,跨平台最好的应该是MP4文件,因此我们说MP4格式是最常见的多媒体文件格式。本章首先重点介绍MP4封装的基本格式。
如果要了解MP4的格式信息,首先要清楚几个概念,具体如下。
·MP4文件由许多个Box与FullBox组成
·每个Box由Header和Data两部分组成
·FullBox是Box的扩展,其在Box结构的基础上,在Header中增加8位version标志和24位的flags标志
·Header包含了整个Box的长度的大小(size)和类型(type),当size等于0时,代表这个Box是文件的最后一个Box。当size等于1时,说明Box长度需要更多的位来描述,在后面会定义一个64位的largesize用来描述Box的长度。当Type为uuid时,说明这个Box中的数据是用户自定义扩展类型
·Data为Box的实际数据,可以是纯数据,也可以是更多的子Box
·当一个Box中Data是一系列的子Box时,这个Box又可以称为Container(容器)Box
MP4文件中Box的组成可以用下表所示的列表进行排列,下表中标记“√”的Box为必要Box,否则为可选Box。
在MP4文件中,Box的结构与上表所描述的一般没有太大的差别,当然,因为MP4的标准中描述的moov与mdat的存放位置前后并没有进行强制要求,所以有些时候moov这个Box在mdat的后面,有些时候moov被存放在mdat的前面。在互联网的视频点播中,如果希望MP4文件被快速打开,则需要将moov存放在mdat的前面;如果放在后面,则需要将MP4文件下载完成后才可以进行播放。
上表中已经介绍过,moov容器定义了一个MP4文件中的数据信息,类型是moov,是一个容器Atom,其至少必须包含以下三种Atom中的一种:
·mvhd标签,Movie Header Atom,存放未压缩过的影片信息的头容器
·cmov标签,Compressed Movie Atom,压缩过的电影信息容器,此容器不常用
·rmra标签,Reference Movie Atom,参考电影信息容器,此容器不常用
也可以包含其他容器信息,例如影片剪辑信息Clipping atom(clip)、一个或几个trakAtom(trak)、一个Color Table Atom(ctab)和一个User Data Atom(udta)。
其中,mvhd中定义了多媒体文件的time scale、duration以及display characteristics。而trak中定义了多媒体文件中的一个track的信息,track是多媒体文件中可以独立操作的媒体单位,例如一个音频流就是一个track、一个视频流就是一个track。
使用二进制查看工具打开一个MP4文件查看其内容,可以了解前面所讲到的MP4文件容器信息:
关于读取这个moov容器的方式,可以参考下表。
通过解析该moov容器的字节长度,可以看到,该容器共包含0x00031175(201077)字节,容器的类型为moov;接着继续在这个moov容器中往下解析,下一个容器的大小为0x0000006c(108)字节,类型为mvhd;然后继续在moov容器中往下解析:
分析完mvhd之后,从上面的输出中可以看到下一个moov中的容器是一个trak标签,这个trak容器的大小是0x00018459(99417)字节,类型是trak。解析完该trak之后,又进入到moov容器中解析下一个trak,下一个trak的解析方式与这个trak的解析方式相同,可以看到下面文件内容的trak的大小为0x00018c46(101446)字节:
解析完这个音频的trak之后,接下来可以看到还有一个moov容器中的子容器,就是udta容器,这个udta容器的解析方式与前面解析trak的方式基本相同,可以从下面的文件数据中看到,udta的大小为0x00000062(98)字节:
根据前面描述过的信息可以得知,udta+视频trak+音频trak+mvhd+moov描述大小之后得出来的总大小,刚好为201077字节,与前面得出来的moov的大小相等。
前面描述了针对moov容器下面的子容器的解析,接下来继续解析moov子容器中的子容器。
从文件内容中可以看到,mvhd容器的大小为0x0000006c字节,mvhd的解析方式如下表所示。
按照上表所示的方式对文件数据解析出来的mvhd的内容所对应的信息如下表所示。
解析mvhd之后,可以看到下一个track ID为0x00000003,接下来就开始解析trak,解析trak的时候同样也包含了多个子容器。
trak容器中定义了媒体文件中的一个track的信息,一个媒体文件中可以包含多个trak,每个trak都是独立的,具有自己的时间和空间占用的信息,每个trak容器都有与它关联的media容器描述信息。trak容器的主要使用目的具体如下。
·包含媒体数据的引用和描述(media track)
·包含modifier track信息
·流媒体协议的打包信息(hint track),hint track可以引用或者复制对应的媒体采样数据
hint track和modifier track必须保证完整性,同时要与至少一个media track一起存在。
一个trak容器中要求必须要有一个Track Header Atom(tkhd)、一个Media Atom(mdia),其他的Atom都是可选的,例如如下的atom选项。
·Track剪辑容器:Track Clipping Atom(clip)
·Track画板容器:Track Matte Atom(matt)
·Edit容器:Edit Atom(edts)
·Track参考容器:Track Reference Atom(tref)
·Track配置加载容器:Track Load Settings Atom(load)
·Track输出映射容器:Track Input Map Atom(imap)
·用户数据容器:User Data Atom(udta)
解析的方式如下表所示。
参考表的占用情况,然后打开MP4文件查看文件中的二进制数据,如下:
从文件的数据内容中可以看到,这个trak的大小为0x00018459(99417)字节,下面的子容器的大小为0x0000005c(92)字节,这个子容器的类型为tkhd;跳过92字节后,接下来读到的trak子容器的大小为0x00000024(36)字节,这个子容器的类型为edts;跳过36字节后,接下来读到的trak子容器的大小为0x000183d1(99281)字节,这个子容器的类型为mdia;通过分析可以得到trak+tkhd+edts+mdia子容器的大小加起来刚好为99417字节,trak读取完毕。
解析tkhd容器的方式请参考下表。
下面具体看一个tkhd的内容,然后根据上表的内容做一个信息的对应,这个tkhd对应的值如下表所示。
上表为解析视频trak容器的tkhd,下面再分析一个音频的tkhd:
解析trak的方法前面已经讲过,现在重点解析音频的tkhd,并用表格的形式将数据表示出来,具体见下表。
从上述两个例子中可以看出,音频与视频的trak的tkhd的大小相同,里面的内容会随着音视频trak类型的不同而有所不同。至此trak的tkhd解析完毕。
解析完tkhd之后,接下来就可以分析一下trak容器的子容器了。Media Atom的类型是mdia,其必须包含如下容器。
·一个媒体头:Media Header Atom(mdhd)
·一个句柄参考:Handler Reference(hdlr)
·一个媒体信息:Media Infomation(minf)和用户数据User Data Atom(udta)
这个容器的解析方式如下表所示。
下面先来参考一下MP4文件的数据:
从文件的内容可以看到这个mdia容器的大小为0x000183d1(99281)字节,mdia容器下面包含了三大子容器,分别为mdhd、hdlr和minf,其中mdhd的大小为0x00000020(32)字节;hdlr大小为0x0000002d(45)字节;minf大小为0x00001837c(99196)字节;mdia容器信息+mdhd+hdlr+minf容器大小刚好为99281字节;至此mdia容器解析完毕。
mdhd容器被包含在各个track中,描述Media的Header,其包含的信息如表3-10所示。
当版本字段为0时,解析与当前版本字段为1时的解析稍微有所不同,这里介绍的为常见的解析方式。
下面根据表格的解析方式将对应的数据解析出来:
从打开文件的内容中可以将对应的数据逐一解析出来,具体见下表。
从上表可以看出这个Media Header的大小是32字节,类型是mdhd,版本为0,生成时间与媒体修改时间都为0,计算单位时间是90000,媒体时间戳长度为13698577,语言编码是0x55C4,至此mdhd标签解析完毕。
音频时长可以根据Duration/TimeScale的方式来计算,根据本例中的数据可以计算出音频的时间长度为152秒钟。
hdlr容器中描述了媒体流的播放过程,该容器中包含的内容如下表所示。
根据下表的读取方式,读取示例文件中的内容数据,根据文件内容看到的信息,可以将内容读取出来,对应的值如下表所示。
从上表中解析出来的对应值可以看出来,这是一个视频的track对应的数据,对应组件的名称为VideoHandler和一个0x00结尾,hdlr容器解析完毕。
minf容器中包含了很多重要的子容器,例如音视频采样等信息相关的容器,minf容器中的信息将作为音视频数据的映射存在,其内容信息具体如下。
·视频信息头:Video Media Information Header(vmhd子容器)
·音频信息头:Sound Media Information Header(smhd子容器)
·数据信息:Data Information(dinf子容器)
·采样表:Sample Table(stbl子容器)
前面已经介绍过解析minf的方式,下面就来详细介绍一下解析vmhd、smhd、dinf以及stbl容器的方式。
vmhd容器内容的格式如下表所示。
根据这个表格读取容器中的内容进行解析,其数据如下:
根据文件中的内容将数据解析出来,对应的值如下表所示。
上表所示为视频的Header的解析,下面就来看一下音频的Header的解析。
smhd容器的格式如表3-16所示。
根据表解析文件中的音频对应的数据,解析数据如下:
根据文件内容将数据解析出来之后,对应的值如下表所示。
dinf容器是一个用于描述数据信息的容器,其定义的是音视频数据的信息,这是一个容器,它包含子容器dref。下面就来列举一个解析dinf及其子容器dref的例子,dref的解析方式如表3-18所示。
stbl容器又称为采样参数列表的容器(Sample Table Atom),该容器包含转化媒体时间到实际的sample的信息,也说明了解释sample的信息,例如,视频数据是否需要解压缩、解压缩算法是什么等信息。其所包含的子容器具体如下。
·采样描述容器:Sample Description Atom(stsd)
·采样时间容器:Time To Sample Atom(stts)
·采样同步容器:Sync Sample Atom(stss)
·Chunk采样容器:Sample To Chunk Atom(stsc)
·采样大小容器:Sample Size Atom(stsz)
·Chunk偏移容器:Chunk Offset Atom(stco)
·Shadow同步容器:Shadow Sync Atom(stsh)
stbl包含track中media sample的所有时间和数据索引,利用这个容器中的sample信息,就可以定位sample的媒体时间,决定其类型、大小,以及如何在其他容器中找到紧邻的sample。如果Sample Table Atom所在的track没有引用任何数据,那么它就不是一个有用的media track,不需要包含任何子Atom。
如果Sample Table Atom所在的track引用了数据,那么其必须包含以下子Atom。
·采样描述容器
·采样大小容器
·Chunk采样容器
·Chunk偏移容器
所有的子表都有相同的sample数目。
stbl是必不可少的一个Atom,而且必须包含至少一个条目,因为它包含了数据引用Atom检索media sample的目录信息。没有sample description,就不可能计算出media sample存储的位置。Sync Sample Atom是可选的,如果没有,则表明所有的sample都是sync sample。
edts容器定义了创建Movie媒体文件中一个track的一部分媒体,所有的edts数据都在一个表里,包括每一部分的时间偏移量和长度,如果没有该表,那么这个track就会立即开始播放,一个空的edts数据用来定位到track的起始时间偏移位置,如下表所示。
Trak中的edts数据如下:
这个Edts Atom的大小为0x00000024(36)字节,类型为edts;其中包含了elst子容器,elst子容器的大小为0x0000001c(28)字节,edts容器+elst子容器的大小为48字节,至此,edts容器解析完毕。
至此,MP4文件的格式解析标准已经介绍完毕,按照以上的解析方式,读者将会根据对应的解析方式解析MP4文件,然后读取MP4中的音视频数据和对应的媒体信息。由于使用二进制查看工具解析MP4文件需要逐字节地解析,比较耗费时间和精力,我们可以借助分析工具来进行辅助解析。接下来将介绍MP4文件常用的查看工具以及FFmpeg对MP4文件的支持情况。
可用来分析MP4封装格式的工具比较多,除了FFmpeg之外,还有一些常用的工具,如Elecard StreamEye、mp4box、mp4info等;下面简要介绍一下这几款常见的工具。
Elecard StreamEye是一款非常强大的视频信息查看工具,能够查看帧的排列信息,将I帧、P帧、B帧以不同颜色的柱状展现出来,而且柱的长短将根据帧的大小展示;还能够通过Elecard StreamEye分析MP4的封装的内容信息,包括流的信息、宏块的信息、文件头的信息、图像的信息以及文件的信息等;还能够根据每一帧的顺序逐帧查看,可以看到每一帧的详细信息与状态,Elecard StreamEye查看MP4的内容信息如图所示。
mp4box是GPAC项目中的一个组件,可以通过mp4box针对媒体文件进行合成、拆解等操作,其操作信息大概如下:
MP4Box [option] input [option]
-h general general options help
-h hint hinting options help
-h import import options help
-h encode encode options help
-h meta meta handling options help
-h extract extraction options help
-h dump dump options help
-h swf Flash (SWF) options help
-h crypt ISMA E&A options help
-h format supported formats help
-h rtp file streamer help
-h live BIFS streamer help
-nodes lists supported MPEG4 nodes
-node NodeName gets MPEG4 node syntax and QP info
-xnodes lists supported X3D nodes
-xnode NodeName gets X3D node syntax
-snodes lists supported SVG nodes
-snode NodeName gets SVG node syntax
-languages lists supported ISO 639 languages
-quiet quiet mode
-noprog disables progress
-v verbose mode
-logs set log tools and levels, formatted as a ':'-separated list of toolX[:toolZ]@levelX
-version gets build version
从以上的帮助信息中可以看到,mp4box还有很多子帮助项,例如DASH切片、编码、metadata、BIFS流、ISMA、SWF相关帮助信息等。下面就来使用mp4box分析一下output.mp4的信息,内容如下:
* Movie Info *
Timescale 1000 - Duration 00:02:32.207
Fragmented File no - 2 track(s)
File suitable for progressive download (moov before mdat)
File Brand isom - version 512
Created: UNKNOWN DATE
File has no MPEG4 IOD/OD
iTunes Info:
Encoder Software: Lavf58.20.100
Track # 1 Info - TrackID 1 - TimeScale 90000 - Duration 00:02:32.206
Media Info: Language "Undetermined" - Type "vide:avc1" - 4414 samples
Visual Track layout: x=0 y=0 width=544 height=960
MPEG-4 Config: Visual Stream - ObjectTypeIndication 0x21
AVC/H264 Video - Visual Size 544 x 960
AVC Info: 1 SPS - 1 PPS - Profile High @ Level 6
NAL Unit length bits: 32
Self-synchronized
Track # 2 Info - TrackID 2 - TimeScale 44100 - Duration 00:02:32.137
Media Info: Language "Undetermined" - Type "soun:mp4a" - 6550 samples
MPEG-4 Config: Audio Stream - ObjectTypeIndication 0x40
MPEG-4 Audio MPEG-4 Audio AAC LC - 1 Channel(s) - SampleRate 44100
Synchronized on stream 1
Alternate Group ID 1
从输出的内容中可以看到,对应的解析信息如Timescale、Duration等,与前面介绍的MP4原理一节中所看到的解析的MP4文件所得到的数据相同。
mp4info也是一个不错的MP4分析工具,而且是可视化的工具,可以将MP4文件中的各Box解析出来,并将其中的数据展现出来,分析MP4文件内容时使用mp4info将会更方便。
如图所示,通过mp4info可以解析MP4文件容器,解析到Atom的格式可以直接展现出来,相关的Atom解析信息比之前逐字节的读取解析相对方便、易用很多。
根据前面介绍过的查看FFmpeg的MP4文件的Demuxer的方法,使用命令行ffmpeg -h demuxer=mp4查看MP4文件的Demuxer信息:
Demuxer mov,mp4,m4a,3gp,3g2,mj2 [QuickTime / MOV]:
Common extensions: mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif.
如输出内容所示,通过查看FFmpeg的help信息,可以看到MP4的Demuxer与mov、3gp、m4a、3g2、mj2的Demuxer相同,解析MP4文件的参数如表所示。
在解析MP4文件时,通过FFmpeg解析时也可以通过参数ignore_editlist忽略EditList Atom对MP4进行解析;关于MP4的Demuxer操作通常使用默认配置即可,这里将不会做过多的解释与举例说明。
上节中提到过,MP4与mov、3gp、m4a、3g2、mj2的Demuxer相同,它们的Muxer也差别不大,但是是不同的Muxer,尽管在ffmpeg中使用的都是同一套format进行的封装与解封装。MP4的封装相对解封装来说稍微复杂一些,因为要封装的时候可选参数多一些,可以通过表来了解相关的参数。
从参数的列表中可以看到,MP4的muxer支持的参数比较复杂,例如支持在视频关键帧处切片、支持设置moov容器大小的最大值、支持设置encrypt加密等。下面就对常见的参数进行举例说明。
正常情况下ffmpeg生成moov是在mdat写完成之后再写入,可以通过参数faststart将moov容器移动至mdat的前面,下面参考一个例子:
ffmpeg -i 123.flv -c copy -f mp4 output1.mp4
从图中可以看到moov容器是在mdat的下面,如果使用参数faststart就会在生成完上述的结构之后将moov移动到mdat前面:
ffmpeg -i 123.flv -c copy -f mp4 -movflags faststart output2.mp4
然后使用mp4info查看MP4的容器顺序,可以看到moov被移动到了mdat前面,如图
当使用生成DASH格式的时候,里面使用的一种特殊的MP4格式,可以通过dash参数来生成
ffmpeg -i 123.flv -c copy -f mp4 -movflags dash output3.mp4
使用mp4info查看容器的格式信息,稍微有些特殊,具体的信息已在前面有过详细介绍,如图所示。
从图3-5中可以看到,这个DASH格式的MP4文件存储的容器信息与常规的MP4格式有些差别,其主要以三种容器为主:sidx、moof和mdat。
ISMV为微软发布的一个流媒体格式,通过参数isml可以发布ISML直播流,将ISMV推流至IIS服务器,可以通过参数isml进行发布:
ffmpeg -re -i input.mp4 -c copy -movflags isml+frag_keyframe -f ismv Stream
在网络的直播与点播场景中,FLV也是一种常见的格式,FLV是Adobe发布的一种可以作为直播也可以作为点播的封装格式,其封装格式非常简单,均以FLVTAG的形式存在,并且每一个TAG都是独立存在的,接下来就来详细介绍一下FLV标准。
FLV文件格式分为两部分:一部分为FLV文件头,另一部分为FLV文件内容。
根据上表可以看出FLV文件头格式中签名字段占用了三字节,最终组成的三个字符分别为“FLV”;然后是文件的版本,常见的为1;接下来的一个字节前边5位为0,接着音频展示设置为1,然后下一位为0,再下一位为视频展示设置为1。如果是一个音视频都展示的FLV文件,那么这个字节会设置为0x05(00000101)。然后是4字节的数据,为FLV文件头数据的偏移位置。下面就以一个FLV文件具体分析一下:
从FLV文件数据内容可以分析出来如下结果。
·3字节的标签:“F”“L”“V”
·1字节的FLV文件版本:0x01
·5位的保留标记类型:00000b
·1位的音频显示标记类型:1b
·1位的保留标记类型:0b
·1位的视频显示标记类型:1b
·4字节的文件头数据偏移:0x00000000
至此,FLV的文件头解析完毕。
FLV文件内容格式解析见表
从表中可以看到FLV文件内容的格式主要为FLVTAG,FLVTAG分为两部分,分别为TAGHeader部分与TAGBody部分,表中提到了TAG的类型为FLVTAG,那么下面就来介绍一下FLVTAG的格式。
从表中可以看到FLVTAG的Header部分信息如下。
·保留位占用2位,最大为11b
·滤镜位占用1位,最大为1b
·TAG类型占用5位;最大为11111b,与保留位、滤镜位共用一个字节,常见的为0x08、0x09、0x12;在处理时,一般默认将保留位与滤镜位设置为0
·数据大小占用24位(3字节),最大为0xFFFFFF(16777215)字节
·时间戳大小占用24位(3字节),最大为0xFFFFFF(16777215)毫秒,转换为秒等于16777秒,转换为分钟为279分钟,转换为小时为4.66小时,所以如果使用FLV的格式,采用这个时间戳最大可以存储至4.66小时
·扩展时间戳大小占用8位(1字节),最大为0xFF(255),扩展时间戳使得FLV原有的时间戳得到了扩展,不仅仅局限于4.66个小时,还可以存储得更久,1193个小时,以天为单位转换过来大约为49.7天
·流ID占用24位(3字节),最大为0xFFFFFF;不过FLV中一直将其存储为0
紧接着在FLVTAG的header之后存储的数据为TAG的data,大小为FLVTAG的Header中DataSize中存储的大小,存储的数据分为视频数据、音频数据及脚本数据,下面就来分别介绍这三种数据的格式。
如果从FLVTAG的Header中读取到TagType为0x09,则该TAG为视频数据TAG,FLV支持多种视频格式,下面就来看一下视频数据VideoData部分的相关说明,见表。
从FLVTAG的Header中解析到TagType为0x08之后,这个TAG为音频,其与视频TAG类似,音频TAG里面可以封装的压缩音频编码也可以有很多种,下面就来具体看一下。AudioTag数据格式解析见表。
当FLVTAG读取的TagType类型值为0x12时,这个数据为ScriptData类型,Script-Data常见的展现方式是FLV的Metadata,里面存储的数据格式一般为AMF数据,下面就来简单描述一下ScriptData的存储格式,见表。
关于FLV的ScriptData内容解析部分,可以参考FLV标准文档,其中包含了更多更详细的说明,参考链接为:http://www.adobe.com/content/dam/Adobe/en/devnet/flv/pdfs/video_file_format_spec_v10.pdf,本节将重点介绍三个重要类型,接下来就来讲解FFmpeg转封装FLV的操作。
使用FFmpeg生成FLV格式相对来说比较简单,下面就来查看FFmpeg生成FLV文件时可以使用的参数,具体见表。
根据表中的参数可以看出,在生成FLV文件时,写入视频、音频数据时均需要写入Sequence Header数据,如果FLV的视频流中没有Sequence Header,那么视频很有可能不会显示出来;如果FLV的音频流中没有Sequence Header,那么音频很有可能不会被播放出来。所以需要将ffmpeg中的参数flvflags的值设置为aac_seq_header_detect,其将会写入音频AAC的Sequence Header。
从前文的FLV标准中可以看到,FLV封装中可以支持的视频编码主要包含如下内容。
如果封装FLV时,内部的音频或者视频不符合标准时,那么它们是肯定封装不进FLV的,而且还会报错,下面就来看看将mpeg4视频封装进FLV时将会出现什么错误:
ffmpeg -i input_ac3.mp4 -c copy -f flv output.flv
命令行执行后输出内容如下:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf54.6.100
Duration: 00:04:13.00, start: 0.000000, bitrate: 511 kb/s
Stream #0:0[0x1](und): Video: mpeg4 (Simple Profile) (mp4v / 0x7634706D), yuv420p, 1280x720 [SAR 1:1 DAR 16:9], 505 kb/s, 5 fps, 5 tbr, 5 tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 3 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
[flv @ 000002965ee39c40] Codec mpeg4 is not supported in the official FLV specification,
[flv @ 000002965ee39c40] use vstrict=-1 / -strict -1 to use it anyway.
Could not write header for output file #0 (incorrect codec parameters ?): Invalid argument
Error initializing output stream 0:1 --
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
Last message repeated 1 times
从以上的输出内容中可以看出,FLV容器中并没有支持mpeg4视频编码,所以出现报错。
为了解决这类问题,可以进行转码,将音频从AC3转换为AAC或者MP3这类FLV标准支持的音频即可:
ffmpeg -i input.mp4 -vcodec h264 -acodec copy -f flv output.flv
如果原视频封装中本身就都是FLV标准所支持的音视频编码,那么封装就会很顺利,所以,如果只是从一种封装格式转换成FLV格式的话,那么可以先确认源文件中的编码是否为FLV所支持的格式。
在网络视频点播文件为FLV格式文件时,人们常用yamdi工具先对FLV文件进行一次转换,主要是将FLV文件中的关键帧建立一个索引,并将索引写入Metadata头中,这个步骤用FFmpeg同样也可以实现,使用参数add_keyframe_index即可:
ffmpeg -i input.mp4 -c copy -f flv -flvflags add_keyframe_index output.flv
命令行执行之后生成的output.flv文件的metadata中即带有关键帧索引信息,具体如下:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'output3.mp4':
Metadata:
major_brand : iso5
minor_version : 512
compatible_brands: iso5iso6mp41
encoder : Lavf59.34.102
Duration: 00:00:21.10, start: 0.000000, bitrate: 2476 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, 1969 kb/s, 30 fps, 30 tbr, 16k tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Output #0, flv, to 'output9.flv':
Metadata:
major_brand : iso5
minor_version : 512
compatible_brands: iso5iso6mp41
encoder : Lavf59.34.102
Stream #0:0(und): Video: h264 (High) ([7][0][0][0] / 0x0007), yuv420p(progressive), 1280x720, q=2-31, 1969 kb/s, 30 fps, 30 tbr, 1k tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1(und): Audio: aac (LC) ([10][0][0][0] / 0x000A), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Stream #0:1 -> #0:1 (copy)
Press [q] to stop, [?] for help
frame= 628 fps=0.0 q=-1.0 Lsize= 6392kB time=00:00:20.91 bitrate=2503.3kbits/s speed=1.14e+03x
video:6359kB audio:5kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.439748%
如上述文件数据内容所示,该FLV文件中包含了关键帧索引信息,这些关键帧索引信息并不是FLV的标准字段,但是由于其被广泛使用,已经成为常用的字段,所以FFmpeg中同样也支持了这个功能。
当生成的FLV出现问题时,或者需要分析FLV内容时,如果有一个可视化工具进行分析将会更加容易,所以可以考虑使用flvparse进行FLV格式的分析,如图所示。
除了使用flvparse工具分析FLV文件之外,还可以使用FlvAnalyzer,打开FLV之后分析的FLV看到的信息会比flvparse更全面一些,如图3-7所示。
除了以上两款FLV解析工具之外,同样还可以使用ffprobe解析FLV文件,并且其还能够将关键帧索引的相关信息打印出来:
ffprobe -v trace -i output.flv
这条命令行执行之后,输出结果如下:
[flv @ 00000243b2f0e380] Format flv probed with size=2048 and score=100
[flv @ 00000243b2f0e380] Before avformat_find_stream_info() pos: 13 bytes read:32768 seeks:0 nb_streams:0
[flv @ 00000243b2f0e380] type:18, size:686, last:-1, dts:0 pos:21
[flv @ 00000243b2f0e380] keyframe stream hasn't been created
[flv @ 00000243b2f0e380] type:9, size:45, last:-1, dts:0 pos:722
[flv @ 00000243b2f0e380] keyframe filepositions = 796 times = 0
[flv @ 00000243b2f0e380] keyframe filepositions = 2587912 times = 8334
[flv @ 00000243b2f0e380] keyframe filepositions = 5205550 times = 16667
[flv @ 00000243b2f0e380] 0 17 0
[flv @ 00000243b2f0e380] type:8, size:7, last:-1, dts:0 pos:782
[flv @ 00000243b2f0e380] 1 AF 0
[flv @ 00000243b2f0e380] type:9, size:92455, last:-1, dts:0 pos:804
[flv @ 00000243b2f0e380] 0 17 0
从以上输出的内容中可以看到,信息中包含了keyframe关键帧存储在文件中的偏移位置以及时间戳。至此,FFmpeg转FLV文件的常用功能已经介绍完毕。
M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。M3U 和 M3U8 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。下面就来看一下M3U8的最简单的例子:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:8.333000,
output0.ts
#EXTINF:8.334000,
output1.ts
#EXTINF:4.263822,
output2.ts
#EXT-X-ENDLIST
从这个例子中可以看到如下几个字段,其含义具体如下。
·EXTM3U
M3U8文件必须包含的标签,并且必须在文件的第一行。
·EXT-X-VERSION
M3U8文件的版本,常见的是3,其实版本已经发展了很多了,期间也对不少标记进行了增删。
·EXT-X-TARGETDURATION
每一个分片都会有一个分片自己的duration,这个标签是最大的那个分片的浮点数四舍五入后的整数值,例如1.02四舍五入后的整数为1,2.568四舍五入后的整数为3。
·EXT-X-MEDIA-SEQUENCE
M3U8直播时的直播切片序列,当播放打开M3U8时,以这个标签的值为参考,播放对应的序列号的切片。当然关于客户端播放M3U8的标准还有更多的讲究,下面就来逐项进行介绍。
分片必须是动态改变的,序列不能相同,并且序列必须是增序的。
·EXTINF
EXTINF为M3U8列表中每一个分片的duration,如上面例子输出信息中的第一个分片的duration为8.333000秒;在EXTINF标签中除了duration值,还可以包含可选的描述信息,主要为标注切片信息,使用逗号分隔开
。
EXTINF下面的信息为具体的分片信息,分片存储路径可以为相对路径,也可以为绝对路径,也可以为互联网的URL链接地址。
·EXT-X-ENDLIST
若出现EXT-X-ENDLIST标签,则表明该M3U8文件不会再产生更多的切片,可以理解为该M3U8已停止更新,并且播放分片到这个标签后结束。M3U8不仅仅是可以作为直播,也可以作为点播存在,在M3U8文件中保留所有切片信息最后使用EXT-X-ENDLIST结尾,这个M3U8即为点播M3U8。
·EXT-X-STREAM-INF
EXT-X-STREAM-INF标签出现在M3U8中时,主要是出现在多级M3U8文件中时,例如M3U8中包含子M3U8列表,或者主M3U8中包含多码率M3U8时;该标签后需要跟一些属性,下面就来逐一说明这些属性。
BANDWIDTH:BANDWIDTH的值为最高码率值
,当播放EXT-X-STREAM-INF下对应的M3U8时占用的最大码率,这个参数是EXT-X-STREAM-INF标签中必须要包含的属性。
AVERAGE-BANDWIDTH:AVERAGE-BANDWIDTH的值为平均码率值
,当播放EXT-X-STREAM-INF下对应的M3U8时占用的平均码率,这个参数是一个可选参数。
CODECS:CODECS的值用于声明EXT-X-STREAM-INF下面对应M3U8里面的音频编码、视频编码的信息
,例如,若AAC-LC的音频与视频为H.264Main Profile、Level3.0的话,则CODECS值为“mp4a.40.2,avc1.4d401e”,这个属性应该出现在EXT-X-STREAM-INF标签里,但是并不是所有的M3U8中都可以看到,仅供参考。
下面针对EXT-X-STREAM-INF举一个实际的例子:
EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1000000
http://example.com/low.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2560000,AVERAGE-BANDWIDTH=2000000
http://example.com/mid.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=7680000,AVERAGE-BANDWIDTH=6000000
http://example.com/hi.m3u8
EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS="mp4a.40.5"
http://example.com/audio-only.m3u8
在这个M3U8文件中,使用了4个EXT-X-STREAM-INF标签来标注子M3U8的属性:最高码率为1.28M、平均码率为1M的M3U8,最高码率为2.56M、平均码率为2M的M3U8,最高码率为7.68M、平均码率为6M的M3U8,以及只有65K的音频编码的M3U8。
FFmpeg中自带HLS的封装参数,使用HLS格式即可进行HLS的封装,但是生成HLS的时候有各种参数可以进行参考,例如设置HLS列表中切片的前置路径
、生成HLS的TS切片时设置TS的分片参数
、生成HLS时设置M3U8列表中保存的TS个数等,详细参数请参考下表。
常规的从文件转换HLS直播时,使用的参数如下:
ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb output.m3u8
输出内容如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:7
#EXTINF:2.134000,
output7.ts
#EXTINF:1.666000,
output8.ts
#EXTINF:7.434000,
output9.ts
#EXTINF:8.333000,
output10.ts
#EXTINF:7.828111,
output11.ts
#EXT-X-ENDLIST
因为默认是HLS直播,所以生成的M3U8文件内容会随着切片的产生而更新,如果仔细观察,会发现命令行中多了一个参数“-bsf:v h264_mp4toannexb”,这个参数的作用是将MP4中的H.264数据转换为H.264AnnexB标准的编码,AnnexB标准的编码常见于实时传输流中
。如果源文件为FLV、TS等可作为直播传输流的视频,则不需要这个参数。生成HLS时还有一些参数可以设置,下面就来逐一介绍。
start_number参数用于设置M3U8列表中的第一片的序列数,使用start_number参数设置M3U8中第一片的序列数为300,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -start_number 300 output.m3u8
输出的M3U8内容如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:307
#EXTINF:2.134000,
output307.ts
#EXTINF:1.666000,
output308.ts
#EXTINF:7.434000,
output309.ts
#EXTINF:8.333000,
output310.ts
#EXTINF:7.828111,
output311.ts
#EXT-X-ENDLIST
从输出的M3U8内容可以看到,切片的第一片的编号是300,上面的命令行参数的-start_number参数已生效。
hls_time参数用于设置M3U8列表中切片的duration;
例如使用如下命令行控制转码切片长度为10秒钟左右一片,该切片规则采用的方式是从关键帧处开始切片,所以时间并不是很均匀,如果先转码再进行切片,则会比较规律:
ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -hls_time 10 output.m3u8
命令行执行后,输出的M3U8内容如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:13
#EXT-X-MEDIA-SEQUENCE:2
#EXTINF:8.333000,
output2.ts
#EXTINF:8.167000,
output3.ts
#EXTINF:12.767000,
output4.ts
#EXTINF:8.333000,
output5.ts
#EXTINF:7.828111,
output6.ts
#EXT-X-ENDLIST
从输出的M3U8内容可以看到,TS文件的每一片的时长都在10秒钟左右,hls_time 10参数生效。
hls_list_size参数用于设置M3U8列表中TS切片的个数
,通过hls_list_size可以控制M3U8列表中TS分片的个数,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -hls_list_size 3 output.m3u8
命令执行后输出的M3U8内容如下,列表中最多有3个TS分片:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:9
#EXTINF:7.434000,
output9.ts
#EXTINF:8.333000,
output10.ts
#EXTINF:7.828111,
output11.ts
#EXT-X-ENDLIST
从输出的M3U8内容可以看出,在M3U8文件窗口中只保留了3片TS的文件信息,hls_list_size设置生效。
hls_wrap参数用于为M3U8列表中TS设置刷新回滚参数,当TS分片序号等于hls_wrap参数设置的数值时回滚,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -bsf:v h264_mp4toannexb -hls_wrap 3 output.m3u8
命令行执行后输出的M3U8内容如下,当切片序号大于2时,序号回滚为0:
EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:62
#EXTINF:5.000000,
output2.ts
#EXTINF:6.960000,
output0.ts
#EXTINF:3.200000,
output1.ts
#EXTINF:3.840000,
output2.ts
#EXTINF:0.960000,
output0.ts
从输出的M3U8内容可以看出,生成的TS序号已经被回滚,M3U8内容中出现了两个编号为2的TS片,两个编号为0的TS片。
注意:
FFmpeg中的这个hls_wrap配置参数对CDN缓存节点的支持并不友好,并且会引起很多不兼容相关的问题,在新版本的FFmpeg中该参数将被弃用
。
hls_base_url参数用于为M3U8列表中的文件路径设置前置基本路径参数,因为在FFmpeg中生成M3U8时写入的TS切片路径默认为与M3U8生成的路径相同,但是实际上TS所存储的路径既可以为本地绝对路径,也可以为当前相对路径,还可以为网络路径,因此使用hls_base_url参数可以达到该效果
,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_base_url http://192.168.0.1/live/ -bsf:v h264_mp4toannexb output.m3u8
命令行执行后输出的M3U8内容如下,可以看到M3U8中增加了绝对路径:
EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:3.760000,
http://192.168.0.1/live/output0.ts
#EXTINF:1.880000,
http://192.168.0.1/live/output1.ts
#EXTINF:1.760000,
http://192.168.0.1/live/output2.ts
#EXTINF:1.040000,
http://192.168.0.1/live/output3.ts
#EXTINF:1.560000,
http://192.168.0.1/live/output4.ts
从输出的M3U8内容可以看到,每一个TS文件的前面都加上了一个http链接前缀,因为使用了hls_base_url设置的参数已生效。
hls_segment_filename参数用于为M3U8列表设置切片文件名的规则模板参数
,如果不设置hls_segment_filename参数,那么生成的TS切片文件名模板将与M3U8的文件名模板相同,设置hls_segment_filename规则命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_segment_filename test_output-%d.ts -bsf:v h264_mp4toannexb output.m3u8
命令行执行后输出的M3U8内容如下,TS切片规则可以通过参数被正确的设置:
从输出的M3U8内容与打开的M3U8文件名来看,TS分片的文件名前缀与M3U8文件名不相同,这说明可以通过参数hls_segment_filename来设置HLS的TS分片名。
hls_flags参数包含了一些子参数,子参数包含了正常文件索引、删除过期切片、整数显示duration、列表开始插入discontinuity标签、M3U8结束不追加endlist标签等
。
·delete_segments子参数
使用delete_segments参数用于删除已经不在M3U8列表中的旧文件,这里需要注意的是,FFmpeg删除切片时会将hls_list_size大小的2倍作为删除的依据,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_flags delete_segments -hls_list_size 4 -bsf:v h264_mp4toannexb output.m3u8
命令行执行后,生成的切片与M3U8列表文件内容如下:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:8
#EXTINF:1.666000,
output8.ts
#EXTINF:7.434000,
output9.ts
#EXTINF:8.333000,
output10.ts
#EXTINF:7.828111,
output11.ts
#EXT-X-ENDLIST
从输出的内容可以看到,切片已经切到了11片,但是目录中只有编号从7至11的切片,其他早期的切片已全部被删除,这是因为使用了-hls_flags delete_segments参数。
·round_durations子参数
使用round_durations子参数实现切片信息的duration为整型
,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_flags round_durations -bsf:v h264_mp4toannexb output.m3u8
命令行执行后生成的M3U8内容如下,duration为整型:
#EXTM3U
#EXT-X-VERSION:2
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:6
#EXTINF:2,
output6.ts
#EXTINF:2,
output7.ts
#EXTINF:2,
output8.ts
#EXTINF:7,
output9.ts
#EXTINF:8,
output10.ts
从输出的M3U8文件内容中可以看到,每一片TS的时长均为正数
,而不是平常所看到的浮点数,因为设置的hls_flags round_durations已生效
。
·discont_start子参数
discont_start子参数在生成M3U8的时候在切片信息的前边插入discontinuity标签,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_flags discont_start -bsf:v h264_mp4toannexb output.m3u8
命令行执行后生成的M3U8内容如下,在切片前边加入了discontinuity标签:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:7
#EXTINF:2.134000,
output7.ts
#EXTINF:1.666000,
output8.ts
#EXTINF:7.434000,
output9.ts
#EXTINF:8.333000,
output10.ts
#EXTINF:7.828111,
output11.ts
#EXT-X-ENDLIST
从M3U8输出的内容可以看到,输出的M3U8在第一片TS信息的前面有一个EXT-X-DISCONTINUTY的标签,这个标签常用于在切片不连续时作特别声明用。
·omit_endlist子参数
omit_endlist子参数在生成M3U8结束的时候若不在文件末尾则不追加endlist标签,因为在常规的生成M3U8文件结束时,FFmpeg会默认写入endlist标签,使用这个参数可以控制在M3U8结束时不写入endlist标签
,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -hls_flags omit_endlist -bsf:v h264_mp4toannexb output.m3u8
命令行执行完成并在文件转M3U8结束之后,M3U8文件的末尾处不会追加endlist标签:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:7
#EXTINF:2.134000,
output7.ts
#EXTINF:1.666000,
output8.ts
#EXTINF:7.434000,
output9.ts
#EXTINF:8.333000,
output10.ts
#EXTINF:7.828111,
output11.ts
从M3U8输出的内容可以看到,在生成HLS文件结束时并没有在M3U8末尾处追加EXT-X-ENDLIST标签
。
·split_by_time子参数
split_by_time子参数生成M3U8时是根据hls_time参数设定的数值作为秒数参考对TS进行切片的
,并不一定要遇到关键帧,从之前的例子中可以看到hls_time参数设定了值之后,切片生成的TS的duration有时候远大于设定的值,使用split_by_time即可解决这个问题,命令行如下:
ffmpeg -re -i input.ts -c copy -f hls -hls_time 2 -hls_flags split_by_time output.m3u8
命令行执行完成之后,hls_time参数设置的切片duration已经生效,效果如下:
EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:61
#EXTINF:2.040000,
output61.ts
#EXTINF:2.000000,
output62.ts
#EXTINF:1.920000,
output63.ts
#EXTINF:2.080000,
output64.ts
#EXTINF:0.520000,
output65.ts
从输出的内容可以看到,生成的切片在没有遇到关键帧时依然可以与hls_time设置的切片的时长相差不多。
注意:
split_by_time参数必须与hls_time配合使用
,并且使用split_by_time参数时有可能会影响首画面体验,例如花屏或者首画面显示慢的问题,因为视频的第一帧不一定是关键帧。
使用use_localtime参数可以以本地系统时间为切片文件名,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f hls -use_localtime 1 -bsf:v h264_mp4toannexb output.m3u8
命令行执行后生成的内容如下:
从输出的M3U8的内容与TS切片的命名可以看到,切片的名称是以本地时间来命名的。
method参数用于设置HLS将M3U8及TS文件上传至HTTP服务器,使用该功能的前提是需要有一台HTTP服务器,支持上传相关的方法,例如PUT、POST等,可以尝试使用Nginx的webdav模块来完成这个功能,method方法的PUT方法可用于实现通过HTTP推流HLS的功能,首先需要配置一个支持上传文件的HTTP服务器,本例使用Nginx来作为HLS直播的推流服务器,并且需要支持WebDAV功能,Nginx配置如下:
location / {
root html/;
index index.html index.htm;
client_max_body_size 10M;
dav_access group:rw all:rw;
dav_methods PUT DELETE MKCOL COPY MOVE;
}
配置完成后启动Nginx即可。通过ffmpeg执行HLS推流命令行如下:
ffmpeg -i input.mp4 -c copy -f hls -hls_time 3 -hls_list_size 0 -method PUT -t 30 http://127.0.0.1/test/output_test.m3u8
命令行执行完毕后,在Nginx对应的配置目录下面将会有ffmpeg推流上传的HLS相关的M3U8以及TS文件,效果如下:
-rw-rw-rw- 1 nobody nobody 2665464 Mar 22 11:43 output_test0.ts
-rw-rw-rw- 1 nobody nobody 2739160 Mar 22 11:43 output_test1.ts
-rw-rw-rw- 1 nobody nobody 2661892 Mar 22 11:43 output_test2.ts
-rw-rw-rw- 1 nobody nobody 1641804 Mar 22 11:43 output_test3.ts
-rw-rw-rw- 1 nobody nobody 224 Mar 22 11:43 output_test.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:8
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:8.333000,
output_test0.ts
#EXTINF:8.334000,
output_test1.ts
#EXTINF:8.333000,
output_test2.ts
#EXTINF:5.063289,
output_test3.ts
#EXT-X-ENDLIST
至此,FFmpeg转HLS的功能介绍完毕。
视频文件切片与HLS基本类似,但是HLS切片在标准中只支持TS格式的切片,并且是直播与点播切片,既可以使用segment方式进行切片,也可以使用ss加上t参数进行切片,下面重点介绍一下segment与ss加上t参数对视频文件进行剪切的方法。
上表为使用segment生成文件切片的详细参数列表,有些参数与HLS的参数基本相同,下面着重介绍一些不同的。
通过使用segment_format来指定切片文件的格式,前面讲述过HLS切片的格式主要为MPEGTS文件格式,那么在segment中,可以根据segment_format来指定切片文件的格式,其既可以为MPEGTS切片,也可以为MP4切片,还也可以为FLV切片等,下面举例说明:
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 test_output-%d.mp4
述命令行表示将一个MP4文件切割为MP4切片,切出来的切片文件的时间戳与上一个MP4的结束时间戳是连续的。下面就来查看文件列表和文件内容以确认一下:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:26 test_output-0.mp4
-rw-r--r-- 1 root root 2618287 Mar 22 14:27 test_output-10.mp4
-rw-r--r-- 1 root root 2461255 Mar 22 14:27 test_output-11.mp4
-rw-r--r-- 1 root root 2656711 Mar 22 14:26 test_output-1.mp4
-rw-r--r-- 1 root root 2579435 Mar 22 14:26 test_output-2.mp4
-rw-r--r-- 1 root root 2618121 Mar 22 14:27 test_output-3.mp4
-rw-r--r-- 1 root root 1676073 Mar 22 14:27 test_output-4.mp4
-rw-r--r-- 1 root root 985570 Mar 22 14:27 test_output-5.mp4
-rw-r--r-- 1 root root 456802 Mar 22 14:27 test_output-6.mp4
-rw-r--r-- 1 root root 687446 Mar 22 14:27 test_output-7.mp4
-rw-r--r-- 1 root root 439174 Mar 22 14:27 test_output-8.mp4
-rw-r--r-- 1 root root 2335657 Mar 22 14:27 test_output-9.mp4
然后查看第一片分片MP4的最后的时间戳:
# ffprobe -v quiet -show_packets -select_streams v test_output-0.mp4 2> x|grep pts_time | tail -n 3
pts_time=8.366016
pts_time=8.299023
pts_time=8.333008
接下来再查看第二片分片MP4的最开始的时间戳:
# ffprobe -v quiet -show_packets -select_streams v test_output-1.mp4 2> x|grep pts_time | head -n 3
pts_time=8.400000
pts_time=8.533984
pts_time=8.466992
从示例中可以看到test_output-0.mp4的最后的视频时间戳与test_output-1.mp4的起始时间戳刚好为一个正常的duration
,也就是0.040秒。
使用segment切割文件时,不仅仅可以切割MP4,同样也可以切割TS或者FLV等文件,生成的文件索引列表名称也可以指定名称,当然,列表不仅仅是M3U8,也可以是其他的格式:
·生成ffconcat格式索引文件
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -segment_list_type ffconcat -segment_list output.lst test_output-%d.mp4
上面这条命令将生成ffconcat格式的索引文件名output.lst,这个文件将会生成一个MP4切片的文件列表:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:40 test_output-0.mp4
-rw-r--r-- 1 root root 2656711 Mar 22 14:40 test_output-1.mp4
-rw-r--r-- 1 root root 2579435 Mar 22 14:40 test_output-2.mp4
-rw-r--r-- 1 root root 2618121 Mar 22 14:40 test_output-3.mp4
-rw-r--r-- 1 root root 1676073 Mar 22 14:41 test_output-4.mp4
# cat output.lst
ffconcat version 1.0
file test_output-0.mp4
file test_output-1.mp4
file test_output-2.mp4
file test_output-3.mp4
file test_output-4.mp4
从输出的文件与output.lst内容可以看到,输出的列表格式是ffconcat格式,这种格式常见于虚拟轮播等场景。
·生成FLAT格式索引文件
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -segment_list_type flat -segment_list filelist.txt test_output-%d.mp4
上面这条命令将生成一个MP4切片的文本文件列表:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:44 test_output-0.mp4
-rw-r--r-- 1 root root 2656711 Mar 22 14:44 test_output-1.mp4
-rw-r--r-- 1 root root 2579435 Mar 22 14:44 test_output-2.mp4
-rw-r--r-- 1 root root 2618121 Mar 22 14:44 test_output-3.mp4
-rw-r--r-- 1 root root 1676073 Mar 22 14:44 test_output-4.mp4
# cat filelist.txt
test_output-0.mp4
test_output-1.mp4
test_output-2.mp4
test_output-3.mp4
test_output-4.mp4
从上面的内容可以看出,切片列表被列在了一个TXT文本当中。
·生成CSV格式索引文件
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -segment_list_type csv -segment_list filelist.csv test_output-%d.mp4
上述这条命令将会生成CSV格式的列表文件,列表文件中的内容分为三个字段,文件名、文件起始时间和文件结束时间:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:47 test_output-0.mp4
-rw-r--r-- 1 root root 2656711 Mar 22 14:47 test_output-1.mp4
-rw-r--r-- 1 root root 2579435 Mar 22 14:48 test_output-2.mp4
-rw-r--r-- 1 root root 2618121 Mar 22 14:48 test_output-3.mp4
-rw-r--r-- 1 root root 1676073 Mar 22 14:48 test_output-4.mp4
# cat filelist.csv
test_output-0.mp4,0.000000,8.400326
test_output-1.mp4,8.400000,16.733333
test_output-2.mp4,16.733984,25.067318
test_output-3.mp4,25.066992,33.400326
test_output-4.mp4,33.400000,38.733333
从输出的内容可以看到切片文件的信息生成到了CSV文件,CSV文件可以用类似于操作数据库的方式进行操作,也可以根据CSV生成视图图像。
·生成M3U8格式索引文件
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -segment_list_type m3u8 -segment_list output.m3u8 test_output-%d.mp4
生成M3U8列表不仅仅可以生成MPEGTS格式文件,同样还可以生成其他格式:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:51 test_output-0.mp4
-rw-r--r-- 1 root root 2656711 Mar 22 14:51 test_output-1.mp4
-rw-r--r-- 1 root root 2579435 Mar 22 14:51 test_output-2.mp4
-rw-r--r-- 1 root root 2618121 Mar 22 14:51 test_output-3.mp4
-rw-r--r-- 1 root root 1676073 Mar 22 14:52 test_output-4.mp4
[root@iZwz9ge5xssb2cdfqx85o2Z test]# cat output.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:9
#EXTINF:8.400326,
test_output-0.mp4
#EXTINF:8.333333,
test_output-1.mp4
#EXTINF:8.333333,
test_output-2.mp4
#EXTINF:8.333333,
test_output-3.mp4
#EXTINF:5.333333,
test_output-4.mp4
#EXT-X-ENDLIST
从输出的内容可以看到输出的M3U8与使用HLS模块生成的M3U8基本相同。
使每一片切片的时间戳归0可使用reset_timestamps进行设置,命令行如下:
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -reset_timestamps 1 test_output-%d.mp4
命令行执行完成之后,可以查看一下是否每一个切片的时间戳都从0开始。
查看生成的切片文件:
# ls -l test_output-*.mp4
-rw-r--r-- 1 root root 2587723 Mar 22 14:55 test_output-0.mp4
-rw-r--r-- 1 root root 2656687 Mar 22 14:55 test_output-1.mp4
-rw-r--r-- 1 root root 2579411 Mar 22 14:55 test_output-2.mp4
-rw-r--r-- 1 root root 2618097 Mar 22 14:55 test_output-3.mp4
-rw-r--r-- 1 root root 1676049 Mar 22 14:55 test_output-4.mp4
然后查看一下第一片末尾的时间戳:
#ffprobe -v quiet -show_packets -select_streams v test_output-0.mp4 2> x|grep pts_time | tail -n 3
pts_time=8.366016
pts_time=8.299023
pts_time=8.333008
然后再查看一下第二片开始的时间戳:
#ffprobe -v quiet -show_packets -select_streams v test_output-1.mp4 2> x|grep pts_time | head -n 3
pts_time=0.000000
pts_time=0.133984
pts_time=0.066992
从验证的效果来看,每一片的开始时间戳均已归0,参数设置生效
。
对文件进行切片时,有时候需要均匀的切片,有时候需要按照指定的时间长度进行切片,segment可以根据指定的时间点进行切片
,下面举例说明:
ffmpeg -re -i input.mp4 -c copy -f segment -segment_format mp4 -segment_times 13,39,52 test_output-%d.mp4
根据命令行的参数可以看到,切片的时间点分别为第13秒、第39秒和第52秒,在这三个时间点进行切片
。
在FFmpeg中,使用ss可以进行视频文件的seek定位,ss所传递的参数为时间值,t所传递的参数也为时间值,下面就来举例说明ss与t的作用。
在前面章节中介绍FFmpeg基本参数时,粗略地介绍过FFmpeg的基本转码原理,FFmpeg自身的ss参数可以用作切片定位起始时间点,例如从一个视频文件的第10秒钟开始截取内容:
ffmpeg -ss 10 -i input.mp4 -c copy output.ts
命令行执行之后,生成的output.ts将会比input.mp4的视频少8秒,因为output.ts是从input.mp4的第8秒开始截取的,使用前面介绍过的ffprobe分别获得input.mp4与output.ts的文件duration并进行对比,信息如下:
# ffprobe -v quiet -show_format input.mp4 |grep duration; ffprobe -v quiet -show_format output.ts |grep duration
duration=70.466000
duration=62.200033
如以上的输出结果所示,input.mp4的duration是70秒,而output.ts的duration是62秒,相差时间为8秒。
使用FFmpeg截取视频除了可以指定开始截取位置,还可以指定截取数据的长度,FFmpeg的t参数可以指定截取的视频长度,例如截取input.mp4文件的前10秒的数据:
ffmpeg -i input.mp4 -c copy -t 10 -copyts output.mp4
命令行执行完之后,会生成一个时间从0开始的output.mp4,查看一下input.mp4与output.mp4的起始时间与长度相关信息:
# ffprobe -v quiet -show_format input.mp4 |grep start_time; ffprobe -v quiet -show_format output.mp4 |grep start_time
start_time=0.000000
start_time=0.000000
# ffprobe -v quiet -show_format input.mp4 |grep duration;ffprobe -v quiet -show_format output.mp4 |grep duration
duration=70.466000
duration=10.067000
从两个文件的duration信息可以看到,input的start_time是0,duration是70.00,而output.mp4的start_time也是0,duration则是10,参数生效。
FFmpeg支持ss与t两个参数一同使用以达到切割视频的某一段的效果,但其并不能指定输出文件的start_time,而且也不希望时间戳归0,可以使用output_ts_offset来达到指定输出文件的start_time的目的
:
ffmpeg -i input.mp4 -c copy -t 10 -output_ts_offset 120 output.mp4
命令行执行之后输出的output.mp4文件的start_time即将被指定为120,下面就来看一下其效果:
[FORMAT]
filename=output.mp4
nb_streams=2
nb_programs=0
format_name=mov,mp4,m4a,3gp,3g2,mj2
format_long_name=QuickTime / MOV
start_time=120.000000
duration=130.067000
size=3292800
bit_rate=202529
probe_score=100
TAG:major_brand=isom
TAG:minor_version=512
TAG:compatible_brands=isomiso2avc1mp41
TAG:encoder=Lavf59.27.100
[/FORMAT]
从输出的内容可以看到start_time是从120秒开始,而duration是130秒,指定开始时间与duration操作生效。
FFmpeg除了转封装、转码之外,还可以提取音频流
,例如需要将音频流提取出来然后合成之后插入到另一个封装中的情况,下面就来看一下FFmpeg提取MP4文件中的AAC音频流的方法
:
ffmpeg -i input.mp4 -vn -acodec copy output.aac
通过FFmpeg将视频中的音频流抽取出来,执行上述命令之后,将输出如下信息:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.34.102
Duration: 00:01:10.47, start: 0.000000, bitrate: 2507 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, 2496 kb/s, 30 fps, 30 tbr, 16k tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Output #0, adts, to 'output.aac':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.27.100
Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Stream mapping:
Stream #0:1 -> #0:0 (copy)
Press [q] to stop, [?] for help
size= 39kB time=00:01:10.37 bitrate= 4.5kbits/s speed=6.97e+03x
video:0kB audio:18kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 115.410141%
从输出的内容可以看到,输入的视频文件中包含视频流与音频流,输出信息中只有AAC音频,生成的output.aac文件内容则为AAC音频流文件
。
有时在视频编辑场景中需要将视频流提取出来进行编辑,或者与另一路视频流进行合并等操作
,这时可以使用FFmpeg来完成:
ffmpeg -i input.mp4 -vcodec copy -an output.h264
通过FFmpeg将视频中的视频流抽取出来,执行上述命令之后,将输出如下信息:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.34.102
Duration: 00:01:10.47, start: 0.000000, bitrate: 2507 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, 2496 kb/s, 30 fps, 30 tbr, 16k tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 2 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
Output #0, h264, to 'output.h264':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf59.27.100
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, q=2-31, 2496 kb/s, 30 fps, 30 tbr, 30 tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream mapping:
Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
frame= 2113 fps=0.0 q=-1.0 Lsize= 21465kB time=00:01:10.36 bitrate=2499.0kbits/s speed=2.47e+03x
从输出的内容可以看出,输入的视频文件中包含音频流与视频流,输出信息中只有H.264视频,生成的output.h264则为H.264视频流文件。
与H.264的抽取方法类似,下面再列举一个从音视频文件中抽取H.265数据的例子:
ffmpeg -i input.mp4 -vcodec copy -an -bsf hevc_mp4toannexb -f hevc output.hevc
通过FFmpeg将视频文件中的视频流抽取出来,执行上述命令之后,将输出如下信息:
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input_hevc.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf57.66.102
Duration: 00:00:10.08, start: 0.000000, bitrate: 1180 kb/s
Stream #0:0(und): Video: hevc (Main) (hev1 / 0x31766568), yuv420p(tv, progressive), 1280x714 [SAR 1:1 DAR 640:357], 1044 kb/s, 25 fps, 25 tbr, 12800 tbn, 25 tbc (default)
Metadata:
由于输入文件input.mp4的容器格式为MP4,MP4中存储的视频数据并不是标准的annexb格式,所以需要将MP4的视频存储格式存储为annexb格式,输出的HEVC格式可以直接使用播放器进行观看。
在使用FFmpeg进行格式转换、编码转换操作时,所占用的系统资源各有不同,如果使用FFmpeg仅仅转换封装格式而并非转换编码,那么其使用的CPU资源并不多,下面来看一下转换封装时的CPU使用率:
ffmpeg -re -i input.mp4 -c copy -f mpegts output.ts
通过上图可以看出,使用FFmpeg进行封装转换时并不会占用大量的CPU资源,因为使用FFmpeg进行封装转换时主要是以读取音视频数据、写入音视频数据为主,并不会涉及复杂的计算。
如果使用FFmpeg进行编码转换,则需要进行大量的计算,从而将会占用大量的CPU资源:
ffmpeg -re -i input.mp4 -vcodec libx264 -acodec copy -f mpegts output.ts
命令执行之后就开始进行转码操作,执行之后可使用top查看cpu使用率,结果上图所示。
从图中可以看的使用FFmpeg进行视频编码转换时CPU的使用情况,CPU使用率相对比较高,其实在进行转码操作时CPU的使用率会非常高,因为涉及了大量的计算,使用情况取决于计算的复杂程度。
本章重点讲解了音视频文件转MP4、FLV、HLS以及视频文件切片处理等的相关知识,并分析了常用的MP4、FLV、HLS的标准格式并给出了相应的FFmpeg应用举例,通过本章的学习,读者可以掌握大部分媒体文件转换格式的实现方法。