由于工作需要,临时被老大吩咐去研究一个FFmpeg工具,通过linux命令行去将一个视频中的音频提取出来并合成到另一个视频中,最终的效果是要保证2个视频中的音频都在一个视频中播放。

  但是本人对FFmpeg一无所知,故先去了解一下FFmpeg是什么,没办法,生命在于折腾!

FFmpeg介绍

  以下内容摘自维基百科:https://zh.wikipedia.org/wiki/FFmpeg

  FFmpeg 是一个自由软件,可以运行音频和视频多种格式的录影、转换、流功能[1],包含了libavcodec——这是一个用于多个项目中音频和视频的解码器库,以及libavformat——一个音频与视频格式转换库。

  “FFmpeg”这个单词中的“FF”指的是“Fast Forward(快速前进)”[2]。有些新手写信给“FFmpeg”的项目负责人,询问FF是不是代表“Fast Free”或者“Fast Fourier”等意思,“FFmpeg”的项目负责人回信说:“Just for the record, the original meaning of "FF" in FFmpeg is "Fast Forward"...”

  这个项目最初是由Fabrice Bellard发起的,而现在是由Michael Niedermayer在进行维护。许多FFmpeg的开发者同时也是MPlayer项目的成员,FFmpeg在MPlayer项目中是被设计为服务器版本进行开发。 

  FFmpeg可使用众多参数,参数内容会根据ffmpeg版本而有差异,使用前建议先参考参数及编解码器的叙述。此外,参数明细可用ffmpeg -h显示;编解码器名称等明细可用ffmpeg -formats显示。

下列为较常使用的参数:

主要参数:

  • -i——设置输入文件名。
  • -f——设置输出格式。
  • -y——若输出文件已存在时则覆盖文件。
  • -fs——超过指定的文件大小时则结束转换。
  • -t——指定输出文件的持续时间,以秒为单位。
  • -ss——从指定时间开始转换,以秒为单位。
  • -t-ss时间开始转换(如-ss 00:00:01.00 -t 00:00:10.00即从00:00:01.00开始到00:00:11.00)。
  • -title——设置标题。
  • -timestamp——设置时间戳。
  • -vsync——增减Frame使影音同步。
  • -c——指定输出文件的编码。
  • -metadata——更改输出文件的元数据
  • -help——查看帮助信息

