
| pixel_typeとThrowError()について 2004. 7.11 (01版) |
|---|
--------- avisynth.h : GenericVideoFilterの定義(抜粋) -----
// instantiable null filter
class GenericVideoFilter : public IClip {
protected:
PClip child;
VideoInfo vi;
public:
GenericVideoFilter(PClip _child) : child(_child) { vi = child->GetVideoInfo(); }
-------------------------------------------------------------
|
--------- avisynth.h : VideoInfoの定義(抜粋) --------------
// Colorspace properties.
enum {
CS_BGR = 1<<28,
CS_YUV = 1<<29,
CS_INTERLEAVED = 1<<30,
CS_PLANAR = 1<<31
};
// Specific colorformats
enum { CS_UNKNOWN = 0,
CS_BGR24 = 1<<0 | CS_BGR | CS_INTERLEAVED,
CS_BGR32 = 1<<1 | CS_BGR | CS_INTERLEAVED,
CS_YUY2 = 1<<2 | CS_YUV | CS_INTERLEAVED,
CS_YV12 = 1<<3 | CS_YUV | CS_PLANAR, // y-v-u, planar
CS_I420 = 1<<4 | CS_YUV | CS_PLANAR, // y-u-v, planar
CS_IYUV = 1<<4 | CS_YUV | CS_PLANAR // same as above
};
int pixel_type; // changed to int as of 2.5
// useful functions of the above
bool HasVideo() const { return (width!=0); }
bool HasAudio() const { return (audio_samples_per_second!=0); }
bool IsRGB() const { return !!(pixel_type&CS_BGR); }
bool IsRGB24() const { return (pixel_type&CS_BGR24)==CS_BGR24; } // Clear out additional properties
bool IsRGB32() const { return (pixel_type & CS_BGR32) == CS_BGR32 ; }
bool IsYUV() const { return !!(pixel_type&CS_YUV ); }
bool IsYUY2() const { return (pixel_type & CS_YUY2) == CS_YUY2; }
bool IsYV12() const { return ((pixel_type & CS_YV12) == CS_YV12)||((pixel_type & CS_I420) == CS_I420); }
bool IsColorSpace(int c_space) const { return ((pixel_type & c_space) == c_space); }
bool Is(int property) const { return ((pixel_type & property)==property ); }
bool IsPlanar() const { return !!(pixel_type & CS_PLANAR); }
-------------------------------------------------------------
|
次に、エラーを通知して処理を中止する方法ですが、
これは IScriptEnvironmentの中の
ThrowError()
関数を使用します。
--------- avisynth.h : IScriptEnvironmentの定義(抜粋) --------------
class IScriptEnvironment {
public:
__declspec(noreturn) virtual void __stdcall ThrowError(const char* fmt, ...) = 0;
-------------------------------------------------------------
|
Sample::Sample(PClip _child, IScriptEnvironment* env) : GenericVideoFilter(_child) {
if (!vi.IsYUY2())
env->ThrowError("Sample: requires YUY2 input.");
}
|
また、pixel_typeを変更する場合は、
vi.pixel_type = VideoInfo::CS_YUY2;
としますが、この説明は後日あらためて別の章で行います。
※pixel_typeの値は、avisynth 1.0x/2.0x系と2.5x系では異なります。
| データアクセスの基礎(1) [YUY2編] 2004. 7.14 (02版) |
|---|
| 2004. 7.11 (01版) |
YUY2のビデオデータを入力して、色を消してモノクロのビデオデータにしてみようと思います。
YUY2のColor Formatを思い出してください。YUY2は以下のようになっていましたね。



モノクロにするには、uとvの値を全て128にすれば良いのでした。
では、書いてみましょう。
【処理1】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
//*** in-place filtering ***
PVideoFrame src = child->GetFrame(n, env);
env->MakeWritable(&src);
const int pitch = src->GetPitch();
const int rowsize = src->GetRowSize();
const int height = src->GetHeight();
const int modulo = pitch - src->GetRowSize();
const BYTE *srcp = src->GetReadPtr();
BYTE *dstp = src->GetWritePtr();
for (int j=0; j<height; j++) {
for (int i=1; i<rowsize; i+=2) {
dstp[j*pitch+i] = 0x80; // u or v
}
}
return src;
}
|
const int pitch = src->GetPitch();
avisynthでは、YUY2の場合、データ境界は常にquadword単位(8の倍数、multiple of 8, mod 8)と
なっているため、pitchも8の倍数となります。
BYTE* dstp = src->GetWritePtr();
dstpはフレームデータの格納されている領域(1次元配列のデータテーブル)の先頭を指していますね。
データにはポインタでアクセスしても良いし、配列のデータとしてアクセスすることもできます。
for (int j=0; j<height; j++) {
for (int i=1; i<rowsize; i+=2) {
dstp[j*pitch+i] = 0x80; // u or v
}
}
2重のループで1画面の全データをなめています。
外側のループは、変数 j で高さ、つまりライン数分回します。
内側のループは、変数 i で1ライン分のデータを処理しています。
i+=2 これは i=i+2と記述するのと同義です。
(前者の記述はより最適化に向いてるわけですが、コンパイラの最適化により
生成するコードに差は出ないので好きな方で書けばよいと思います。)
iが1,3,5,7,...をとるようにしています。
(上図を見れば、これでuとvのデータに交互にアクセスできていることが理解できるでしょう。)
j*pitch+i pitchはライン間の距離でしたね。
2重のループとこの式で、1画面全てのuとvのデータの位置を指すことができます。
0x80 16進数表記です。10進数で記述すれば、128です。
|
【一口ポイント - MakeWritable -】
MakeWritableの前後では、ポインタの指すアドレスは変化しているかも知れない。
const BYTE* srcp0 = src->GetReadPtr();
BYTE* dstp0 = src->GetWritePtr();
env->MakeWritable(&src);
const BYTE *srcp = src->GetReadPtr();
BYTE *dstp = src->GetWritePtr();
{
char msg[256];
sprintf(msg,"\nsrcp=%08x - %08x\ndstp=%08x - %08x", srcp0, srcp, dstp0, dstp);
OutputDebugString(msg);
}
でdebugviewで結果を見ると、
[388] srcp=03d60020 - 04020020
[388] dstp=00000000 - 04020020
[388]
[388] srcp=03d60020 - 0c3d0020
[388] dstp=00000000 - 0c3d0020
となっています。
つまり、env->GetReadPtr() の方は、指しているアドレスは変化することもありますが、
env->MakeWritable(&src)の前でも後でも構いません。
でも env->GetWritePtr() の方は必ず後で実行する必要があるということです。
実際にMakeWritable()で何が行われているのか、もうちょっと見てみましょう。
(改行位置を多少変更しています)
-------- core\avisynth.cpp 807: -------------------------------
bool ScriptEnvironment::MakeWritable(PVideoFrame* pvf) {
const PVideoFrame& vf = *pvf;
// If the frame is already writable, do nothing.
if (vf->IsWritable()) {
return false;
}
// Otherwise, allocate a new frame (using NewVideoFrame) and
// copy the data into it. Then modify the passed PVideoFrame
// to point to the new buffer.
const int row_size = vf->GetRowSize();
const int height = vf->GetHeight();
PVideoFrame dst;
if (vf->GetPitch(PLANAR_U)) {
// we have no videoinfo, so we can only assume that it is Planar
// Always V first on internal images
dst = NewPlanarVideoFrame(row_size, height, FRAME_ALIGN,false);
} else {
dst = NewVideoFrame(row_size, height, FRAME_ALIGN);
}
BitBlt(dst->GetWritePtr(), dst->GetPitch(), vf->GetReadPtr(),
vf->GetPitch(), row_size, height);
// Blit More planes (pitch, rowsize and height should be 0, if none is present)
BitBlt(dst->GetWritePtr(PLANAR_V), dst->GetPitch(PLANAR_V), vf->GetReadPtr(PLANAR_V),
vf->GetPitch(PLANAR_V), vf->GetRowSize(PLANAR_V), vf->GetHeight(PLANAR_V));
BitBlt(dst->GetWritePtr(PLANAR_U), dst->GetPitch(PLANAR_U), vf->GetReadPtr(PLANAR_U),
vf->GetPitch(PLANAR_U), vf->GetRowSize(PLANAR_U), vf->GetHeight(PLANAR_U));
*pvf = dst;
return true;
}
-------------------------------------------------------------------
-------- avisynth.h 305: -----------------------------------------
class VideoFrame {
const BYTE* GetReadPtr() const { return vfb->GetReadPtr() + offset; }
BYTE* GetWritePtr() const {
if (vfb->GetRefcount()>1) {
_ASSERT(FALSE);
//throw AvisynthError("Internal Error - refcount was more than one!");
}
return IsWritable() ? (vfb->GetWritePtr() + offset) : 0;
}
}
-------------------------------------------------------------------
つまり、Writableなら何もしませんが、
Writableで無かったら、新たにVideoFrameを獲得してデータをコピーしています。
|
別の記述をしてみましょう。
【処理2】
for (int j=0; j<height; j++) {
for (int i=0; i<rowsize; i+=4) {
dstp[i+1] = 0x80; // u
dstp[i+3] = 0x80; // v
}
dstp += pitch;
}
|
これは、vとuを個別にアクセスしています。 内側の i ループは、4バイト単位のステップ幅です。 |
同じことをポインタでアクセスするように変更してみましょう。
【処理3】
for (int j=0; j<height; j++) {
for (int i=0; i<rowsize>>2; i++) {
dstp++; // y0 skip!!
*dstp++ = 0x80; // u
dstp++; // y1 skip!!
*dstp++ = 0x80; // v
}
dstp += modulo;
}
|
ここで注意することは、1ライン分のデータには余白がくっついているかも知れないので、 その分をスキップすることを忘れないようにすることです。 |
今度はDWORD単位でアクセスしてみましょう。
【処理4】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
//*** in-place filtering ***
PVideoFrame src = child->GetFrame(n, env);
env->MakeWritable(&src);
const int pitch = src->GetPitch();
const int rowsize = src->GetRowSize();
const int height = src->GetHeight();
const int modulo = pitch - src->GetRowSize();
const BYTE *srcp = src->GetReadPtr();
DWORD *dstp = (DWORD*)src->GetWritePtr();
for (int j=0; j<height; j++) {
for (int i=0; i<rowsize>>2; i++) {
*dstp++ = (*dstp & 0x00ff00ff) | 0x80008000; // vvyyuuyy
}
dstp = (DWORD*)((BYTE*)dstp + modulo);
}
return src;
}
|
DWORD* dstp = (DWORD*)src->GetWritePtr();
このcast変換はCスタイル(old style)です。
c++的には、reinterpret_cast<DWORD*>()となります。
0x00ff00ff, 0x80008000 // vvyyuuyy
ご存知だと思いますが、Intel x86 CPUはメモリアクセスはリトルエンディアンです。
(下位アドレスが下位バイトから入ります。
余談ですがモトローラCPUはビッグエンディアンです。)
メモリ上で、下位アドレスから順に 00 01 02 03 とあれば、
レジスタに読み込んだ場合には、03 02 01 00 となります。
なので、メモリ上 (y0,u,y1,v)という並びは、DWORD単位のレジスタアクセスでは
(v,y1,u,y0)となります。
dstp++;
dstp = (DWORD*)((BYTE*)dstp + modulo);
dstpは、(DWORD*)なので、++はDWORD単位にインクリメントされます。
moduloはBYTE単位での値なので、こういうcastが必要になってきます。
dstp += (modulo>>2);
でも構いません。
|
最後にQUADWORD単位にしてみましょう。
【処理5】
PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
//*** in-place filtering ***
PVideoFrame src = child->GetFrame(n, env);
env->MakeWritable(&src);
const int pitch = src->GetPitch();
const int rowsize = (src->GetRowSize() + 4) & -8;
const int height = src->GetHeight();
const int modulo = pitch - src->GetRowSize();
const __int64 *srcp = (__int64*)src->GetReadPtr();
__int64 *dstp = (__int64*)src->GetWritePtr();
for (int j=0; j<height; j++) {
for (int i=0; i<rowsize>>3; i++) {
*dstp++ = (*dstp & 0x00ff00ff00ff00ffi64) | 0x8000800080008000i64;
}
dstp += (modulo>>3);
}
return src;
}
|
const int rowsize = (src->GetRowSize() + 4) & -8;
rowsizeを8の倍数にround-upしています。
場合によっては、実際のrowsizeより大きくなりpadding dataにまで書き込むことに
なりますが、問題はありません。
またこの切り上げは、rowsize = (src->GetRowSize() + 7) & ~7;
と書いたほうが一般的かも知れません。
|
どうでしょうか。
スケルトンでは「お約束」ばかりでC++の文法を知っていても何が何だかわからなかったかも
知れませんが、実際の処理でやっと馴染みのある式が出てきたことだと思います。
これで大体どうやってデータを取り扱うのかイメージが掴めたでしょうから、
色々と応用してみてください。
| データアクセスの基礎(2) [YV12編] 2004. 7.14 (02版) |
|---|
| 2004. 7.13 (01版) |


