A simple RESTful web service in nodejs (3)

wget.js完成后,只剩下最后一步了:视频文件的转码与合并。ffmpeg的安装就不赘述了,我是根据ffmpeg官网上的教程一步步编译安装的。用yum或者apt-get方式应该也行,就是版本可能没有那么新。

这个进程的主要功能是把flv等格式的视频,转换成标准mp4视频,并且合并成一个文件。一般来说,flv的视频编码和mp4同是h264,而音频编码往往不一致。所以flv转mp4大部分情况下可以直接copy视频流,只需要转换音频流。像B站那种,经常flv连音频流也是aac的,那就更简单了,只换个container就行。

可以借用下面两张图片直观地说明一下:
flv2mp4
flv2mp4_convert

一开始其实想用exec()直接通过linux命令行执行ffmpeg各种命令的,但是不好处理执行结果的返回以及和主进程的状态通信,所以还是用了一个叫node-fluent-ffmpeg的module,它提供了一层API方便调用(但是用下来感觉并不方便…),最重要的是可以从主进程fork()了。

注意fluent-ffmpeg有个问题,即当你没有使用ffmpeg默认的audio encoder,而是使用libfdk_aac这种的话,输出音频质量设定的参数名是不一样的!这样就造成了.audioQuality()方法不起作用,因为那里是写死了用”-aq”的。详情见一则Github的issue
解决方案有两个,一是把fluent-ffmpeg代码中lib/options/audio.js的下面这行

改成

或者不用.audioQuality(),直接用.outputOptions()来指定相应参数。我这里选择前者。

下面开始正题。首先还是从server.js取得参数,这次是已下载的视频文件路径信息(server.js向ffmpeg.js传递了俩参数,id和视频数量)。

接着是三板斧:探测编码、转码、合并。
首先定义一个函数,探测视频文件的编码。因为本程序用于同视频文件的不同分块之间的合并,默认所有分块都使用了同样的codecs,因此只检测第一个分块即可:

ffprobe是ffmpeg软件包中的一个探测视频编码的工具,用法和输出见参考资料。
这里因为不是很确定video部分是在steams[0]还是steams[1],所以直接把所有steams里的codec_name推到一个数组里,后面再处理。顺便也取得了container名称(flv, mp4等),以及视频整体码率。

接着是转码阶段。如果ffprobe检测出视频编码是h264,或者音频编码是aac,则直接传递copy参数给encoder,即直接拷贝视频/音频流,免去了重编码的步骤。一般来说视频网站针对PC客户端比较少直接提供mp4的,所以这里基本还是得把container从flv转成mp4。

这里偷了个小懒:当判断video codec不是h264时,设定的视频转码输出码率是原视频总体码率,而不是原视频的视频部分码率。话说转码总会有点画质损失不是?码率设高点可能效果更能接近原视频。

做完了必要的转码操作,终于到了最后一关:合并。
好事多磨,最后果然还是发现有个坑:用fluent-ffmpeg的mergeToFile()方法无法实现想要的效果,见我和fluent-ffmpeg作者在github上的讨论。简单说来就是mergeToFile()实现的转码方式不支持copy方法。
又仔细研究了下fluent-ffmpeg相关源代码,终于找到了解决方案:

这里使用了fs module的writeFileSync()方法,不能用async版本的,因为后面马上要用这个文件,必须确定已经正常创建。做完合并后,发message给主进程报告最终生成文件的路径。

到这里三板斧都已打造完成,开始舞动起来!然而立刻发现了一个坑爹的问题:怎样按次序执行这三个步骤?也就是说,怎样让这三个异步函数一个接一个执行,而不是将它们改造成阻塞式函数?
这里再次偷了个懒,继续祭出setInterval大杀器(orz)…

其实更好的做法也许是使用async module的series()方法,这个是用于顺序执行异步函数的。不过因为这三个函数本身其实是阻塞式的,它们内部调用的ffmpeg()才是非阻塞的,所以那三个函数内调用callback的时机应该是接收到ffmpeg()返回的’end’ event的时候,而不是写在函数的末尾。

本系列文章到此结束,有什么问题、建议,请移步github issues

References:
[1] https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
[2] https://github.com/fluent-ffmpeg/node-fluent-ffmpeg/issues/496
[3] https://github.com/fluent-ffmpeg/node-fluent-ffmpeg/issues/348
[4] https://github.com/caolan/async
[5] http://justinklemm.com/node-js-async-tutorial/
[6] http://stackoverflow.com/questions/6898779/how-to-write-asynchronous-functions-for-node-js
[7] https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options
[8] https://addpipe.com/blog/flv-to-mp4/
[9] https://trac.ffmpeg.org/wiki/Concatenate
[10] https://trac.ffmpeg.org/wiki/Encode/AAC
[11] https://trac.ffmpeg.org/wiki/Encode/H.264
[12] https://trac.ffmpeg.org/wiki/FFprobeTips
[13] https://trac.ffmpeg.org/wiki/CompilationGuide/Centos

本文是悠然居(wordpress.youran.me)原创文章,如转载必须保留此告示。

本文为悠然居(https://wordpress.youran.me/)的原创文章,转载请注明出处!

声明: 本文采用 BY-NC-SA 协议进行授权. 转载请注明转自: A simple RESTful web service in nodejs (3)
  1. No comments yet.