2016年8月29日月曜日

MPEG2DecPlus

最近、MPEG2DecPlusというプロジェクトを始めました。

MPEG2DecPlus

事の起こりはavs2pipemodのgithubのissue trackerにmaki_rxrz氏からtypoの指摘が来ていたことです。
で、typo自体はすぐ直したのですが(バイナリはリリースしてないけど)、ついでにちょっと覗いてみたmaki氏のDGMPGDecレポジトリ(国内放送TS向け修正版)に興味が出たのですね。

「そういや64bit用のDGDecodeって、けっこう昔にJossyD氏がビルドしたやつしかないんだっけ」とか思いながら試しにビルドしてみたら、32bit用バイナリでも結構大変な目にあいました。
まずコードにいくつかNASM用のアセンブラが入っているため、VisualStudioだけではビルドできない。
さらにMASM用のアセンブラもVC6のころのMASM用に書かれているため、古いMASMを拾ってくる必要があるというものでした(数日後には最新のMASMに合わせたコードに書き換える修正が入りましたが)。

ものがMPEG2デコーダだけにソフトウェア特許絡みで面倒そうなためか、maki氏はバイナリは配布していません。
でも自ビルドするにはいささか敷居が高そうだし(もし5年前の自分だったら15分ほどでほっぽりだしてます)、こりゃ何とかしといたほうがよさそうだなぁとか思ったわけです。

で、やったことは
・アセンブラ(MASM、NASM、インラインASM)を削除(x87FPU用のASMなんて読みたくないし、MMX、MMX2、3DNow!とかもいい加減古すぎ)
・削除したASMの替わりにintrinsic(最低ラインはSSSE3以上を目安)で書き直し
・VFAPI用コードの削除(AviUtlやTMPGEnc2.5はこれなしでもavs読めるし、もういらんでしょ)
・YV16、YV24出力対応(YUY2やRGB24で出力するのはavs2.6以降ではデメリットしかない)
・MPEG2Source以外のフィルタの書き直し
といったところです。

さてここで一番問題になるのがMPEG2Sourceのポストプロセス処理(cpuとかcpu2とか)とBlindPPです。
なにが難しいのかといえば、これはTrbarry氏がメイン処理部分をすべてインラインアセンブラのみで書いていてCのコードを残していないため、なにをやっているのかさっぱりわからないからです。
DctFilterもそうでしたが、あの人のコードはいつも参考になりません。
とりあえず半日ほど格闘してみましたがどうにもCに書き直すだけでも大変そうで埒があかないので、諦めて全部捨てることにしました。

かくして64bitでビルドするための障害はすべて消え去ったので、ついに昨日、0.0.0としてファーストリリースについにこじつけることができました。

で、バイナリですが、JEEB氏が再配布してくれることになったため、右のほうのリンク先から入手できます。

現状、ソフトウェア特許を認めていないEU圏のフィンランド人がドイツだかフランスだかにあるサーバで配布してるので、特許料請求とかも関係なくって安心ですね。

まあ、その特許もあらかた切れてるらしいので日本やアメリカで配っても大丈夫なのかもしれないけど、本当に大丈夫なのか調べるのも面倒ですし…。

2016年8月18日木曜日

DCTFilter その4

DCTFilterを更新しました。ver.0.5.0です。

バイナリ

追加・変更等
・avisynth2.6用のコードのバグを修正。
・avisynth+の追加フォーマットに対応。

avs2.6のみで使われるコードにバグがあったのを修正するついでにavs+の10/12/14bitフォーマットにも対応させました。
もともと内部処理はこれを見越してのfloat演算なので、入力クリップのbit深度とRGBかYUVかを判定するコードを追加するだけでした。


さて、このフィルタはMT_NICE_FILTERとして使えるように書いたつもりです。
そしてユーザー側で特にMT modeを設定しない限りはMT_NICE_FILTERとして設定されるようにもしています。
ところがDeblock_QEDにこれを使ってマルチスレッドで実行すると映像が壊れることがあるという報告を一件受けました。
で、試しに手元のサンプル映像に掛けてみたのですが、特にそんな様子もありません。

そもそもMT_NICE_FILTERというのは、avisynth+がマルチスレッドで動かそうとする際に特に障害となるような要素を持っていないだけの、ただのシングルスレッドで動くフィルタのことです。
マルチスレッドで動かしているのはavisynth+のほうなんですから、バグるなら報告先が違うような気がするんですが…?

あと、不具合が出るソースのサンプルもスクリプトも実行環境の説明も何もなしにただバグると言われても正直困ります。
再現可能な情報の提供がなければ動きようがないのです。
逆に再現率が高く再現方法も手軽なものであれば、そのバグは大抵すぐに直ります。

というわけで、バグを見つけられた方はなるべく再現方法も教えてください。お願いします。

追記:報告者によると、これのほかにavs+やらいろいろ更新したら再現しなくなったそうです…まあ、そんなところだろうとは思ってたけどね。

2016年8月17日水曜日

最近のAvisynth+を使う場合の色々について

