An Illustrated Example

Here is a complete working filter. It was adopted from the original AviSynth 1.0 documentation. This example can also be found in the file "invert.c".

Please note that all types generally start with "AVS_" and all functions generally start with "avs_", but I will often leave the "avs" prefix off.

The Code

Here's Invert.cpp:

#include "avisynth_c.h"

AVS_VideoFrame * AVSC_CC invert_get_frame (AVS_FilterInfo * p, int n)
{
  AVS_VideoFrame * src, * dest;
  int row_size, height, src_pitch, dest_pitch;
  const BYTE * src_data;
  BYTE * dest_data;
  int y, x;

  src = avs_get_frame(p->child, n);
  dest = avs_new_video_frame(p->env, &p->vi);

  src_data  = avs_get_read_ptr(src);
  dest_data = avs_get_write_ptr(dest);
  src_pitch = avs_get_pitch(src);
  dest_pitch = avs_get_pitch(dest);

  row_size = avs_get_row_size(src);
  height = avs_get_height(src);

  for (y = 0; y < height; y++) {
    for (x = 0; x < row_size; x++)
      dest_data[x] = src_data[x] ^ 255;
    src_data += src_pitch;
    dest_data += dest_pitch;
  }

  avs_release_video_frame(src);
  return dest;
}

AVS_Value AVSC_CC invert_create (AVS_ScriptEnvironment * env, 
                                   AVS_Value args, void * dg)
{
  AVS_Value v;
  AVS_FilterInfo * fi;
  AVS_Clip * new_clip = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1);
  if (avs_is_planar(&fi->vi)) {
    v = avs_new_value_error("Video must be on a single plane");
  } else {
    fi->get_frame = invert_get_frame;
    v = avs_new_value_clip(new_clip);
  }
  avs_release_clip(new_clip);
  return v;
}

const char * AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment * env)
{
  avs_add_function(env, "Invert", "c", invert, 0);
  return "`Invert' sample plugin";
}

Compile this file into a DLL named Invert.dll. To do so you will need to link with avisynth_c.lib.

Now create an Avisynth script which looks something like this:

LoadCPlugin("d:\path\Invert.dll")
AVISource("d:\path2\video.avi")
Invert()
If all is well, you should see a photo negative of your video clip when you open this script.

How it works

Here's a line-by-line breakdown of Invert.cpp.

#include "avisynth_c.h"
This header declares all the structures, functions and miscellaneous constants that you might need when writing a plugin. All external plugins should #include it.
AVS_VideoFrame * AVSC_CC invert_get_frame (AVS_FilterInfo * p, int n)
This function is the main callback functions for our filter. When called it should return frame n. It returns this frame as a pointer to AVS_VideoFrame.

AVSC_CC stands for Avisynth calling convention. Right now it is cdecl but it is likely to change to stdcall in a future version. By using AVSC_CC you should be able to maintain source code compatibility when the calling convention changes.

  AVS_VideoFrame * src, * dest;
  int row_size, height, src_pitch, dest_pitch;
  const BYTE * src_data;
  BYTE * dest_data;
  int y, x;
Just declaring all of the variables we will need up front.
  src = avs_get_frame(p->child, n);
"child" is a member of AVS_FilterInfo, of type AVS_Clip. It generally contains the clip that was passed into the filter. For our filter to produce frame n we need the corresponding frame of the input. If you need a different frame from the input, all you have to do is pass a different frame number get_frame.

get_frame calls are usually intercepted by Avisynth's internal caching code, so the frame request may never actually reach the child filter.

  dest = avs_new_video_frame(p->env, &p->vi);
