Filter SDK/CMerge

From Avisynth wiki
(Difference between revisions)
Jump to: navigation, search
 
(3 intermediate revisions by one user not shown)
Line 1: Line 1:
 +
Here is another working Avisynth C plugin. It's called "Merge", and it merges two clips according to a weight.
  
 +
Here's Merge.c:
  
 +
#include <stdlib.h>
 
  #include "avisynth_c.h"
 
  #include "avisynth_c.h"
#include <stdlib.h> // free, malloc
 
 
   
 
   
 
  typedef struct Merge {
 
  typedef struct Merge {
Line 47: Line 49:
 
       dstp += dst_pitch;
 
       dstp += dst_pitch;
 
     }
 
     }
+
 
     avs_release_frame(src);
 
     avs_release_frame(src);
 
     avs_release_frame(src2);
 
     avs_release_frame(src2);
Line 104: Line 106:
 
       }
 
       }
 
     } else {
 
     } else {
       params->weight = 0.5; // default value
+
       params->weight = 0.5f; // default value
 
     }
 
     }
 
   
 
   
Line 115: Line 117:
 
     avs_release_clip(new_clip);
 
     avs_release_clip(new_clip);
 
     return v;
 
     return v;
}
+
}
 
+
 
+
 
  const char* AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment* env)
 
  const char* AVSC_CC avisynth_c_plugin_init(AVS_ScriptEnvironment* env)
 
  {
 
  {
Line 123: Line 124:
 
     return "Merge sample C plugin";
 
     return "Merge sample C plugin";
 
  }
 
  }
 +
 +
== Line by line breakdown ==
 +
 +
Here's a line-by-line breakdown of Merge.c. I won't repeat the comments in the previous example InvertNeg.c, so read that first if needed. The declaration of the class is as follows
 +
 +
#include <stdlib.h> // free, malloc
 +
 +
This header defines free and malloc which we will need later on.
 +
 +
Note one could also include windows.h instead. However this gives a 'warning C4005: 'EXTERN_C' : macro redefinition' when compiling with MSVC++. I guess this needs to be fixed avisynth_c.h.
 +
 +
typedef struct Merge {
 +
    AVS_Clip* clip2;
 +
    double weight;
 +
} Merge; // this name (ie Merge) is an alias for 'struct Merge'
 +
 +
In C we don't have classes. We will use a structure to declare our parameter variables (clip2 and weight in this example). The members of a structure are public by default (C++ plugins: recall that in a C++ class they are private by default).
 +
 +
