前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >音视频平凡之路之FFmpeg全面介绍

音视频平凡之路之FFmpeg全面介绍

作者头像
马上就说
发布2020-11-11 17:33:30
1.8K0
发布2020-11-11 17:33:30
举报
文章被收录于专栏:码上就说码上就说

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_%03d.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知识分享,感谢与我一起成长,长按关注一下吧.

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 音视频平凡之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
媒体处理
媒体处理(Media Processing Service,MPS)是一种云端音视频处理服务。基于腾讯多年音视频领域的深耕,为您提供极致的编码能力,大幅节约存储及带宽成本、实现全平台播放,同时提供视频截图、音视频增强、内容理解、内容审核等能力,满足您在各种场景下对视频的处理需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档