2011年1月31日月曜日

中間出力のマルチプロセス化

先日、JEEB氏がAviSynth2.6.0αの非公式ビルドを始めた。

(JEEB) そういや、うちのAvs2.6ビルドってChikuzen氏がテストした奴と性能がどんな感じ?
(Chikuzen) 若干スピードが上がってるみたいよ
(Chikuzen) ちょっとベンチマーク回してみるか
      official 2.6.0α2       JEEB's 2.6.0(20110125)
1st   00:01:11.003(14.08fps)   00:01:07.254(14.87fps)
2nd   00:01:11.010(14.08fps)   00:01:07.003(14.92fps)
3rd   00:01:10.753(14.13fps)   00:01:07.003(14.92fps) (スクリプト等は前回と同じ)
(JEEB) interesting

たしかに興味深い。
どうやら最適化はα2配布後にさらに進んでいたようである。

さて、前回、自分はマルチスレッドによる高速化ではなく、マルチプロセスによる高速化を選んだことを書いた。
ここで問題となるのが、同時にエンコードするための複数のソースがない場合である。
そもそも自分は放送ソースの保存用エンコなんてまったくしない。そんなことには興味がない。
いろいろなフィルタやエンコーダーを試して遊ぶのが好きなのである。
当然、エンコードするべきソースは大抵1本しかなく、3本同時にエンコードなんていままでに数えるほどしかやったことがない。

では、どうすればいいのか?
エンコード対象がひとつしかないのであれば、ひとつを複数に分割してやればいいだけだ。
3本同時にエンコードできる余裕があるなら、1本を3分割してそれぞれ中間出力し、ふたたびくっつけてやればよい。
それが簡単にできるのがAviSynthである。
中間出力用のファイルサイズを気にするような時代でもないしね。

てことで、自動で分割->中間出力->結合->エンコードを行うAvsPマクロを書いた。
#multiprocess intermediate file generator.py
#dividing source script into X -> generate intermediate files at same time -> final encode
#requiaments : VirtualDub.exe, vcf file, ffmpeg or x264 (optional)
import sys
import os.path
import subprocess

vdub = r'C:\VirtualDub-1.9.11\VirtualDub.exe'
vcf  = r'C:\VirtualDub-1.9.11\fastrecomp_uly0_noaudio.vcf'

entries = avsp.GetTextEntry(['Number of dividing', 'SetMemoryMax(?)'], ['3', '1024'])
dnum, mmax = [int(i) for i in entries]

imf_outdir = avsp.GetDirectory('Select a directory to output intermediate files.')

encbin  = avsp.GetFilename('Set Path to Encoder(ffmpeg or x264)')
#encbin = r'G:\Enctools\ffmpeg.exe'
#encbin = r'G:\Enctools\x264_x64.exe'
encout  = avsp.GetSaveFilename('Set final output file name')
encopts = avsp.GetTextEntry('Input commandline options').split(' ')

source = avsp.SaveScript()
frames = avsp.GetVideoFramecount()

if dnum > 1 and mmax > 0 and imf_outdir and frames and os.path.isfile(vdub) and os.path.isfile(vcf):
    start = 0
    vdubprocess = []
    catfilename = os.path.splitext(source)[0] + '_imf.avs'
    catfile = open(catfilename, 'w')

    for i in range(dnum):
        end = frames / dnum * (i + 1)
        if i < dnum - 1:
            trimline = 'trim(%i, %i)' % (start, end)
        else:
            trimline = 'trim(%i, 0)' % start
        lines = 'SetMemoryMax(%i)\nImport("%s")\n%s\n' % (mmax, source, trimline)
        start = end + 1

        imf_avsname = os.path.join(imf_outdir, os.path.basename(source)).rstrip('avs') + 'imf_%02i.avs' % i
        file = open(imf_avsname, 'w')
        file.write(lines)
        file.close()

        imf_aviname = os.path.splitext(imf_avsname)[0] + '.avi'
        vdubargs = [vdub, '/s', vcf, '/p', imf_avsname, imf_aviname, '/r', '/c', '/x']
        p = subprocess.Popen(vdubargs)
        vdubprocess.append(p)

        if i < dnum - 1:
            catfile.write('AVISource("%s") + \\\n' % imf_aviname)
        else:
            catfile.write('AVISource("%s")\n' % imf_aviname)

    catfile.close()

    if encbin and encout:
        for p in vdubprocess:
            p.wait()

        if os.path.basename(encbin).count('ffmpeg'):
            encargs = [encbin, '-i', catfilename] + encopts + ['-y', encout]
        elif os.path.basename(encbin).count('x264'):
            encargs = [encbin, catfilename, '-o', encout] + encopts
        else:
            sys.exit()

        subprocess.Popen(encargs)

2011年1月18日火曜日

AviSynth2.6.0α

さまざまな試行と思索(ってほどのものでもないが)の結果、現在の自分はAviSynth2.6.0α(32bit)を使っている。