typedef allows you to declare instances of a structure without using keyword "struct", since the second 'Merge' is an alias for 'struct Merge'. For more information have a look [http://stackoverflow.com/questions/1675351/typedef-struct-vs-struct-definitions here] and [http://www.cs.usfca.edu/~wolber/SoftwareDev/C/CStructs.htm here].
 +
 +
AVS_VideoFrame* AVSC_CC Merge_get_frame(AVS_FilterInfo* fi, int n)
 +
 +
This method is called to make our filter produce frame n of its output.
 +
 +
    Merge* params = (Merge*) fi->user_data;
 +
 +
This fetches instance parameters.
 +
 +
    AVS_VideoFrame* src = avs_get_frame(fi->child, n);
 +
    const BYTE*    srcp = avs_get_read_ptr(src);
 +
    int            src_pitch = avs_get_pitch(src);
 +
 +
    AVS_VideoFrame* src2 = avs_get_frame(params->clip2, n);
 +
    const BYTE*    srcp2 = avs_get_read_ptr(src2);
 +
    int            src2_pitch = avs_get_pitch(src2);
 +
 +
Gives read pointers to both our input clips.
 +
 +
    AVS_VideoFrame* dst = avs_new_video_frame(fi->env, &fi->vi);
 +
 +
Creates a new frame according to what we set up in create_Merge.
 +
 +
    BYTE*          dstp = avs_get_write_ptr(dst);
 +
    int            dst_pitch = avs_get_pitch(dst);
 +
    int            dst_rowsize = avs_get_row_size(dst); // in bytes!
 +
    int            dst_height = avs_get_height(dst);
 +
 +
Gives a write pointer to our output clips.
 +
 +
    if (avs_is_rgb32(&fi->vi))
 +
      ch_tot = 4;
 +
    else // rgb24
 +
      ch_tot = 3;
 +
 +
This filter requires RGB24 or RGB32. ch_tot is the number of channels.
 +
 +
    w = params->weight;
 +
 +
The variable w is the input parameter weight. We will use that for better readability.
 +
 +
    for (y=0; y<dst_height; ++y) {
 +
      for (x=0; x<dst_rowsize; x+=ch_tot) {
 +
          dstp[x]  = (int)((1-w)*srcp[x] + w*srcp2[x]+0.5);    // B
 +
          dstp[x+1] = (int)((1-w)*srcp[x+1] + w*srcp2[x+1]+0.5); // G
 +
          dstp[x+2] = (int)((1-w)*srcp[x+2] + w*srcp2[x+2]+0.5); // R
 +
      }
 +
      srcp += src_pitch;
 +
      srcp2 += src2_pitch;
 +
      dstp += dst_pitch;
 +
    }
 +
 +
This code does the actual work. It blends the red, green and blue channels together.
 +
 +
    avs_release_frame(src);
 +
    avs_release_frame(src2);
 +
 +
Once we are done with the video frames of both clips they need to be released. Releasing a video frame does not actually delete it but decrements the reference count. (C++ plugins: recall this happens automatically when returning the destination frame.)
 +
 +
    return dst;
 +
 +
GetFrame returns the newly-created frame.
 +
 +
void AVSC_CC free_Merge(AVS_FilterInfo* fi)
 +
{
 +
    Merge* params = (Merge*) fi->user_data;
 +
    avs_release_clip(params->clip2);
 +
    free(params);
 +
}
 +
 +
There are no constructors and deconstructors in C, but we need something to release memory. This is done by the function free_Merge which is defined above.
 +
 +
AVS_Value AVSC_CC create_Merge(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.
 +
 +
    AVS_Clip* new_clip = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1);
 +
 +
The new clip parameters are initialized from the input clip. fi is filled in according to the parent clip's parameters. You may modify fi afterwards to change the returned clip's parameters.
 +
 +
    Merge *params = (Merge*)malloc(sizeof(Merge));
 +
 +
Allocates memory and create a new instance of params.
 +
 +
    if (!params)
 +
      return avs_void;
 +
 +
When failed to allocate memory, return avs_void.
 +
 +
    if (!avs_is_rgb(&fi->vi)) {
 +
      return avs_new_value_error("Input video must be in RGB format!");
 +
    }
 +
 +
As stated above there are no constructors and deconstructors in C, so the necessary checks on the input parameters must be done here. This is before setting the get_frame callback.
 +
 +
    tmp = avs_array_elt(args, 1);
 +
    if (avs_defined(tmp)) {
 +
      params->clip2 = avs_take_clip(tmp, env);
 +
 +
The second input parameter should be the second clip. It is actually an AVS_Value, and avs_take_clip converts it to a clip. The parser will check that tmp contains a clip (as specified in avs_add_function), so we know it contains a clip at this stage. Below we will check the clip contains video.
 +
 +
      vi2 = avs_get_video_info(params->clip2);
 +
      if (!avs_has_video(vi2)) {
 +
          return avs_new_value_error("Second clip must be a video clip!");
 +
      }
 +
 +
If it has no video, an error is returned.
 +
 +
      else if (!avs_is_color_space(vi2, fi->vi.pixel_type))
 +
      {
 +
          return avs_new_value_error("Second video must have the same pixel-type as the first clip!");
 +
      }
 +
 +
If it has video, but the colorspace (that is the pixeltype) of the second clip is not the same as the one of the first clip, an error is returned.
 +
 +
      else if (vi2->width != fi->vi.width || vi2->height != fi->vi.height)
 +
      {
 +
          return avs_new_value_error("Input and second clip sizes must match!");
 +
      }
 +
 +
If the colorspace is the same, but the dimensions of both clips are different, an error is returned.
 +
 +
    } else {
 +
      return avs_new_value_error("Second clip is missing!");
 +
    }
 +
 +
If the second clip is missing, an error is returned. Although strictly not necessary as explained above, since this check is done by the parser.
 +
 +
    tmp = avs_array_elt(args, 2);
 +
    if (avs_defined(tmp)) {
 +
      params->weight = avs_as_float(tmp);
 +
 +
The third input parameter should be the weight. It is actually an AVS_Value,
 +
 +
      if ((params->weight<0.0f) || (params->weight>1.0f)) {
 +
          return avs_new_value_error("Make sure that '0.0 <= weight <= 1.0'!");
 +
      }
 +
 +
The weight should be in [0,1], if not an error is returned.
 +
 +
    } else {
 +
      params->weight = 0.5; // default value
 +
    }
 +
 +
There are no default values in the C API, so they are set explicitly. Here the weight is set to 0.5 by default.
 +
 +
    fi->user_data = (void*) params;
 +
 +
Store updated params (members of our structure Merge).
 +
 +
    fi->get_frame = Merge_get_frame;
 +
 +
Here we set up FilterInfo:
 +
* For audio-only filters you need to implement get_audio. The remaining two members that can be implemented are set_cache_hints and get_parity.
 +
* For source filters, avs_new_c_filter should be called as avs_new_c_filter(env, &fi, avs_void, 0). Thus with the last parameter, store_child, set to false. In this case all four members (get_frame, get_audio, set_cache_hints and get_parity) should be implemented.
 +
 +
    fi->free_filter = free_Merge;
 +
 +
Clean up everything.
 +
 +
    v = avs_new_value_clip(new_clip);
 +
 +
The return
 +
value is then set to the new clip returned by avs_new_c_filter.
 +
 +
    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 the new clip.
 +
 +
[[Category:C API]]

Latest revision as of 23:17, 13 January 2017

Here is another working Avisynth C plugin. It's called "Merge", and it merges two clips according to a weight.

Here's Merge.c:

#include <stdlib.h>
#include "avisynth_c.h"

typedef struct Merge {
   AVS_Clip* clip2;
   double weight;
} Merge;

AVS_VideoFrame* AVSC_CC Merge_get_frame(AVS_FilterInfo* fi, int n)
{
   Merge* params = (Merge*) fi->user_data;

   AVS_VideoFrame* src = avs_get_frame(fi->child, n);
   const BYTE*     srcp = avs_get_read_ptr(src);
   int             src_pitch = avs_get_pitch(src);

   AVS_VideoFrame* src2 = avs_get_frame(params->clip2, n);
   const BYTE*     srcp2 = avs_get_read_ptr(src2);
   int             src2_pitch = avs_get_pitch(src2);

   AVS_VideoFrame* dst = avs_new_video_frame(fi->env, &fi->vi);
   BYTE*           dstp = avs_get_write_ptr(dst);
   int             dst_pitch = avs_get_pitch(dst);
   int             dst_rowsize = avs_get_row_size(dst); // in bytes!
   int             dst_height = avs_get_height(dst);

   int x, y, ch_tot;
   double w;

   if (avs_is_rgb32(&fi->vi))
      ch_tot = 4;
   else // rgb24
      ch_tot = 3;

   w = params->weight;

   for (y=0; y<dst_height; ++y) {
      for (x=0; x<dst_rowsize; x+=ch_tot) {
         dstp[x]   = (int)((1-w)*srcp[x] + w*srcp2[x]+0.5);     // B
         dstp[x+1] = (int)((1-w)*srcp[x+1] + w*srcp2[x+1]+0.5); // G
         dstp[x+2] = (int)((1-w)*srcp[x+2] + w*srcp2[x+2]+0.5); // R
      }
      srcp += src_pitch;
      srcp2 += src2_pitch;
      dstp += dst_pitch;
   }

   avs_release_frame(src);
   avs_release_frame(src2);

   return dst;
}

void AVSC_CC free_Merge(AVS_FilterInfo* fi)
{
   Merge* params = (Merge*) fi->user_data;
   avs_release_clip(params->clip2);
   free(params);
}

AVS_Value AVSC_CC create_Merge(AVS_ScriptEnvironment* env, AVS_Value args, void* user_data)
{
   AVS_Value v;
   AVS_Value tmp;
   AVS_FilterInfo* fi;
   const AVS_VideoInfo* vi2;

   AVS_Clip* new_clip = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1);

   Merge *params = (Merge*)malloc(sizeof(Merge));
   if (!params)
      return avs_void;

   if (!avs_is_rgb(&fi->vi)) {
      return avs_new_value_error("Input video must be in RGB format!");
   }

   tmp = avs_array_elt(args, 1);
   if (avs_defined(tmp)) {
      params->clip2 = avs_take_clip(tmp, env);
      vi2 = avs_get_video_info(params->clip2);
      if (!avs_has_video(vi2)) {
         return avs_new_value_error("Second clip must be a video clip!");
      } 
      else if (!avs_is_color_space(vi2, fi->vi.pixel_type))
      {
         return avs_new_value_error("Second video must have the same pixel-type as the first clip!");
      } 
      else if (vi2->width != fi->vi.width || vi2->height != fi->vi.height)
      {
         return avs_new_value_error("Input and second clip sizes must match!");
      }
   } else {
      return avs_new_value_error("Second clip is missing!");
   }

   tmp = avs_array_elt(args, 2);
   if (avs_defined(tmp)) {
      params->weight = avs_as_float(tmp);
      if ((params->weight<0.0f) || (params->weight>1.0f)) {
         return avs_new_value_error("Make sure that '0.0 <= weight <= 1.0'!");
      }
   } else {
      params->weight = 0.5f; // default value
   }

   fi->user_data = (void*) params;
   fi->get_frame = Merge_get_frame;
   fi->free_filter = free_Merge;

   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, "Merge", "cc[weight]f", create_Merge, 0);
   return "Merge sample C plugin";
}

