本文是使用SAPikachu开发的MP_Pipeline工具过程中的一些笔记和心得。欢迎回帖讨论、纠正补充。
阅读本文不需要有大量视频知识,但需要有一定AviSynth基础。因此建议中级以上用户阅读本文。完全不了解AviSynth的初学者请先学习AviSynth入门。
MP_Pipeline的发布贴 http://www.nmm-hd.org/newbbs/viewtopic.php?f=7&t=498
里面有下载链接、更新日志和一些原作者的说明。
一、MP_Pipeline基础知识
- MP_Pipeline是什么
MP_Pipeline是由SAPikachu开发的多线程运行AviSynth(以下略作AVS)脚本的辅助工具。
通常,在下面这种经典场景中,AVS占用的系统资源相对有限。但当脚本中使用LSF、MCTD、TGMC等很复杂的脚本滤镜的时候,就会面临AVS消耗大量内存导致脚本无法启动或在压制过程中崩溃的风险。代码: 全选
mpeg2source("01.d2v") tfm(pp=3,mode=5,chroma=true,slow=2) tdecimate() fft3dfilter(sigma=1) crop(8,0,-8,0) lanczosresize(640,480)
为了解决此类问题,SAPikachu开发了MP_Pipeline这个工具,通过将avs脚本分成多个部分、并将每个部分分别放在子进程中运行,来避免一个avs进程占用过多内存最终崩溃的问题。
理论上,由于进程间的数据传递和同步,会造成轻微的性能下降。但因为SAPikachu对性能进行了大量优化,将性能损失控制在“理论”范围内,所以实际应用过程中几乎不用考虑性能因素。
但是,MP_Pipeline的使用是以大量消耗内存为代价的。请参照下图: 请准备至少8G内存。
能实现类似功能的还有leiming开发的sora系列工具(sorasmsource、sorasmserver、sorathread等)。 - MP_Pipeline的版本
本文以编写时的最新版本MP_Pipeline 0.14为准。MP_Pipeline的版本号稍显个性,区别版本高低不是看小数本身大小,而是看小数点后面的数字大小。比如0.14版就比0.3和0.2都新(14 > 3 > 2)。刚刚接触的人请注意不要搞混。
使用MP_Pipeline首先需要根据正在使用的avs的版本选择正确的MP_Pipeline版本。使用x86版avs,需要使用对应的x86版MP_Pipeline。x64版亦然。本文将使用x86版作例子。
确定版本之后,首先要将对应版本目录中的全部文件解压并放在一起。比如将x86版的文件全部解压到c:\AviSynthPlugins:
代码: 全选
c:\AviSynthPlugins\MP_Pipeline_readme.avs # 说明文件,不需要import。
c:\AviSynthPlugins\MP_Pipeline.dll.win64 # x64版插件,需要时自动调用无需loadplugin。
c:\AviSynthPlugins\MP_Pipeline.dll.slave.exe # x86子进程容器,需要时后台自动运行。
c:\AviSynthPlugins\MP_Pipeline.dll.win64.slave.exe # x64子进程容器,需要时后台自动运行。
c:\AviSynthPlugins\MP_Pipeline.dll # 32bit插件,使用MP_Pipeline唯一需要loadplugin的文件。
代码: 全选
loadplugin("c:\AviSynthPlugins\MP_Pipeline.dll")
MP_Pipeline("""
#段落1
avisource("01.avi")
### ###
#段落2
QTGMC()
### ###
#段落3
MCTD(settings="high")
""")
在这里希望读者记住一个概念:在本文中,我们将### ###分出的每个部分称作一个“段落”,每个“段落”一个进程。
三、MP_Pipeline的进阶应用
除了将avs脚本分进程消化以外,MP_Pipeline还提供了诸多附加命令,来实现众多高级功能。需要注意的是,每个命令都只对加入了该命令的段落有效,对以外的段落无效。
- 使用外置滤镜
- 载入外置滤镜需要注意
若使用外置滤镜,需要在使用该滤镜的段落中载入。就算在不同段落中使用同一个滤镜,也要在每个使用该滤镜的段落中都载入。
错误的例子:这样写会报告找不到滤镜。代码: 全选
loadplugin("c:\AviSynthPlugins\MP_Pipeline.dll") loadplugin("c:\AviSynthPlugins\DGDecode.dll") loadplugin("c:\AviSynthPlugins\TIVTC.dll") import("c:\AviSynthPlugins\SomeScript.dll") MP_Pipeline(""" mpeg2source("01.d2v") SomeScript() ### ### tfm(pp=3,mode=5,chroma=true,slow=2) tdecimate() SomeScript() """)
正确例子:代码: 全选
loadplugin("c:\AviSynthPlugins\MP_Pipeline.dll") MP_Pipeline(""" loadplugin("c:\AviSynthPlugins\DGDecode.dll") import("c:\AviSynthPlugins\SomeScript.avsi") mpeg2source("01.d2v") SomeScript() ### ### loadplugin("c:\AviSynthPlugins\TIVTC.dll") import("c:\AviSynthPlugins\SomeScript.avsi") tfm(pp=3,mode=5,chroma=true,slow=2) tdecimate() SomeScript() """)
- 通过全局代码功能载入外置滤镜(inherit命令)
少量滤镜分别写还好,但如果需要用到LSF、MCTD、TGMC这样需要载入大量依存滤镜的脚本的话,分开写会让代码变得混乱且难以维护。
MP_Pipeline提供了全局代码段功能来解决这个问题。全局代码段通过inherit命令:来指定,包含在这个区间的代码会自动在主段落、当前段落、和当前段落以下的全部段落开始处执行。代码: 全选
### inherit start ### SomeCode ### inherit end ###
上面的例子使用全局代码功能重写的话:这里需要注意的是,全局部分只对主进程段落、当前段落、当前段落以下的段落有效。比如在段落2中插入了全局代码,只会在主进程段落、段落2、段落3、段落4...有效,段落1是无效的。代码: 全选
MP_Pipeline(""" ### inherit start ### loadplugin("c:\AviSynthPlugins\MP_Pipeline.dll") loadplugin("c:\AviSynthPlugins\DGDecode.dll") loadplugin("c:\AviSynthPlugins\TIVTC.dll") import("c:\AviSynthPlugins\SomeScript.dll") ### inherit end ### mpeg2source("01.d2v") SomeScript() ### ### tfm(pp=3,mode=5,chroma=true,slow=2) tdecimate() SomeScript() """)
- 载入外置滤镜需要注意
- 分支(branch命令)
不少情况下,一个很慢的滤镜无法将CPU跑满。那么就可以将影片分成多个部分,同时运行来最大限度地提高系统资源的利用效率。
MP_Pipeline提供了branch命令来实现这个功能。branch命令的用法如下:branch命令有两个参数,第一个参数指定分割的进程数,是必选参数;第二个参数指定每段大小,是可选参数,默认为1。代码: 全选
### branch: <number of branch processes>[, <thunk size>]
比如### branch: 3,5代表该段滤镜分为3个进程同时执行,每5帧为一组进行分配。以此类推。原作者的例子:代码: 全选
进程 A 处理: 0, 1, 2, 3, 4, 15,16,17,18,19, ... 进程 B 处理: 5, 6, 7, 8, 9, 20,21,22,23,24, ... 进程 C 处理: 10,11,12,13,14, 25,26,27,28,29, ...
TNLMeans()将会被分成四个进程运行。代码: 全选
MP_Pipeline(""" loadplugin("c:\AviSynthPlugins\DGDecode.dll") mpeg2source("01.d2v") ### ### loadplugin("c:\AviSynthPlugins\TNLMeans.dll") TNLMeans() ### branch: 4 ### ### """)
需要注意的是,branch功能只适用于2D滤镜(空间滤镜),将branch用于3D滤镜(时间轴滤镜)将会极大地降低效率,并且会给滤镜效果带来不可知的影响。 - 跨x86/x64使用滤镜(platform命令)
在同时装有x86和x64 AviSynth的64bit系统中,该命令允许在64bit进程中使用x86滤镜,或在32bit进程中使用x64滤镜。该命令有一个参数,只能是win32或win64。用法:代码: 全选
### platform: <win32|win64>
就能在x86版x264压制时使用x64滤镜了,反之在x64版x264中使用x86滤镜也可以(其实这个功能的主要用途是后者)。代码: 全选
MP_Pipeline(""" loadplugin("c:\AviSynthPlugins\DGDecode.dll") mpeg2source("01.d2v") ### ### loadplugin("c:\AviSynthPlugins\fft3dfilter_x64.dll") fft3dfilter(sigma=1) ### platform: win64 ### ### """)
- 使用预读(prefetch命令)
MP_Pipeline提供了prefetch命令进行预读,提高滤镜执行效率。prefetch命令有两个参数,第一个是每个clip提前当前帧预读的帧数;第二个是当前帧后保留的帧数。代码: 全选
### prefetch: <max frames in cache per clip>, <frames to keep behind current frame>
需要注意的是,当使用会random access(随机访问)的滤镜的时候不要使用该功能。
例:在第一段落中预读16帧。代码: 全选
MP_Pipeline(""" aviource("source.avi") QTGMC() ### prefetch: 16, 0 ### ### """) MCTD()
- 跨段落传递clip
- 在MP_Pipeline中,默认状态下段落和段落之间能传递的clip只有last,但如果需要传递其他的clip的话,可以用传递clip的功能。MP_Pipeline提供了export clip和pass clip两种传递方式,他们使用场合各不相同。
需要注意的是,这两种方式都仅支持clip型变量,其他类型的变量无法跨段落传递。 - 使用export clip
作用是将一个段落中的某个clip型变量的最终值(=整个段落全部执行完毕之后的值)传递到下一个段落。无论export clip语句写在段落的哪个位置,传递的都将是该clip的最终值。比如:尽管export语句写在了段落中间,但在下一个段落中得到的a仍将是经过了Invert()的。代码: 全选
MP_Pipeline(""" ColorBars().ConvertToYV12 a = last b = last ### export clip: a, b a = a.Invert() ### ### return a """)
- 使用pass clip
pass clip命令的作用,是将上一个段落中export出来的clip原封不动地传递给下一个段落。比如:尽管在段落2中对a加了Greyscale(),但由于用的是pass clip,所以a将以段落1中export出来的状态,被原封不动地传递给段落3。段落3中得到的a不受段落2中的处理影响。代码: 全选
MP_Pipeline(""" ColorBars().ConvertToYV12 a = last b = last ### export clip: a, b a = a.Invert() ### ### a = a.Greyscale() ### pass clip: a ### ### return a """)
要注意的是,pass clip必须用在export clip的段落的下一个段落中。
- 在MP_Pipeline中,默认状态下段落和段落之间能传递的clip只有last,但如果需要传递其他的clip的话,可以用传递clip的功能。MP_Pipeline提供了export clip和pass clip两种传递方式,他们使用场合各不相同。
- 锁定线程到单核心
在多核心/超线程(Hyper-Threading)系统中,在段落中加入:可以将该段落的进程锁定到某个CPU核心/线程中。具体分配到哪个核心/线程由轮替(round-robin)决定。代码: 全选
### lock threads to cores
- MP_Pipeline的内置常量:MP_PIPELINE_BRANCH_ID
值为当前BRANCH的编号。可以这样使用:有助于调教调试脚本。代码: 全选
Subtitle(string(MP_PIPELINE_BRANCH_ID), y=30)
- Q:最后一个Block写在MP_Pipeline(""" """)里面和外面有什么区别?
A:貌似没有区别。经实测:与代码: 全选
MP_Pipeline(""" ColorBars().ConvertToYV12 ### ### Invert() """)
等效。代码: 全选
MP_Pipeline(""" ColorBars().ConvertToYV12 ### ### """) Invert()
- Q:如何锁定线程到指定核心?
A:不知道。大概不能指定核心。 - Q:是否有其他隐藏功能?
A:不知道,或许有。 - Q:是否有其他隐藏常量/变量?
A:不知道,或许有。 - Q:喂!这算哪门子Q&A啊!!
A:╮(¬_¬)╭萨……