Ticket #4143: 0002-lavf-Add-coreimage-filter-for-GPU-based-image-filter.patch

File 0002-lavf-Add-coreimage-filter-for-GPU-based-image-filter.patch, 28.8 KB (added by Thilo Borgmann, 10 years ago)

Preliminary patch from ffmpeg-devel

  • Changelog

    From 4aef8c0d09e109cedd92e17cc04a6ef6236c07ab Mon Sep 17 00:00:00 2001
    From: Thilo Borgmann <thilo.borgmann@mail.de>
    Date: Sun, 13 Mar 2016 21:08:18 +0100
    Subject: [PATCH 2/2] lavf: Add coreimage filter for GPU based image filtering
     on OSX.
    
    ---
     Changelog                  |   1 +
     MAINTAINERS                |   1 +
     configure                  |   2 +
     doc/filters.texi           |  67 ++++++
     libavfilter/Makefile       |   1 +
     libavfilter/allfilters.c   |   1 +
     libavfilter/vf_coreimage.m | 551 +++++++++++++++++++++++++++++++++++++++++++++
     7 files changed, 624 insertions(+)
     create mode 100644 libavfilter/vf_coreimage.m
    
    diff --git a/Changelog b/Changelog
    index 1f57f5e..5053a86 100644
    a b version <next>:  
    1212- ciescope filter
    1313- protocol blacklisting API
    1414- MediaCodec H264 decoding
     15- coreimage filter (GPU based image filtering on OSX)
    1516
    1617
    1718version 3.0:
  • MAINTAINERS

    diff --git a/MAINTAINERS b/MAINTAINERS
    index 531c21d..a993a67 100644
    a b Filters:  
    370370  vf_colorbalance.c                     Paul B Mahol
    371371  vf_colorkey.c                         Timo Rothenpieler
    372372  vf_colorlevels.c                      Paul B Mahol
     373  vf_coreimage.m                        Thilo Borgmann
    373374  vf_deband.c                           Paul B Mahol
    374375  vf_dejudder.c                         Nicholas Robbins
    375376  vf_delogo.c                           Jean Delvare (CC <jdelvare@suse.com>)
  • configure

    diff --git a/configure b/configure
    index 1b189328..da51e06 100755
    a b frei0r_filter_extralibs='$ldl'  
    52555255frei0r_src_filter_extralibs='$ldl'
    52565256ladspa_filter_extralibs='$ldl'
    52575257nvenc_encoder_extralibs='$ldl'
     5258coreimage_filter_extralibs="-framework QuartzCore -framework AppKit -framework OpenGL"
    52585259
    52595260if ! disabled network; then
    52605261    check_func getaddrinfo $network_extralibs
    enabled avisynth && { { check_lib2 "windows.h" LoadLibrary; } ||  
    54835484                               die "ERROR: LoadLibrary/dlopen not found for avisynth"; }
    54845485enabled cuda              && check_lib cuda.h cuInit -lcuda
    54855486enabled chromaprint       && require chromaprint chromaprint.h chromaprint_get_version -lchromaprint
     5487enabled coreimage_filter  && { check_header_objcc QuartzCore/CoreImage.h || disable coreimage_filter; }
    54865488enabled decklink          && { check_header DeckLinkAPI.h || die "ERROR: DeckLinkAPI.h header not found"; }
    54875489enabled frei0r            && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; }
    54885490enabled gmp               && require2 gmp gmp.h mpz_export -lgmp
  • doc/filters.texi

    diff --git a/doc/filters.texi b/doc/filters.texi
    index d5d619e..7d0bb26 100644
    a b convolution="-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 -1 0 -1 1 1 0 1 2:-2 -  
    49554955Copy the input source unchanged to the output. This is mainly useful for
    49564956testing purposes.
    49574957
     4958@anchor{coreimage}
     4959@section coreimage
     4960
     4961Video filtering on GPU using Apple's CoreImage API on OSX.
     4962
     4963Hardware acceleration is based on an OpenGL context. Usually, this means it is processed by video hardware. However, software-based OpenGL implementations exist which means there is no guarantee for hardware processing. It depends on the respective OSX.
     4964
     4965There are many filters and image generators provided by Apple that come with a large variety of options. The filter has to be referenced by its name along with its options.
     4966
     4967The coreimage filter accepts the following options:
     4968@table @option
     4969@item list_filters
     4970List all available filters along with all their respective options as well as possible minimum and maximum values along with the default values.
     4971@example
     4972    coreimage=list_filters=true
     4973@end example
     4974
     4975@item filter
     4976Specifiy all filters by their respective name and options.
     4977Use @var{list_filters} to determine all valid filter names and options.
     4978Numerical options are specified by a float value and are automatically clamped to their respective value range.
     4979Vector and color options have to be specified by a list of space separated float values. Character escaping has to be done.
     4980A special option name @code{default} is available to use default options for a filter.
     4981It is required to specify either @code{default} or at least one of the filter options.
     4982All omitted options are used with their default values.
     4983The syntax of the filter string is as follows:
     4984@example
     4985filter=<NAME>@@<OPTION>=<VALUE>[@@<OPTION>=<VALUE>][@@...][#<NAME>@@<OPTION>=<VALUE>[@@<OPTION>=<VALUE>][@@...]]
     4986@end example
     4987@end table
     4988
     4989Several filters can be chained for successive processing without GPU-HOST transfers allowing for fast processing of complex filter chains.
     4990Currently, only filters with zero (generators) or exactly one (filters) input image and one output image are supported.
     4991Also, transition filters are not yet usable as intended.
     4992
     4993Some filters generate output images with additional padding depending on the respective filter kernel. The padding is automatically removed to ensure the filter output has the same size as the input image.
     4994For image generators, the size of the output image is determined by the given input image. The generators do not use the pixel information of the input image to generate their output. However, the generated output is blended onto the input image, resulting in partial or complete coverage of the output image.
     4995
     4996@subsection Examples
     4997
     4998@itemize
     4999
     5000@item
     5001List all filters available:
     5002@example
     5003coreimage=list_filters=true
     5004@end example
     5005
     5006@item
     5007Use the CIBoxBlur filter with default options to blur an image:
     5008@example
     5009coreimage=filter=CIBoxBlur@@default
     5010@end example
     5011
     5012@item
     5013Use a filter chain with CISepiaTone at default values and CIVignetteEffect with its center at 100x100 and a radius of 50 pixels:
     5014@example
     5015coreimage=filter=CIBoxBlur@@default#CIVignetteEffect@@inputCenter=100\ 100@@inputRadius=50
     5016@end example
     5017
     5018@item
     5019Use nullsrc and CIQRCodeGenerator to create a QR code for the FFmpeg homepage, given as complete and escaped command-line for Apple's standard bash shell:
     5020@example
     5021./ffmpeg -f lavfi -i nullsrc=s=100x100,coreimage=filter=CIQRCodeGenerator@@inputMessage=https\\\\\://FFmpeg.org/@@inputCorrectionLevel=H -frames:v 1 QRCode.png
     5022@end example
     5023@end itemize
     5024
    49585025@section crop
    49595026
    49605027Crop the input video to given dimensions.
  • libavfilter/Makefile

    diff --git a/libavfilter/Makefile b/libavfilter/Makefile
    index 956a077..9ce6559 100644
    a b OBJS-$(CONFIG_COLORLEVELS_FILTER) += vf_colorlevels.o  
    133133OBJS-$(CONFIG_COLORMATRIX_FILTER)            += vf_colormatrix.o
    134134OBJS-$(CONFIG_CONVOLUTION_FILTER)            += vf_convolution.o
    135135OBJS-$(CONFIG_COPY_FILTER)                   += vf_copy.o
     136OBJS-$(CONFIG_COREIMAGE_FILTER)              += vf_coreimage.o
    136137OBJS-$(CONFIG_COVER_RECT_FILTER)             += vf_cover_rect.o lavfutils.o
    137138OBJS-$(CONFIG_CROP_FILTER)                   += vf_crop.o
    138139OBJS-$(CONFIG_CROPDETECT_FILTER)             += vf_cropdetect.o
  • libavfilter/allfilters.c

    diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
    index e5080b5..91b0dde 100644
    a b void avfilter_register_all(void)  
    154154    REGISTER_FILTER(COLORMATRIX,    colormatrix,    vf);
    155155    REGISTER_FILTER(CONVOLUTION,    convolution,    vf);
    156156    REGISTER_FILTER(COPY,           copy,           vf);
     157    REGISTER_FILTER(COREIMAGE,      coreimage,      vf);
    157158    REGISTER_FILTER(COVER_RECT,     cover_rect,     vf);
    158159    REGISTER_FILTER(CROP,           crop,           vf);
    159160    REGISTER_FILTER(CROPDETECT,     cropdetect,     vf);
  • new file libavfilter/vf_coreimage.m

    diff --git a/libavfilter/vf_coreimage.m b/libavfilter/vf_coreimage.m
    new file mode 100644
    index 0000000..283f62f
    - +  
     1/*
     2 * Copyright (c) 2016 Thilo Borgmann
     3 *
     4 * This file is part of FFmpeg.
     5 *
     6 * FFmpeg is free software; you can redistribute it and/or
     7 * modify it under the terms of the GNU Lesser General Public
     8 * License as published by the Free Software Foundation; either
     9 * version 2.1 of the License, or (at your option) any later version.
     10 *
     11 * FFmpeg is distributed in the hope that it will be useful,
     12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14 * Lesser General Public License for more details.
     15 *
     16 * You should have received a copy of the GNU Lesser General Public
     17 * License along with FFmpeg; if not, write to the Free Software
     18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
     19 */
     20
     21/**
     22 * @file
     23 * Video processing based on Apple's CoreImage API
     24 */
     25
     26#import <QuartzCore/CoreImage.h>
     27#import <AppKit/AppKit.h>
     28
     29#include "avfilter.h"
     30#include "formats.h"
     31#include "internal.h"
     32#include "video.h"
     33#include "libavutil/internal.h"
     34#include "libavutil/opt.h"
     35#include "libavutil/pixdesc.h"
     36
     37typedef struct CoreImageContext {
     38    const AVClass *class;
     39
     40    CFTypeRef       glctx;              ///< OpenGL context
     41    CGContextRef    cgctx;              ///< Bitmap context for image copy
     42    CFTypeRef       input_image;        ///< Input image container for passing into Core Image API
     43    CGColorSpaceRef color_space;        ///< Common color space for input image and cgcontext
     44    int             bits_per_component; ///< Shared bpc for input-output operation
     45
     46    char            *filter_string;     ///< The complete user provided filter definition
     47    CFTypeRef       *filters;           ///< CIFilter object for all requested filters
     48    int             num_filters;        ///< Amount of filters in *filters
     49
     50    bool            list_filters;       ///< Option used to list all available filters
     51} CoreImageContext;
     52
     53/** Determine image properties from input link of filter chain.
     54 */
     55static int config_input(AVFilterLink *link)
     56{
     57    CoreImageContext *ctx          = link->dst->priv;
     58    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
     59    ctx->bits_per_component        = av_get_bits_per_pixel(desc) / desc->nb_components;
     60
     61    return 0;
     62}
     63
     64/** Print a list of all available filters including options and respective value ranges and defaults.
     65 */
     66static void list_filters(CoreImageContext *ctx)
     67{
     68    // querying filters and attributes
     69    NSArray *filter_names = [CIFilter filterNamesInCategories:nil];
     70    NSEnumerator *filters = [filter_names objectEnumerator];
     71
     72    NSString *filter_name;
     73    while (filter_name = [filters nextObject]) {
     74        av_log(ctx, AV_LOG_INFO, "Filter: %s\n", [filter_name UTF8String]);
     75        NSString *input;
     76
     77        CIFilter *filter             = [CIFilter filterWithName:filter_name];
     78        NSDictionary *filter_attribs = [filter attributes]; // <nsstring, id>
     79        NSArray      *filter_inputs  = [filter inputKeys];  // <nsstring>
     80
     81        for (input in filter_inputs) {
     82            NSDictionary *input_attribs = [filter_attribs valueForKey:input];
     83            NSString *input_class       = [input_attribs valueForKey:kCIAttributeClass];
     84            if ([input_class isEqualToString:@"NSNumber"]) {
     85                NSNumber *value_default = [input_attribs valueForKey:kCIAttributeDefault];
     86                NSNumber *value_min     = [input_attribs valueForKey:kCIAttributeSliderMin];
     87                NSNumber *value_max     = [input_attribs valueForKey:kCIAttributeSliderMax];
     88
     89                av_log(ctx, AV_LOG_INFO, "\tOption: %s\t[%s]\t[%s %s][%s]\n",
     90                    [input UTF8String],
     91                    [input_class UTF8String],
     92                    [[value_min stringValue] UTF8String],
     93                    [[value_max stringValue] UTF8String],
     94                    [[value_default stringValue] UTF8String]);
     95            } else {
     96                av_log(ctx, AV_LOG_INFO, "\tOption: %s\t[%s]\n",
     97                    [input UTF8String],
     98                    [input_class UTF8String]);
     99            }
     100        }
     101    }
     102}
     103
     104/** Get an appropriate video buffer for filter processing.
     105 */
     106static AVFrame *get_video_buffer(AVFilterLink *link, int w, int h)
     107{
     108    CoreImageContext *ctx = link->dst->priv;
     109    AVFrame *frame;
     110
     111    frame = ff_get_video_buffer(link->dst->outputs[0], w, h);
     112
     113    if (!frame) {
     114        av_log(ctx, AV_LOG_ERROR, "Getting video buffer failed.\n");
     115    }
     116
     117    return frame;
     118}
     119
     120/** Define input and output formats for this filter.
     121 */
     122static int query_formats(AVFilterContext *fctx)
     123{
     124    static const enum AVPixelFormat inout_fmts_rgb[] = {
     125        AV_PIX_FMT_ARGB,
     126        AV_PIX_FMT_NONE
     127    };
     128
     129    AVFilterFormats *inout_formats;
     130    int ret;
     131
     132    if (!(inout_formats = ff_make_format_list(inout_fmts_rgb))) {
     133        return (AVERROR(ENOMEM));
     134    }
     135
     136    if ((ret = ff_formats_ref(inout_formats, &fctx->inputs[0]->out_formats)) < 0 || // out
     137        (ret = ff_formats_ref(inout_formats, &fctx->outputs[0]->in_formats)) < 0) { // in
     138        return ret;
     139    }
     140
     141    return 0;
     142}
     143
     144/** Apply all valid filters successively to the input image.
     145 *  The final output image is copied from the GPU by "drawing" using a bitmap context.
     146 */
     147static int filter_frame(AVFilterLink *link, AVFrame *frame)
     148{
     149    CoreImageContext *ctx = link->dst->priv;
     150    int i;
     151
     152    // assume one input image and one output image for now
     153    if (!frame->data[0]) {
     154        av_log(ctx, AV_LOG_ERROR, "No input image given.");
     155        return AVERROR(EINVAL);
     156    }
     157
     158    // (re-)initialize input image
     159    const CGSize frame_size = {
     160        frame->width,
     161        frame->height
     162    };
     163
     164    NSData *data = [NSData dataWithBytesNoCopy:frame->data[0]
     165                           length:frame->height*frame->linesize[0]
     166                           freeWhenDone:NO];
     167
     168    CIImage *ret = [(__bridge CIImage*)ctx->input_image initWithBitmapData:data
     169                                                        bytesPerRow:frame->linesize[0]
     170                                                        size:frame_size
     171                                                        format:kCIFormatARGB8
     172                                                        colorSpace:ctx->color_space]; //kCGColorSpaceGenericRGB
     173    if (!ret) {
     174        av_log(ctx, AV_LOG_ERROR, "Input image could not be initialized.\n");
     175        return AVERROR_EXTERNAL;
     176    }
     177
     178    CIFilter *filter       = NULL;
     179    CIImage *filter_input  = (__bridge CIImage*)ctx->input_image;
     180    CIImage *filter_output = NULL;
     181
     182    // successively apply all filters
     183    for (i = 0; i < ctx->num_filters; i++) {
     184        if (i) {
     185            // set filter input to previous filter output
     186            filter_input    = [(__bridge CIImage*)ctx->filters[i-1] valueForKey:kCIOutputImageKey];
     187            CGRect out_rect = [filter_input extent];
     188            if (out_rect.size.width > frame->width || out_rect.size.height > frame->height) {
     189                // do not keep padded image regions after filtering
     190                out_rect.origin.x    = 0.0f;
     191                out_rect.origin.y    = 0.0f;
     192                out_rect.size.width  = frame->width;
     193                out_rect.size.height = frame->height;
     194            }
     195            filter_input = [filter_input imageByCroppingToRect:out_rect];
     196        }
     197
     198        filter = (__bridge CIFilter*)ctx->filters[i];
     199
     200        @try {
     201            [filter setValue:filter_input forKey:kCIInputImageKey];
     202        } @catch (NSException *exception) {
     203            if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
     204                av_log(ctx, AV_LOG_ERROR, "An error occurred: %s.", [exception.reason UTF8String]);
     205                return AVERROR_EXTERNAL;
     206            } else {
     207                av_log(ctx, AV_LOG_WARNING, "Selected filter does not accept an input image.\n");
     208            }
     209        }
     210    }
     211
     212    // get output of last filter
     213    filter_output = [filter valueForKey:kCIOutputImageKey];
     214
     215    if (!filter_output) {
     216        av_log(ctx, AV_LOG_ERROR, "Filter output not available.\n");
     217        return AVERROR_EXTERNAL;
     218    }
     219
     220    // do not keep padded image regions after filtering
     221    CGRect out_rect = [filter_output extent];
     222    if (out_rect.size.width > frame->width || out_rect.size.height > frame->height) {
     223        av_log(ctx, AV_LOG_DEBUG, "Cropping output image.\n");
     224        out_rect.origin.x    = 0.0f;
     225        out_rect.origin.y    = 0.0f;
     226        out_rect.size.width  = frame->width;
     227        out_rect.size.height = frame->height;
     228    }
     229
     230    CGImageRef out = [(__bridge CIContext*)ctx->glctx createCGImage:filter_output
     231                                                      fromRect:out_rect];
     232
     233    if (!out) {
     234        av_log(ctx, AV_LOG_ERROR, "Cannot create valid output image.\n");
     235    }
     236
     237    // create bitmap context on the fly for rendering into current frame->data[]
     238    if (ctx->cgctx) {
     239        CGContextRelease(ctx->cgctx);
     240        ctx->cgctx = NULL;
     241    }
     242    size_t out_width    = CGImageGetWidth(out);
     243    size_t out_height   = CGImageGetHeight(out);
     244
     245    if (out_width > frame->width || out_height > frame->height) { // this might result in segfault
     246        av_log(ctx, AV_LOG_WARNING, "Output image has unexpected size: %lux%lu (expected: %ix%i). This may crash...\n",
     247               out_width, out_height, frame->width, frame->height);
     248    }
     249    ctx->cgctx = CGBitmapContextCreate(frame->data[0],
     250                                       frame->width,
     251                                       frame->height,
     252                                       ctx->bits_per_component,
     253                                       frame->linesize[0],
     254                                       ctx->color_space,
     255                                       (uint32_t)kCGImageAlphaPremultipliedFirst); // ARGB
     256    if (!ctx->cgctx) {
     257        av_log(ctx, AV_LOG_ERROR, "CGBitmap context cannot be created.\n");
     258        return AVERROR_EXTERNAL;
     259    }
     260
     261    // copy ("draw") the output image into the frame data
     262    const CGRect rect = {{0,0},{frame->width, frame->height}};
     263    CGContextDrawImage(ctx->cgctx, rect, out);
     264
     265    return ff_filter_frame(link->dst->outputs[0], frame);
     266}
     267
     268/** Set an option of the given filter to the provided key-value pair.
     269 */
     270static void set_option(CoreImageContext *ctx, CIFilter *filter, const char *key, const char *value)
     271{
     272        NSString *input_key = [NSString stringWithUTF8String:key];
     273        NSString *input_val = [NSString stringWithUTF8String:value];
     274
     275        NSDictionary *filter_attribs = [filter attributes]; // <nsstring, id>
     276        NSDictionary *input_attribs  = [filter_attribs valueForKey:input_key];
     277
     278        NSString *input_class = [input_attribs valueForKey:kCIAttributeClass];
     279        NSString *input_type  = [input_attribs valueForKey:kCIAttributeType];
     280
     281        if (!input_attribs) {
     282            av_log(ctx, AV_LOG_WARNING, "Skipping unknown option: \"%s\".\n",
     283                   [input_key UTF8String]); // [[filter name] UTF8String]) not currently defined...
     284            return;
     285        }
     286
     287        av_log(ctx, AV_LOG_DEBUG, "key: %s, val: %s, #attribs: %lu, class: %s, type: %s\n",
     288               [input_key UTF8String],
     289               [input_val UTF8String],
     290               input_attribs ? (unsigned long)[input_attribs count] : -1,
     291               [input_class UTF8String],
     292               [input_type UTF8String]);
     293
     294        if ([input_class isEqualToString:@"NSNumber"]) {
     295            float input          = input_val.floatValue;
     296            NSNumber *max_value  = [input_attribs valueForKey:kCIAttributeSliderMax];
     297            NSNumber *min_value  = [input_attribs valueForKey:kCIAttributeSliderMin];
     298            NSNumber *used_value = nil;
     299
     300#define CLAMP_WARNING {        \
     301av_log(ctx, AV_LOG_WARNING, "Value of \"%f\" for option \"%s\" is out of range [%f %f], clamping to \"%f\".\n", \
     302       input,                  \
     303       [input_key UTF8String], \
     304       min_value.floatValue,   \
     305       max_value.floatValue,   \
     306       used_value.floatValue); \
     307}
     308            if (input > max_value.floatValue) {
     309                used_value = max_value;
     310                CLAMP_WARNING;
     311            } else if (input < min_value.floatValue) {
     312                used_value = min_value;
     313                CLAMP_WARNING;
     314            } else {
     315                used_value = [NSNumber numberWithFloat:input];
     316            }
     317
     318            [filter setValue:used_value forKey:input_key];
     319        } else if ([input_class isEqualToString:@"CIVector"]) {
     320            CIVector *input = [CIVector vectorWithString:input_val];
     321
     322            if (!input) {
     323                av_log(ctx, AV_LOG_WARNING, "Skipping invalid CIVctor description: \"%s\".\n",
     324                       [input_val UTF8String]);
     325                return;
     326            }
     327
     328            [filter setValue:input forKey:input_key];
     329        } else if ([input_class isEqualToString:@"CIColor"]) {
     330            CIColor *input = [CIColor colorWithString:input_val];
     331
     332            if (!input) {
     333                av_log(ctx, AV_LOG_WARNING, "Skipping invalid CIColor description: \"%s\".\n",
     334                       [input_val UTF8String]);
     335                return;
     336            }
     337
     338            [filter setValue:input forKey:input_key];
     339        } else if ([input_class isEqualToString:@"NSString"]) { // set display name as string with latin1 encoding
     340            [filter setValue:input_val forKey:input_key];
     341        } else if ([input_class isEqualToString:@"NSData"]) { // set display name as string with latin1 encoding
     342            NSData *input = [NSData dataWithBytes:(const void*)[input_val cStringUsingEncoding:NSISOLatin1StringEncoding]
     343                                    length:[input_val lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding]];
     344
     345            if (!input) {
     346                av_log(ctx, AV_LOG_WARNING, "Skipping invalid NSData description: \"%s\".\n",
     347                       [input_val UTF8String]);
     348                return;
     349            }
     350
     351            [filter setValue:input forKey:input_key];
     352        } else {
     353            av_log(ctx, AV_LOG_WARNING, "Skipping unsupported option class: \"%s\".\n",
     354                   [input_class UTF8String]);
     355            avpriv_report_missing_feature(ctx, "Handling of some option classes");
     356            return;
     357        }
     358}
     359
     360/** Create a filter object by a given name and set all options to defaults.
     361 *  Overwrite any option given by the user to the provided value in filter_options.
     362 */
     363static CIFilter* create_filter(CoreImageContext *ctx, const char *filter_name, AVDictionary *filter_options)
     364{
     365    // create filter object
     366    CIFilter *filter = [CIFilter filterWithName:[NSString stringWithUTF8String:filter_name]];
     367
     368    // set default options
     369    [filter setDefaults];
     370
     371    // set user options
     372    if (filter_options) {
     373        AVDictionaryEntry *o = NULL;
     374        while ((o = av_dict_get(filter_options, "", o, AV_DICT_IGNORE_SUFFIX))) {
     375            set_option(ctx, filter, o->key, o->value);
     376        }
     377    }
     378
     379    return filter;
     380}
     381
     382/** Initialize all filters, parse all provided options or just list all available filters if requested.
     383 */
     384static av_cold int init(AVFilterContext *fctx)
     385{
     386    CoreImageContext *ctx     = fctx->priv;
     387    AVDictionary *filter_dict = NULL;
     388    AVDictionaryEntry *f      = NULL;
     389    AVDictionaryEntry *o      = NULL;
     390    int ret;
     391    int i;
     392
     393    if (ctx->list_filters) {
     394        list_filters(ctx);
     395        return AVERROR_EXIT;
     396    }
     397
     398    if (ctx->filter_string) {
     399        // parse filter string (filter=name@opt=val@opt2=val2#name2@opt3=val3) for filters seperated by #
     400        av_log(ctx, AV_LOG_DEBUG, "Filter_string: %s\n", ctx->filter_string);
     401        ret = av_dict_parse_string(&filter_dict, ctx->filter_string, "@", "#", AV_DICT_MULTIKEY); // parse filter_name:all_filter_options
     402        if (ret) {
     403            av_log(ctx, AV_LOG_ERROR, "Parsing of filters failed.\n");
     404            return AVERROR(EIO);
     405        }
     406        ctx->num_filters = av_dict_count(filter_dict);
     407        av_log(ctx, AV_LOG_DEBUG, "Filter count: %i\n", ctx->num_filters);
     408
     409        // allocate CIFilter array
     410        ctx->filters = av_mallocz(ctx->num_filters * sizeof(CIFilter*));
     411        if (!ctx->filters) {
     412            av_log(ctx, AV_LOG_ERROR, "Could not allocate filter array.\n");
     413            return AVERROR(ENOMEM);
     414        }
     415
     416        // parste filters for option key-value pairs (opt=val@opt2=val2) seperated by @
     417        i = 0;
     418        while ((f = av_dict_get(filter_dict, "", f, AV_DICT_IGNORE_SUFFIX))) {
     419            AVDictionary *filter_options = NULL;
     420
     421            if (strncmp(f->value, "default", 7)) { // not default
     422                ret = av_dict_parse_string(&filter_options, f->value, "=", "@", 0); // parse option_name:option_value
     423                if (ret) {
     424                    av_log(ctx, AV_LOG_ERROR, "Parsing of filter options for \"%s\" failed.\n", f->key);
     425                    return AVERROR(EIO);
     426                }
     427            }
     428
     429            if (av_log_get_level() >= AV_LOG_DEBUG) {
     430                av_log(ctx, AV_LOG_DEBUG, "Creating filter %i: \"%s\":\n", i, f->key);
     431                if (!filter_options) {
     432                    av_log(ctx, AV_LOG_DEBUG, "\tusing default options\n");
     433                } else {
     434                    while ((o = av_dict_get(filter_options, "", o, AV_DICT_IGNORE_SUFFIX))) {
     435                        av_log(ctx, AV_LOG_DEBUG, "\t%s: %s\n", o->key, o->value);
     436                    }
     437                }
     438            }
     439
     440            ctx->filters[i] = CFBridgingRetain(create_filter(ctx, f->key, filter_options));
     441            if (!ctx->filters[i]) {
     442                av_log(ctx, AV_LOG_ERROR, "Could not create filter \"%s\".\n", f->key);
     443                return AVERROR(EINVAL);
     444            }
     445
     446            i++;
     447        }
     448    } else {
     449        av_log(ctx, AV_LOG_ERROR, "No filters specified.\n");
     450        return AVERROR(EINVAL);
     451    }
     452
     453    // create GPU context on OSX
     454    const NSOpenGLPixelFormatAttribute attr[] = {
     455        NSOpenGLPFAAccelerated,
     456        NSOpenGLPFANoRecovery,
     457        NSOpenGLPFAColorSize, 32,
     458        0
     459    };
     460
     461    NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:(void *)&attr];
     462    ctx->color_space                  = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
     463    ctx->glctx                        = CFBridgingRetain([CIContext contextWithCGLContext:CGLGetCurrentContext()
     464                                                         pixelFormat:[pixel_format CGLPixelFormatObj]
     465                                                         colorSpace:ctx->color_space
     466                                                         options:nil]);
     467
     468    if (!ctx->glctx) {
     469        av_log(ctx, AV_LOG_ERROR, "CIContext not created.\n");
     470        return -1;
     471    }
     472
     473    // Creating an empty input image as input container for the context
     474    ctx->input_image = CFBridgingRetain([CIImage emptyImage]);
     475
     476    return 0;
     477}
     478
     479/** Uninitialize all filters, contexts and free all allocated memory.
     480 */
     481static av_cold void uninit(AVFilterContext *fctx)
     482{
     483#define SafeCFRelease(ptr) { \
     484    if (ptr) {               \
     485        CFRelease(ptr);      \
     486        ptr = NULL;          \
     487    }                        \
     488}
     489
     490    CoreImageContext *ctx = fctx->priv;
     491
     492    SafeCFRelease(ctx->glctx);
     493    SafeCFRelease(ctx->cgctx);
     494    SafeCFRelease(ctx->color_space);
     495    SafeCFRelease(ctx->input_image);
     496
     497    if (ctx->filters) {
     498        for (int i = 0; i < ctx->num_filters; i++) {
     499            SafeCFRelease(ctx->filters[i]);
     500        }
     501        av_free(ctx->filters);
     502    }
     503
     504}
     505
     506static const AVFilterPad avfilter_vf_coreimage_inputs[] = {
     507    {
     508        .name             = "default",
     509        .type             = AVMEDIA_TYPE_VIDEO,
     510        .get_video_buffer = get_video_buffer,
     511        .filter_frame     = filter_frame,
     512        .config_props     = config_input,
     513    },
     514    { NULL }
     515};
     516
     517static const AVFilterPad avfilter_vf_coreimage_outputs[] = {
     518    {
     519        .name = "default",
     520        .type = AVMEDIA_TYPE_VIDEO,
     521    },
     522    { NULL }
     523};
     524
     525#define OFFSET(x) offsetof(CoreImageContext, x)
     526#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
     527static const AVOption coreimage_options[] = {
     528    { "list_filters", "list available filters",  OFFSET(list_filters), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, .flags = FLAGS, "list_filters" },
     529    { "filter", "names and options of filters to apply",  OFFSET(filter_string), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = FLAGS },
     530    { NULL }
     531};
     532
     533static const AVClass coreimage_class = {
     534    .class_name = "coreimage",
     535    .item_name  = av_default_item_name,
     536    .option     = coreimage_options,
     537    .version    = LIBAVUTIL_VERSION_INT,
     538    .category   = AV_CLASS_CATEGORY_FILTER,
     539};
     540
     541AVFilter ff_vf_coreimage = {
     542    .name          = "coreimage",
     543    .description   = NULL_IF_CONFIG_SMALL("Video filtering using CoreImage API."),
     544    .init          = init,
     545    .uninit        = uninit,
     546    .priv_size     = sizeof(CoreImageContext),
     547    .priv_class    = &coreimage_class,
     548    .inputs        = avfilter_vf_coreimage_inputs,
     549    .outputs       = avfilter_vf_coreimage_outputs,
     550    .query_formats = query_formats,
     551};