[edit] Line by line breakdown

Here's a line-by-line breakdown of Merge.c. I won't repeat the comments in the previous example InvertNeg.c, so read that first if needed. The declaration of the class is as follows

#include <stdlib.h> // free, malloc

This header defines free and malloc which we will need later on.

Note one could also include windows.h instead. However this gives a 'warning C4005: 'EXTERN_C' : macro redefinition' when compiling with MSVC++. I guess this needs to be fixed avisynth_c.h.

typedef struct Merge {
   AVS_Clip* clip2;
   double weight;
} Merge; // this name (ie Merge) is an alias for 'struct Merge'

In C we don't have classes. We will use a structure to declare our parameter variables (clip2 and weight in this example). The members of a structure are public by default (C++ plugins: recall that in a C++ class they are private by default).

typedef allows you to declare instances of a structure without using keyword "struct", since the second 'Merge' is an alias for 'struct Merge'. For more information have a look here and here.

AVS_VideoFrame* AVSC_CC Merge_get_frame(AVS_FilterInfo* fi, int n)

This method is called to make our filter produce frame n of its output.

   Merge* params = (Merge*) fi->user_data;

This fetches instance parameters.

   AVS_VideoFrame* src = avs_get_frame(fi->child, n);
   const BYTE*     srcp = avs_get_read_ptr(src);
   int             src_pitch = avs_get_pitch(src);

   AVS_VideoFrame* src2 = avs_get_frame(params->clip2, n);
   const BYTE*     srcp2 = avs_get_read_ptr(src2);
   int             src2_pitch = avs_get_pitch(src2);