The new_video_frame function allocates space for a video frame of the supplied size. (In this case it will hold our filter's output.) The frame buffer is uninitialized raw memory.

"env" is a member of FilterInfo which is a pointer to AVS_ScriptEnvironment. One instance of AVS_ScriptEnvironment is created for each AVS script.

"vi" is another member of FilterInfo. It is a struct of type AVS_VideoInfo, which contains information about the clip (like frame size, frame rate, pixel format, audio sample rate, etc.). new_video_frame uses the information in this struct to return a frame buffer of the appropriate size.

Frame buffers are reused once all references to them go away. So usually memory won't need to be allocated from the heap.

  src_data  = avs_get_read_ptr(src);
  dest_data = avs_get_write_ptr(dest);
All frame buffers are readable, but not all are writable.

The rule about writability is this: A buffer is writable if and only if its reference count is one. In other words, you can only write to a buffer if no one else might be reading it. This rule guarantees that as long as you hold on to a video frame and don't write to it yourself, that frame will remain unchanged. Any buffer you get from new_video_frame is guaranteed to be writable. However, frames you get from other clips via get_frame may not be writable, in which case get_write_ptr will return a null pointer.

There is an is_writable method which you can call to find out if a buffer is writable or not, and there's a make_writable method (described below) to ensure that it is.

  src_pitch = avs_get_pitch(src);
  dest_pitch = avs_get_pitch(dest);
The "pitch" of a frame buffer is the offset (in bytes) from the beginning of one scan line to the beginning of the next. The source and destination buffers won't necessarily have the same pitch.

Buffers created by new_video_frame are always quadword (8-byte) aligned and always have a pitch that is a multiple of 8.

  row_size = avs_get_row_size(src);
The row size is the length of each row in bytes (not pixels). It's usually equal to the pitch or slightly less, but it may be significantly less if the frame in question has been through Crop.

Since our source and destination frames have the same width and pixel format, they will always have the same row size. Thus I only need one row_size variable, and I could just as well have used dest instead of src.

  height = avs_get_height(src);
The height is the height in pixels. Again, for our filter this is the same for the source and the destination.
  for (y = 0; y < height; y++) {
    for (x = 0; x < row_size; x++)
      dest_data[x] = src_data[x] ^ 255;
    src_data += src_pitch;
    dest_data += dest_pitch;
  }
This is the code that does the actual work. The "src_data += src_pitch; dest_data += dest_pitch;" idiom is a useful way of dealing with potentially differing pitches without too much grief.
  avs_release_video_frame(src);
Once we are done with a video frame it needs to be released. Releasing a video frame does not actually delete it but decrements the reference count.
  return dest;
Finally we return the dest video frame.
AVS_Value AVSC_CC invert_create (AVS_ScriptEnvironment * env, 
                                   AVS_Value args, void * user_data)
In order to use our new filter, we need a scripting-language function which creates an instance of it. This is that function.

Script functions written in C take three arguments. env contains the same ScriptEnvironment pointer that will later be passed to GetFrame. args contais all the arguments passed to the function by the script. user_data contains the void pointer which you passed to AddFunction (see below). Usually you won't need this.

AVS_Value is a variant type which can hold any one of the following: a boolean value (true/false); an integer; a floating-point number; a string; a video clip; an array of AVS_Values; or nothing ("undefined"). You can test which one it is with the methods is_bool, is_int, is_float, is_string, is_clip, is_array, and defined (which returns true if the AVS_Value is not "undefined"). You can get the value with as_bool, as_int, etc. for arrays, you can use the array_size method to get the number of elements, and array_elt get the elements themselves. For convenience, is_float and as_float will work with integers also. But boolean values are not treated as numeric (unlike C).

The name "invert_create" is arbitrary. This function will actually be known as "Invert" in scripts, because that's the name we pass to AddFunction below.

  AVS_Value v;
v will serve as the return value
  AVS_FilterInfo * fi;
  AVS_Clip * new_clip = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1);
