Filter SDK/CInvertNeg

From Avisynth wiki
Revision as of 23:43, 13 January 2017 by Admin (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

I'll start off with a complete, working Avisynth C plugin. It's called "InvertNeg", and it produces a photo-negative of the input clip.

Here's InvertNeg.c:

#include "avisynth_c.h"

AVS_VideoFrame* AVSC_CC InvertNeg_get_frame(AVS_FilterInfo* fi, int n)
{
   AVS_VideoFrame* src;
   AVS_VideoFrame* dst;
   int row_size, height, src_pitch, dst_pitch;
   const BYTE* srcp;
   BYTE* dstp;
   int x, y, p;

   int planes[] = {AVS_PLANAR_Y, AVS_PLANAR_V, AVS_PLANAR_U};

   src = avs_get_frame(fi->child, n);
   dst = avs_new_video_frame(fi->env, &fi->vi);

   for (p=0; p<3; p++) {
      srcp = avs_get_read_ptr_p(src, planes[p]);
      dstp = avs_get_write_ptr_p(dst, planes[p]);
      src_pitch = avs_get_pitch_p(src, planes[p]);
      dst_pitch = avs_get_pitch_p(dst, planes[p]);
      row_size = avs_get_row_size_p(dst, planes[p]);
      height = avs_get_height_p(dst, planes[p]);

      for (y = 0; y < height; y++) {
         for (x = 0; x < row_size; x++) {
            dstp[x] = srcp[x] ^ 255;
         }
         srcp += src_pitch;
         dstp += dst_pitch;
      }
   }

   avs_release_video_frame(src);
   return dst;
}

AVS_Value AVSC_CC Create_InvertNeg(AVS_ScriptEnvironment* env, AVS_Value args, void* user_data)
{
   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) || !avs_is_yuv(&fi->vi)) {
      return avs_new_value_error("InvertNeg: planar YUV data only!");
   }

   fi->get_frame = InvertNeg_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, "InvertNeg", "c", Create_InvertNeg, 0);
   return "InvertNeg sample c-plugin";
}

Link to avisynth.lib and when using the MSVC++ compiler add the definition file. Compile this file into a DLL named InvertNeg.dll. See compiling instructions. Now create an Avisynth script which looks something like this:

LoadCPlugin("d:\path\InvertNeg.dll")
clip = BlankClip().ConvertToYV12()
return clip.InvertNeg()

Line by line breakdown

Here's a line-by-line breakdown of InvertNeg.c:

#include "avisynth_c.h"

This header declares all the classes and miscellaneous constants that you might need when writing a plugin. All external plugins should #include it.

External plugins do not link with avisynth.dll, so they can't directly access functions that are defined in the main Avisynth source code. Therefore, every important function in avisynth_c.h is either defined inline or (???).

AVS_VideoFrame* AVSC_CC InvertNeg_get_frame(AVS_FilterInfo* fi, 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 stdcall (it used to be cdecl when the C interface was exposes through seperate plugin). By using AVSC_CC you should be able to maintain source code compatibility when the calling convention changes.

   AVS_VideoFrame* src;
   AVS_VideoFrame* dst;
   int row_size, height, src_pitch, dst_pitch;
   const BYTE* srcp;
   BYTE* dstp;
   int x, y, p;

Declaring all of the variables we will need must be done up front (for the C++ plugin this is not necessary). Doing it in between the code will get you strange compile errors.

   int planes[] = {AVS_PLANAR_Y, AVS_PLANAR_V, AVS_PLANAR_U};

The plugin requires planar YUV data, so we need to process the planes. Note the constants (such as AVS_PLANAR_Y) are the same as the ones in the C++ inteface, except for the 'AVS_' in front of it.

   src = avs_get_frame(fi->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 clip. If you need a different frame from the input, all you have to do is pass a different frame number get_frame.

Note the functions (such as avs_get_frame) are similar as the ones in the C++ inteface, except for the 'avs_' in front of it.

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

   dst = avs_new_video_frame(fi->env, &fi->vi);

The avs_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 AVS_FilterInfo which is a pointer to AVS_ScriptEnvironment. One instance of AVS_ScriptEnvironment is created for each AVS script.

"vi" is another member of AVS_FilterInfo. It is a structure of type AVS_VideoInfo, which contains information about the clip (like frame size, frame rate, pixel format, audio sample rate, etc.). avs_new_video_frame uses the information in this structure 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.

   for (p=0; p<3; p++) {

Loop over all planes.

      srcp = avs_get_read_ptr_p(src, planes[p]);
      dstp = avs_get_write_ptr_p(dst, planes[p]);

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 avs_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 avs_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 avs_make_writable method (described below) to ensure that it is.

      src_pitch = avs_get_pitch_p(src, planes[p]);
      dst_pitch = avs_get_pitch_p(dst, planes[p]);

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 avs_new_video_frame are always double word (16-byte) aligned and always have a pitch that is a multiple of 16.

      row_size = avs_get_row_size_p(dst, planes[p]);

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_p(dst, planes[p]);

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++) {
            dstp[x] = srcp[x] ^ 255;
         }
         srcp += src_pitch;
         dstp += dst_pitch;
      }
   }