Gives read pointers to both our input clips.

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

Creates a new frame according to what we set up in create_Merge.

   BYTE*           dstp = avs_get_write_ptr(dst);
   int             dst_pitch = avs_get_pitch(dst);
   int             dst_rowsize = avs_get_row_size(dst); // in bytes!
   int             dst_height = avs_get_height(dst);

Gives a write pointer to our output clips.

   if (avs_is_rgb32(&fi->vi))
      ch_tot = 4;
   else // rgb24
      ch_tot = 3;

This filter requires RGB24 or RGB32. ch_tot is the number of channels.

   w = params->weight;

The variable w is the input parameter weight. We will use that for better readability.

   for (y=0; y<dst_height; ++y) {
      for (x=0; x<dst_rowsize; x+=ch_tot) {
         dstp[x]   = (int)((1-w)*srcp[x] + w*srcp2[x]+0.5);     // B
         dstp[x+1] = (int)((1-w)*srcp[x+1] + w*srcp2[x+1]+0.5); // G
         dstp[x+2] = (int)((1-w)*srcp[x+2] + w*srcp2[x+2]+0.5); // R
      }
      srcp += src_pitch;
      srcp2 += src2_pitch;
      dstp += dst_pitch;
   }

This code does the actual work. It blends the red, green and blue channels together.

   avs_release_frame(src);
   avs_release_frame(src2);