"avs_new_c_filter" creates a new filter. AVS_Clip is an abstract type representing a filter. In this case it will represent our new filter. "env" is the current script environment. "fi" will be set to point to our filters AVS_FilterInfo which is the concrete representation of a C filter. We will modify this structure later. "avs_array_elt(args, 0)" is accessing the first value of args, which is generally the parent clip. The last paramater, is a boolean value "store_child". It should generally always be set to true.
  if (avs_is_planar(&fi->vi)) {
    v = avs_new_value_error("Video must be on a single plane");
  } else {
Our filter will not work unless the video is stored on a single plane. If the image is planar than an error is returned. This is done be setting the return value to an error condition.
    fi->get_frame = invert_get_frame;
Here we are setting the get_frame callback. There are other callbacks but since "store_child" was set we do not have to worry about implementing all of them.
    v = avs_new_value_clip(new_clip);
The return value is then set to the new clip returned by new_c_filter. avs_new_value_clip converts the clip info an AVS_Value. It also creates a new reference to the clip.
  }
  avs_release_clip(new_clip);
We must now release the clip returned by new_c_filter. In the case of an error the clip is no longer needed. Otherwise a new reference is created to the clip with new_value_clip so we must release the orignal reference.
  return v;
Finally we return. "v" is either an error or the new clip.
const char * AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment * env) {
This is the only function which gets exported from the DLL. It is called by the script function LoadPlugin the first time this plugin in loaded in a particular script. If several scripts are open at once and more than one of them loads this plugin, avisynth_c_plugin_init may be called more than once with different ScriptEnvironments. Therefore: The main purpose of the avisynth_c_plugin_init function is to call avs_add_function.
  avs_add_function(env, "Invert", "c", invert, 0);
As promised, we now call add_function to let Avisynth know of the existence of our filter. This function takes four arguments: the name of the new script function; the parameter-type string; the C function implementing the script function; and the user_data cookie.

The parameter type string specifies the paramaters for the function. The return value is untyped. It consists of a series of letters specinging the parameter types with 'c' for clip, 'i' for integer, 's' for string, 'b' for boolean, and 'f' for float. In addition:

  return "`Invert' sample plugin";
The return value of AvisynthPluginInit is a string which can contain any message you like, such as a notice identifying the version and author of the plugin. This string becomes the return value of LoadPlugin, and will almost always be ignored. You can also just return 0 if you prefer.

As an in-place filter

The Invert filter could easily do its work in a single buffer, rather than copying from one buffer to another. Here's a new implementation of get_frame that does this.
AVS_VideoFrame * AVSC_CC invert_inplace_get_frame (AVS_FilterInfo * p, int n)
{
  AVS_VideoFrame * frame;
  int row_size, height, pitch;
  BYTE * data;
  int y, x;

  frame = avs_get_frame(p->child, n);

  avs_make_writable(p->env, &frame);

  data = avs_get_write_ptr(frame);
  pitch = avs_get_pitch(frame);
  row_size = avs_get_row_size(frame);
  height = avs_get_height(frame);

  for (y = 0; y < height; y++) {
    for (x = 0; x < row_size; x++)
      data[x] ^= 255;
    data += pitch;
  }

  return frame;
}
The key difference between this version of the function and the original version is the presence of the avs_make_writable function. This is necessary because this time "we don't know where that frame's been." Someone else in the filter chain may be holding a reference to it, in which case we won't be allowed to write to it. make_writable will make a copy of the frame if someone else still has a reference to it, otherwise it will do nothing.

More Info

This should be enough to get you started.

As already mentioned this example can be found in "invert.c". More examples can be found in "examples.c".

For more information see the API Reference and the original AviSynth development documentation.

Copyright

This example was adopted from the original documentation written for AviSynth 1.0 by Ben Rudiak-Gould. Once upon a time it was available at http://math.berkeley.edu/~benrg/avisynth-extensions.html. A current copy can be found at http://www.avisynth.org/index.php?page=BensAviSynthDocs. I could not find a copyright notice to this document. I assume it is okay to use.

Copyright © Ben Rudiak-Gould.
Copyright © 2004 Kevin Atkinson under the GPL GNU General Public License as published by the Free Software Foundation version 2.0.


Last Modified January 1, 2004 by Kevin Atkinson.
Avisynth can be found at www.avisynth.org. The latest version of the C interface can be found at kevin.atkinson.dhs.org/avisynth_c/.