前回の記事についてたコメントあたりから察するに、どうもAvisynth+の基本的な使い方を書いておいた方がいいように感じたので書いておくことにします。
なお、この記事の内容はあくまでも現時点(r2161)でのものですから、今後どのように変わるか(あるいは変わらないか)はわかりません。

・SetLogParams
SetLogParamsはr2064で追加されたログ機能です。
avisynth+を実行中に発生した色々な情報を出力します。
SetLogParams(string file, int log_level)
fileはログの出力先の指定で、"stderr"だと標準エラー出力、"stdout"だと標準出力、 "D:\foo\bar\avslog.txt"のようにファイル名にすればファイルに出力されます。
log_levelは出力するログの範囲の指定で、LOG_DEBUGなら詳細なすべてのログ、LOG_INFOならば普通のログ、LOG_WARNINGならば警告及びエラーのみ、LOG_ERRORならばエラーのみを出力します。

この機能によりAvisynth+本体及びプラグインによって発生した疑わしい挙動や不具合が大変わかりやすくなり、バグレポートしやすくなりました。
今後コマンドラインのツールを使う場合はSetLogParams("stderr", LOG_INFO)、AviUtlやVirtualDubのようなGUIツールと併用する時はSetLogParams("出力ファイル名", LOG_INFO)をスクリプトの先頭になるべくつけるようにすることをお薦めします。

・SetMemoryMax
こちらはavisynth2.5の頃からのお馴染みですが、どれくらいに設定しているでしょうか?
SetMemoryMax(int mem)
avisynth+は規定値として32bitの場合(より正確には物理メモリより仮想メモリのほうが少ない場合、つまり64bitOS上で32bit版を使う場合)は128MB、64bitの場合(というか物理メモリより仮想メモリのほうが多い場合)は1GBをavisynth本体が使うメモリ量の上限として物理メモリを確保しようとします。
よってこれ以上を使うことが予想される場合、もしくはもっと少なくてもよい場合を除けば、これはスクリプトに書く必要はありません。

また重要なのは、このメモリ上限はあくまでもavisynth+本体が使用するメモリ量であり、プラグインフィルタが使うメモリ量ではないということです。
もしあるプラグインが中間バッファの確保にmallocやstd::vectorを使っても、その分はカウントされません。
Avisynth+には新たにバッファプール機能が追加されたため、この機能を使えばプラグインのメモリ使用量もavisynth+本体で管理することが可能になりましたが、実際に使っているプラグイン作者は現時点では私くらいしかいません(特に問題は見当たらないのですが、一応実験的な機能ってことになってますし)。
よって下手に大きな上限値を設定すると、プラグインのほうでメモリ不足を起こす可能性が出てきます。

で、あるスクリプトの実行時にどれくらいメモリを必要とするかは、使用するフィルタ及びソースの解像度等に依存するわけですが、もし設定された上限値では足りない、若しくはもう少しあったほうがより円滑に処理出来る場合は、先ほどのSetLogParamsによるログで教えてくれるようになりました。

というわけで、決め打ちに走るのもまあ悪くはないかもしれませんが、適切な量を調べてみるのもいいのではないかと思います。
とくにAviUtlと併用する場合はアプリケーション側も32bitなのにメモリ食いまくりますんで、絞れるところは絞るべきでしょう。

・SetFilterMTMode
Avisynth+の目玉機能の一つはフレームレベルでのマルチスレッドですが、皆さん使っていますでしょうか?
私は(ここ数年はエンコはせずにコード書くだけの人なので)あまり使ってません。

とりあえずこのフィルタはMT_NICE_FILTERだ、これはMT_MALTI_INSTANCEじゃないとダメだ、とあれこれやるわけですが、ここで重要なのは"DEFAULT_MT_MODE"をどれに設定するかです。

現時点ではDEFAULT_MT_MODEは3(MT_SERIALIZED)以外ありえません。

"DEFAULT_MT_MODE"は、MT modeが設定されていないすべてのフィルタに影響するため、下手に2(MT_MALTI_INSTANCE)などにしようものなら安全な実行はまったく保証されなくなります。

そもそもいままで書かれてきたavisynthプラグインのほとんどはシングルスレッドでの実行を前提としているものが多く、また処理内容的にもマルチスレッド化はスライスレベルでしか実現不可能なものもあります。
このページには"mode 3 is evil."なんて書かれていますが、時間軸IIRフィルタを邪悪呼ばわりするとか、正直頭がおかしいんじゃないかと思います。単にちょっと前までのavisynth+MTにとって都合が悪かったものをそうやってくさしてるだけです。

本当に邪悪なのはmode 2です。
mode 2で動かせるフィルタは、すべてmode 1で動かせるように改変可能なフィルタです。
でも実際にこれまで書かれてきたフィルタを全部改造していくのはちょっと無理なんで、フィルタクラスを複数インスタンス化しそれらを同時に動かすという力技でごまかしているのがmode 2の実態です。
ちなみにmode 2というのは、avisynthMTのSetMTMode(2)と基本的に同じものです。Avisynth+ではキャッシュ機能も改良されたのでメモリ使用量も減って、かなり安定度も上がりました(が、それでも安全とは言いきれない)。