This is the code that does the actual work. The "srcp += src_pitch; dstp += 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 dst;

GetFrame returns the newly-created frame.

AVS_Value AVSC_CC Create_InvertNeg(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 avs_is_bool, avs_is_int, avs_is_float, avs_is_string, avs_is_clip, avs_is_array, and avs_defined (which returns true if the AVS_Value is not "undefined"). You can get the value with avs_as_bool, avs_as_int, etc. for arrays, you can use the avs_array_size method to get the number of elements, and avs_array_elt get the elements themselves. For convenience, avs_is_float and avs_as_float will work with integers also. But boolean values are not treated as numeric (unlike C).

The name "Create_InvertNeg" is arbitrary. This function will actually be known as "InvertNeg" in scripts, because that's the name we pass to avs_add_function 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 filter 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 parameter, is a boolean value "store_child". It should generally always be set to true.

   if (!avs_is_planar(&fi->vi) || !avs_is_yuv(&fi->vi)) {
      return avs_new_value_error("InvertNeg: planar YUV data only!");
   }

Our filter will not work unless the video is stored as YUV planar. If this is not the case than an error is returned.

   fi->get_frame = InvertNeg_get_frame;

Here we are setting the get_frame callback. There are other callbacks (like get_audio, set_cache_hints and get_parity), but since "store_child" was set to true we do not have to worry about implementing all of them. Just the ones we need.

   v = avs_new_value_clip(new_clip);

The return value is then set to the new clip returned by avs_new_c_filter. avs_new_value_clip stores the clip in an AVS_Value which will be returned by our Create_InvertNeg. It also creates a new reference to the clip.

   avs_release_clip(new_clip);

We must now release the clip returned by avs_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 avs_new_value_clip so we must release the original reference.

   return v;

Finally we return our new clip. We could also have returned an error instead (instead of returning an error right away).

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 LoadCPlugin 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:

  • You should not save the env parameter in a global variable.
  • If you need to initialize any static data, you should do it in DLLMain, not in this function.

The main purpose of the avisynth_c_plugin_init function is to call avs_add_function.

   avs_add_function(env, "InvertNeg", "c", Create_InvertNeg, 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:

  • Any type can be followed with a '*' or '+' to indicate "zero or more" or "one or more" respectively. In this case all the matching arguments will be gathered into a sub-array. For example, if your type string is "is+f", then the integer argument will be args[0], the string arguments will be args[1][0], args[1][1], etc. (and there will be avs_array_size(args[1]) of them), and the float argument will be args[2].
  • '.' matches a single argument of any type. To match multiple arguments of any type, use ".*" or ".+".
  • Named arguments can be specified in [brackets] before the type. Named arguments are also optional arguments; if the user omits them, they will be of the undefined type instead of the type you specify.
   return "InvertNeg sample c-plugin";

The return value of avisynth_c_plugin_init 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 LoadCPlugin, and will almost always be ignored. You can also just return 0 if you prefer.

As an in-place filter

The InvertNeg 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:

#include "avisynth_c.h"

AVS_VideoFrame* AVSC_CC InvertNeg_get_frame(AVS_FilterInfo* fi, int n)
{
   AVS_VideoFrame* src;
   int row_size, height, src_pitch;
   BYTE* srcp;
   int x, y, p;

   int planes[] = {AVS_PLANAR_Y, AVS_PLANAR_V, AVS_PLANAR_U};

   src = avs_get_frame(fi->child, n);
   avs_make_writable(fi->env, &src);

   for (p=0; p<3; p++) {
      srcp = avs_get_write_ptr_p(src, planes[p]);
      src_pitch = avs_get_pitch_p(src, planes[p]);
      row_size = avs_get_row_size_p(src, planes[p]);
      height = avs_get_height_p(src, planes[p]);

      for (y = 0; y < height; y++) {
         for (x = 0; x < row_size; x++) {
            srcp[x] = srcp[x] ^ 255; // or srcp[x] =^ 255;
         }
         srcp += src_pitch;
      }
   }

   return src;
}

AVS_Value AVSC_CC Create_InvertNeg(AVS_ScriptEnvironment* env, AVS_Value args, void* user_data)
{
   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) || !avs_is_yuv(&fi->vi)) {
      v = avs_new_value_error("InvertNeg: planar YUV data only!");
   } else {
      fi->get_frame = InvertNeg_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, "InvertNeg", "c", Create_InvertNeg, 0);
   return "InvertNeg sample c-plugin";
}

The key difference between this version of the function and the original version is the presence of the avs_make_writable callback. This is necessary because this time "we don't know where that source frame has 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.

Old versions

Kevin's AviSynth C API Docs is the documentation written for the deprecated avisynth_c.dll by Kevin Atkinson, in its original form.

Personal tools