| | 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 | |
| | 37 | typedef 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 | */ |
| | 55 | static 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 | */ |
| | 66 | static 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 | */ |
| | 106 | static 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 | */ |
| | 122 | static 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 | */ |
| | 147 | static 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 | */ |
| | 270 | static 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 { \ |
| | 301 | av_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 | */ |
| | 363 | static 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 | */ |
| | 384 | static 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 | */ |
| | 481 | static 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 | |
| | 506 | static 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 | |
| | 517 | static 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 |
| | 527 | static 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 | |
| | 533 | static 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 | |
| | 541 | AVFilter 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 | }; |