PVideoFrame __stdcall Sample::GetFrame(int n, IScriptEnvironment *env) {
//*** in-place filtering ***
PVideoFrame src = child->GetFrame(n, env);
env->MakeWritable(&src);
const int pitchY = src->GetPitch(PLANAR_Y);
const int pitchUV = src->GetPitch(PLANAR_U);
const int rowsizeY = src->GetRowSize(PLANAR_Y);
const int rowsizeUV = src->GetRowSize(PLANAR_U);
//const int rowsizeY = src->GetRowSize(PLANAR_Y_ALIGNED); //Aligned rowsize (mod16)
//const int rowsizeUV = src->GetRowSize(PLANAR_U_ALIGNED); //Aligned rowsize (mod8)
const int heightY = src->GetHeight(PLANAR_Y);
const int heightUV = src->GetHeight(PLANAR_U);
const int moduloY = pitchY - rowsizeY;
const int moduloUV = pitchUV - rowsizeUV;
const BYTE *srcpY = src->GetReadPtr(PLANAR_Y);
const BYTE *srcpU = src->GetReadPtr(PLANAR_U);
const BYTE *srcpV = src->GetReadPtr(PLANAR_V);
BYTE *dstpY = src->GetWritePtr(PLANAR_Y);
BYTE *dstpU = src->GetWritePtr(PLANAR_U);
BYTE *dstpV = src->GetWritePtr(PLANAR_V);
int i,j;
//----- No touch data in Y plane. -----
// for (j=0; j<heightY; j++) {
// for (i=0; i<rowsizeY; i++) {
// dstpY[i] = dstpY[i];
// }
// dstpY += pitchY;
// }
for (j=0; j<heightUV; j++) {
for (i=0; i<rowsizeUV; i++) {
dstpU[i] = 0x80;
dstpV[i] = 0x80;
}
dstpU += pitchUV;
dstpV += pitchUV;
}
return src;
}
|
const int pitchUV = src->GetPitch(PLANAR_U);
const int rowsizeUV = src->GetRowSize(PLANAR_U);
const int heightUV = src->GetHeight(PLANAR_U);
const int moduloUV = pitchUV - rowsizeUV;
U,V平面では、これらは同じ値になるため、共通にしています。
//const int rowsizeY = src->GetRowSize(PLANAR_Y_ALIGNED); //Aligned rowsize (mod16)
//const int rowsizeUV = src->GetRowSize(PLANAR_U_ALIGNED); //Aligned rowsize (mod8)
PLANAR_Y_ALIGNED,PLANAR_U_ALIGNEDを使うと、mod16、mod8に切り上げた値が通知されます。
これらは、8バイト単位、16バイト単位(主にMMX/SSE,SSE2のため)
で処理する場合に便利です。
この例題のモノクロ化にするには、Yの値はいじらないので記述はありませんが、
もしYの値を操作したい場合は、コメントアウトしているループを参考にしてください。
この例では、ソースがプログレッシブでもインターレースでも区別は不要です。
これを区別しなければならない場合については、また別途解説します。
|
YUY2の場合と同様に、4バイト単位、8バイト単位等のアクセスも各々考えてみてください。
| 引数(arguments)いろいろ (1) --- AVSValue 2004. 7.27 (01版) |
|---|
| 分類 | 型 | length |
|---|---|---|
| クリップ(IClip*) | PClip | 4 bytes |
| 整数型(Interger) | int | 4 bytes |
| 実数型(Floating pointer) | float | 4 bytes |
| 論理型(Boolian) | bool | 1 byte |
| 文字列型(String) | const char * | 4 bytes |
【例】
AVSValue array_A[10];
AVSValue array_B[20];
AVSValue array_list[] = { AVSValue(array_A, sizeof(array_A)/sizeof(array_A[0])),
AVSValue(array_B, sizeof(array_B)/sizeof(array_B[0])),
};
AVSValue all = AVSValue(array_list, sizeof(array_list)/sizeof(array_list[0]));
AVSValue vals;
array_A[0] = AVSValue(10);
array_A[1] = AVSValue(true);
array_A[2] = AVSValue(11.1);
array_A[3] = AVSValue("huuuuum!!");
array_B[0] = array_A[0];
array_B[1] = array_A[1];
array_B[2] = array_A[2];
array_B[3] = array_A[3];
vals = all;
sprintf(msg, "%d %s %.1f %s",
vals[1][0].AsInt(),
vals[1][1].AsBool() ? "true" : "false",
vals[1][2].AsFloat(),
vals[1][3].AsString()
);
OutputDebugString(msg);
sprintf(msg, "array_A.size=%d array_B.size=%d",
vals[0].ArraySize(), vals[1].ArraySize());
OutputDebugString(msg);
【結果】
10 true 11.10 huuuuum!!
array_A.size=10 array_B.size=20
| 指定文字 | 意味 |
|---|---|
| c | クリップ型(clip) |
| i | 整数型(int) |
| f | 実数型(float) |
| b | 論理型(bool) |
| s | 文字列型(string) |
| . | 任意型 |
| * | 0個以上の配列型 |
| + | 1個以上の配列型 |
| [ ] | 引数の名前付け |
例えば、引数として、clip, 整数型、実数型、文字列型、論理型を各1個ずつとる場合、
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("Sample", "cifsb", Create_Sample, 0);
return "`Sample' Sample plugin";
}
|
LoadPlugin("f:\src\sample\release\sample.dll")
AVISource("foo.avi")
Sample(arg1=1, arg2=1.0, arg3="abc", arg4=true)
return last
|
env->AddFunction("Sample", "c[arg1]i[arg2]f[arg3]s[arg4]b", Create_Sample, 0);
|
この場合の引数の受け渡しは、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
return new Sample( args[0].AsClip()
, args[1].AsInt()
, args[2].AsFloat()
, args[3].AsString()
, args[4].AsBool()
, env);
}
class Sample : public GenericVideoFilter {
public:
Sample(PClip _child, int arg1, float arg2, const char *arg3, bool arg4, IScriptEnvironment* env);
|
また、ここで arg をまとめて、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
return new Sample(args, env);
}
class Sample : public GenericVideoFilter {
public:
Sample(AVSValue args, IScriptEnvironment* env) : GenericVideoFilter(args[0].AsClip()) {
// ----- initialization ------
}
}
|
初期値を与える場合は、
AVSValue __cdecl Create_Sample(AVSValue args, void* user_data, IScriptEnvironment* env) {
return new Sample( args[0].AsClip()
, args[1].AsInt(1)
, args[2].AsFloat(1.0)
, args[3].AsString("abc")
, args[4].AsBool(true)
, env);
}
|
ところで引数は果たして幾つまで取れるのでしょうか?
試したことはありませんが、60個までなら大丈夫だと思います。
最後に、AVSValueは実際には以下のように定義されています。
--------- avisynth.h : AVSValueの定義(抜粋) --------------
class AVSValue {
public:
AVSValue() { type = 'v'; }
AVSValue(IClip* c) { type = 'c'; clip = c; if (c) c->AddRef(); }
AVSValue(const PClip& c) { type = 'c'; clip = c.GetPointerWithAddRef(); }
AVSValue(bool b) { type = 'b'; boolean = b; }
AVSValue(int i) { type = 'i'; integer = i; }
// AVSValue(__int64 l) { type = 'l'; longlong = l; }
AVSValue(float f) { type = 'f'; floating_pt = f; }
AVSValue(double f) { type = 'f'; floating_pt = float(f); }
AVSValue(const char* s) { type = 's'; string = s; }
AVSValue(const AVSValue* a, int size) { type = 'a'; array = a; array_size = size; }
AVSValue(const AVSValue& v) { Assign(&v, true); }
~AVSValue() { if (IsClip() && clip) clip->Release(); }
AVSValue& operator=(const AVSValue& v) { Assign(&v, false); return *this; }
// Note that we transparently allow 'int' to be treated as 'float'.
// There are no int<->bool conversions, though.
bool Defined() const { return type != 'v'; }
bool IsClip() const { return type == 'c'; }
bool IsBool() const { return type == 'b'; }
bool IsInt() const { return type == 'i'; }
// bool IsLong() const { return (type == 'l'|| type == 'i'); }
bool IsFloat() const { return type == 'f' || type == 'i'; }
bool IsString() const { return type == 's'; }
bool IsArray() const { return type == 'a'; }
PClip AsClip() const { _ASSERTE(IsClip()); return IsClip()?clip:0; }
bool AsBool() const { _ASSERTE(IsBool()); return boolean; }
int AsInt() const { _ASSERTE(IsInt()); return integer; }
// int AsLong() const { _ASSERTE(IsLong()); return longlong; }
const char* AsString() const { _ASSERTE(IsString()); return IsString()?string:0; }
double AsFloat() const { _ASSERTE(IsFloat()); return IsInt()?integer:floating_pt; }
bool AsBool(bool def) const { _ASSERTE(IsBool()||!Defined()); return IsBool() ? boolean : def; }
int AsInt(int def) const { _ASSERTE(IsInt()||!Defined()); return IsInt() ? integer : def; }
double AsFloat(double def) const { _ASSERTE(IsFloat()||!Defined()); return IsInt() ? integer : type=='f' ? floating_pt : def; }
const char* AsString(const char* def) const { _ASSERTE(IsString()||!Defined()); return IsString() ? string : def; }
int ArraySize() const { _ASSERTE(IsArray()); return IsArray()?array_size:1; }
const AVSValue& operator[](int index) const {
_ASSERTE(IsArray() && index>=0 && index<array_size);
return (IsArray() && index>=0 && index<array_size) ? array[index] : *this;
}
private:
short type; // 'a'rray, 'c'lip, 'b'ool, 'i'nt, 'f'loat, 's'tring, 'v'oid, or 'l'ong
short array_size;
union {
IClip* clip;
bool boolean;
int integer;
float floating_pt;
const char* string;
const AVSValue* array;
// __int64 longlong;
};
void Assign(const AVSValue* src, bool init) {
if (src->IsClip() && src->clip)
src->clip->AddRef();
if (!init && IsClip() && clip)
clip->Release();
// make sure this copies the whole struct!
((__int32*)this)[0] = ((__int32*)src)[0];
((__int32*)this)[1] = ((__int32*)src)[1];
}
};
------------------------------------------------------------
|
| 引数いろいろ(2) FilterRangeEx part(1) --- env->Invoke(), env->FunctionExists() 2004. 7.27 (01版) |
|---|
F:\Src
|
+--- include
| |
| +--- avisynth.h
|
+--- sample
| |
| +--- sample.cpp
| |
| +--- makefile
|
+--- FilterRangeEx
|
+--- FilterRangeEx.cpp
|
+--- makefile
さて、パラメタチェック用の文字列はどう書けばよいでしょうか?
パラメタが不定数なので、直接指定することはできませんね。
また、指定filterが取る引数の型もそれが幾つあるのかも不定ですね。
こういう場合には、任意型の配列を使えば解決します。
"ci*[filter]s.*"
このようにすれば良いわけです。
この指定により、argsには、
| args[0] | clip |
| args[1][0], args[1][1],... | 区間 int, int,... |
| args[2] | filter名 |
| args[3][0], args[3][1],... | filterの持つ引数列 |
------------------ avisynth.h : 抜粋 -------------------------
class IScriptEnvironment {
public:
class NotFound /*exception*/ {}; // thrown by Invoke and GetVar
typedef AVSValue (__cdecl *ApplyFunc)(AVSValue args, void* user_data, IScriptEnvironment* env);
virtual void __stdcall AddFunction(const char* name, const char* params, ApplyFunc apply, void* user_data) = 0;
virtual bool __stdcall FunctionExists(const char* name) = 0;
virtual AVSValue __stdcall Invoke(const char* name, const AVSValue args, const char** arg_names=0) = 0;
--------------------------------------------------------------
|
AVSValue args[] = { child, 150 };
PClip newclip = env->Invoke("ColorYUY2", AVSValue(args,2)).AsClip();
|
AVSValue args[] = { child, 50, 50, "tv->pc" };
static const char *arg_names[] = { 0, "cont_u", "cont_v", "levels" }
PClip newclip = env->Invoke("ColorYUY2", AVSValue(args,4), arg_names).AsClip();
|
if (!env->FunctionExists("ColorYUY2"))
env->TrowError("`ColorYUY2' filter not found.");
|
ここで、実際にこのInvoke関数を使う場合に問題になってくるのがクリップです。
どのクリップに対してInvokeしてもらうのかといえば、当然FilterRangeEx自体に受け渡されてきた
クリップになります。そこで引数リストの先頭にクリップを追加してみましょう。
for (int i=0; i<args.ArraySize(); i++) {
new_args[i+1] = args[i];
}
new_args[0] = AVSValue(clip);
|
最後に、指定フィルタを適用する区間の指定についてですが、もう少し細かく仕様を規定しましょう。
(1) クリップの範囲外の場合は、範囲内に納める。
(2) 終了フレーム位置の指定は、開始フレーム位置より等しいか大きいものである必要がある。
(3) 複数の指定区間はそれぞれ独立とし、区間が前後しようが重複してようが構わないものとする。
(4) 区間指定が省略されれば、クリップ全体が指定されたものとする。
(5) 開始フレーム位置と終了フレーム位置は必ずペアになっていなければならない。
区間は、AVSValue _sectionsで受け渡されるものとすると、以下のような感じになります。
FilterRangeEx::FilterRangeEx(PClip _child, AVSValue _sections, const char *_filter, AVSValue _filter_args
, void *_user_data, IScriptEnvironment* env)
: GenericVideoFilter(_child), filter(_filter), user_data(_user_data) {
int i;
num_sections = _sections.ArraySize();
if((num_sections & 1) != 0)
env->ThrowError("FilterRangeEx: 'start' and 'end' parameters are not a pair.\n");
if(num_sections == 0) {
num_sections = 2;
sections[0] = 0;
sections[1] = vi.num_frames - 1;
} else {
bool flag_err = false;
for(i=0; i<num_sections; i++) {
sections[i] = _sections[i].AsInt();
if(sections[i] < 0) sections[i] = 0;
if(sections[i] >= vi.num_frames) sections[i] = vi.num_frames - 1;
if((i&1)==1) {
if(sections[i-1] > sections[i]) {
flag_err = true;
break;
}
}
}
if(flag_err) env->ThrowError("FilterRangeEx: 'end' is less than 'start'.\n");
}
}
|
| 引数いろいろ(3) FilterRangeEx part(2) --- Tryブロックと例外ハンドラ 2004. 7.31 (02版) |
|---|
| 2004. 7.27 (01版) |
PClip newclip = child;
try {
newclip = env->Invoke(filter, args).AsClip();
}
catch(IScriptEnvironment::NotFound) {
//--- error handler (1) ----
}
catch(AvisynthError ae) {
//--- error handler (2) ----
//throw //re-create exception object
}
|
これでどのような動作になるのか、具体例を見てみましょう。
-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
PClip filtered = child;
try {
filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), names).AsClip();
}
catch(IScriptEnvironment::NotFound) {
env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
}
return filtered->GetFrame(n, env);
}
-----------------------------------------------------
|
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY3", 150)
return last
又は
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY2", "abc")
return last
|

