Log in

View Full Version : Basic Help with New Plugin Development


Pages : [1] 2

jmac698
11th December 2010, 23:43
I'd like to make a new plugin. I've managed to compile an example from SimpleSample SDK and calculate on pixels from one input clip to one output clip. The problem is I get lost at C++ and my C skills are from 15 years ago.
I would like to ask for help as to how to modify the simplesample to accept 3 clips and two ints like: choosebestof3(clip v1, clip v2, clip v3, int thresh1, int thresh 2) in YUY2.
The next step is a loop over all pixels (column first), extracting pixels from all 3 clips, (my algorithm to be written here), then saving a pixel to the destination. If you could include this loop, wow that's like 95% of my plugin and I'm forever grateful :)
I'm using LCC-Win32 which has no problem compiling the C examples.
I'm sure this sounds silly but, I'm really rusty :( I love VDub's FilterFactory because it's sooo easy but also fast and convenient.

Also announcing, new filter to choose best clip of 3 per pixel to erase pulsed noises. To be written someday :)

Gavino
11th December 2010, 23:51
SimpleSample17 has 2 clips and an int as parameters.
That should point you in the right direction.

jmac698
12th December 2010, 04:39
I wasn't able to compile simplesample, in fact I was thinking of cblend.cpp. For SimpleSample1.7 I got a lot of errors. So I'm now going with something that works. The following will work:
Download example_v2.zip from http://forum.doom9.org/showthread.php?t=118065&highlight=compile+cblend&page=2
Unpack, open a command prompt, type
set PATH=%PATH%;C:\Dev-Cpp\bin
go to the example directory, and type mingw32-make. It compiles the cblend plugin. It's now ready to be installed to AviSynth. The previously mentioned thread has instructions to change the plugin's name.

To use it, copy avisynth_c.dll to your plugins, then LoadCPlugin("cblend.dll"). What it actually does is blend two clips. This all works on Avisynth 2.58.

Total download size is 9MB. Compare this to about 2GB for VSC++2005, sp1, platform sdk, and a very long installation time.

The silly thing is, I can't get the linker to work within the GUI options of that system, which is why I'm compiling it manually. You can still open cblend.cpp in the editor to work on it though. I also make a little batch file to compile and drag a shortcut to my desktop.

jmac698
12th December 2010, 07:01
First steps in your filter, just modify the sample source for now. Decide what parameters you need. Write your function to accept these parameters. Do parameter checking. Inform AviSynth of your parameters and function. More details at http://kevin.atkinson.dhs.org/avisynth_c/example.html

You know, I think I'll write this up in my own sample, and then write a template generator which just asks the name of your plugin and the arguments it takes, what colourspaces it uses, and then leaves a blank spot for you to work. It took me at last 5 hours just to get my first compile to work in a script :( And I still haven't made my own parameters yet!

jmac698
12th December 2010, 07:40
Yay, I think I finally understand how to write a filter. Total gr0k time: 8 hours.

jmac698
13th December 2010, 00:57
Ok, I setup a plugin to take in 3 clips, but how do I access each one? In the sample I'm using, with just one input, he does:
AVS_VideoFrame* src_frame = avs_get_frame(fi->child, n);
So how would I get a src_frame1, src_frame2, src_frame3 instead?

I can't find a sample with source for c api with two clips. The only sample I have is demosaic. I tried Average as that looked hopeful, but I can't understand it. The magic bit was:
AVSValue array = arguments[0];
int noarguments = array.ArraySize();
It just looks too different from the C api. The C++ example is using push_back somehow, it's probably a totally different way of doing things.

Gavino
13th December 2010, 11:02
Probably not what you want to hear, but it would be much easier using the standard C++ API.

Anyway, in the C API I believe what you need to do is save the filter args in fi->user_data - you do this in (the C equivalent of) your filter constructor. Then in your get_frame function, you will have access to them via fi->user_data, just like you use fi->child there.

StainlessS
13th December 2010, 22:54
Might want to see here:

http://avisynth.org/mediawiki/Filter_SDK/SDK_necessaries

You should be able to use via VS C++ Toolkit 2003 together with Platform SDK and Codeblocks IDE.

Might be a little more tricky to set up at the start but save a lot of headaches overall.
Note TK2003 does not come with debug libraries.

Codeblocks IDE approx 11MB
TK2003 approx 32 MB
SDK approx 346 MB

TK2003 has an optimizing compiler that performs better than VS CPP 6 and its free.

jmac698
13th December 2010, 23:36
I think Gavino's right,