前回記事のコメントで私がなかなかScriptClipの不具合に気づかなかったのは、SetFilterMTMode("DEFAULT_MT_MODE", MT_SERIALIZED)と書かれたavsをオートローディングフォルダに突っ込んでるのを忘れてたからなんですね。


と、だらだらと書いてみました。
もし参考になった方がいましたら幸いです。

2016年8月14日日曜日

avs2pipemod その22

更新しました。

ver.1.1.0になります。
追記:バグ1件見つけたので1.1.1を上げました。

バイナリ

変更/追加
- avisynth+の追加色空間のサポート
- y4mbitsオプションの削除
- filtersオプションの追加

昨晩、avisynth+にあらたに10/12/14bitおよびPlanarRGBが追加されました。
これを見てもらえば、だいたいどんな感じになったのかなんとなくわかってもらえるのではないかと思います。

いや、もう、なんというか、やりすぎじゃありませんかね?
14bitとかYUV420でアルファに対応とか、基地外沙汰というかFFmpeg脳というか、まじで勘弁してほしいんですが。

2016年8月10日水曜日

alloca vs vector

avisynthプラグインの書き方は人それぞれですが、一つの指標として次のようなものがあります。

AviSynth Plugin Writing Tips

これはAviSynth Wiki内に置かれたAvisynth+特設ページ内の記事ですが、特に書き慣れていない人、これからなにかしら書いてみたい人は一度は目を通しておくことをお勧めします。

軽く要約すると、
  • 例外は基本的に使わない。
  • 使う場合は発生時の処理もプラグインdll内で完結させる。上流(avisynth本体)に投げる場合はIScriptEnvironment::ThrowError()を必ず使うこと。

  • 自分でマルチスレッド処理を実装しないこと。
  • 特に最近のAvisynth+は単にGetFrame()を同時に複数実行するレベルではなく、全体的なバランスをとりつつキャッシュコントロールするレベルまで進歩しています(AvisynthMTとThreadRequestを両方実装したような感じかな)。 追加フィルタレベルでマルチスレッド化なんてやってもオーバーヘッドが増えるかデッドロック起こすだけで無駄に終わりますし、特にOpenMPの使用は(Avisynth+では)最悪手です。 シングルスレッドだとC++のみでは性能が出ない場合はSIMDを使うかGPUプログラミングに行くしかありません。

  • 使用するヘッダ(avisynth.h)はAvisynth+の最新のものを使うこと。
  • Avisynth+のヘッダはAvisynth本家の最新ヘッダと完全互換を謳っていますし、実際にそうなっています。特に64bit化を考えるならAvisynth+のものを使うしかありません。

で、それに続いてWriting better AviSynth pluginsという記事があります。


  • YUY2用のコードは書かない
  • メイン処理用関数はクラスメンバーにしない
  • memcpyの再実装はしない
  • Y,U,V各プレーンで同じ処理をする場合にコピペはしない

こちらも概ね正しいですが、一番最後のDon’t be afraid of allocaだけは参考にしない方がよいようです。

最近のコンパイラ(確認した限りではVS2013以降)であればstd::vectorを使うべきあり、もはやallocaを使う意義はまったくありません。

実際に私はallocaをstd::vectorに置き換えてみましたが、スピードはほぼ等速か、むしろstd::vectorのほうがほんの少し速かったです。

わざわざ初期化したり最後に個々の要素を開放したりは手間になるだけです。


ちなみにstd::vectorではなくnew/deleteで配列の確保/解放をしてみると、これも変わらないか若干vectorのほうが速い感じになりました。
どうやらC++においては動的配列の場合、_aligned_malloc/_mm_malloc以外はstd::vectorにしておくのが間違いがないようですね。



2016年8月4日木曜日

DCTFilter その3

また更新しました。
もうリンクはバイナリだけでいいかな。
右のほうにgithubへのリンク付けておいたので、ソースコードに興味のある物好きな方は適当に漁ってください。

バイナリ

0.4.0です。
4x4DCTも出来るようにしました。
あとplaneのWidth/Heightが8の倍数でない時でも、4の倍数であれば8x8DCTができるようにしました。
単に余った右端及び下端は4x4DCTで済ませてるだけです。

いや、1920x1080のYUV420でベンチマーク取ろうとしたところで出来ないことを思い出したので、つい勢いでやってしまいました。

これだと都合が悪い場合は、これまでのようにpadding/cropでもして下さい。

2016年8月2日火曜日

DCTFilter その2

DCTFilterを更新しました。
いろいろ細々と最適化とかやってたら0.3.0になりました。

DCTFilter

バイナリ

- 最適化を進めた結果、単精度浮動小数点処理でもSSE4.1(int32->uint16の高速変換に必要)が使えれば、オリジナルのDctFilter(MMX/ISSE/SSE2?で整数処理)とほぼ同等のスピードで動くようになりました。AVX2/FMA3が使えるCPUならこっちのほうが速いです。
- DCTFilterD()を追加しました。
- avs2.6もサポートするようになりました。

書いてみてから改めて調べてみると、このフィルタは結構面白い使い方が出来るようですね。
なんだかお気に入りになりそうです。