Q.なぜ64bitAviSynthじゃないの?
A.いろいろ不具合が多い上に開発が止まっているから。
 開発が順調なら人柱覚悟で使ってバグレポートもするが、現状停止中でforkする人もいないでは使う甲斐がない。
 まあ、気長に待つしかないかなぁ。

Q.なんでMT使わないの?
A.SetMTMode()はとにかく不安定すぎる。
 しかも同じスクリプトでもクラッシュしたりしなかったりと運用で回避を図ろうにも傾向がつかめない。
 たとえ速くても、完走出来ないマラソンランナーは正選手にはなれないのである。
 MT()のほうはSetMTMode()ほどひどくはないが…。

Q.なんでstableな2.5.8ではなく、2.6.0αなの?
A.安定していて速いから。

そう、2.6.0αはまだアルファなのに安定している。
どれくらい安定しているかといえば、既知の問題がα2公開後1年以上経つのにこの程度しかないくらい堅い。
しかも2.5.8に存在するバグもいくつか取れている。(これではどちらがstableなのかと小一時間…)

そして速い。 2.5.8->2.6.0の最適化の進み具合はすさまじい。 例えば次のようなベンチマークをしてみるとよくわかる。
#benchmark.avs
ImageReader("test_1280x720.bmp", 0, 999)
BlackmanResize(720, 480)
Spline64Resize(1920, 1080)
GaussResize(512, 384)

$ for i in {1..3}; do avs2avi benchmark.avs -c null -o n; done
これを2.5.8と2.6.0αでやってみると結果は
              2.5.8                   2.6.0α
1st   00:02:26.256(6.84fps)    00:01:10.753(14.13fps)
2nd   00:02:26.256(6.84fps)    00:01:10.764(14.13fps)
3rd   00:02:26.257(6.84fps)    00:01:10.753(14.13fps)  (CPU使用率はすべて30%弱)
特に重めなresizerをRGB32でかけた結果がこれである。
たまに高速化のためと称してMT()使って縦横別々にリサイズとかしてる人を見かけるけど、そんなもの手間をかけて無駄にCPU使用率を上げているだけですな。

2.6.0αはMT対応はしていないが、それ自体は高速である。
問題はプラグインのほうで、これがボトルネックになるわけだが…でもその場合はCPU自体はそれほど使ってないよね?
CPUに余裕があるなら同時に複数本走らせればいいだけだ。
この場合、1本あたりのスピードは低下してしまうが、3本同時にやれば終了するまでにかかる時間は1本ずつ3本エンコするのと比べてだいたい半分程度にはなるだろう。
1本あたり最大2GBのメモリを使うことになるが(pipeを使えばもう少し増える)、自分の環境は64bitOSで8GBつんでいるので、まだ余裕はある。
・スピードが結果的に倍程度まであがり
・CPUを無駄なく使い
・安定した出力を得られ
・互換性も特に気にする必要がない

現状ではやはりこれが一番いいんじゃないかなぁ。

2011年1月11日火曜日

RawSource.dll

AviSynthのRawSourceがffmpegやy4m.auoで出力したY4Mファイルを読めないので何とかしたいというお話。

YUV4MPEG2(Y4M)は非圧縮なYUVデータに動画の解像度やフレームレートといったものをヘッダーとして追加したシンプルなファイル形式である。
もともとはMJPEGToolsなるもので使われていたものらしいが、なかなか取り扱いが便利なので、今日ではffmpeg,MEncoder,x264といろいろなエンコーダーがY4M入力に対応している。
たとえばx264はただのYUVもY4Mも入力に使用出来るが、ただのYUVだと
$ x264 input.yuv --input-csp i420 --input-res 720x480 --fps 24000/1001 --sar 40:33 \
[other options] -o output.h264
てな具合にオプションを指定しなければならない。
一方、Y4Mファイルであれば、解像度等はヘッダーに書かれているので
$ x264 input.y4m [other options] -o output.h264
と、手間が省ける。
うん、便利だね。

さて、RawSource.dllは非圧縮ファイルを扱うためのAviSynthプラグインである。
このRawSource、Y4Mも読めることになってはいるのだが、いかんせん書かれたのが2006年と古いせいか、想定されているヘッダーが旧式である。ffmpegやAviUtlのYUV4MPEG2出力プラグインで出力される新しい形式(Cタグで色空間指定付)のものだと"YUV4MPEG2 header error"となって手が出ない。
「だれか何とかしてくれないかなー」と最初にこのことに気づいてから、かれこれ一年半ほど待ってみたが、そのような奇特な人はついに現れなかった(まあ黙って待っててもそうそう都合のいいことが起こるわけもないわな)。
そこで一念発起して自分でやってみることにしたのである。

まずC言語入門講座とかその手のサイトを2つほど回って、Hello Worldとかfizzbuzzとかを書くこと5日間。
そろそろ出来るかなと、ためしにやってみたら本当に出来た。ちなみに一番難しかったのはVisualStudioの操作方法だった。なんでAviSynthプラグインはmingwでビルドできないんだよチクショウめ。

とりあえず出来上がったものはDoom9に投稿したので、あとはツッコミ待ちである。