AVS_VideoFrame* AVSC_CC cblend_get_frame(AVS_FilterInfo* fi, int n)
{
// fetch instance parameters
CBlend* params = (CBlend*) fi->user_data; //Gavino's suggestion
// source frame - same as 'last'
AVS_VideoFrame* src_frame = avs_get_frame(fi->child, n);
// clip2 frame - a second clip we passed to the plugin
AVS_VideoFrame* clip2_frame = avs_get_frame(params->clip2, n);

Gavino
14th December 2010, 00:36
Right. Again using cblend as an example, note also the code in the 'constructor' create_cblend():
// Instance data
CBlend* params = new CBlend();

// Parameters
int pnum = 0;
++pnum; params->clip2 = avs_defined(avs_array_elt(args, pnum)) ? avs_take_clip(avs_array_elt(args, pnum), env) : 0;
You will have to do something similar in your constructor to handle and save whatever parameters you expect, and also extend the definition of struct CBlend as appropriate.

Malaksbane
14th December 2010, 09:45
You should be able to use via VS C++ Toolkit 2003 together with Platform SDK and Codeblocks IDE.

I don't think VS 2003 is available from MS anymore.

jmac698
14th December 2010, 09:50
Actually I found a copy and updated the wiki.
So, I made my first plugin, it removes pulse noise, and it actually somewhat effective. Need to improve the algorithm, however.
I'll let you know if I have any more problems, and write up something to help the next beginner :)
Thanks for all your help!

StainlessS
14th December 2010, 17:55
@jmac698
Think this may be similar to your plugin, source is a little unkempt but looks like it
should work pretty good. The usage is in japanese (if I remember correct) so
at the end of this link is an english usage note.

http://forum.doom9.org/showthread.php?t=151426&highlight=depulse

Edit did you finally go with C++ or C?

jmac698
14th December 2010, 20:46
S,
Thanks for the link. Actually I have all of them, declick, depulse, descratch, despot, deVCR, and all the heaviest denoisers (depulse was the best). This is an extremely bad source, but I found a way to do something none of them do :)
Careful modelling will improve your detection.
I used C, the C++ is like reading arabic to me. It's not just guessing at the syntax, there's totally foreign concepts involved.
I'm sure it's simple if you know it. But C was just as simple, I don't see how C++ could actually be simpler...

Where can I initialize a table and keep it for the life of my filter? I need a sine table. And does anyone know an algorithm for subpixel shift that's fast? i.e. to move .1 pixels left.

StainlessS
15th December 2010, 19:26
Well, not knowing the C interface, could not say where to put your lifelong
table, in C++ it would be in the constructor, so whatever passes for a constructor
in the C interface. (And kill it in what passes for the C Destructor).

I shall knock up a small sample explaining for a C guy what all the weird stuff is in the
C++ interface, once you know what it means and does, it's actually very easy.
You just keep a sort of template, fill in the blanks and then forget it and get on with
the C coding. There will however every now and then come something a little out
of the ordinary, but I just usually take a look at the source of a plugin that does
something similar to what is required and see what was done to achieve it.
I'll do it right now, and when done I'll PM you with a link if that's OK.

I'll pass on the subpixel shift.

Gavino
15th December 2010, 19:48
does anyone know an algorithm for subpixel shift that's fast? i.e. to move .1 pixels left.
A simple algorithm (equivalent to BilinearResize) is to set (using what I hope is an obvious notation)
pixel(x, y) = 0.1*pixel(x-1, y) + 0.9*pixel(x,y)

jmac698
15th December 2010, 21:23
Yes, that's obviously a bilinear resize, it might be slow in C but it's just what I need.
S, that would be extremely helpful I think, to generations to come. What caused me a huge amount of time is just figuring out how to compile, how to get and check my parameters, and the formatting of the pixels. After that I was free to program, and the actual filter was working in 30 minutes - compared to over 10 hours getting there.
At this very moment I'm still lost how to actually read the rest of my parameters... but see the demosaic source code or warp industries(?) webpage.

kemuri-_9
16th December 2010, 01:26
you may get some benefit reading the avs64 branch of ffmpegsource (http://ffmpegsource.googlecode.com/svn/branches/avs64) (specifcally the src/avisynth_c section) which is using the C plugin style to be compatible with gcc.

you can compare it to the src/avisynth folder and see how the code looks in the C++ version.

Gavino
16th December 2010, 11:57
pixel(x, y) = 0.1*pixel(x-1, y) + 0.9*pixel(x,y)
Did you spot the error? :)
Should of course be x+1 - to shift the image left, you want to pick up some of the pixel on the right.

Other points (that may already be obvious to you):
- need to decide how to handle edge conditions (eg mirror or pad);
- for YUV chroma, allow for subsampling (pixels are twice as far apart), so 0.1 and 0.9 become 0.05 and 0.95.