影像参数:

  • -b:v——设置影像流量,默认为200Kbit/秒。(单位请引用下方注意事项
  • -r——设置帧率值,默认为25。
  • -s——设置画面的宽与高。
  • -aspect——设置画面的比例。
  • -vn——不处理影像,于仅针对声音做处理时使用。
  • -vcodec( -c:v )——设置影像影像编解码器,未设置时则使用与输入文件相同之编解码器。

声音参数:

  • -b:a——设置每Channel(最近的SVN版为所有Channel的总合)的流量。(单位请引用下方注意事项
  • -ar——设置采样率。
  • -ac——设置声音的Channel数。
  • -acodec ( -c:a ) ——设置声音编解码器,未设置时与影像相同,使用与输入文件相同之编解码器。
  • -an——不处理声音,于仅针对影像做处理时使用。
  • -vol——设置音量大小,256为标准音量。(要设置成两倍音量时则输入512,依此类推。)

FFmpeg实践

  由上面的介绍可以看出,FFmpeg其实就是linux工具,可以通过linux命令行对视频音频进行处理。由于公司的linux系统已经安装过了,这里便不对FFmpeg的安装进行说明了。

  可是怎么使用呢?首先我们要知道FFmpeg是怎么对视频音频进行管理的,-i 命令可以指定输入文件,如果不指定输出文件可以用来查看文件的存储结构,如输入以下命令:

  ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv

    

java FFmpeg 合并视频 ffmpeg合并视频和音频_java FFmpeg 合并视频

  我们指定了2个输入文件,Input #0代表是第一个输入文件,flv是文件的格式, from后面是文件名,duration为文件的时长,start是开始时间,bitrate是码率,Stream #0:0 是指第一个输入文件的第一个流文件,Video(视频流)/Audio(音频流),h264(视频编码)/acc(音频编码),以及其他音视频相关的参数。

  而从图中可以看出,我们指定的2个输入文件都拥有1个视频流和1个音频流,并且它们的编码类型是一致的。那么目的就比较明确的了,我需要把第二个视频中的音频提取出来放到第一个视频中,并且保留第一个视频的音视频,但是第二个视频时长稍微长了一点,需要截取到和第一个视频时长一致。

  可是如何提取出音频文件呢?通过Google了解到可以通过【-map】 参数去指定文件中的流文件进行输出,这个维基百科上没有提到啊,看来还有很多命令需要去慢慢挖掘。于是我通过下面命令试了一下,成功的将音频提取出来了。-map 0:1 将第一个输入文件的第二个流文件输出到out.wav中,因为上面已经看到了音频流都是在第二个位置。

     ffmpeg -i L002BMG19041110.patient.flv -map 0:1 out.wav 

java FFmpeg 合并视频 ffmpeg合并视频和音频_c#_02

  真不错,感觉挺方便的,看来已经成功一半了,暗自窃喜中。已经将第二个视频的音频提取出来了,接下来将提取出来的音频添加到第一个视频就完事了。这个可以通过 copy 命令来实现,因为两个文件音频编码都是一致的,无需重新编码,故使用 -c 不重新编码,没有质量损失,-shortest选项将导致输出持续时间与最短输入流的持续时间相匹配。

  ffmpeg -i L002BMG19041110.flv -i out.wav -c copy -shortest output.flv

  

java FFmpeg 合并视频 ffmpeg合并视频和音频_java FFmpeg 合并视频_03

  看似好像要成功了,好像太顺利了一点,不太对劲!于是通过flv.js的demo页面进行了测试(地址:http://bilibili.github.io/flv.js/demo/),果然没有成功,没有out.wav中的声音。于是我看了一下output.flv的内容

 

java FFmpeg 合并视频 ffmpeg合并视频和音频_java FFmpeg 合并视频_04

  里面还是有2个流,一个视频流一个音频流,看来是没有将out.wav的音频拷进去。这可怎么办好呢?梦里寻他千百度,蓦然回首...咦,发现有一条命令就可以搞定的方法,赶紧试试。

  ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv -c copy -map 0 -map 1:1 -y -shortest output.flv

  按照老外的说法 -map 0 可以取一个视频中的所有流,然后 -map 1:1 取第二个视频中音频流,因为outout.flv文件已经存在了,加个 -y 把旧文件替换掉。

java FFmpeg 合并视频 ffmpeg合并视频和音频_c#_05

  不对啊,这个提示好像是 -map 0 的问题。于是我换成了 -map 0:0 试了一下

  ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv -c copy -map 0:0 -map 1:1 -y -shortest out.flv

java FFmpeg 合并视频 ffmpeg合并视频和音频_ffmpeg_06

  没有出错,但是测试发现只是把第一个视频的Video和第二个视频的Audio合成到一起了,那么能不能再加个 -map 0:1 把第一个视频中的Audio也加进来呢?

java FFmpeg 合并视频 ffmpeg合并视频和音频_ide_07

  结果提示flv最多只能有一个Audio,那我换成MP4格式的呢

java FFmpeg 合并视频 ffmpeg合并视频和音频_java FFmpeg 合并视频_08

  好像可以,于是我把out.mp4下载下来播放后发现还是只有第一个视频的Video和第二个视频的Audio,然后我又看了下这个out.mp4文件的内容:

java FFmpeg 合并视频 ffmpeg合并视频和音频_php_09

  MP4文件的内容和flv的不太一样,flv内容主要是流文件,而MP4还多了媒体数据。而且我们发现这个mp4文件里其实已经有1个Video和2个Audio了,但是 Stream #0:1 Audio 后面有个 (default),应该是默认播放这个音频,好像MP4一次只能播放一个音频,不能同时播放2个,看来我们目标还不是添加一个音频,而是把2个音频合成为一个。应该是这样没错了,哈哈,我真棒!

  通过一段摸索,我又发现一个选项好像符合我的要求,那就是 -filter_complex ,经过多番搜索和多次尝试后,了解了下 -filter_complex 的用法,然后开始正式动手了,就是以下命令。将a代表音频,v代表视频,下面的意思是将第一个视频的Audio和第二个视频的Audio这两个输入合并为一个,然后通过 -map 0:v 取第一个视频的Video, -map "[a]" 取合成后的Audio,copy后输出到output.flv,-shortest取最短的时长进行合并。

  ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv -filter_complex "[0:a][1:a]amerge=inputs=2[a]" -map 0:v -map "[a]" -c:v copy -shortest output.flv

java FFmpeg 合并视频 ffmpeg合并视频和音频_c#_10

  由于这里吗,没有加 -y ,故中间提示我output.flv已存在,是否替换掉,输入y按回车就好了,输入n则会停止操作。感觉已经成功了啊,测试了后发现两个视频的声音都有了,因为flv最多支持1个音频,应该是两个音频合成了一个音频没错了。由于我是戴耳机听的,听着声音还是有点不太对,原来虽然是两个声音都播放,但是一个耳机播放一个,真让人头大。

  又经过不断的寻找,终于在FFmpeg的官网找到一种说法

  

java FFmpeg 合并视频 ffmpeg合并视频和音频_php_11

  大概意思是可以通过 -ac 1 选项来将不同的声道合成为立体声。于是在上面命令的基础上加了 -ac 1

  ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv -filter_complex "[0:a][1:a]amerge=inputs=2[a]" -map 0:v -map "[a]" -ac 1 -c:v copy -shortest output.flv

java FFmpeg 合并视频 ffmpeg合并视频和音频_c#_12

  经过测试后,完美解决。最终效果为,将两个视频中的2个Video和2Audio通过 ffmpeg -i L002BMG19041110.flv -i L002BMG19041110.patient.flv -filter_complex "[0:a][1:a]amerge=inputs=2[a]" -map 0:v -map "[a]" -ac 1 -c:v copy -shortest output.flv 命令合成为一个视频,该视频中有1个Video和1个Audio,其中Video来自第一个视频,Audio来自两个视频的混音。

  真是不折腾,不成长啊!