Once we are done with the video frames of both clips they need to be released. Releasing a video frame does not actually delete it but decrements the reference count. (C++ plugins: recall this happens automatically when returning the destination frame.)

   return dst;

GetFrame returns the newly-created frame.

void AVSC_CC free_Merge(AVS_FilterInfo* fi)
{
   Merge* params = (Merge*) fi->user_data;
   avs_release_clip(params->clip2);
   free(params);
}

There are no constructors and deconstructors in C, but we need something to release memory. This is done by the function free_Merge which is defined above.

AVS_Value AVSC_CC create_Merge(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.

   AVS_Clip* new_clip = avs_new_c_filter(env, &fi, avs_array_elt(args, 0), 1);

The new clip parameters are initialized from the input clip. fi is filled in according to the parent clip's parameters. You may modify fi afterwards to change the returned clip's parameters.

   Merge *params = (Merge*)malloc(sizeof(Merge));

Allocates memory and create a new instance of params.

   if (!params)
      return avs_void;

When failed to allocate memory, return avs_void.

   if (!avs_is_rgb(&fi->vi)) {
      return avs_new_value_error("Input video must be in RGB format!");
   }

As stated above there are no constructors and deconstructors in C, so the necessary checks on the input parameters must be done here. This is before setting the get_frame callback.

   tmp = avs_array_elt(args, 1);
   if (avs_defined(tmp)) {
      params->clip2 = avs_take_clip(tmp, env);

The second input parameter should be the second clip. It is actually an AVS_Value, and avs_take_clip converts it to a clip. The parser will check that tmp contains a clip (as specified in avs_add_function), so we know it contains a clip at this stage. Below we will check the clip contains video.

      vi2 = avs_get_video_info(params->clip2);
      if (!avs_has_video(vi2)) {
         return avs_new_value_error("Second clip must be a video clip!");
      }

If it has no video, an error is returned.

      else if (!avs_is_color_space(vi2, fi->vi.pixel_type))
      {
         return avs_new_value_error("Second video must have the same pixel-type as the first clip!");
      }

If it has video, but the colorspace (that is the pixeltype) of the second clip is not the same as the one of the first clip, an error is returned.

      else if (vi2->width != fi->vi.width || vi2->height != fi->vi.height)
      {
         return avs_new_value_error("Input and second clip sizes must match!");
      }

If the colorspace is the same, but the dimensions of both clips are different, an error is returned.

   } else {
      return avs_new_value_error("Second clip is missing!");
   }

If the second clip is missing, an error is returned. Although strictly not necessary as explained above, since this check is done by the parser.

   tmp = avs_array_elt(args, 2);
   if (avs_defined(tmp)) {
      params->weight = avs_as_float(tmp);

The third input parameter should be the weight. It is actually an AVS_Value,

      if ((params->weight<0.0f) || (params->weight>1.0f)) {
         return avs_new_value_error("Make sure that '0.0 <= weight <= 1.0'!");
      }

The weight should be in [0,1], if not an error is returned.

   } else {
      params->weight = 0.5; // default value
   }

There are no default values in the C API, so they are set explicitly. Here the weight is set to 0.5 by default.

   fi->user_data = (void*) params;

Store updated params (members of our structure Merge).

   fi->get_frame = Merge_get_frame;

Here we set up FilterInfo:

  • For audio-only filters you need to implement get_audio. The remaining two members that can be implemented are set_cache_hints and get_parity.
  • For source filters, avs_new_c_filter should be called as avs_new_c_filter(env, &fi, avs_void, 0). Thus with the last parameter, store_child, set to false. In this case all four members (get_frame, get_audio, set_cache_hints and get_parity) should be implemented.
   fi->free_filter = free_Merge;

Clean up everything.

   v = avs_new_value_clip(new_clip);

The return value is then set to the new clip returned by avs_new_c_filter.

   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 the new clip.

Personal tools