For better performance, do calcs as scaled 16-bit integer instead of floating point.

jmac698
17th December 2010, 12:39
How do I print debug strings? How can I view them?

Gavino
17th December 2010, 13:59
See here (http://avisynth.org/mediawiki/Filter_SDK/Compiling_instructions#How_to_debug_AviSynth_plugins_.28the_short_way.29).

jmac698
17th December 2010, 21:49
Ok, thanks guys, but here's an odd question: a variable's value is disappearing a few statements later, which ends up causing an endless loop for me. Could some sort of optimizing flag be erasing this?
Has anyone ran into weird bugs with GCC?

StainlessS
17th December 2010, 23:22
a variable's value is disappearing a few statements later

Very mystical, any chance of a few lines of code.

Gavino
17th December 2010, 23:23
What happens if you change the compiler optimisation settings?

However, I would take a good hard look at your code. In my experience, at least 95% of the times when a compiler bug is suspected, it turns out to be something wrong with the code, usually memory corruption elsewhere (like running off the end of an array) overwriting a seemingly unrelated variable.

jmac698
17th December 2010, 23:58
Hey,
Just wanted to leave an example for the world of a *working* pixel line shift. No, it's not bilinear yet, I'm still in the algorithmic development stage (I mean, for my plugin in general, not the pixel shift!), when I am ready it will be time for optimization and quality improvement.

// Fill color, make a parameter
fU=128;
fV=128;
fY=16; // This is your basic black, I heard it's in this year :)
// Now shift a line by BestJitter pixels, negative means to the left
if (BestJitter<0) {Left=-BestJitter*4; Right=dest_row_size;}
else if (BestJitter>=0) {Left=0; Right=dest_row_size-BestJitter*4;}
for (int x = Left; x < Right; x += 4) {
aY1=src_data[x]; // This part can be made a lot shorter, I just want to remind myself of the pixel layout
aU=src_data[x+1];
aY2=src_data[x+2];
aV=src_data[x+3];
dest_data[x+1+BestJitter*4] = aU;
dest_data[x+3+BestJitter*4] = aV;
dest_data[x+0+BestJitter*4] = aY1;
dest_data[x+2+BestJitter*4] = aY2;
}
// Need to fill remainder; left side (if fill isn't needed, one assignment and one test are done then skip, should put an IF here)
for (int x= 0; x < BestJitter*4; x+=4) {
dest_data[x] = fY;
dest_data[x+1] = fU;
dest_data[x+2] = fY;
dest_data[x+3] = fV;
}
// Right side
for (int x= dest_row_size+BestJitter*4; x < dest_row_size; x+=4) {
dest_data[x] = fY;
dest_data[x+1] = fU;
dest_data[x+2] = fY;
dest_data[x+3] = fV;
}

Gavino
18th December 2010, 01:36
// Now shift a line by BestJitter pixels, negative means to the left
Is the comment wrong, or the code?
It seems to me it shifts by 2*BestJitter pixels.
(Since in YUY2 each block of 4 bytes holds 2 luma pixels.)

jmac698
18th December 2010, 01:42
ahhh... that would explain a lot :) Thanks G, your just the set of other eyes I needed!
And for the record, that code works and does something, but only two pixels at a time.
UPDATE: Dealing with shared color (4:2:2/4:2:0) formats forced me to do interpolation anyhow, so now I've worked out an elegant algorithm to do it all - subpixel shifting, rotation, fill, or edge color extend. I need to use 3 pointers at once and pixel weights that change whether I'm on an odd/even pixel... well you'll see.
So is this why planar formats were introducted to Avisynth? I can see how using packed with color is a headache, is it easy in assembly?

jmac698
18th December 2010, 10:42
How can I guarantee an unsigned 32bit int in gcc or vs c++? Is there some type made in avisynth?

Gavino
18th December 2010, 12:33
If you have '#include <stdint.h>', you can use uint32_t.
But that might limit your code to gcc only, ie not vc++ (not sure about that).

Wilbert
18th December 2010, 14:36
@jmac698,

Sorry for interrupting this thread. Since you found out how to make/compile plugins using the c api interface, i'd like to ask you to contribute something to wiki. What i would like to see is the following:

1) SimpleSample 1.6 and SimpleSample 1.7 using the c api.
2) Description about the c api itself. (http://avisynth.org/mediawiki/Filter_SDK contains some stuff about the c++ api, but it's still incomplete.)

Could you help with some of this?

kemuri-_9
18th December 2010, 15:32
If you have '#include <stdint.h>', you can use uint32_t.
But that might limit your code to gcc only, ie not vc++ (not sure about that).

there are msvc compatible versions of stdint.h/inttypes.h available at http://msinttypes.googlecode.com/svn/trunk/
though msvc 2010 started supplying stdint.h

jmac698
18th December 2010, 22:59
Wilbert,
I'd love to but I still haven't figured out fetching arguments. One thing at a time...

kemuri-_9
18th December 2010, 23:09
Wilbert,
I'd love to but I still haven't figured out fetching arguments. One thing at a time...

you aren't explaining enough of what kind of problem you are having, 'fetching out arguments' is too vague for me to understand what you are trying to say.

jmac698
19th December 2010, 02:15
Fetching arguments, literally. This fetches a clip:
++pnum; params->clip = avs_defined(avs_array_elt(args, pnum)) ?
avs_take_clip(avs_array_elt(args, pnum), env) : 0;

Now watch as I start to write a doc on how to fetch any arguments:

2.1 Passing Parameters to your Plugin

For this example, again we will start with the C source of Demosaic (http://avisynth.org/warpenterprises/files/demosaic_20071206.zip)

The example source defines a function, in AviSynth script terms, as:
Demosaic(clip, string "mosaic")
However, for our purposes we want to define a plugin like this:
Merge(clip clip1, clip clip2, int weight)

Step 1: Define a place to store the parameters

In this step, we will change the existing code:

enum Mosaic {
BAYER
};

struct Demosaic {
Mosaic mosaic;
};

Into a new version:

struct Params
{
AVS_Clip* clip2;
int Weight;
};


Step 2: Tell Avisynth to parse the new parameters

In this step, we will use a special code to inform the Avisynth parser to look for, and save our parameters.
We will change the existing code:

const char* AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment* env)
{
avs_add_function(env, "Demosaic", "c[mosaic]s", create_filter, 0);
return "Demosaic plugin";
}

To the new version:
const char* AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment* env)
{
avs_add_function(env, "Merge", "c[weight]i", create_filter, 0);
return "Merge plugin";
}

The return "Merge plugin"; statement returns a value to the LoadCPlugin() command (in Avisynth script) upon loading our plugin. In practice, almost no one checks this return value.
The code avs_add_function(env, "Merge", "c[weight]i", create_filter, 0); does a few things; first "Merge" will be the name of our Avisynth script function (i.e., in the same sense that "Tweak" is a built-in function). Next, we use a special code to define the expected parameters. In this case, "c" refers to a clip while "i" refers to an int. If we had used "ci", the function would simply expect a clip and an int, like this:
Merge(clip1,2)[code]
However, we wanted to use a named parameter, which is an optional parameter in functions which can also be named and placed in any order. For example, we could use this script:
[code]Merge(clip1,weight=2) or simply leave it out: Merge(clip1). To specify the name, we include it in square brackets before the parameter type, which in this example, is "[weight]i".

Reference
The special codes for defining your parameters:
'c' for clip, 'i' for integer, 's' for string, 'b' for boolean, and 'f' for float
There are a few more features which are explained at: http://kevin.atkinson.dhs.org/avisynth_c/example.html

Step 3: Fetching and checking your parameters
In this final step, we will actually read the parameters which the Avisynth parser has saved for us. We will change the original code:
int pnum = 0;
++pnum; mosaic_str = avs_defined(avs_array_elt(args, pnum)) ? avs_as_string(avs_array_elt(args, pnum)) : "Bayer";

To the following:
int pnum = 0;
++pnum; params->clip2 = avs_defined(avs_array_elt(args, pnum)) ?
avs_take_clip(avs_array_elt(args, pnum), env) : 0;

++pnum; params->weight = avs_defined(avs_array_elt(args, pnum)) ?
avs_as_int(avs_array_elt(args, pnum)) : 1; // Default value for weight here

It turns out that avs_array_elt contains our list of arguments in the order they were supplied in the script. We are going to expect the first parameter to be a clip, and the second to be an int. The first element, numbered 0, in the array is the value of last, which is also our first clip. pnum will be a counter to fetch each parameter, starting with number 1 (int pnum=0; ++pnum leaves pnum=1). avs_defined() is a function which checks to see if the parameter exists. So we are seeing if parameter #1 exists, and if so, assign it to params->clip2 which is the structure we've defined to hold our parameters. If it doesn't exists, we'll just put a 0 there. The function avs_take_clip actually takes our clip.

In the second parameter fetch, we use avs_as_int to fetch our weight. This function doesn't need an "env" argument. Instead of return 0 if the parameter isn't there, we will take the opportunity to also set a default value for weight here.

Next comes checking the parameters. Even though it seems like we've saved the values, we actually don't know what they really are - they're just data at this point, that we've assumed were a clip and an int. Next we have to actually double-check this. Original code:
// Check params
retval = avs_void;