ところで、Invoke関数にarg_namesを追加すると、引数の型が不一致の際には、
exceptionが捕まえられずにすり抜けてしまいました。
-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
PClip filtered = child;
try {
static const char *arg_names[] = { 0, "off_y", "cont_u", "cont_v" };
filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), arg_names).AsClip();
}
catch(IScriptEnvironment::NotFound) {
env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
}
return filtered->GetFrame(n, env);
}
-----------------------------------------------------
|
loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY2", "abc")
return last
|

-----------------------------------------------------
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
PClip filtered = child;
try {
static const char *arg_names[] = { 0, "off_y", "cont_u", "cont_v" };
filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), arg_names).AsClip();
}
catch(IScriptEnvironment::NotFound) {
env->ThrowError("FilterRangeEx: fuction '%s' not found.\n", filter);
}
catch(AvisynthError ae) {
char msg[256];
AVSValue subtitle_args[] = { child, "", 0, 32 };
sprintf(msg, "FilterRangeEx: catch exception in '%s'.\n", filter);
subtitle_args[1] = AVSValue(msg);
filtered = env->Invoke("subtitle", AVSValue(subtitle_args,3)).AsClip();
subtitle_args[0] = AVSValue(filtered);
subtitle_args[1] = AVSValue(ae.msg);
filtered = env->Invoke("subtitle", AVSValue(subtitle_args,4)).AsClip();
//throw;
}
return filtered->GetFrame(n, env);
}
-----------------------------------------------------
|

