Avisynth Plugin Development in C

From Avisynth wiki
Revision as of 12:57, 11 June 2016 by Jmac698 (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Note: This guide is in development. Currently, only the section Compiling Your First Plugin has been tested. The rest contains some errors, so use at your own risk!

Note: for new development, it is recommended to develop in C++ using the Avisynth 2.6 API. Please refer to the guide, Avisynth Plugin Development in CPP

Original written by jmac698 with corrections by the Avisynth community.

Contents

Introduction

This tutorial takes you step-by-step through the process of creating a new C plugin for AviSynth by modifying an existing example. By the end of this tutorial, you should be able a plugin which returns a normal value or which works on a single frame. The target audience is the beginning level C programmer.

Why Develop a Plugin in C?

Some reasons to use C based plugin development are that,

  • Free compilers can be used, such as MingW and online code editors such as https://codeanywhere.com/features/editor; only Microsoft Visual C++ or the free Community Edition may be used to create AviSynth C++ plugins
  • You are only familiar with C
  • It may be simpler to learn for a beginner
  • There is no download (with online code editor) or small download (with mingw)
  • You want to use any .NET language, PureBasic, or Delphi

Choosing a License

Before starting development, be aware of these license requirements:

  • The 2.6 C API requires that your plugin uses the GPL license. The GPL license requires that you release the source code.
  • The 2.5 C API can use your own license, but the complete source of AviSynth 2.5 and the C API header must be made available along with your plugin binary

Plugin Compatibility

Plugins programmed in the 2.5 C API can also be used with higher versions of AviSynth, however they can't access any newer features.

Setting up your Development Environment

This tutorial was developed with Code::Blocks, a free development environment. Please follow these steps:

  • Download codeblocks-13.12mingw-setup.exe from the CodeBlocks homepage and run it
  • The first screen reads "Welcome to the CodeBlocks Setup Wizard". Click Next.
  • The GPL license is displayed. Click I Agree.
  • Choose any extra components to install if desired, but the default is sufficient. Click Next.
  • Choose a destination folder. Click Install. Complete the wizard.

Compiling Your First Plugin

Before making our own plugin, first we will learn how to compile an existing plugin. Please follow these steps:

  • Ensure that you included the option "Install FilterSDK" when installing AviSynth; otherwise reinstall AviSynth with that option.
  • Create a C Projects folder under My Documents.
  • Obtain the sample plugin Demosaic from demosaic_20071206.zip. Unzip the files to C Projects. There should now be demosaic, avisynth_c and avisynth_c_2_5 folders.
  • Delete the avisynth_c and avisynth_c_2_5 folders.
  • Delete this file from demosaic: makefile
  • Make a new folder, lib under Demosaic.
  • Copy (C:\Program Files\ or other installation directory then:) AviSynth 2.5\Extras\avisynth.lib to demosaic\lib.
  • Copy (C:\Program Files\ or other installation directory then:) AviSynth 2.5\FilterSDK\include\avisynth_c.h to demosaic.
  • Start Code::Blocks. Click Create a new project.
  • Click Projects, then click Dynamic Link Library, then click Go.
  • The wizard page "Dynamic Link Library" appears. Click Next.
  • For Project title, use Demosaic. Choose the My Documents\C Projects folder you created in a previous step. Click Next.
  • Under compiler, choose GNU GCC Compiler. Accept the defaults and click Finish.
  • Right click "Demosaic", and choose Remove files. A list showing main.cpp and main.h appear. Click OK. Click Yes.
  • Delete these files from demosaic: main.cpp, main.h
  • Right click "Demosaic" and choose Add Files. Add the demosaic.cpp file found in demosaic.
  • A window asks you to choose which targets the file belongs to. Click OK.
  • Right click Demosaic and choose Add Files. Add the avisynth_c.h file found in 'demosaic. Click OK.
  • Select the menu Project->Build Options.... Select Demosaic in the tree.
  • Select the tab Linker settings. Click Add. Browse for the file Demosaic\lib\avisynth.lib.
  • You are asked "Keep this as a relative path?". Click Yes. Click OK. Click OK.
  • Select the menu Build->Select target->Release.
  • Select the menu Build->Build.
  • Verify that the file demosaic\bin\Release\Demosaic.dll was made. Copy this file to your (C:\Program Files\ or other installation directory then:) AviSynth 2.5 folder.
  • Test the plugin by writing an AviSynth script:
Load_Stdcall_Plugin("Demosaic.dll")
colorbars(pixel_type="YV12")
Demosaic()

This should display a grey test pattern with some colour fringes.

  • Save the project with File->Save project.

Troubleshooting:

  • Note that I found problems using full paths with spaces. The .lib file can only be found when placed in a folder lib and using relative paths.
  • If you have a script error, try using the original demosaic.dll in the Demosaic folder. Note that this file starts with a lower case d, so adjust your script accordingly.
  • The test.avs which comes with the plugin uses LoadCPlugin. It is better to use Load_Stdcall_Plugin because the avisynth_c.dll plugin may be auto-loaded. This is a plugin which overrides the functionality of LoadCPlugin, such that it won't load demosaic.dll anymore.

Passing Parameters to your Plugin

For this example, again we will start with the C source of Demosaic (demosaic_20071206.zip)

The example source defines a function, in AviSynth script terms, as:

Demosaic(clip clip1, 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", "cc[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) 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:

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: AviSynth C Interface Illustrated Example by Kevin Atkinson.

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 AviSynth C Interface API Reference.

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];
                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.

Putting it all together

Download and compile the final sample code from .... Install, then open the Merge.cbp file, the project for Code::Blocks. Simply Build->Build. 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.

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

External Links

Personal tools