if (stricmp(mosaic_str, "bayer") == 0)
params->mosaic = BAYER;
else
retval = avs_new_value_error("Mosaic mode can be: \"Bayer\"");

if (!avs_defined(retval) && !avs_is_yuy2(&fi->vi) && !avs_is_yv12(&fi->vi) && !avs_is_y8(&fi->vi)) {
retval = avs_new_value_error("Input video format can be: YUY2, YV12, Y8");
}


New code:

// Check params
retval = avs_void;

if (!avs_defined(retval) && !avs_is_yuy2(&fi->vi)) {
retval = avs_new_value_error("Input video format can be: YUY2");
}

if (!avs_defined(retval) && !avs_is_int(params->weight) {
retval = avs_new_value_error("weight must be an int");
}

There's a few things to go over here. First of all, in the larger context we are defining what our plugin returns here, and that is what retval is. If you return void, it's the same as returning nothing - you'd see "Not a clip" if you tried to play it. We will cover returning a new clip shortly.
!avs_defined(retval) is basically saying, "if retval hasn't been defined yet...", and this is logic to continue checking parameters. In other words, if one of these checks fails, we will set an error message clip as a retval, then the next check will be skipped because a retval has been set. It's just a way to check each parameter just once and skip all other checks as soon as a problem is found.
Now for the actual checking: !avs_is_yuy2(&fi->vi) checks the colour format of our first clip; there are predefined functions for checking every format. Likewise, !avs_is_int(params->weight) checks if our weight is really an int.
avs_new_value_error() is a function that creates a new video clip with a message in it, similar to messageclip() in script. We use this to inform the user of the error.
If all parameters are found to be correct, we finally return the output clip. Original code (unchanged):
// If no errors, all is fine, return clip value
if (!avs_defined(retval)) {
retval = avs_new_value_clip(new_clip);
}


Reference
There are numerous checks for video type:

# int avs_is_rgb(const AVS_VideoInfo * p)
# int avs_is_rgb24(const AVS_VideoInfo * p)
# int avs_is_rgb32(const AVS_VideoInfo * p)
# int avs_is_yuy(const AVS_VideoInfo * p)
# int avs_is_yuy2(const AVS_VideoInfo * p)
# int avs_is_yv12(const AVS_VideoInfo * p)
There are various checks for other parameters:
# int avs_defined(AVS_Value v)
# int avs_is_clip(AVS_Value v)
# int avs_is_bool(AVS_Value v)
# int avs_is_int(AVS_Value v)
# int avs_is_float(AVS_Value v)
# int avs_is_string(AVS_Value v)
# int avs_is_array(AVS_Value v)
# int avs_is_error(AVS_Value v)
And as mentioned, we can return an error clip or a new clip; we can also return int, float, and string values which can be read by the runtime environment:
# AVS_Value avs_new_value_bool(int v0)
# AVS_Value avs_new_value_int(int v0)
# AVS_Value avs_new_value_string(const char * v0)
# AVS_Value avs_new_value_float(float v0)
# AVS_Value avs_new_value_error(const char * v0)
# AVS_Value avs_new_value_clip(AVS_Clip * v0)
# AVS_Value avs_new_value_array(AVS_Value * v0, int size)
For more information, please review http://kevin.atkinson.dhs.org/avisynth_c/api.html

3 Writing the actual function
After all that work, finally we can write a few simple lines of code to actually merge our videos! However, there's *still* a few more things you need to know. The various video formats are stored in memory in different ways; although we've got a pointer to an area of memory which holds our video, it can be *interpreted* in different ways, even though it's the same size! This is where handling of YUY2 vs YV12 and RGB comes in. We'll start with YUY2 in our example.
YUY2 is where every two luma pixels share a color. A color is stored as two values, U and V. We won't go into what U and V means, except to say that if you average two pixels together, everything works out.
YUY2 is stored in this format in memory, from the initial pointer to increasing values in memory, a byte at a time: Y1 U Y2 V. If you access the memory 4 bytes at a time (that is, as a 32 bit unsigned int), due to the Intel assembly stored values as Least Significant Byte order, the int would look like this: 0xVVYYUUYY, where each doubled letter represents one HEX value. For example, if our pixels were Y1=35, Y2=40, U=V=128 (which I'll tell you represents no color or grey), we'd have a hex value of 0x80288023.
I mention that only as a reminder for the optimizing section. For now we'll simply deal with them as bytes for clarity.
New code:

aY1=src_data[x+Jitter*4];
bY1=clip2_data[x];
aU=src_data[x+1];
bU=clip2_data[x+1];
aY2=src_data[x+2];
bY2=clip2_data[x+2];
aV=src_data[x+3];
bV=clip2_data[x+3];

Will give us a pair of pixels each from the two input clips in easy to use byte values. Finally, *finally*, we can simply write these 4 lines of code to finish our function:

dest_data[x+1] = (aU/weight+bU*weight);
dest_data[x+3] = (aV/weight+bV*weight);
dest_data[x+0] = (aY1/weight+bY1*weight);
dest_data[x+2] = (aY2/weight+bY2*weight);
Just remember, again, that [x] is Y1, [x+1] is U, [x+2] is Y2, and [x+3] is V.

4 Putting it all together
Download and compile the final sample code from .... I used BloodShed Dev C++ for development. It is a windows IDE for use with the mingw compiler, which is a port of gcc to windows. It is a small download of 8MB and gets you quickly started in writing plugins.
Download the IDE from: http://www.bloodshed.net/devcpp.html
Install, then open the Merge.dev file. Note, I haven't found a way to make tool generate a proper makefile, but a Makefile has been included in the package, and the Project Options have been set to use a custom Makefile.
Siimply Execute->Compile.
You now have a fresh merge.dll in your project directory. In order to test it, I've include some simple windows batch files. Double click the install.bat icon. This will copy the merge.dll to your Avisynth directory. Note: ensure that you have no scripts open using the plugin, or the file will fail to overwrite any existing plugin version in your Avisynth directory.
Next, open merge.avs in for example AvsP, then press F5. You should see a semitransparent message overlaid on the background. The weight value will change the degree of transparency, where a higher value makes the message stronger.

4 Conclusion
In this manual, we've covered the basic steps necessary to convert an existing example to a new filter, with pointers on how to extend the concepts to include any set of parameters, to return a clip or runtime variable, and to access pixels pairs as stored in YUY2 colour format. In the next set of tutorials, it is planned to cover other colour formats, and some notes on optimizing your code for better execution speed.
I hope you have found this tutorial useful. If you have any comments or corrections, please see the ongoing discussion at:
http://forum.doom9.org/forumdisplay.php?f=69

jmac698
19th December 2010, 02:18
And now for the bad news: I have no idea how accurate all that was. I just made it up by guessing :) That was completely off the top of my head in 30-50 minutes. I haven't compiled the parameter checking code in an actual plugin yet; I will do that soon. There's some parts which I know are wrong as well. Please comment.

Update: For example, for the proposed Merge function to work with colour, the U/V variables would have to be signed bytes to work with the multiply. It also needs range checking. Parameter checking for clip2 is missing. I'm not sure if input clip checking is right, this "fi" stuff needs to be checked. I don't know what env is. I didn't explain how to turn the reference listings into actual code.
In the introduction I would write, the purpose of this tutorial is to modify existing source code to write a new pixel-manipulating plugin with the minimum explanation necessary. Most of the code will be treated as "it just works - forget it" to get on with the actual filter.

Gavino
19th December 2010, 12:19
That's a pretty good write-up for just 30-50 mins work. Well done.

I've spotted a few mistakes:


struct Params
{
AVS_Clip* clip2;
int Weight;
};
In the later code you use weight. C is case-sensitive.
avs_add_function(env, "Merge", "c[weight]i", create_filter, 0);
The 'child' clip needs to be included as the first argument, so it should be "cc[weight]i".
It turns out that avs_array_elt contains our list of arguments in the order they were supplied in the script.
Actually, 'args' contains the arguments - avs_array_elt is a function that extracts the elements from an array (here, args).
Next comes checking the parameters. Even though it seems like we've saved the values, we actually don't know what they really are - they're just data at this point, that we've assumed were a clip and an int.
You can safely assume that the data types are correct, the parser checks the call to your plugin matches the parameters definition (eg "cci"). What your code needs to do is check, for example, that a clip has the right properties, or an integer is in a valid range.
if (!avs_defined(retval) && !avs_is_int(params->weight) {
...
This probably won't compile, since weight is a (C) int and avs_is_int expects an AVS_Value. What you should be checking here is that weight has a valid value (unless any int value is acceptable).

kemuri-_9
19th December 2010, 16:58
here's some excerpts from what i did for ffms2 (Since i wrote the C plugin portion of it - though altered here to not reflect how the plugin non-standardly interacts with the C API):
It provides some examples of how to parse the arguments.

ffms2/c_plugin/src/avisynth_c/avs_convert.h (http://ffmpegsource.googlecode.com/svn/branches/c_plugin/src/avisynth_c/avs_convert.h)

/* conversion driver mimicking the automatic casting and converting the C++ version has */

static inline char as_bool( AVS_Value val, char def )
{ return avs_is_bool( val ) ? !!avs_as_bool( val ) : !!def; }

static inline AVS_Clip *as_clip( AVS_Value val, AVS_ScriptEnvironment *env )
{ return avs_is_clip( val ) ? avs_take_clip( val, env ) : NULL; }

static inline AVS_Value as_elt( AVS_Value val, int elt )
{ return elt < avs_array_size( val ) ? avs_array_elt( val, elt ) : avs_void; }

static inline float as_float( AVS_Value val, float def )
{ return avs_is_float( val ) ? avs_as_float( val ) : def; }

static inline int as_int( AVS_Value val, int def )
{ return avs_is_int( val ) ? avs_as_int( val ) : def; }

static inline const char *as_string( AVS_Value val, const char *def )
{ return avs_is_string( val ) ? avs_as_string( val ) : def; }

static inline AVS_Value clip_val( AVS_Clip *clip )
{ AVS_Value v; avs_set_to_clip( &v, clip ); return v; }


ffms2/c_plugin/src/avisynth_c/avisynth.c (http://ffmpegsource.googlecode.com/svn/branches/c_plugin/src/avisynth_c/avisynth.c)

...
static AVS_Value AVSC_CC create_FFVideoSource( AVS_ScriptEnvironment *env, AVS_Value args, void *user_data )
{
FFMS_Init( avs_to_ff_cpu_flags( avs_get_cpu_flags( env ) ), as_bool( as_elt( args, 15 ), 0 ) );
init_ErrorInfo( ei );

AVS_Value elt0 = as_elt( args, 0 );
if( !avs_is_string( elt0 ) )
return avs_new_value_error( "FFVideoSource: No source specified" );

const char *src = as_string( elt0, NULL );
int track = as_int( as_elt( args, 1 ), -1 );
char cache = as_bool( as_elt( args, 2 ), 1 );
const char *cache_file = as_string( as_elt( args, 3 ), "" );
int fps_num = as_int( as_elt( args, 4 ), -1 );
int fps_den = as_int( as_elt( args, 5 ), 1 );
const char *pp = as_string( as_elt( args, 6 ), "" );
int threads = as_int( as_elt( args, 7 ), -1 );
const char *timecodes = as_string( as_elt( args, 8 ), "" );
int seek_mode = as_int( as_elt( args, 9 ), 1 );
int rff_mode = as_int( as_elt( args, 10 ), 0 );
int width = as_int( as_elt( args, 11 ), 0 );
int height = as_int( as_elt( args, 12 ), 0 );
const char *resizer = as_string( as_elt( args, 13 ), "BICUBIC" );
const char *csp_name = as_string( as_elt( args, 14 ), "" );
....

AVS_Value video = FFVideoSource_create( env, src, track, index, fps_num, fps_den, pp, threads,
seek_mode, rff_mode, width, height, resizer, csp_name ); // defined in ff_vidsource.c
if( avs_is_error( video ) )
FFMS_DestroyIndex( index );
return video;
}

...
static AVS_Value AVSC_CC create_SWScale( AVS_ScriptEnvironment *env, AVS_Value args, void *user_data )
{
AVS_Value child = as_elt( args, 0 );
int dst_width = as_int( as_elt( args, 1 ), 0 );
int dst_height = as_int( as_elt( args, 2 ), 0 );
const char* resizer = as_string( as_elt( args, 3 ), "BICUBIC" );
const char* dst_pix_fmt = as_string( as_elt( args, 4 ), "" );
return FFSWScale_create( env, child, dst_width, dst_height, resizer, dst_pix_fmt ); // defined in ff_swscale.c
}



const char *AVSC_CC avisynth_c_plugin_init( AVS_ScriptEnvironment* env )
{
...
avs_add_function( env, "FFVideoSource", "[source]s[track]i[cache]b[cachefile]s[fpsnum]i[fpsden]i[pp]s[threads]i[timecodes]s[seekmode]i[rffmode]i[width]i[height]i[resizer]s[colorspace]s[utf8]b", create_FFVideoSource, 0 );
...
avs_add_function( env, "SWScale", "c[width]i[height]i[resizer]s[colorspace]s", create_SWScale, 0 );
return "FFmpegSource - The Second Coming V2.0 Final";
}

jmac698
10th November 2011, 03:36
@kemuri,
I tried your conversions. I think I know how they work now, but in

static inline AVS_Clip *as_clip( AVS_Value val, AVS_ScriptEnvironment *env )
{ return avs_is_clip( val ) ? avs_take_clip( val, env ) : NULL; }

NULL isn't defined anywhere. What should it be?

I was trying something like

params->weight=avs_array_elt(args, pnum).d.integer;

But I can use this thing I found in avisynth_c.h instead:

params->weight=avs_as_int(avs_array_elt(args, pnum));

or even yours:

params->weight=as_int(avs_array_elt(args, pnum));

TheFluff
10th November 2011, 11:03
NULL isn't defined anywhere. What should it be?

NULL is a null pointer. It's defined, just trust me on this one.

kemuri-_9
10th November 2011, 14:33
@kemuri,
I tried your conversions. I think I know how they work now, but in

static inline AVS_Clip *as_clip( AVS_Value val, AVS_ScriptEnvironment *env )
{ return avs_is_clip( val ) ? avs_take_clip( val, env ) : NULL; }

NULL isn't defined anywhere. What should it be?


NULL is a standard macro definition used in C++ and C
in C++, it is usually simply '0'.
in C, it generally is '((void*)0)';

the fact that you don't know this is continuing to show that you still are not aware of basic programming concepts...

most languages have a concept of 'null' or 'NULL' to indicate an invalid pointer.



params->weight=as_int(avs_array_elt(args, pnum));


this is invalid, it needs to be something along the lines of

params->weight=as_int(avs_array_elt(args, pnum), 0);

this functionality is to mimic the way the C++ version allows default values if the value was not defined.

compare C++ lines 104-119 (http://ffmpegsource.googlecode.com/svn/trunk/src/avisynth/avisynth.cpp) to C lines 127-142 (http://ffmpegsource.googlecode.com/svn/branches/c_plugin/src/avisynth_c/avisynth.c)

jmac698
10th November 2011, 14:44
Thanks. I'm getting an error:

In function 'AVS_Clip* as_clip(AVS_Value, AVS_ScriptEnvironment*)':
error: 'NULL' was not declared in this scope

So I just set it to 0 and it built. But that seems to be two weird things I've noticed about my compiler, one I noticed FLT_MANT_DIG is not defined, and now neither is null. Is it possible there's some basic header I didn't include?

TheFluff
10th November 2011, 15:27
Thanks. I'm getting an error:

So I just set it to 0 and it built. But that seems to be two weird things I've noticed about my compiler, one I noticed FLT_MANT_DIG is not defined, and now neither is null. Is it possible there's some basic header I didn't include?

Including any standard library header (such as stdio.h, stdlib.h, stddef.h, string.h, time.h, etc etc) should define NULL. If it doesn't, your stdlib is broken and does not conform to C89. It's also defined in a lot of other places.

jmac698
10th November 2011, 16:24
Good news, I finally found FLT_MANT_DIG in C:\MinGW\lib\gcc\mingw32\4.6.1\include\float.h
Why I have two float.h still isn't clear and don't they conflict?

And NULL was found in C:\MinGW\lib\gcc\mingw32\4.6.1\include\stddef.h. I added #include "stddef.h" and NULL works now. Thanks.

/* A null pointer constant. */

#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL /* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0)
#else /* C++ */
#define NULL 0
#endif /* C++ */
#endif /* G++ */
#endif /* NULL not defined and <stddef.h> or need NULL. */
#undef __need_NULL

Gavino
10th November 2011, 16:33
Good news, I finally found FLT_MANT_DIG in C:\MinGW\lib\gcc\mingw32\4.6.1\include\float.h
Why I have two float.h still isn't clear and don't they conflict?
In my installation, C:\MinGW\include\float.h invokes the other one (via the #include_next directive).

jmac698
10th November 2011, 16:36
Confirmed, thanks. And I still didn't find my 23, 54, and 112 numbers, just some more indirection :)

Gavino
10th November 2011, 16:48
As I said, I think the actual values are built in to the compiler and you can write a program (#include'ing float.h) to print them out.

amtm
10th November 2011, 17:11
jmac you should really read a C or C++ primer before you continue. It will be helpful in eliminating these issues you are having. These are extremely basic concepts you learn first when programming in these languages. Not to mention you'll write much better code and be more efficient without needing to do constant modification due to not understanding fundamental concepts of the language.

jmac698
10th November 2011, 17:20
amtm,
I download the book Thinking in C++ http://www.lib.ru.ac.th/download/e-books/TIC2Vone.pdf
I searched the book for all occurrences of NULL and read some of the chapters, but nowhere does it explain what I just learned here. Do you know of a book which can teach me these basic concepts? Thanks.

amtm
10th November 2011, 17:32
I haven't read that book but my favorite as a primer that teaches modern C++ style is C++ Primer 4th Ed by Stanley Lippman. When I got back into C++ programming about 10 months ago it was a great read to relearn the basics and pretty advanced stuff too. Now it's not going to teach you where every little macro is defined in the standard library (most books won't) but it'll give you a good grounding that you are desperately lacking.

amtm
10th November 2011, 17:47
And I know you want to immediately chunk out this code, but reading that primer will make your life much easier. Even if you only read the first part which covers all the basics.