2011年6月23日木曜日

ARDeformation.dll その2

さて、この前公開してみたARDeformation.dllですが、よく考えてみると設計ミスをしていることに気づきました。
ARResizeの引数の順番が、どう考えてもおかしい。
modeを一番最初にもってきてしまっているため、使用時は"dar"であれ"sar"であれ、常にmodeを指定するか、引数を名前付きで指定しなければならなくなってしまっています。

例)
DAR16:9にリサイズしたい場合 … ARResize("dar", 16, 9) もしくは ARResize(ar_x=16, ar_y=9)
SAR40:33にリサイズしたい場合 … ARResize("sar", 40, 33)

これでは使いにくいので、引数の順番を変更しました。
とりあえずmodeは一番最後に変更したため、sar/parの場合は常にmode="sar"(or "par")とするようになりました。

例)
DAR16:9にリサイズしたい場合 … ARResize(16, 9)
SAR40:33にリサイズしたい場合 … ARResize(40, 33, mode="sar")

他の順番も少し変更したので、詳細はreadmeを読んでください。

ARDeformation_20110622.zip
https://github.com/chikuzen/ARDeformation

2011年6月17日金曜日

Fix wavi

昨日、ちょっとしたきっかけで、wavi.exeがクラッシュすることがあるのに気づいた。
なぜか音声ファイル(.wav)の書き出し終了時に落ちるのである。

(Chikuzen) wavi.exeが何故かクラッシュするのでコードを覗いてみたが
(Chikuzen) あまりにも汚いので10分の1で投げ出してしまった
(VFRmaniac) 笑
(Chikuzen)とりあえずCleanupAndExit()でクラッシュするみたいなので、そのあたりにfprintfを大量に仕込んでみると
(Chikuzen) LocalFree()で失敗することがわかった
(VFRmaniac) いきなり長文のstderrで吹いた
(VFRmaniac) 二重解放?
(Chikuzen) 多分そう >二重開放
(Chikuzen) そもそもどこでバッファ確保してるんだろ、これ
(VFRmaniac) ちょっとまて...
(VFRmaniac) LPBYTE buffer[2][BUFFER_SIZE];
(VFRmaniac) これ、動的に確保してなくね?
(VFRmaniac) LocalAlloc も LocalReAllocもみつかんねーぞ!
(VFRmaniac) http://msdn.microsoft.com/ja-jp/library/cc430156.aspx
(VFRmaniac) この行 不要じゃね?
(Chikuzen) そうよね、配列使って静的に確保してるよね

てなわけで直ったので、ついでにtebasuna51氏のパッチあててDoom9に置いてきました
http://forum.doom9.org/showthread.php?t=161639

2011年6月15日水曜日

RawSource.dll その5

AviSynth2.6用に大幅に書き直したものです。

RawSource_26_dll_20110614.zip
https://github.com/chikuzen/RawSource_2.6x

*I444,YV24の読み込みに対応。
*YUV411をYUY2ではなくYV411で読み込むように変更。
*I422,YV16はYV16で読み込むように変更。
*Y8はYV12ではなくY8で読み込むように変更(これにより奇数解像度に対応)。

必要なもの
AviSynth2.6.0alpha2以降
SSEマシン
msvcr100.dll

2011年6月14日火曜日

ARDeformation.dll

さて、前回のコードにさらに手を加えてみたのがこのプラグインです。

更新しました
https://github.com/chikuzen/ARDeformation

使い方

DARPadding(clip, float "dar_x", float "dar_y", int "align", int "color")

  指定したDARにあわせて、左右または上下にベタを付与します。
  dar_x: DARの分子(水平方向)の値 (デフォルト:clip.width)
  dar_y: DARの分母(垂直方向)の値 (デフォルト:clip.height)
  align:左(または上)にパディングされるベタの幅を、この値の倍数になるように調整する(デフォルト:16)
  color:ベタの色指定 (デフォルト:$00000000(黒))

なんらかの理由でレターボックス/ピラーボックスにしてエンコしないといけない場合に使うといいかも知れません(もちろん使わないにこしたことはありませんが)。
alignのデフォルトは16なので、そのまま使えば若干左寄り(若しくは上寄り)になります。それが嫌な場合はalignを1にすれば、(ほぼ)ど真ん中になります。

