From 53bac1b4bd2a2a3b94ee92713e980a39cd8672a3 Mon Sep 17 00:00:00 2001 From: Clément Bœsch Date: Sun, 4 May 2014 00:56:59 +0200 Subject: doc: add a tutorial for writing filters. --- doc/writing_filters.txt | 424 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 424 insertions(+) create mode 100644 doc/writing_filters.txt diff --git a/doc/writing_filters.txt b/doc/writing_filters.txt new file mode 100644 index 0000000000..c7923e884f --- /dev/null +++ b/doc/writing_filters.txt @@ -0,0 +1,424 @@ +This document is a tutorial/initiation for writing simple filters in +libavfilter. + +Foreword: just like everything else in FFmpeg, libavfilter is monolithic, which +means that it is highly recommended that you submit your filters to the FFmpeg +development mailing-list and make sure it is applied. Otherwise, your filter is +likely to have a very short lifetime due to more a less regular internal API +changes, and a limited distribution, review, and testing. + +Bootstrap +========= + +Let's say you want to write a new simple video filter called "foobar" which +takes one frame in input, changes the pixels in whatever fashion you fancy, and +outputs the modified frame. The most simple way of doing this is to take a +similar filter. We'll pick edgedetect, but any other should do. You can look +for others using the `./ffmpeg -v 0 -filters|grep ' V->V '` command. + + - cp libavfilter/vf_{edgedetect,foobar}.c + - sed -i s/edgedetect/foobar/g -i libavfilter/vf_foobar.c + - sed -i s/EdgeDetect/Foobar/g -i libavfilter/vf_foobar.c + - edit libavfilter/Makefile, and add an entry for "foobar" following the + pattern of the other filters. + - edit libavfilter/allfilters.c, and add an entry for "foobar" following the + pattern of the other filters. + - ./configure ... + - make -j ffmpeg + - ./ffmpeg -i tests/lena.pnm -vf foobar foobar.png + +If everything went right, you should get a foobar.png with Lena edge-detected. + +That's it, your new playground is ready. + +Some little details about what's going on: +libavfilter/allfilters.c:avfilter_register_all() is called at runtime to create +a list of the available filters, but it's important to know that this file is +also parsed by the configure script, which in turn will define variables for +the build system and the C: + + --- after running configure --- + + $ grep FOOBAR config.mak + CONFIG_FOOBAR_FILTER=yes + $ grep FOOBAR config.h + #define CONFIG_FOOBAR_FILTER 1 + +CONFIG_FOOBAR_FILTER=yes from the config.mak is later used to enable the filter in +libavfilter/Makefile and CONFIG_FOOBAR_FILTER=1 from the config.h will be used +for registering the filter in libavfilter/allfilters.c. + +Filter code layout +================== + +You now need some theory about the general code layout of a filter. Open your +libavfilter/vf_foobar.c. This section will detail the important parts of the +code you need to understand before messing with it. + +Copyright +--------- + +First chunk is the copyright. Most filters are LGPL, and we are assuming +vf_foobar is as well. We are also assuming vf_foobar is not an edge detector +filter, so you can update the boilerplate with your credits. + +Doxy +---- + +Next chunk is the Doxygen about the file. See http://ffmpeg.org/doxygen/trunk/. +Detail here what the filter is, does, and add some references if you feel like +it. + +Context +------- + +Skip the headers and scroll down to the definition of FoobarContext. This is +your local state context. It is already filled with 0 when you get it so do not +worry about uninitialized read into this context. This is where you put every +"global" information you need, typically the variable storing the user options. +You'll notice the first field "const AVClass *class"; it's the only field you +need to keep assuming you have a context. There are some magic you don't care +about around this field, just let it be (in first position) for now. + +Options +------- + +Then comes the options array. This is what will define the user accessible +options. For example, -vf foobar=mode=colormix:high=0.4:low=0.1. Most options +have the following pattern: + name, description, offset, type, default value, minimum value, maximum value, flags + + - name is the option name, keep it simple, lowercase + - description are short, in lowercase, without period, and describe what they + do, for example "set the foo of the bar" + - offset is the offset of the field in your local context, see the OFFSET() + macro; the option parser will use that information to fill the fields + according to the user input + - type is any of AV_OPT_TYPE_* defined in libavutil/opt.h + - default value is an union where you pick the appropriate type; "{.dbl=0.3}", + "{.i64=0x234}", "{.str=NULL}", ... + - min and max values define the range of available values, inclusive + - flags are AVOption generic flags. See AV_OPT_FLAG_* definitions + +In doubt, just look at the other AVOption definitions all around the codebase, +there are tons of examples. + +Class +----- + +AVFILTER_DEFINE_CLASS(foobar) will define a unique foobar_class with some kind +of signature referencing the options, etc. which will be referenced in the +definition of the AVFilter. + +Filter definition +----------------- + +At the end of the file, you will find foobar_inputs, foobar_outputs and +the AVFilter ff_vf_foobar. Don't forget to update the AVFilter.description with +a description of what the filter does, starting with a capitalized letter and +ending with a period. You'd better drop the AVFilter.flags entry for now, and +re-add them later depending on the capabilities of your filter. + +Callbacks +--------- + +Let's now study the common callbacks. Before going into details, note that all +these callbacks are explained in details in libavfilter/avfilter.h, so in +doubt, refer to the doxy in that file. + +init() +~~~~~~ + +First one to be called is init(). It's flagged as cold because not called +often. Look for "cold" on +http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html for more +information. + +As the name suggests, init() is where you eventually initialize and allocate +your buffers, pre-compute your data, etc. Note that at this point, your local +context already has the user options initialized, but you still haven't any +clue about the kind of data input you will get, so this function is often +mainly used to sanitize the user options. + +Some init()s will also define the number of inputs or outputs dynamically +according to the user options. A good example of this is the split filter, but +we won't cover this here since vf_foobar is just a simple 1:1 filter. + +uninit() +~~~~~~~~ + +Similarly, there is the uninit() callback, doing what the name suggest. Free +everything you allocated here. + +query_formats() +~~~~~~~~~~~~~~~ + +This is following the init() and is used for the format negotiation, basically +where you say what pixel format(s) (gray, rgb 32, yuv 4:2:0, ...) you accept +for your inputs, and what you can output. All pixel formats are defined in +libavutil/pixfmt.h. If you don't change the pixel format between the input and +the output, you just have to define a pixel formats array and call +ff_set_common_formats(). For more complex negotiation, you can refer to other +filters such as vf_scale. + +config_props() +~~~~~~~~~~~~~~ + +This callback is not necessary, but you will probably have one or more +config_props() anyway. It's not a callback for the filter itself but for its +inputs or outputs (they're called "pads" - AVFilterPad - in libavfilter's +lexicon). + +Inside the input config_props(), you are at a point where you know which pixel +format has been picked after query_formats(), and more information such as the +video width and height (inlink->{w,h}). So if you need to update your internal +context state depending on your input you can do it here. In edgedetect you can +see that this callback is used to allocate buffers depending on these +information. They will be destroyed in uninit(). + +Inside the output config_props(), you can define what you want to change in the +output. Typically, if your filter is going to double the size of the video, you +will update outlink->w and outlink->h. + +filter_frame() +~~~~~~~~~~~~~~ + +This is the callback you are waiting from the beginning: it is where you +process the received frames. Along with the frame, you get the input link from +where the frame comes from. + + static int filter_frame(AVFilterLink *inlink, AVFrame *in) { ... } + +You can get the filter context through that input link: + + AVFilterContext *ctx = inlink->dst; + +Then access your internal state context: + + FoobarContext *foobar = ctx->priv; + +And also the output link where you will send your frame when you are done: + + AVFilterLink *outlink = ctx->outputs[0]; + +Here, we are picking the first output. You can have several, but in our case we +only have one since we are in a 1:1 input-output situation. + +If you want to define a simple pass-through filter, you can just do: + + return ff_filter_frame(outlink, in); + +But of course, you probably want to change the data of that frame. + +This can be done by accessing frame->data[] and frame->linesize[]. Important +note here: the width does NOT match the linesize. The linesize is always +greater or equal to the width. The padding created should not be changed or +even read. Typically, keep in mind that a previous filter in your chain might +have altered the frame dimension but not the linesize. Imagine a crop filter +that halves the video size: the linesizes won't be changed, just the width. + + <-------------- linesize ------------------------> + +-------------------------------+----------------+ ^ + | | | | + | | | | + | picture | padding | | height + | | | | + | | | | + +-------------------------------+----------------+ v + <----------- width -------------> + +Before modifying the "in" frame, you have to make sure it is writable, or get a +new one. Multiple scenarios are possible here depending on the kind of +processing you are doing. + +Let's say you want to change one pixel depending on multiple pixels (typically +the surrounding ones) of the input. In that case, you can't do an in-place +processing of the input so you will need to allocate a new frame, with the same +properties as the input one, and send that new frame to the next filter: + + AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + + // out->data[...] = foobar(in->data[...]) + + av_frame_free(&in); + return ff_filter_frame(outlink, out); + +In-place processing +~~~~~~~~~~~~~~~~~~~ + +If you can just alter the input frame, you probably just want to do that +instead: + + av_frame_make_writable(in); + // in->data[...] = foobar(in->data[...]) + return ff_filter_frame(outlink, in); + +You may wonder why a frame might not be writable. The answer is that for +example a previous filter might still own the frame data: imagine a filter +prior to yours in the filtergraph that needs to cache the frame. You must not +alter that frame, otherwise it will make that previous filter buggy. This is +where av_frame_make_writable() helps (it won't have any effect if the frame +already is writable). + +The problem with using av_frame_make_writable() is that in the worst case it +will copy the whole input frame before you change it all over again with your +filter: if the frame is not writable, av_frame_make_writable() will allocate +new buffers, and copy the input frame data. You don't want that, and you can +avoid it by just allocating a new buffer if necessary, and process from in to +out in your filter, saving the memcpy. Generally, this is done following this +scheme: + + int direct = 0; + AVFrame *out; + + if (av_frame_is_writable(in)) { + direct = 1; + out = in; + } else { + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + // out->data[...] = foobar(in->data[...]) + + if (!direct) + av_frame_free(&in); + return ff_filter_frame(outlink, out); + +Of course, this will only work if you can do in-place processing. To test if +your filter handles well the permissions, you can use the perms filter. For +example with: + + -vf perms=random,foobar + +Make sure no automatic pixel conversion is inserted between perms and foobar, +otherwise the frames permissions might change again and the test will be +meaningless: add av_log(0,0,"direct=%d\n",direct) in your code to check that. +You can avoid the issue with something like: + + -vf format=rgb24,perms=random,foobar + +...assuming your filter accepts rgb24 of course. This will make sure the +necessary conversion is inserted before the perms filter. + +Timeline +~~~~~~~~ + +Adding timeline support +(http://ffmpeg.org/ffmpeg-filters.html#Timeline-editing) is often an easy +feature to add. In the most simple case, you just have to add +AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC to the AVFilter.flags. You can typically +do this when your filter does not need to save the previous context frames, or +basically if your filter just alter whatever goes in and doesn't need +previous/future information. See for instance commit 86cb986ce that adds +timeline support to the fieldorder filter. + +In some cases, you might need to reset your context somehow. This is handled by +the AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL flag which is used if the filter +must not process the frames but still wants to keep track of the frames going +through (to keep them in cache for when it's enabled again). See for example +commit 69d72140a that adds timeline support to the phase filter. + +Threading +~~~~~~~~~ + +libavfilter does not yet support frame threading, but you can add slice +threading to your filters. + +Let's say the foobar filter has the following frame processing function: + + dst = out->data[0]; + src = in ->data[0]; + + for (y = 0; y < inlink->h; y++) { + for (x = 0; x < inlink->w; x++) + dst[x] = foobar(src[x]); + dst += out->linesize[0]; + src += in ->linesize[0]; + } + +The first thing is to make this function work into slices. The new code will +look like this: + + for (y = slice_start; y < slice_end; y++) { + for (x = 0; x < inlink->w; x++) + dst[x] = foobar(src[x]); + dst += out->linesize[0]; + src += in ->linesize[0]; + } + +The source and destination pointers, and slice_start/slice_end will be defined +according to the number of jobs. Generally, it looks like this: + + const int slice_start = (in->height * jobnr ) / nb_jobs; + const int slice_end = (in->height * (jobnr+1)) / nb_jobs; + uint8_t *dst = out->data[0] + slice_start * out->linesize[0]; + const uint8_t *src = in->data[0] + slice_start * in->linesize[0]; + +This new code will be isolated in a new filter_slice(): + + static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) { ... } + +Note that we need our input and output frame to define slice_{start,end} and +dst/src, which are not available in that callback. They will be transmitted +through the opaque void *arg. You have to define a structure which contains +everything you need: + + typedef struct ThreadData { + AVFrame *in, *out; + } ThreadData; + +If you need some more information from your local context, put them here. + +In you filter_slice function, you access it like that: + + const ThreadData *td = arg; + +Then in your filter_frame() callback, you need to call the threading +distributor with something like this: + + ThreadData td; + + // ... + + td.in = in; + td.out = out; + ctx->internal->execute(ctx, filter_slice, &td, NULL, FFMIN(outlink->h, ctx->graph->nb_threads)); + + // ... + + return ff_filter_frame(outlink, out); + +Last step is to add AVFILTER_FLAG_SLICE_THREADS flag to AVFilter.flags. + +For more example of slice threading additions, you can try to run git log -p +--grep 'slice threading' libavfilter/ + +Finalization +~~~~~~~~~~~~ + +When your awesome filter is finished, you have a few more steps before you're +done: + + - write its documentation in doc/filters.texi, and test the output with make + doc/ffmpeg-filters.html. + - add a FATE test, generally by adding an entry in + tests/fate/filter-video.mak, add running make fate-filter-foobar GEN=1 to + generate the data. + - add an entry in the Changelog + - edit libavfilter/version.h and increase LIBAVFILTER_VERSION_MINOR by one + (and reset LIBAVFILTER_VERSION_MICRO to 100) + - git add ... && git commit -m "avfilter: add foobar filter." && git format-patch -1 + +When all of this is done, you can submit your patch to the ffmpeg-devel +mailing-list for review. If you need any help, feel free to come on our IRC +channel, #ffmpeg-devel on irc.freenode.net. -- cgit v1.2.3