loadplugin("f:\src\FilterRangeEx\release\FilterRangeEx.dll")
BlankClip(width=640, height=480, color=$cccc00, pixel_type="YUY2")
FilterRangeEx(10,19, "ColorYUY3", 150)
return last
|

| 引数いろいろ(4) FilterRangeEx part(3) --- Eval,ScriptClip呼び出し、user_data 2004. 7.31 (03版) |
|---|
| 2004. 7.27 (01版) |
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("FilterRangeEx", "ci*[filter]s.*", Create_FilterRangeEx, 0);
env->AddFunction("FilterRangeEx", "ci*[filter]s", Create_FilterRangeEx, 0);
return "`FilterRangeEx' --- limited range section filtering";
}
|
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("FilterRangeEx", "ci*[filter]s.*", Create_FilterRangeEx, (void*)1);
env->AddFunction("FilterRangeEx", "ci*[filter]s", Create_FilterRangeEx, (void*)2);
return "`FilterRangeEx' --- limited range section filtering";
}
|
AVSValue filter_args[2];
filter_args[0] = AVSValue(child);
filter_args[1] = AVSValue("ColorYUY2(off_u=150)");
PClip filtered = env->Invoke("ScriptClip", AVSValue(filter_args, 2)).AsClip();
|
次にEvalを使ってみましょう。
考え方は、フィルタを適用したクリップを別に作成しておいて、
指定範囲内か範囲外かでクリップを切り分けるというものです。
コンストラクタの中で、
evaled = env->Invoke("Eval", AVSValue(&filter_args[1], 1));
を実行
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
PClip filtered = child;
if(nが範囲内なら) filtered = evaled.AsClip();
return filtered->GetFrame(n, env);
}
|
AVISource("foo.avi")
a = trim(0, start-1)
b = trim(start, end).filter()
c = trim(end+1, 0)
return a+b+c
|
(1) ApplyRangeEx(clip clip, string arg1_name, string arg2_name, ... \
int s1, int e1, int s2, int e2, ... , string "filter", filter_args ... )
(2) FilterRangeEx(clip clip, string arg1_name, string arg2_name, ... \
int s1, int e1, int s2, int e2, ... , string "filter", filter_args ... )
(3) ApplyRangeEx(clip clip, string arg1_name, string arg2_name, ... \
int s1, int e1, int s2, int e2, ... , string "filter")
(4) FilterRangeEx(clip clip, string arg1_name, string arg2_name, ... \
int s1, int e1, int s2, int e2, ... , string "filter")
(1)〜(3)は、指定フィルタを直接Invokeしますが、(4)はScriptClip(又はEval)をInvokeします。
#include <windows.h>
#include <stdio.h>
#include "avisynth.h"
#define MY_NAME1 "ApplyRangeEx"
#define MY_NAME2 "FilterRangeEx"
#define USE_SCRIPTCRIP
enum {
ERROR_NOT_SUPPORT_COLORSPACE,
ERROR_NOT_PAIR_PARAMETER,
ERROR_START_END_INVALID,
ERROR_NOTFOUND,
ERROR_EXCEPTION,
};
char *ERRMSG[] = {
"%s: requires YUY2 input.\n",
"%s: 'start' and 'end' parameters are not a pair.\n",
"%s: 'end' is less than 'start'.\n",
"%s: fuction '%s' not found.\n",
"%s: catch exception in '%s'.\n"
};
class FilterRangeEx : public GenericVideoFilter {
private:
int num_sections, num_filter_args;
void *user_data;
int sections[60];
const char *filter;
AVSValue filter_args[61];
const char *filter_arg_names[60];
AVSValue evaled;
char msg[256]; // for debug message
public:
FilterRangeEx(AVSValue _args, void *_user_data, IScriptEnvironment* env);
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
private:
const char *GetName() { return (((int)user_data&1)==1 ? MY_NAME1 : MY_NAME2); }
};
FilterRangeEx::FilterRangeEx(AVSValue _args, void *_user_data, IScriptEnvironment* env)
: GenericVideoFilter(_args[0].AsClip()), filter(_args[3].AsString()), user_data(_user_data) {
int i;
//if (!vi.IsYUY2())
// env->ThrowError(ERRMSG[ERROR_NOT_SUPPORT_COLORSPACE], GetName());
num_sections = _args[2].ArraySize();
num_filter_args = _args[4].ArraySize();
if((num_sections & 1) != 0)
env->ThrowError(ERRMSG[ERROR_NOT_PAIR_PARAMETER], GetName());
if(num_sections == 0) {
num_sections = 2;
sections[0] = 0;
sections[1] = vi.num_frames - 1;
} else {
bool flag_err = false;
for(i=0; i%lt;num_sections; i++) {
sections[i] = _args[2][i].AsInt();
if(sections[i] < 0) sections[i] = 0;
if(sections[i] >= vi.num_frames) sections[i] = vi.num_frames - 1;
if((i&1)==1) {
if(sections[i-1] > sections[i]) {
flag_err = true;
break;
}
}
}
if(flag_err) env->ThrowError(ERRMSG[ERROR_START_END_INVALID], GetName());
}
filter_args[0] = child;
switch((int)user_data) {
case 1:
case 2:
for(i=0; i<num_filter_args; i++) {
filter_args[i+1] = _args[4][i];
}
num_filter_args++;
break;
default:
filter_args[1] = filter;
num_filter_args = 2;
break;
}
/*
{
char tmpbuf[256], *tokenptr;
strncpy(tmpbuf, filter, 255);
if((tokenptr = strtok(tmpbuf, " (\t")) != NULL) {
if(!env->FunctionExists(tokenptr))
env->ThrowError(ERRMSG[ERROR_NOTFOUND], GetName(), tokenptr);
}
}
*/
for(i=0; i<sizeof(filter_arg_names)/sizeof(filter_arg_names[0]); i++) {
filter_arg_names[i] = 0;
}
for(i=0; i<_args[1].ArraySize(); i++) {
if(_args[1][i].AsString()[0]) {
filter_arg_names[i+1] = _args[1][i].AsString();
}
}
#ifndef USE_SCRIPTCRIP
if((int)user_data == 4) {
evaled = env->Invoke("Eval", AVSValue(&filter_args[1], 1));
}
#endif
}
PVideoFrame __stdcall FilterRangeEx::GetFrame(int n, IScriptEnvironment* env) {
PClip filtered = child;
bool flag_range_in = false;
int i;
for(i=0; i<num_sections; i+=2) {
if( (sections[i]<=n) && (sections[i+1]>=n) ) {
flag_range_in = true;
break;
}
}
try {
if(flag_range_in) {
switch((int)user_data) {
case 4:
#ifdef USE_SCRIPTCRIP
filtered = env->Invoke("ScriptClip", AVSValue(filter_args, 2)).AsClip();
#else
if(evaled.IsClip()) filtered = evaled.AsClip();
#endif
break;
default:
filtered = env->Invoke(filter, AVSValue(filter_args, num_filter_args), filter_arg_names).AsClip();
break;
}
}
}
catch(IScriptEnvironment::NotFound) {
env->ThrowError(ERRMSG[ERROR_NOTFOUND], GetName(), filter);
}
catch(AvisynthError ae) {
AVSValue subtitle_args[] = { child, "", 0, 32 };
sprintf(msg, ERRMSG[ERROR_EXCEPTION], GetName(), filter);
subtitle_args[1] = AVSValue(msg);
filtered = env->Invoke("subtitle", AVSValue(subtitle_args,3)).AsClip();
subtitle_args[0] = AVSValue(filtered);
subtitle_args[1] = AVSValue(ae.msg);
filtered = env->Invoke("subtitle", AVSValue(subtitle_args,4)).AsClip();
//throw;
}
return filtered->GetFrame(n, env);
}
AVSValue __cdecl Create_FilterRangeEx(AVSValue args, void* user_data, IScriptEnvironment* env) {
return new FilterRangeEx( args, user_data, env );
}
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction(MY_NAME1, "cs*i*[filter]s.*", Create_FilterRangeEx, (void*)1);
env->AddFunction(MY_NAME2, "cs*i*[filter]s.*", Create_FilterRangeEx, (void*)2);
env->AddFunction(MY_NAME1, "cs*i*[filter]s", Create_FilterRangeEx, (void*)3);
env->AddFunction(MY_NAME2, "cs*i*[filter]s", Create_FilterRangeEx, (void*)4);
return "`FilterRangeEx' --- limited range section filtering";
}
|
| 引数いろいろ(5) FilterRangeEx part(4) --- おまけ ユーザ関数バージョン 2004. 7.27 (01版) |
|---|
【その1】
function F(clip clip, int "s1", int "e1", int "s2", int "e2"
\ , int "s3", int "e3", int "s4", int "e4", int "s5", int "e5") {
clip = IsInt(s1) ? clip.ApplyRange(s1,e1,"KenkunNR",256,2,24) : clip
clip = IsInt(s2) ? clip.ApplyRange(s2,e2,"KenkunNR",256,2,24) : clip
clip = IsInt(s3) ? clip.ApplyRange(s3,e3,"KenkunNR",256,2,24) : clip
clip = IsInt(s4) ? clip.ApplyRange(s4,e4,"KenkunNR",256,2,24) : clip
clip = IsInt(s5) ? clip.ApplyRange(s5,e5,"KenkunNR",256,2,24) : clip
return clip
}
|
【その2】
---- test.avs ---- 切り取り線-------------------------
LoadPlugin("warpsharp.dll")
AVISource("foo.avi")
ws = WScript("test.js", "JAVAScript")
ws.WSInvoke("FilterRangeEx", last, "ColorYUV(off_u=255)", 10, 19, 30, 39, 50, 59)
return last
---- test.avs ---- 切り取り線-------------------------
---- test.js ---- 切り取り線-------------------------
//************************************************************************************
//** FilterRangeEx(clip clip, string filter, int s1, int e1, int s2, int s2, ...) **
//** ( must be 0 <= s1 <= e1 < s2 <= e2 < s3 <= e3 <... ) **
//************************************************************************************
function FilterRangeEx(clip, filter, avs_args)
{
var start = new Array(), end = new Array();
var parm_err = false;
for (var i=2; i < FilterRangeEx.arguments.length; i+=2)
{
if (i+1 > FilterRangeEx.arguments.length
|| isNaN(FilterRangeEx.arguments[i])
|| isNaN(FilterRangeEx.arguments[i+1])
|| FilterRangeEx.arguments[i] > FilterRangeEx.arguments[i+1] )
{
parm_err = true; break;
}
else
{
start[start.length] = FilterRangeEx.arguments[i];
end[end.length] = FilterRangeEx.arguments[i+1];
}
}
for(i=1; i<start.length; i++)
{
if(start[i] <= end[i-1]) { parm_err=true; break; }
}
return parm_err ? AVS.MessageClip("parameter error") : FilterRange_exec(clip, filter, start, end);
}
function FilterRange_exec(clip, filter, start, end)
{
AVS.last = clip;
AVS.Eval("framecount = last.framecount()");
var framecount = AVS.framecount;
var range_first = (start[0]==0) ? "" : "Trim(0," + String(start[0]-1) + ") + ";
for(var i=0; i<start.length; i++)
{
var range_in = "Trim(" + String(start[i]) + "," + String(end[i]) + ")." + filter;
AVS.work = (i==0) ? AVS.Eval(range_first + range_in) : AVS.Eval("work + " + range_in);
if(i+1<start.length)
{
if(end[i]+1 < start[i+1])
{
AVS.range_out = AVS.trim(clip, end[i]+1, start[i+1]-1);
AVS.work = AVS.Eval("work + range_out");
}
}
}
if(end[end.length-1]+1 < framecount)
{
AVS.work = AVS.Eval("work + trim(" + String(end[end.length-1]+1) + ", last.framecount()-1)" );
}
return AVS.work;
}
---- test.js ---- 切り取り線-------------------------
|
【その3】にーやん作成の再帰呼び出しを使ったスクリプト 〜May 10, 2004の日記より抜粋
○説明
minamina氏作FilterRange関数を改造して、フィルタを適用できる範囲の数を増やした。
フィルタの適用範囲の数を調節することも可能。
メインのFilterRangePlus関数とサブのRecursiveA, RecursiveB, RecursiveCの3つの関数を
セットで使用します。
○書式
FilterRangePlus(clip clip, string "filter", int "times",
\ int "s1", int "e1", int "s2", int "e2", int "s3", int "e3", ...)
○パラメータ
filter:使用したいフィルタ
times:フィルタを適用する範囲の数
s*:適用開始点(*は数字)
e*:適用終了点(*は数字)
※s*,e*は増やすことが可能(下記「応用」を参照)
○使用例
[重要]
適用範囲を複数指定する場合、フレーム番号が大きい範囲から順番に指定してください。
たとえば、3-6, 12-17, 21-28の3つの範囲を指定する時は、21-28, 12-17, 3-6の順になります。
×3,6, 12,17, 21,28
○21,28, 12,17, 3,6
FilterRangePlus("GreyScale()", 3, 21,28, 12,17, 3,6)
○やってはいけない
・フィルタ適用範囲の数(times)が間違っている。多すぎる場合はエラーが出るようにしています。
・フィルタ適用範囲の順番が小さい番号からになっている(例)6,9, 13,17
・フィルタ適用範囲がかぶっている(例)12,16, 8,14
・フィルタ適用範囲が連続している(例)5,9, 3,5→3,9と1つにまとめる
○応用(フィルタ適用範囲の数をふやす)
関数の()内に「,int "s*", int "e*"」を追加することで、フィルタ適用範囲の数を
増やすことができます。(*は数字)
(例)5箇所指定できるようにする
function FilterRangePlus(〜 int "e3", int "s4", int "e4", int "s5", int "e5")
○メイン関数
function FilterRangePlus(clip clip, string "filter", int "times",
\ int "s1", int "e1", int "s2", int "e2", int "s3", int "e3")
{
Assert(IsInt(Eval("s" + String(times))), "timesが正しくない")
Assert(s1 > s2 && e2 < s1, "範囲指定が正しくない")
d = (Eval("s" + String(times)) < 2) ? clip.Trim(0, -1) :
\ clip.Trim(0, Eval("s" + String(times)) - 1)
Eval(RecursiveA("", times, filter))
Eval(RecursiveB("", times-1))
c0 = (e1 > clip.FrameCount()-2) ? BlankClip(clip,1) : clip.Trim(e1 + 1, 0)
c = (Eval("s" + String(times)) == 0) ? Eval(RecursiveC("", (times*2)-1) + "c0") :
\ d + Eval(RecursiveC("", (times*2)-1) + "c0")
c = (e1 > clip.FrameCount()-2) ? c.Trim(0, clip.FrameCount()-1) : c
return c
}
○サブ関数
function RecursiveA(string s, int times, string filter)
{
return (times == 0) ? s :
\ "c" + String(times*2-1) + " = clip.trim(s" + String(times) + ", e" +
\ String(times) + ")." + filter + " " + RecursiveA(s, times-1, filter)
}
function RecursiveB(string s, int times)
{
return (times == 0) ? s :
\ "c" + String(times*2) + " = clip.trim(e" + String(times+1) + "+1, s" +
\ String(times) + "-1)" + " " + RecursiveB(s, times-1)
}
function RecursiveC(string s, int times)
{
return (times == 0) ? s : "c" + String(times) + " + " + RecursiveC(s, times-1)
}
|