FFmpeg中的FF全称是"Fast Forward",后面的mpeg全称是"Moving Picture Experts Group"(动态图像专家组),FFmpeg既是一款音视频编解码工具,也是一组音视频编解码开发套件,作为编解码开发套件,它为开发者提供了丰富的音视频处理的调用接口。
1.FFmpeg组成
从代码结构上来看, FFmpeg可以分为:
- libavcodec: 编解码库, 该模块是最重要的模块之一, 封装了Codec层, 但是有一些Codec是有License要求的, FFmpeg不会默认添加, 可以以插件的形式编译进来, 例如libx264/fdk-aac/mp3lame等等, FFmpeg这样看来就像一个平台, 其他的模块可以以插件的形式随意接入.
- libavdevice: 输入输出设备库, 例如需要编译出播放声音或者播放视频的工具ffplay, 该模块必须打开, 当然也要支持libsdl才可以的.
- libavfilter: 音视频过滤库,该模块提供了包括音频特效和视频特效的处理, 现今对音视频的处理要求很高, 这个模块越来越重要啦.
- libavformat: 文件格式和协议库, 这个模块相当重要, 和libavcodec一样重要, 封装了 Protocol层和Demuxer/Muxer层, 支持几乎你知道的所有协协议.
- libavresample: 这个模块已经废弃了,最新的使用libswresample替代.
- libavutil: 核心工具库, 该模块是最基础的模块之一, 下面许多的模块都依赖该库做一些基本的音视频处理操作.
- libpostproc: 该模块可以用于后期处理, 当我们使用libavfilter的时候要打开这个模块的开关, 因为libavfilter中需要用到这个模块的基础函数.
- libswresample: 该模块用于音频的重采样, 可以对数字音频进行声道数/数据格式/采样率等多种信息的转换.
- libswscale: 该模块是将图像进行格式转换的, 可以将YUV格式转换为RGB格式.
从功能来划分,FFmpeg可以分为:
- ffplay: FFmpeg还提供播放器的功能,使用FFmpeg的avformat与avcodec,可以播放各种媒体文件或者流,如果想要使用ffplay,那么系统首先需要有SDL来进行ffplay的基础支撑。ffplay提供了音视频显示和播放相关的图像信息、音频的波形信息等。
- ffmpeg: 音视频处理
- ffprobe: ffprobe也是FFmpeg编译后生成的可执行程序,ffprobe非常强大的多媒体分析工具。可以从媒体文件或者媒体流中获得相应的媒体信息。它可以分析媒体容器中的音频和视频是什么编码格式媒体的总时长、复合码率等等信息。
- ffserver: 音视频服务器搭建
ffmpeg是FFmpeg源代码编译后生成的一个可执行程序,其可以作为命令工具集使用,具体的使用方法下面会详细介绍。下面会有详细的使用指令介绍。
ffmpeg的主要工作流程如下:
- 解封装
- 解码
- 编码
- 封装
其中需要经过6个主要的步骤:
- 读取输入源
- 进行音视频的解封装
- 解码每一帧音视频数据
- 编码每一帧音视频数据
- 进行音视频的重新封装
- 输出到目标
ffmpeg首先读取输入源,然后通过Demuxer将音视频包解封装,这个动作通过调用libavformat中的接口可以实现,接下来通过Decoder进行解码,将音视频通过Decoder解包成为YUV或者PCM这样的原始数据,Decoder通过libavcodec中的接口即可实现,然后通过Encoder将对应的数据进行编码,编码可以通过libavcodec中的接口来实现,接下来将编码后的音视频数据包通过Muxer进行封装,Muxer封装通过libavformat中的接口即可实现,输出成为输出流。
2.FFmpeg基础命令
2.1 ffmpeg基础命令
ffmpeg工具非常重要,在很多场景下都使用ffmpeg来实现转码,ffmpeg的常见命令大概分为6个部分。
- ffmpeg指令
- 公共操作参数部分
- 文件主要操作参数部分
- 视频操作参数部分
- 音频操作参数部分
- 字幕操作参数部分
ffmpeg --help | ffmpeg命令基础信息 |
---|---|
ffmpeg -L | ffmpeg目前所支持的license协议 |
ffmpeg -version | 查看ffmpeg的版本,包括子模块版本的详细信息 |
ffmpeg -formats | 查看当前使用的ffmpeg是否支持对应的视频格式 |
ffmpeg -codecs | 查看ffmpeg支持的编解码格式 |
ffmpeg -encoders | 查看ffmpeg支持的编码格式 |
ffmpeg -decoders | 查看ffmpeg支持的解码格式 |
ffmpeg -filters | 查看ffmpeg支持的滤镜 |
ffmpeg --help full | 查看ffmpeg支持的所有封装格式、编解码器、滤镜处理器 |
ffmpeg -h muxer=flv | 查看flv封装器的参数支持 |
ffmpeg -h demuxer=flv | 查看flv解封装器的参数支持 |
ffmpeg -h encoder=h264 | 查看h264编码器的参数支持 |
ffmpeg -h decoder=h264 | 查看h264解码器的参数支持 |
ffmpeg -h filter=colorkey | 查看colorkey滤镜的参数支持 |
ffmpeg的封装转换功能包含在AVFormat模块中,通过libavformat库进行Mux和Demux操作。可以查看一下ffmpeg中的AVFormatContext数据结构:ffmpeg/libavformat/avformat.h文件中。这个在讲解源码的时候会详细阐述。
ffmpeg的编解码部分的功能主要是通过AVCodec模块来完成的,通过libavcodec库进行Encode与Decode操作。多媒体编码格式的种类有很多,可以查看AVCodecContext数据结构中的参数。
ffmpeg工具的主要作用是编码、解码、转码以及媒体格式转换,ffmpeg常用于进行转码操作,可以通过设置AVCodec与AVFormat的操作参数来进行封装与编码的操作。
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vcodec flv -b:v 200k -r 15 -an output.flv
- 上面只是操作了视频,并没有显示音频
- -b:v 200k 表示码率,码率从原来的633 kb/s 变成 200 kb/s
2.2 ffplay基础命令
正常在mac中要使用ffplay命令,需要安装sdl库,brew install sdl2.0,ffplay不仅仅是播放器,同时也是测试ffmpeg的codec引擎、format引擎,以及filter引擎工具,并且还可以用来进行可视化的媒体参数分析。通过ffplay --help进行分析。
从视频的第30秒开始播放,播放10秒钟文件,使用如下命令:ffplay -ss 30 -t 10 output.flv
指定播放视频的标题:ffplay -window_title "Hello, world" output.flv
ffplay有很多参数可供选择,如下:
ffplay的可视化分析:ffplay处理可以播放视频流媒体,还可以作为可视化的流媒体分析工具,可以在播放的时候将解码后的音频数据以音频波形的形式展现出来。
2.3 ffprobe基础命令
ffmpeg作为多媒体处理工具,ffprobe作为多媒体信息查看工具,ffprobe主要用来查看多媒体文件的信息。ffprobe --help查看详细的帮助信息。usage: ffprobe [OPTIONS] [INPUT_FILE]
代码语言:javascript复制ffprobe -show_packets output.flv
//查看多媒体数据包信息:一个多媒体数据有很多个数据包,这儿只选择一个数据包。
代码语言:javascript复制[PACKET]
codec_type=video
stream_index=0
pts=161667
pts_time=161.667000
dts=161667
dts_time=161.667000
duration=66
duration_time=0.066000
convergence_duration=N/A
convergence_duration_time=N/A
size=13797
pos=9131137
flags=K_
[/PACKET]
这也是AVPacket中的内容,这儿开始扯到源码了,下文会详细分析一下源码了。
代码语言:javascript复制ffprobe -show_data -show_packets output.flv
//用来查看包中的具体二进制信息
代码语言:javascript复制[PACKET]
codec_type=video
stream_index=0
pts=161600
pts_time=161.600000
dts=161600
dts_time=161.600000
duration=66
duration_time=0.066000
convergence_duration=N/A
convergence_duration_time=N/A
size=644
pos=9130477
flags=__
data=
00000000: 0000 87c0 81e0 010e 3fbf ffff ffff fffb ........?.......
00000010: 9fff ffe1 9e36 3e1e 142b afff ffff ffb9 .....6>.. ......
00000020: eeff ffff ffff ffff ffff ffff ffff ffff ................
00000030: 73ff ffff ffff ffff f77f ddff ffff ffff s...............
00000040: ffee ffff ffff ffff fffd ef79 b9dc fee3 ...........y....
00000050: ffff ffff ffff dcfb b6fd e7dc ee7f ffff ................
00000060: ffff fffe ecbb 9f7d f71f ffff ff0c f170 .......}.......p
/*
* 省略很多行
*/
[/PACKET]
可以看到AVPacket中具体的二进制信息。
代码语言:javascript复制ffprobe -show_format output.flv
查看多媒体的封装格式:这儿其实能看到关于这个视频的很多信息,这个视频只有1个流通道,起始时间是0,总时长是161.734000 ,文件大小是9144950字节。等等
代码语言:javascript复制Input #0, flv, from 'output.flv':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf58.29.100
Duration: 00:02:41.73, start: 0.000000, bitrate: 452 kb/s
Stream #0:0: Video: flv1, yuv420p, 960x540, 200 kb/s, 15 fps, 15 tbr, 1k tbn
[FORMAT]
filename=output.flv
nb_streams=1
nb_programs=0
format_name=flv
format_long_name=FLV (Flash Video)
start_time=0.000000
duration=161.734000
size=9144950
bit_rate=452345
probe_score=100
TAG:major_brand=isom
TAG:minor_version=512
TAG:compatible_brands=isomiso2avc1mp41
TAG:encoder=Lavf58.29.100
[/FORMAT]
代码语言:javascript复制ffprobe -show_frames output.flv
查看视频文件中的帧 信息:每一帧的详细信息展示出来了,可以直观的看到视频的帧是I帧、P帧或者B帧每一帧的大小也通过pkt_size来显示出来。这儿只显示一帧的数据。
代码语言:javascript复制[FRAME]
media_type=video
stream_index=0
key_frame=1
pkt_pts=161667
pkt_pts_time=161.667000
pkt_dts=161667
pkt_dts_time=161.667000
best_effort_timestamp=161667
best_effort_timestamp_time=161.667000
pkt_duration=66
pkt_duration_time=0.066000
pkt_pos=9131137
pkt_size=13797
width=960
height=540
pix_fmt=yuv420p
sample_aspect_ratio=N/A
pict_type=I
coded_picture_number=2425
display_picture_number=0
interlaced_frame=0
top_field_first=0
repeat_pict=0
color_range=unknown
color_space=unknown
color_primaries=unknown
color_transfer=unknown
chroma_location=unspecified
[SIDE_DATA]
side_data_type=QP table data
[/SIDE_DATA]
[SIDE_DATA]
side_data_type=QP table properties
[/SIDE_DATA]
[/FRAME]
这是AVFrame的信息,pict_type=I 表示是 I 帧。
编码器将多张图像进行编码后生产成一段一段的 GOP ( Group of Pictures ) , 解码器在播放时则是读取一段一段的 GOP 进行解码后读取画面再渲染显示。GOP ( Group of Pictures) 是一组连续的画面,由一张 I 帧和数张 B / P 帧组成,是视频图像编码器和解码器存取的基本单位,它的排列顺序将会一直重复到影像结束。I 帧是内部编码帧(也称为关键帧),P帧是前向预测帧(前向参考帧),B 帧是双向内插帧(双向参考帧)。简单地讲,I 帧是一个完整的画面,而 P 帧和 B 帧记录的是相对于 I 帧的变化。如果没有 I 帧,P 帧和 B 帧就无法解码。
代码语言:javascript复制ffprobe -show_streams output.flv
查看多媒体文件中的流信息;
代码语言:javascript复制[STREAM]
index=0
codec_name=flv1
codec_long_name=FLV / Sorenson Spark / Sorenson H.263 (Flash Video)
profile=unknown
codec_type=video
codec_time_base=1/15
codec_tag_string=[0][0][0][0]
codec_tag=0x0000
width=960
height=540
coded_width=960
coded_height=540
has_b_frames=0
sample_aspect_ratio=N/A
display_aspect_ratio=N/A
pix_fmt=yuv420p
level=-99
color_range=unknown
color_space=unknown
color_transfer=unknown
color_primaries=unknown
chroma_location=unspecified
field_order=unknown
timecode=N/A
refs=1
id=N/A
r_frame_rate=15/1
avg_frame_rate=15/1
time_base=1/1000
start_pts=0
start_time=0.000000
duration_ts=N/A
duration=N/A
bit_rate=200000
max_bit_rate=N/A
bits_per_raw_sample=N/A
nb_frames=N/A
nb_read_frames=N/A
nb_read_packets=N/A
DISPOSITION:default=0
DISPOSITION:dub=0
DISPOSITION:original=0
DISPOSITION:comment=0
DISPOSITION:lyrics=0
DISPOSITION:karaoke=0
DISPOSITION:forced=0
DISPOSITION:hearing_impaired=0
DISPOSITION:visual_impaired=0
DISPOSITION:clean_effects=0
DISPOSITION:attached_pic=0
DISPOSITION:timed_thumbnails=0
[/STREAM]
ffprobe输出信息可以通过key-value样子展示出来,也可以通过xml格式、json格式、csv、flat格式展示出来,展示的方式很多,这儿不一一展开了。
3.FFmpeg扩展操作
3.1 正常文件改变封装格式
这个上面已经介绍过了,mp4转flv封装格式,当然封装格式有很多,你可以随意选择你要想转换的封装格式。
3.2 切片操作
视频文件切片与HLS基本类似,但是HLS切片在标准中支持TS格式的切片,并且是直播与点播切片。M3U8切片操作参考:多媒体文件格式剖析:M3U8篇
下面介绍的切片既可以使用segment方式进行切片,也可以使用ss加上t参数进行切片。所谓的切片就是将原来完整的视频中的一部分提取出来,成为一个或者几个新的文件。
将视频切片成几段视频,每段视频30s,而且切片过程中还需要转码,转码成mp4格式:其中-re表示切片转码,-segment_format表示切片成的编码格式,-segment_time表示切片时间。
代码语言:javascript复制ffmpeg -re -i jeffmony.mp4 -c copy -f segment -segment_time 30 -segment_format mp4 output_d.mp4
ffmpeg也可以使用ss进行视频文件的seek定位,t所传递是总时长,output_ts_offset所传递的是输出文件的起始时间点。
从视频的第8秒开始截取:
代码语言:javascript复制ffmpeg -ss 8 -i output.mp4 -c copy output.ts
截取的总时长是10s,默认从0开始:
代码语言:javascript复制ffmpeg -i output.mp4 -c copy -t 10 output.ts
命令执行后输出的output.ts文件的start_time被定义为120:
代码语言:javascript复制ffmpeg -i output.mp4 -c copy -t 10 -output_ts_offset 120 output.ts
3.3 转码操作
目前H.264编码格式比较火,支持H.264的封装格式有很多,如FLV、MP4、HLS、MKV、TS格式等等。FFmpeg本身不支持H.264格式,而是通过三方库x264来实现支持,可以通过ffmpeg -h encoder=libx264来查看H.264支持的主要像素格式。H.265与H.264很多参数相同,基本上可以通用。
软编码是使用CPU来工作的,有时候性能会比较低,出于编码效率和成本考虑,很多时候都会采用硬编码,常见的硬编码包含Nvidia GPU与Intel QSV两种,Android端当然是MediaCodec了。
3.4 抽取音视频流
当音视频文件出现异常时,除了分析封装数据之外,还需要分析音视频流部分。
抽取音视频文件中的AAC音频流:
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vn -acodec copy output.aac
下面可以看出来输入的数据中有视频和音频,输出的数据中只有音频了。
抽取音视频文件中的H.264视频流:
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vcodec copy -an output.h264
4.FFmpeg滤镜操作
FFmpeg除了具有强大的封装、解封装、编解码功能之外,还包含一个非常强大的组件——滤镜avfilter,avfilter经常用于进行多媒体的处理与编辑。FFmpeg包含非常多的滤镜。目前音视频应用中滤镜非常火,但是Android平台上的滤镜一般都用OpenGL ES,FFmpeg在移动端的滤镜应用还是不多,但是并不妨碍我们了解它,FFmpeg这方面还是相当赞的。
4.1 视频中加上图片
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -i JeffMony.jpg -filter_complex "[1:v]scale=100:100[logo]; [0:v][logo]overlay=x=0:y=0" output2.mp4
上面命令主要是将input.jpg图片变成100*100大小,放在videoplayback.mp4视频中的左上角。[logo]中的logo是别名,这个别名后续在overlay中会用到,overlay=x=0:y=0放在左上角。临时标记名这个用法在ffmpeg操作命令中非常普遍。输出的结果截图如下:可以看到左上角的视频已经打上了这个烙印了,实际上ffmpeg会处理每一帧视频数据,然后将处理放到视频帧中,然后合成一个新的视频帧。
4.2 视频中加水印
FFmpeg可以给视频添加水印,水印可以是文字,也可以是图片,主要用来标记视频所属标记等。其实上面也是加水印的一种方式。
在视频中增加文字水印需要准备的条件比较多,需要有文字库处理相关文件,在编译FFmpeg时需要支持FreeType/FontConfig/iconv,系统中需要有相关的字库,在FFmpeg中增加纯字母水印可以使用drawtext滤镜进行支持,下面看戏drawtext的滤镜参数。
在视频的左上角加上一个“hello,world”,字体使用的是android sdk中的字体,协商字体路径,字大小是100,位置也写明的坐标(20,20)
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vf "drawtext=fontsize=100:fontfile=/Users/jeffmony/Library/Android/sdk/platforms/android-27/data/fonts/DroidSans.ttf:text='hello,world':x=20:y=20" output3.mp4
加上fontcolor=red可以调整字为红色:
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vf "drawtext=fontsize=100:fontfile=/Users/jeffmony/Library/Android/sdk/platforms/android-27/data/fonts/DroidSans.ttf:text='hello,world':fontcolor=red:x=20:y=20" output3.mp4
也可以给文字加上背景,设置背景颜色,box=1表示文字加上背景,boxcolor=XXX 表示文字背景设置的颜色。
有时候希望在视频中加上本地时间作为水印,执行指令如下:
代码语言:javascript复制fffmpeg -i jeffmony.mp4 -vf "drawtext=fontsize=50:fontfile=/Users/jeffmony/Library/Android/sdk/platforms/android-27/data/fonts/DroidSans.ttf:text='%{localtime:%Y-%m-%d %H-%M-%S}':fontcolor=red:x=20:y=20" output5.mp4
但这时候加的只是一个静止的时间,这个时间没有更新,实际应用中我们还希望时间可以随时更新下。
代码语言:javascript复制fffmpeg -i jeffmony.mp4 -vf "drawtext=fontsize=50:fontfile=/Users/jeffmony/Library/Android/sdk/platforms/android-27/data/fonts/DroidSans.ttf:text='%{localtime:%Y-%m-%d %H-%M-%S}':fontcolor=red:x=20:y=20:enable=lt(mod(t,3),1)" output5.mp4
加上enable参数,可以控制水印的刷新时间,这方面的参数非常多,可以实现很多定制化的功能。
除了可以添加文字水印,也可以添加图片水印,为视频添加水印可以使用movie滤镜,下面是滤镜的一些参数:
filename | 输入的文件名,可以是文件、协议、设备 |
---|---|
format_name, f | 的封装格式 |
stream_index, si | 输入的流索引编号 |
seek_point, sp | Seek输入流的时间位置 |
stream, s | 的多个流的流信息 |
loop | 循环次数 |
discontinuity | 支持跳动的时间戳差值 |
在FFmpeg中加入图片水印有两种方式:通过movie指定水印文件路径。通过filter读取输入文件的流并指定为水印。上面已经有很多filter的例子的,下面重点讲下movie的方式。将input.jpg图片通过movie方式打入到视频文件中,将图片大小限定为100*100,并且放在左上角。
代码语言:javascript复制ffmpeg -i jeffmony.mp4 -vf "movie=JeffMony.jpg,scale=100:100 [wm]; [in][wm]overlay=10:10[out]" output.mp4
还可以通过colorkey参数来设置图片的透明效果。
4.3 画中画
知道Android的PIP模式的,一定知道画中画的意思,我们在微信视频聊天的时候,就是典型的画中画的模式。在FFmpeg中,也有这样的应用场景,我们会将多个视频流或者视频文件合成到一个界面中,展示出画中画的效果,这时候经常采用的参数是overlay操作。
下面是overlay滤镜的基本参数:
overlay还有很多组合的参数,这在应用的时候也要也特别注意一下。将jeffmony.mp4视频嵌入到output.mp4视频中,设置jeffmony.mp4大小为 320*180,同时将输出的视频编码为h264,,这是典型的画中画模式的应用。
代码语言:javascript复制ffmpeg -re -i jeffmony.mp4 -vf "movie=output.mp4,scale=320x180[test]; [in][test]overlay [out]" -vcodec libx264 output2.mp4
当然也可以通过overlay控制子视频在显示画面的任意位置。
代码语言:javascript复制ffmpeg -re -i jeffmony.mp4 -vf "movie=output.mp4, scale=320x180[test]; [in][test]overlay=x=main_w/2-160:y=main_h/2-90 [out]" -vcodec libx264 output3.mp4
当然也可以让界面上的视频动态变化,就是位置随意变动,借助一些执行函数实现变动。
代码语言:javascript复制ffmpeg -re -i jeffmony.mp4 -vf "movie=output.mp4, scale=320x180[test]; [in][test]overlay=x='if(gte(t,2), -w (t-2)*20, NAN)':y=0 [out]" -vcodec libx264 output4.mp4
这个实现了子视频从主视频的左侧开始渐入视频从左向右游动。
结束语
FFmpeg如同一个金库,音视频所有的知识基本上都能在这里面找到答案。学习好、利用好FFmpeg对提升音视频编程的整体水平有很大的帮助。愿和你一起努力。
关注JeffMony,随时带来音视频/算法/python知识分享,感谢与我一起成长,长按关注一下吧.