Filter SDK/CMerge

From Avisynth wiki
(Difference between revisions)
Jump to: navigation, search
(finish later)
Line 236: Line 236:
 
     }
 
     }
  
...
+
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);
 
     tmp = avs_array_elt(args, 1);
 
     if (avs_defined(tmp)) {
 
     if (avs_defined(tmp)) {
 
       params->clip2 = avs_take_clip(tmp, env);
 
       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);
 
       vi2 = avs_get_video_info(params->clip2);
 
       if (!avs_has_video(vi2)) {
 
       if (!avs_has_video(vi2)) {
 
           return avs_new_value_error("Second clip must be a video clip!");
 
           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))
 
       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!");
 
           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)
 
       else if (vi2->width != fi->vi.width || vi2->height != fi->vi.height)
 
       {
 
       {
 
           return avs_new_value_error("Input and second clip sizes must match!");
 
           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 {
 
     } else {
 
       return avs_new_value_error("Second clip is missing!");
 
       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);
 
     tmp = avs_array_elt(args, 2);
 
     if (avs_defined(tmp)) {
 
     if (avs_defined(tmp)) {
 
       params->weight = avs_as_float(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)) {
 
       if ((params->weight<0.0f) || (params->weight>1.0f)) {
 
           return avs_new_value_error("Make sure that '0.0 <= weight <= 1.0'!");
 
           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 {
 
     } else {
 
       params->weight = 0.5; // default value
 
       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;
 
     fi->user_data = (void*) params;
 
     fi->get_frame = Merge_get_frame;
 
     fi->get_frame = Merge_get_frame;
 
     fi->free_filter = free_Merge;
 
     fi->free_filter = free_Merge;
 +
 +
Here we set up FilterInfo:
 +
* For audio-only filters you need to implement get_audio. Other 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 the last parameter, store_child, is set to false. In this case all four members (get_frame, get_audio, set_cache_hints and get_parity) should be implemented.
 +
* user_data ...
 +
* free_filter ...
 
   
 
   
 
     v = avs_new_value_clip(new_clip);
 
     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);
 
     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;
 
     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)
+
 
{
+
[[Category:C API]]
    avs_add_function(env, "Merge", "cc[weight]f", create_Merge, 0);
+
    return "Merge sample C plugin";
+
}
+

Revision as of 23:30, 12 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";
}

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;
   fi->get_frame = Merge_get_frame;
   fi->free_filter = free_Merge;

Here we set up FilterInfo:

  • For audio-only filters you need to implement get_audio. Other 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 the last parameter, store_child, is set to false. In this case all four members (get_frame, get_audio, set_cache_hints and get_parity) should be implemented.
  • user_data ...
  • free_filter ...
   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. "v" is either an error or the new clip.

Personal tools