ARResize(clip, string "mode", float "ar_x", float "ar_y", bool "expand", float "src_left", float "src_top",
     float "src_right", float "src_bottom", string "resizer", float "ep0", float "ep1", int "dest_w", int "dest_h")

  指定したアスペクト比にあわせて、本体内蔵のリサイザを使ってリサイズします。
  mode: アスペクト比のタイプ("dar","sar","par")を指定します。(デフォルト:"dar")
  ar_x: アスペクト比の分子(水平方向)の値 。(デフォルト:clip.width(dar) or 1(sar/par))
  ar_y: アスペクト比の分母(垂直方向)の値 。(デフォルト:clip.height(dar) or 1(sar/par))
  expand: 拡大するか縮小するかを選びます 。(デフォルト:true(拡大))
  src_*:avisynthの各リサイザの引数と同じです。 (デフォルト:0)
  resizer: 呼び出すリサイザを指定します 。(デフォルト:"Bicubic")
  ep0: 各リサイザ固有の引数その1。lanczosの"taps"やBicubicの"b"を指定します。 (デフォルト:各リサイザの規定値)
  ep1: 各リサイザ固有の引数その2。Bicubicの"c"がこれにあたります。(デフォルト:1.0/3)
  dest_w: 出力のwidthをこの値に矯正します。(デフォルト:0(無効))
  dest_h: 出力のheightをこの値に矯正します。dest_wが0以外の場合は無視されます。(デフォルト:0(無効))
  なおdest_w,dest_hを設定した場合は、expandは無視されます。

例えばDVDソースにARResize("dar",16,9,true,8,0,-8,0,"lanczos4")とすれば、無効領域を削ってLanczos4Resizeで16:9に拡大(854x480、PALなら1024x576)します。

二つとも必要な計算を行って、avisynth.dll内の関数を呼び出しているだけなので、スクリプトでも可能でしょう。
しかし、ARResizeのほうは…これと同等のものをスクリプトで書きたいとは思いませんね。めんどすぎます。そもそもavsのfloatでの計算だと精度が落ちるような気もするし。

興味がある人は、自分でもなにかやってみてください。
結構面白いです。

スクリプトをプラグインにしてみる

前回は簡単な関数をプラグインにしたので、今回は簡単なユーザースクリプト関数をプラグインにしてみることにしました。

まずこんなスクリプトを用意してみます。
#DAR_Padding.avs
function DAR_Padding(clip c, float "dar_x", float "dar_y", int "color", int "align") {
    dar_x = default(float(dar_x), float(c.width))
    dar_y = default(float(dar_y), float(c.height))
    color = default(color, $000000)
    align = default(align, 16)

    Assert(dar_x > 0 && dar_y > 0, "\"dar_x\" and \"dar_y\" need to be positive values.")
    Assert(align >= 1, "align needs to be 1 or heigher integer.")

    flag = c.width * dar_y - c.height * dar_x
    subsample_h = c.IsYUY2 || c.IsYV16 || c.IsYV12 ? 2 : 1
    #IsYV411は現時点(avisynth2.6.0a3)ではまだ作られてないので、YV411非対応
    subsample_v = c.IsYV12 ? 2 : 1

    dest_width  = flag < 0 ? int(ceil(c.height * dar_x / dar_y)) : c.width
    dest_width  = dest_width + (dest_width % subsample_h)

    dest_height = flag > 0 ? int(ceil(c.width * dar_y / dar_x)) : c.height
    dest_height = dest_height + (dest_height % subsample_v)

    pad_left = (dest_width - c.width) / 2
    pad_left = pad_left - (pad_left % subsample_h)
    pad_left = adjust_align(pad_left, align, subsample_h)

    pad_right = dest_width - (c.width + pad_left)

    pad_top = (dest_height - c.height) / 2
    pad_top = pad_top - (pad_top % subsample_v)
    pad_top = adjust_align(pad_top, align, subsample_v)

    pad_bottom = dest_height - (c.height + pad_top)

    return c.AddBorders(pad_left, pad_top, pad_right, pad_bottom, color)
}

function adjust_align(int pad, int align, int subsample) {
    return pad % align == 0 ? pad : adjust_align(pad - subsample, align, subsample)
}
はやい話が指定したDARにあわせて左右(または上下)に自動的にAddBordersでパディングするという関数です。
さて、これをC++で書いてみるとこうなりました。
//DAR_Padding.dll
#include <math.h>
#include "windows.h"
#include "avisynth26.h"

#pragma  warning(disable:4996)

class DAR_Padding : public GenericVideoFilter {

    PClip padded;

public:
    DAR_Padding(PClip _child, const float _dar_x, const float _dar_y,
                const int align, const int color, IScriptEnvironment* env);
    ~DAR_Padding() { }
    PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
};

DAR_Padding::
DAR_Padding(PClip _child, const float _dar_x, const float _dar_y, const int color,
            const int align, IScriptEnvironment* env) : GenericVideoFilter(_child)
{
    double dar_x = !_dar_x ? (double)vi.width : (double)_dar_x;
    double dar_y = !_dar_y ? (double)vi.height : (double)_dar_y;

    double flag = vi.width * dar_y - vi.height * dar_x;
    int dest_width = flag < 0 ? (int)ceil(vi.height * dar_x / dar_y) : vi.width;
    int dest_height = flag > 0 ? (int)ceil(dest_width * dar_y / dar_x) : vi.height;

    int subsample_h = vi.SubsampleH();
    int subsample_v = vi.SubsampleV();
    //SubsampleH(),SubsampleV()は、自分で作ってavisynth.hに追加した関数(YV411対応)。
    dest_width += dest_width % subsample_h;
    dest_height += dest_height % subsample_v;

    int pad_left = (dest_width - vi.width) >> 1;
    pad_left -= (pad_left % subsample_h);
    while(pad_left % align)
        pad_left -= subsample_h;

    int pad_right = dest_width - (vi.width + pad_left);

    int pad_top = (dest_height - vi.height) >> 1;
    pad_top -= (pad_top % subsample_v);
    while(pad_top % align)
        pad_left -= subsample_h;

    int pad_bottom = dest_height - (vi.height + pad_top);

    vi.width = dest_width;
    vi.height = dest_height;

    try {
        AVSValue padargs[6] = {child, pad_left, pad_top, pad_right, pad_bottom, color};
        padded = env->Invoke("AddBorders", AVSValue(padargs, 6)).AsClip();
    } catch (IScriptEnvironment::NotFound) {
        env->ThrowError("DAR_Padding: Couldn't Invoke AddBorders.");
    }
}

PVideoFrame __stdcall DAR_Padding::
GetFrame(int n, IScriptEnvironment* env)
{
    return padded->GetFrame(n, env);
}

AVSValue __cdecl Create_DAR_Padding(AVSValue args, void* user_data, IScriptEnvironment* env)
{
    const float dx = args[1].AsFloat(0.0);
    const float dy = args[2].AsFloat(0.0);
    const int cl = args[3].AsInt(0);
    const int al = args[4].AsInt(16);

    if (dx < 0 || dy < 0)
        env->ThrowError("DAR_Padding: \"dar_x\" and \"dar_y\" need to be positive values.");
    if (align < 1)
        env->ThrowError("DAR_Padding: \"align\" needs to be 1 or higher integer.");

    return new DAR_Padding(args[0].AsClip(), dx, dy, cl, al, env);
}

extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env)
{
    env->AddFunction("DAR_Padding", "c[dar_x]f[dar_y]f[color]i[align]i", Create_DAR_Padding, 0);
    return 0;
}
わかったこと
たしかにC/C++で書くための煩雑な手続き(クラスの宣言とか)は必要になりますが、if/for/whileといったものが使える分、プラグインのほうがむしろ書きやすいかもしれません。
それに、AviSynthの内部情報に直接アクセスできる点も有利です。
三項演算子や再帰を使ってあれこれ考えるのに飽きた人は挑戦してみるといいかも。

2011年6月5日日曜日

FCollections.dll

RawSource.dllの改造も一段落ついたような気がしたので、なにか新しいものを書いてみたくなりました。

あまり難しいものは書けそうにないし、そもそもネタもない。
なにか簡単そうなやつがいいということで、あれこれ考えているうちに目をつけたのがfilter script用関数。
つまりint()とかstring()とかIsClip()とかの類のことですね。
すでに役に立ちそうな関数はあらかた本体の方に揃っているので、それほど役に立つものは書けないだろうけど、初心者の学習用には調度良いような気がしたのでやってみました。

FCollections-20110604.zip
https://github.com/chikuzen/FCollections

以下、説明。

必要なもの:
AviSynth2.5x or later
SSEが使えるマシン(拡張命令セットでSSE有効にしています)
msvcr100.dll

TimeStringToMilliSecond(string "time string"):
時間をあらわす文字列"xx:xx:xx.xxx"をミリ秒単位の整数に変換します。
関数名がやたら長いのでTS2MS()でも使えるようにしてあります。
最初は秒単位の浮動小数点に変換するつもりだったのですが、AviSynthスクリプトはdouble(倍精度浮動小数点)をfloat(単精度浮動小数点)にキャストしてしまうようで、仕方なく整数で返すようにしました(floatの精度では酷い誤差が出てしまう…orz)
SSE搭載マシン(PentiumIII)の発売が1999年、AviSynth2.5の開発はavisynth.hによれば2002年ですから、倍精度をサポートしてないのは首をひねってしまうところですが…。

GCD(int val1, int val2, ...):
最大公約数を返します。

LCM(int val1, int val2, ...):
最小公倍数を返します。

Fibo(int n):
フィボナッチ数を返します。
AviSynthスクリプトの扱える整数は(今のところ)int32_t(-2147483648から2147483647)なので、フィボナッチ数は46(1836311903)までしか扱えません(47は2971215073でout of range)。
0を含めて全部で47しかないなら、わざわざその場で計算させることもなかろうということで、テーブル化してしまいました。
はたしてこれに使い途があるのかどうかはさっぱりわかりません。

IsEven(int n):
nが偶数ならtrue、奇数ならfalseを返します。
(n % 2 == 0)と同じですね。

Clamp(val, low, high):
StickBoy氏のMinMax.dllのやつと全く同じです。
valがlowより小さければlowを、highより大きければhighを、lowとhighの間の値だったらvalをそのまま返します。
FCollectionsを書くに当たって参考にしたのがまさにMinMax.dllのコードだったわけですが、Min()とMax()はAviSynth2.5.8で本体にも入ったのにClamp()は入ってないのでついでに入れてしまいました。

なにか追加してみたい関数が見つかったら、更新するつもりです。