lavfi: Add coreimage filter for GPU based image filtering on OSX.
This commit is contained in:
parent
4ebf0b109c
commit
4d251723c0
@ -15,6 +15,7 @@ version <next>:
|
|||||||
- VC-2 HQ RTP payload format (draft v1) depacketizer
|
- VC-2 HQ RTP payload format (draft v1) depacketizer
|
||||||
- AudioToolbox audio decoders
|
- AudioToolbox audio decoders
|
||||||
- AudioToolbox audio encoders
|
- AudioToolbox audio encoders
|
||||||
|
- coreimage filter (GPU based image filtering on OSX)
|
||||||
|
|
||||||
|
|
||||||
version 3.0:
|
version 3.0:
|
||||||
|
@ -370,6 +370,7 @@ Filters:
|
|||||||
vf_colorbalance.c Paul B Mahol
|
vf_colorbalance.c Paul B Mahol
|
||||||
vf_colorkey.c Timo Rothenpieler
|
vf_colorkey.c Timo Rothenpieler
|
||||||
vf_colorlevels.c Paul B Mahol
|
vf_colorlevels.c Paul B Mahol
|
||||||
|
vf_coreimage.m Thilo Borgmann
|
||||||
vf_deband.c Paul B Mahol
|
vf_deband.c Paul B Mahol
|
||||||
vf_dejudder.c Nicholas Robbins
|
vf_dejudder.c Nicholas Robbins
|
||||||
vf_delogo.c Jean Delvare (CC <jdelvare@suse.com>)
|
vf_delogo.c Jean Delvare (CC <jdelvare@suse.com>)
|
||||||
|
4
configure
vendored
4
configure
vendored
@ -5287,6 +5287,8 @@ frei0r_filter_extralibs='$ldl'
|
|||||||
frei0r_src_filter_extralibs='$ldl'
|
frei0r_src_filter_extralibs='$ldl'
|
||||||
ladspa_filter_extralibs='$ldl'
|
ladspa_filter_extralibs='$ldl'
|
||||||
nvenc_encoder_extralibs='$ldl'
|
nvenc_encoder_extralibs='$ldl'
|
||||||
|
coreimage_filter_extralibs="-framework QuartzCore -framework AppKit -framework OpenGL"
|
||||||
|
coreimagesrc_filter_extralibs="-framework QuartzCore -framework AppKit -framework OpenGL"
|
||||||
|
|
||||||
if ! disabled network; then
|
if ! disabled network; then
|
||||||
check_func getaddrinfo $network_extralibs
|
check_func getaddrinfo $network_extralibs
|
||||||
@ -5516,6 +5518,8 @@ enabled avisynth && { { check_lib2 "windows.h" LoadLibrary; } ||
|
|||||||
die "ERROR: LoadLibrary/dlopen not found for avisynth"; }
|
die "ERROR: LoadLibrary/dlopen not found for avisynth"; }
|
||||||
enabled cuda && check_lib cuda.h cuInit -lcuda
|
enabled cuda && check_lib cuda.h cuInit -lcuda
|
||||||
enabled chromaprint && require chromaprint chromaprint.h chromaprint_get_version -lchromaprint
|
enabled chromaprint && require chromaprint chromaprint.h chromaprint_get_version -lchromaprint
|
||||||
|
enabled coreimage_filter && { check_header_objcc QuartzCore/CoreImage.h || disable coreimage_filter; }
|
||||||
|
enabled coreimagesrc_filter && { check_header_objcc QuartzCore/CoreImage.h || disable coreimagesrc_filter; }
|
||||||
enabled decklink && { check_header DeckLinkAPI.h || die "ERROR: DeckLinkAPI.h header not found"; }
|
enabled decklink && { check_header DeckLinkAPI.h || die "ERROR: DeckLinkAPI.h header not found"; }
|
||||||
enabled frei0r && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; }
|
enabled frei0r && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; }
|
||||||
enabled gmp && require2 gmp gmp.h mpz_export -lgmp
|
enabled gmp && require2 gmp gmp.h mpz_export -lgmp
|
||||||
|
166
doc/filters.texi
166
doc/filters.texi
@ -4955,6 +4955,111 @@ 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 -
|
|||||||
Copy the input source unchanged to the output. This is mainly useful for
|
Copy the input source unchanged to the output. This is mainly useful for
|
||||||
testing purposes.
|
testing purposes.
|
||||||
|
|
||||||
|
@anchor{coreimage}
|
||||||
|
@section coreimage
|
||||||
|
Video filtering on GPU using Apple's CoreImage API on OSX.
|
||||||
|
|
||||||
|
Hardware 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.
|
||||||
|
|
||||||
|
There 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.
|
||||||
|
|
||||||
|
The coreimage filter accepts the following options:
|
||||||
|
@table @option
|
||||||
|
@item list_filters
|
||||||
|
List all available filters and generators along with all their respective
|
||||||
|
options as well as possible minimum and maximum values along with the default
|
||||||
|
values.
|
||||||
|
@example
|
||||||
|
list_filters=true
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item filter
|
||||||
|
Specify all filters by their respective name and options.
|
||||||
|
Use @var{list_filters} to determine all valid filter names and options.
|
||||||
|
Numerical options are specified by a float value and are automatically clamped
|
||||||
|
to their respective value range. Vector and color options have to be specified
|
||||||
|
by a list of space separated float values. Character escaping has to be done.
|
||||||
|
A special option name @code{default} is available to use default options for a
|
||||||
|
filter.
|
||||||
|
|
||||||
|
It is required to specify either @code{default} or at least one of the filter options.
|
||||||
|
All omitted options are used with their default values.
|
||||||
|
The syntax of the filter string is as follows:
|
||||||
|
@example
|
||||||
|
filter=<NAME>@@<OPTION>=<VALUE>[@@<OPTION>=<VALUE>][@@...][#<NAME>@@<OPTION>=<VALUE>[@@<OPTION>=<VALUE>][@@...]][#...]
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item output_rect
|
||||||
|
Specify a rectangle where the output of the filter chain is copied into the
|
||||||
|
input image. It is given by a list of space separated float values:
|
||||||
|
@example
|
||||||
|
output_rect=x\ y\ width\ height
|
||||||
|
@end example
|
||||||
|
If not given, the output rectangle equals the dimensions of the input image.
|
||||||
|
The output rectangle is automatically cropped at the borders of the input
|
||||||
|
image. Negative values are valid for each component.
|
||||||
|
@example
|
||||||
|
output_rect=25\ 25\ 100\ 100
|
||||||
|
@end example
|
||||||
|
@end table
|
||||||
|
|
||||||
|
Several filters can be chained for successive processing without GPU-HOST
|
||||||
|
transfers allowing for fast processing of complex filter chains.
|
||||||
|
Currently, only filters with zero (generators) or exactly one (filters) input
|
||||||
|
image and one output image are supported. Also, transition filters are not yet
|
||||||
|
usable as intended.
|
||||||
|
|
||||||
|
Some 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.
|
||||||
|
|
||||||
|
For image generators, the size of the output image is determined by the
|
||||||
|
previous output image of the filter chain or the input image of the whole
|
||||||
|
filterchain, respectively. The generators do not use the pixel information of
|
||||||
|
this image to generate their output. However, the generated output is
|
||||||
|
blended onto this image, resulting in partial or complete coverage of the
|
||||||
|
output image.
|
||||||
|
|
||||||
|
The @ref{coreimagesrc} video source can be used for generating input images
|
||||||
|
which are directly fed into the filter chain. By using it, providing input
|
||||||
|
images by another video source or an input video is not required.
|
||||||
|
|
||||||
|
@subsection Examples
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
|
||||||
|
@item
|
||||||
|
List all filters available:
|
||||||
|
@example
|
||||||
|
coreimage=list_filters=true
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item
|
||||||
|
Use the CIBoxBlur filter with default options to blur an image:
|
||||||
|
@example
|
||||||
|
coreimage=filter=CIBoxBlur@@default
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item
|
||||||
|
Use a filter chain with CISepiaTone at default values and CIVignetteEffect with
|
||||||
|
its center at 100x100 and a radius of 50 pixels:
|
||||||
|
@example
|
||||||
|
coreimage=filter=CIBoxBlur@@default#CIVignetteEffect@@inputCenter=100\ 100@@inputRadius=50
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item
|
||||||
|
Use 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:
|
||||||
|
@example
|
||||||
|
ffmpeg -f lavfi -i nullsrc=s=100x100,coreimage=filter=CIQRCodeGenerator@@inputMessage=https\\\\\://FFmpeg.org/@@inputCorrectionLevel=H -frames:v 1 QRCode.png
|
||||||
|
@end example
|
||||||
|
@end itemize
|
||||||
|
|
||||||
@section crop
|
@section crop
|
||||||
|
|
||||||
Crop the input video to given dimensions.
|
Crop the input video to given dimensions.
|
||||||
@ -13798,6 +13903,67 @@ cellauto=p='@@@@ @@ @@@@':s=100x400:full=0:rule=18
|
|||||||
|
|
||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
|
@anchor{coreimagesrc}
|
||||||
|
@section coreimagesrc
|
||||||
|
Video source generated on GPU using Apple's CoreImage API on OSX.
|
||||||
|
|
||||||
|
This video source is a specialized version of the @ref{coreimage} video filter.
|
||||||
|
Use a core image generator at the beginning of the applied filterchain to
|
||||||
|
generate the content.
|
||||||
|
|
||||||
|
The coreimagesrc video source accepts the following options:
|
||||||
|
@table @option
|
||||||
|
@item list_generators
|
||||||
|
List all available generators along with all their respective options as well as
|
||||||
|
possible minimum and maximum values along with the default values.
|
||||||
|
@example
|
||||||
|
list_generators=true
|
||||||
|
@end example
|
||||||
|
|
||||||
|
@item size, s
|
||||||
|
Specify the size of the sourced video. For the syntax of this option, check the
|
||||||
|
@ref{video size syntax,,"Video size" section in the ffmpeg-utils manual,ffmpeg-utils}.
|
||||||
|
The default value is @code{320x240}.
|
||||||
|
|
||||||
|
@item rate, r
|
||||||
|
Specify the frame rate of the sourced video, as the number of frames
|
||||||
|
generated per second. It has to be a string in the format
|
||||||
|
@var{frame_rate_num}/@var{frame_rate_den}, an integer number, a floating point
|
||||||
|
number or a valid video frame rate abbreviation. The default value is
|
||||||
|
"25".
|
||||||
|
|
||||||
|
@item sar
|
||||||
|
Set the sample aspect ratio of the sourced video.
|
||||||
|
|
||||||
|
@item duration, d
|
||||||
|
Set the duration of the sourced video. See
|
||||||
|
@ref{time duration syntax,,the Time duration section in the ffmpeg-utils(1) manual,ffmpeg-utils}
|
||||||
|
for the accepted syntax.
|
||||||
|
|
||||||
|
If not specified, or the expressed duration is negative, the video is
|
||||||
|
supposed to be generated forever.
|
||||||
|
@end table
|
||||||
|
|
||||||
|
Additionally, all options of the @ref{coreimage} video filter are accepted.
|
||||||
|
A complete filterchain can be used for further processing of the
|
||||||
|
generated input without CPU-HOST transfer. See @ref{coreimage} documentation
|
||||||
|
and examples for details.
|
||||||
|
|
||||||
|
@subsection Examples
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
|
||||||
|
@item
|
||||||
|
Use CIQRCodeGenerator to create a QR code for the FFmpeg homepage,
|
||||||
|
given as complete and escaped command-line for Apple's standard bash shell:
|
||||||
|
@example
|
||||||
|
ffmpeg -f lavfi -i coreimagesrc=s=100x100:filter=CIQRCodeGenerator@@inputMessage=https\\\\\://FFmpeg.org/@@inputCorrectionLevel=H -frames:v 1 QRCode.png
|
||||||
|
@end example
|
||||||
|
This example is equivalent to the QRCode example of @ref{coreimage} without the
|
||||||
|
need for a nullsrc video source.
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
|
||||||
@section mandelbrot
|
@section mandelbrot
|
||||||
|
|
||||||
Generate a Mandelbrot set fractal, and progressively zoom towards the
|
Generate a Mandelbrot set fractal, and progressively zoom towards the
|
||||||
|
@ -133,6 +133,7 @@ OBJS-$(CONFIG_COLORLEVELS_FILTER) += vf_colorlevels.o
|
|||||||
OBJS-$(CONFIG_COLORMATRIX_FILTER) += vf_colormatrix.o
|
OBJS-$(CONFIG_COLORMATRIX_FILTER) += vf_colormatrix.o
|
||||||
OBJS-$(CONFIG_CONVOLUTION_FILTER) += vf_convolution.o
|
OBJS-$(CONFIG_CONVOLUTION_FILTER) += vf_convolution.o
|
||||||
OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o
|
OBJS-$(CONFIG_COPY_FILTER) += vf_copy.o
|
||||||
|
OBJS-$(CONFIG_COREIMAGE_FILTER) += vf_coreimage.o
|
||||||
OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o
|
OBJS-$(CONFIG_COVER_RECT_FILTER) += vf_cover_rect.o lavfutils.o
|
||||||
OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o
|
OBJS-$(CONFIG_CROP_FILTER) += vf_crop.o
|
||||||
OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o
|
OBJS-$(CONFIG_CROPDETECT_FILTER) += vf_cropdetect.o
|
||||||
@ -282,6 +283,7 @@ OBJS-$(CONFIG_ALLRGB_FILTER) += vsrc_testsrc.o
|
|||||||
OBJS-$(CONFIG_ALLYUV_FILTER) += vsrc_testsrc.o
|
OBJS-$(CONFIG_ALLYUV_FILTER) += vsrc_testsrc.o
|
||||||
OBJS-$(CONFIG_CELLAUTO_FILTER) += vsrc_cellauto.o
|
OBJS-$(CONFIG_CELLAUTO_FILTER) += vsrc_cellauto.o
|
||||||
OBJS-$(CONFIG_COLOR_FILTER) += vsrc_testsrc.o
|
OBJS-$(CONFIG_COLOR_FILTER) += vsrc_testsrc.o
|
||||||
|
OBJS-$(CONFIG_COREIMAGESRC_FILTER) += vf_coreimage.o
|
||||||
OBJS-$(CONFIG_FREI0R_SRC_FILTER) += vf_frei0r.o
|
OBJS-$(CONFIG_FREI0R_SRC_FILTER) += vf_frei0r.o
|
||||||
OBJS-$(CONFIG_HALDCLUTSRC_FILTER) += vsrc_testsrc.o
|
OBJS-$(CONFIG_HALDCLUTSRC_FILTER) += vsrc_testsrc.o
|
||||||
OBJS-$(CONFIG_LIFE_FILTER) += vsrc_life.o
|
OBJS-$(CONFIG_LIFE_FILTER) += vsrc_life.o
|
||||||
|
@ -154,6 +154,7 @@ void avfilter_register_all(void)
|
|||||||
REGISTER_FILTER(COLORMATRIX, colormatrix, vf);
|
REGISTER_FILTER(COLORMATRIX, colormatrix, vf);
|
||||||
REGISTER_FILTER(CONVOLUTION, convolution, vf);
|
REGISTER_FILTER(CONVOLUTION, convolution, vf);
|
||||||
REGISTER_FILTER(COPY, copy, vf);
|
REGISTER_FILTER(COPY, copy, vf);
|
||||||
|
REGISTER_FILTER(COREIMAGE, coreimage, vf);
|
||||||
REGISTER_FILTER(COVER_RECT, cover_rect, vf);
|
REGISTER_FILTER(COVER_RECT, cover_rect, vf);
|
||||||
REGISTER_FILTER(CROP, crop, vf);
|
REGISTER_FILTER(CROP, crop, vf);
|
||||||
REGISTER_FILTER(CROPDETECT, cropdetect, vf);
|
REGISTER_FILTER(CROPDETECT, cropdetect, vf);
|
||||||
@ -302,6 +303,7 @@ void avfilter_register_all(void)
|
|||||||
REGISTER_FILTER(ALLYUV, allyuv, vsrc);
|
REGISTER_FILTER(ALLYUV, allyuv, vsrc);
|
||||||
REGISTER_FILTER(CELLAUTO, cellauto, vsrc);
|
REGISTER_FILTER(CELLAUTO, cellauto, vsrc);
|
||||||
REGISTER_FILTER(COLOR, color, vsrc);
|
REGISTER_FILTER(COLOR, color, vsrc);
|
||||||
|
REGISTER_FILTER(COREIMAGESRC, coreimagesrc, vsrc);
|
||||||
REGISTER_FILTER(FREI0R, frei0r_src, vsrc);
|
REGISTER_FILTER(FREI0R, frei0r_src, vsrc);
|
||||||
REGISTER_FILTER(HALDCLUTSRC, haldclutsrc, vsrc);
|
REGISTER_FILTER(HALDCLUTSRC, haldclutsrc, vsrc);
|
||||||
REGISTER_FILTER(LIFE, life, vsrc);
|
REGISTER_FILTER(LIFE, life, vsrc);
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
#include "libavutil/version.h"
|
#include "libavutil/version.h"
|
||||||
|
|
||||||
#define LIBAVFILTER_VERSION_MAJOR 6
|
#define LIBAVFILTER_VERSION_MAJOR 6
|
||||||
#define LIBAVFILTER_VERSION_MINOR 39
|
#define LIBAVFILTER_VERSION_MINOR 40
|
||||||
#define LIBAVFILTER_VERSION_MICRO 102
|
#define LIBAVFILTER_VERSION_MICRO 102
|
||||||
|
|
||||||
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
|
#define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
|
||||||
|
688
libavfilter/vf_coreimage.m
Normal file
688
libavfilter/vf_coreimage.m
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2016 Thilo Borgmann
|
||||||
|
*
|
||||||
|
* This file is part of FFmpeg.
|
||||||
|
*
|
||||||
|
* FFmpeg is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* FFmpeg is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with FFmpeg; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Video processing based on Apple's CoreImage API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <QuartzCore/CoreImage.h>
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
#include "avfilter.h"
|
||||||
|
#include "formats.h"
|
||||||
|
#include "internal.h"
|
||||||
|
#include "video.h"
|
||||||
|
#include "libavutil/internal.h"
|
||||||
|
#include "libavutil/opt.h"
|
||||||
|
#include "libavutil/pixdesc.h"
|
||||||
|
|
||||||
|
typedef struct CoreImageContext {
|
||||||
|
const AVClass *class;
|
||||||
|
|
||||||
|
int is_video_source; ///< filter is used as video source
|
||||||
|
|
||||||
|
int w, h; ///< video size
|
||||||
|
AVRational sar; ///< sample aspect ratio
|
||||||
|
AVRational frame_rate; ///< video frame rate
|
||||||
|
AVRational time_base; ///< stream time base
|
||||||
|
int64_t duration; ///< duration expressed in microseconds
|
||||||
|
int64_t pts; ///< increasing presentation time stamp
|
||||||
|
AVFrame *picref; ///< cached reference containing the painted picture
|
||||||
|
|
||||||
|
CFTypeRef glctx; ///< OpenGL context
|
||||||
|
CGContextRef cgctx; ///< Bitmap context for image copy
|
||||||
|
CFTypeRef input_image; ///< Input image container for passing into Core Image API
|
||||||
|
CGColorSpaceRef color_space; ///< Common color space for input image and cgcontext
|
||||||
|
int bits_per_component; ///< Shared bpc for input-output operation
|
||||||
|
|
||||||
|
char *filter_string; ///< The complete user provided filter definition
|
||||||
|
CFTypeRef *filters; ///< CIFilter object for all requested filters
|
||||||
|
int num_filters; ///< Amount of filters in *filters
|
||||||
|
|
||||||
|
char *output_rect; ///< Rectangle to be filled with filter intput
|
||||||
|
int list_filters; ///< Option used to list all available filters including generators
|
||||||
|
int list_generators; ///< Option used to list all available generators
|
||||||
|
} CoreImageContext;
|
||||||
|
|
||||||
|
static int config_output(AVFilterLink *link)
|
||||||
|
{
|
||||||
|
CoreImageContext *ctx = link->src->priv;
|
||||||
|
|
||||||
|
link->w = ctx->w;
|
||||||
|
link->h = ctx->h;
|
||||||
|
link->sample_aspect_ratio = ctx->sar;
|
||||||
|
link->frame_rate = ctx->frame_rate;
|
||||||
|
link->time_base = ctx->time_base;
|
||||||
|
|
||||||
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
|
||||||
|
ctx->bits_per_component = av_get_bits_per_pixel(desc) / desc->nb_components;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determine image properties from input link of filter chain.
|
||||||
|
*/
|
||||||
|
static int config_input(AVFilterLink *link)
|
||||||
|
{
|
||||||
|
CoreImageContext *ctx = link->dst->priv;
|
||||||
|
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(link->format);
|
||||||
|
ctx->bits_per_component = av_get_bits_per_pixel(desc) / desc->nb_components;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Print a list of all available filters including options and respective value ranges and defaults.
|
||||||
|
*/
|
||||||
|
static void list_filters(CoreImageContext *ctx)
|
||||||
|
{
|
||||||
|
// querying filters and attributes
|
||||||
|
NSArray *filter_categories = nil;
|
||||||
|
|
||||||
|
if (ctx->list_generators && !ctx->list_filters) {
|
||||||
|
filter_categories = [NSArray arrayWithObjects:kCICategoryGenerator, nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSArray *filter_names = [CIFilter filterNamesInCategories:filter_categories];
|
||||||
|
NSEnumerator *filters = [filter_names objectEnumerator];
|
||||||
|
|
||||||
|
NSString *filter_name;
|
||||||
|
while (filter_name = [filters nextObject]) {
|
||||||
|
av_log(ctx, AV_LOG_INFO, "Filter: %s\n", [filter_name UTF8String]);
|
||||||
|
NSString *input;
|
||||||
|
|
||||||
|
CIFilter *filter = [CIFilter filterWithName:filter_name];
|
||||||
|
NSDictionary *filter_attribs = [filter attributes]; // <nsstring, id>
|
||||||
|
NSArray *filter_inputs = [filter inputKeys]; // <nsstring>
|
||||||
|
|
||||||
|
for (input in filter_inputs) {
|
||||||
|
NSDictionary *input_attribs = [filter_attribs valueForKey:input];
|
||||||
|
NSString *input_class = [input_attribs valueForKey:kCIAttributeClass];
|
||||||
|
if ([input_class isEqualToString:@"NSNumber"]) {
|
||||||
|
NSNumber *value_default = [input_attribs valueForKey:kCIAttributeDefault];
|
||||||
|
NSNumber *value_min = [input_attribs valueForKey:kCIAttributeSliderMin];
|
||||||
|
NSNumber *value_max = [input_attribs valueForKey:kCIAttributeSliderMax];
|
||||||
|
|
||||||
|
av_log(ctx, AV_LOG_INFO, "\tOption: %s\t[%s]\t[%s %s][%s]\n",
|
||||||
|
[input UTF8String],
|
||||||
|
[input_class UTF8String],
|
||||||
|
[[value_min stringValue] UTF8String],
|
||||||
|
[[value_max stringValue] UTF8String],
|
||||||
|
[[value_default stringValue] UTF8String]);
|
||||||
|
} else {
|
||||||
|
av_log(ctx, AV_LOG_INFO, "\tOption: %s\t[%s]\n",
|
||||||
|
[input UTF8String],
|
||||||
|
[input_class UTF8String]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int query_formats(AVFilterContext *fctx)
|
||||||
|
{
|
||||||
|
static const enum AVPixelFormat inout_fmts_rgb[] = {
|
||||||
|
AV_PIX_FMT_ARGB,
|
||||||
|
AV_PIX_FMT_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
AVFilterFormats *inout_formats;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!(inout_formats = ff_make_format_list(inout_fmts_rgb))) {
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = ff_formats_ref(inout_formats, &fctx->inputs[0]->out_formats)) < 0 ||
|
||||||
|
(ret = ff_formats_ref(inout_formats, &fctx->outputs[0]->in_formats)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int query_formats_src(AVFilterContext *fctx)
|
||||||
|
{
|
||||||
|
static const enum AVPixelFormat inout_fmts_rgb[] = {
|
||||||
|
AV_PIX_FMT_ARGB,
|
||||||
|
AV_PIX_FMT_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
AVFilterFormats *inout_formats;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!(inout_formats = ff_make_format_list(inout_fmts_rgb))) {
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ret = ff_formats_ref(inout_formats, &fctx->outputs[0]->in_formats)) < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int apply_filter(CoreImageContext *ctx, AVFilterLink *link, AVFrame *frame)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
// (re-)initialize input image
|
||||||
|
const CGSize frame_size = {
|
||||||
|
frame->width,
|
||||||
|
frame->height
|
||||||
|
};
|
||||||
|
|
||||||
|
NSData *data = [NSData dataWithBytesNoCopy:frame->data[0]
|
||||||
|
length:frame->height*frame->linesize[0]
|
||||||
|
freeWhenDone:NO];
|
||||||
|
|
||||||
|
CIImage *ret = [(__bridge CIImage*)ctx->input_image initWithBitmapData:data
|
||||||
|
bytesPerRow:frame->linesize[0]
|
||||||
|
size:frame_size
|
||||||
|
format:kCIFormatARGB8
|
||||||
|
colorSpace:ctx->color_space]; //kCGColorSpaceGenericRGB
|
||||||
|
if (!ret) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Input image could not be initialized.\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
CIFilter *filter = NULL;
|
||||||
|
CIImage *filter_input = (__bridge CIImage*)ctx->input_image;
|
||||||
|
CIImage *filter_output = NULL;
|
||||||
|
|
||||||
|
// successively apply all filters
|
||||||
|
for (i = 0; i < ctx->num_filters; i++) {
|
||||||
|
if (i) {
|
||||||
|
// set filter input to previous filter output
|
||||||
|
filter_input = [(__bridge CIImage*)ctx->filters[i-1] valueForKey:kCIOutputImageKey];
|
||||||
|
CGRect out_rect = [filter_input extent];
|
||||||
|
if (out_rect.size.width > frame->width || out_rect.size.height > frame->height) {
|
||||||
|
// do not keep padded image regions after filtering
|
||||||
|
out_rect.origin.x = 0.0f;
|
||||||
|
out_rect.origin.y = 0.0f;
|
||||||
|
out_rect.size.width = frame->width;
|
||||||
|
out_rect.size.height = frame->height;
|
||||||
|
}
|
||||||
|
filter_input = [filter_input imageByCroppingToRect:out_rect];
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = (__bridge CIFilter*)ctx->filters[i];
|
||||||
|
|
||||||
|
// do not set input image for the first filter if used as video source
|
||||||
|
if (!ctx->is_video_source || i) {
|
||||||
|
@try {
|
||||||
|
[filter setValue:filter_input forKey:kCIInputImageKey];
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
if (![[exception name] isEqualToString:NSUndefinedKeyException]) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "An error occurred: %s.", [exception.reason UTF8String]);
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
} else {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Selected filter does not accept an input image.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get output of last filter
|
||||||
|
filter_output = [filter valueForKey:kCIOutputImageKey];
|
||||||
|
|
||||||
|
if (!filter_output) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Filter output not available.\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not keep padded image regions after filtering
|
||||||
|
CGRect out_rect = [filter_output extent];
|
||||||
|
if (out_rect.size.width > frame->width || out_rect.size.height > frame->height) {
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "Cropping output image.\n");
|
||||||
|
out_rect.origin.x = 0.0f;
|
||||||
|
out_rect.origin.y = 0.0f;
|
||||||
|
out_rect.size.width = frame->width;
|
||||||
|
out_rect.size.height = frame->height;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGImageRef out = [(__bridge CIContext*)ctx->glctx createCGImage:filter_output
|
||||||
|
fromRect:out_rect];
|
||||||
|
|
||||||
|
if (!out) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Cannot create valid output image.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// create bitmap context on the fly for rendering into current frame->data[]
|
||||||
|
if (ctx->cgctx) {
|
||||||
|
CGContextRelease(ctx->cgctx);
|
||||||
|
ctx->cgctx = NULL;
|
||||||
|
}
|
||||||
|
size_t out_width = CGImageGetWidth(out);
|
||||||
|
size_t out_height = CGImageGetHeight(out);
|
||||||
|
|
||||||
|
if (out_width > frame->width || out_height > frame->height) { // this might result in segfault
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Output image has unexpected size: %lux%lu (expected: %ix%i). This may crash...\n",
|
||||||
|
out_width, out_height, frame->width, frame->height);
|
||||||
|
}
|
||||||
|
ctx->cgctx = CGBitmapContextCreate(frame->data[0],
|
||||||
|
frame->width,
|
||||||
|
frame->height,
|
||||||
|
ctx->bits_per_component,
|
||||||
|
frame->linesize[0],
|
||||||
|
ctx->color_space,
|
||||||
|
(uint32_t)kCGImageAlphaPremultipliedFirst); // ARGB
|
||||||
|
if (!ctx->cgctx) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "CGBitmap context cannot be created.\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy ("draw") the output image into the frame data
|
||||||
|
CGRect rect = {{0,0},{frame->width, frame->height}};
|
||||||
|
if (ctx->output_rect) {
|
||||||
|
@try {
|
||||||
|
NSString *tmp_string = [NSString stringWithUTF8String:ctx->output_rect];
|
||||||
|
NSRect tmp = NSRectFromString(tmp_string);
|
||||||
|
rect = NSRectToCGRect(tmp);
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "An error occurred: %s.", [exception.reason UTF8String]);
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
if (rect.size.width == 0.0f) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Width of output rect is zero.\n");
|
||||||
|
}
|
||||||
|
if (rect.size.height == 0.0f) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Height of output rect is zero.\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CGContextDrawImage(ctx->cgctx, rect, out);
|
||||||
|
|
||||||
|
return ff_filter_frame(link, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply all valid filters successively to the input image.
|
||||||
|
* The final output image is copied from the GPU by "drawing" using a bitmap context.
|
||||||
|
*/
|
||||||
|
static int filter_frame(AVFilterLink *link, AVFrame *frame)
|
||||||
|
{
|
||||||
|
return apply_filter(link->dst->priv, link->dst->outputs[0], frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int request_frame(AVFilterLink *link)
|
||||||
|
{
|
||||||
|
CoreImageContext *ctx = link->src->priv;
|
||||||
|
AVFrame *frame;
|
||||||
|
|
||||||
|
if (ctx->duration >= 0 &&
|
||||||
|
av_rescale_q(ctx->pts, ctx->time_base, AV_TIME_BASE_Q) >= ctx->duration) {
|
||||||
|
return AVERROR_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx->picref) {
|
||||||
|
ctx->picref = ff_get_video_buffer(link, ctx->w, ctx->h);
|
||||||
|
if (!ctx->picref) {
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frame = av_frame_clone(ctx->picref);
|
||||||
|
if (!frame) {
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame->pts = ctx->pts;
|
||||||
|
frame->key_frame = 1;
|
||||||
|
frame->interlaced_frame = 0;
|
||||||
|
frame->pict_type = AV_PICTURE_TYPE_I;
|
||||||
|
frame->sample_aspect_ratio = ctx->sar;
|
||||||
|
|
||||||
|
ctx->pts++;
|
||||||
|
|
||||||
|
return apply_filter(ctx, link, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set an option of the given filter to the provided key-value pair.
|
||||||
|
*/
|
||||||
|
static void set_option(CoreImageContext *ctx, CIFilter *filter, const char *key, const char *value)
|
||||||
|
{
|
||||||
|
NSString *input_key = [NSString stringWithUTF8String:key];
|
||||||
|
NSString *input_val = [NSString stringWithUTF8String:value];
|
||||||
|
|
||||||
|
NSDictionary *filter_attribs = [filter attributes]; // <nsstring, id>
|
||||||
|
NSDictionary *input_attribs = [filter_attribs valueForKey:input_key];
|
||||||
|
|
||||||
|
NSString *input_class = [input_attribs valueForKey:kCIAttributeClass];
|
||||||
|
NSString *input_type = [input_attribs valueForKey:kCIAttributeType];
|
||||||
|
|
||||||
|
if (!input_attribs) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Skipping unknown option: \"%s\".\n",
|
||||||
|
[input_key UTF8String]); // [[filter name] UTF8String]) not currently defined...
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "key: %s, val: %s, #attribs: %lu, class: %s, type: %s\n",
|
||||||
|
[input_key UTF8String],
|
||||||
|
[input_val UTF8String],
|
||||||
|
input_attribs ? (unsigned long)[input_attribs count] : -1,
|
||||||
|
[input_class UTF8String],
|
||||||
|
[input_type UTF8String]);
|
||||||
|
|
||||||
|
if ([input_class isEqualToString:@"NSNumber"]) {
|
||||||
|
float input = input_val.floatValue;
|
||||||
|
NSNumber *max_value = [input_attribs valueForKey:kCIAttributeSliderMax];
|
||||||
|
NSNumber *min_value = [input_attribs valueForKey:kCIAttributeSliderMin];
|
||||||
|
NSNumber *used_value = nil;
|
||||||
|
|
||||||
|
#define CLAMP_WARNING do { \
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Value of \"%f\" for option \"%s\" is out of range [%f %f], clamping to \"%f\".\n", \
|
||||||
|
input, \
|
||||||
|
[input_key UTF8String], \
|
||||||
|
min_value.floatValue, \
|
||||||
|
max_value.floatValue, \
|
||||||
|
used_value.floatValue); \
|
||||||
|
} while(0)
|
||||||
|
if (input > max_value.floatValue) {
|
||||||
|
used_value = max_value;
|
||||||
|
CLAMP_WARNING;
|
||||||
|
} else if (input < min_value.floatValue) {
|
||||||
|
used_value = min_value;
|
||||||
|
CLAMP_WARNING;
|
||||||
|
} else {
|
||||||
|
used_value = [NSNumber numberWithFloat:input];
|
||||||
|
}
|
||||||
|
|
||||||
|
[filter setValue:used_value forKey:input_key];
|
||||||
|
} else if ([input_class isEqualToString:@"CIVector"]) {
|
||||||
|
CIVector *input = [CIVector vectorWithString:input_val];
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Skipping invalid CIVctor description: \"%s\".\n",
|
||||||
|
[input_val UTF8String]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[filter setValue:input forKey:input_key];
|
||||||
|
} else if ([input_class isEqualToString:@"CIColor"]) {
|
||||||
|
CIColor *input = [CIColor colorWithString:input_val];
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Skipping invalid CIColor description: \"%s\".\n",
|
||||||
|
[input_val UTF8String]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[filter setValue:input forKey:input_key];
|
||||||
|
} else if ([input_class isEqualToString:@"NSString"]) { // set display name as string with latin1 encoding
|
||||||
|
[filter setValue:input_val forKey:input_key];
|
||||||
|
} else if ([input_class isEqualToString:@"NSData"]) { // set display name as string with latin1 encoding
|
||||||
|
NSData *input = [NSData dataWithBytes:(const void*)[input_val cStringUsingEncoding:NSISOLatin1StringEncoding]
|
||||||
|
length:[input_val lengthOfBytesUsingEncoding:NSISOLatin1StringEncoding]];
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Skipping invalid NSData description: \"%s\".\n",
|
||||||
|
[input_val UTF8String]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[filter setValue:input forKey:input_key];
|
||||||
|
} else {
|
||||||
|
av_log(ctx, AV_LOG_WARNING, "Skipping unsupported option class: \"%s\".\n",
|
||||||
|
[input_class UTF8String]);
|
||||||
|
avpriv_report_missing_feature(ctx, "Handling of some option classes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a filter object by a given name and set all options to defaults.
|
||||||
|
* Overwrite any option given by the user to the provided value in filter_options.
|
||||||
|
*/
|
||||||
|
static CIFilter* create_filter(CoreImageContext *ctx, const char *filter_name, AVDictionary *filter_options)
|
||||||
|
{
|
||||||
|
// create filter object
|
||||||
|
CIFilter *filter = [CIFilter filterWithName:[NSString stringWithUTF8String:filter_name]];
|
||||||
|
|
||||||
|
// set default options
|
||||||
|
[filter setDefaults];
|
||||||
|
|
||||||
|
// set user options
|
||||||
|
if (filter_options) {
|
||||||
|
AVDictionaryEntry *o = NULL;
|
||||||
|
while ((o = av_dict_get(filter_options, "", o, AV_DICT_IGNORE_SUFFIX))) {
|
||||||
|
set_option(ctx, filter, o->key, o->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold int init(AVFilterContext *fctx)
|
||||||
|
{
|
||||||
|
CoreImageContext *ctx = fctx->priv;
|
||||||
|
AVDictionary *filter_dict = NULL;
|
||||||
|
AVDictionaryEntry *f = NULL;
|
||||||
|
AVDictionaryEntry *o = NULL;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (ctx->list_filters || ctx->list_generators) {
|
||||||
|
list_filters(ctx);
|
||||||
|
return AVERROR_EXIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->filter_string) {
|
||||||
|
// parse filter string (filter=name@opt=val@opt2=val2#name2@opt3=val3) for filters separated by #
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "Filter_string: %s\n", ctx->filter_string);
|
||||||
|
ret = av_dict_parse_string(&filter_dict, ctx->filter_string, "@", "#", AV_DICT_MULTIKEY); // parse filter_name:all_filter_options
|
||||||
|
if (ret) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Parsing of filters failed.\n");
|
||||||
|
return AVERROR(EIO);
|
||||||
|
}
|
||||||
|
ctx->num_filters = av_dict_count(filter_dict);
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "Filter count: %i\n", ctx->num_filters);
|
||||||
|
|
||||||
|
// allocate CIFilter array
|
||||||
|
ctx->filters = av_mallocz_array(ctx->num_filters, sizeof(CIFilter*));
|
||||||
|
if (!ctx->filters) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Could not allocate filter array.\n");
|
||||||
|
return AVERROR(ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse filters for option key-value pairs (opt=val@opt2=val2) separated by @
|
||||||
|
i = 0;
|
||||||
|
while ((f = av_dict_get(filter_dict, "", f, AV_DICT_IGNORE_SUFFIX))) {
|
||||||
|
AVDictionary *filter_options = NULL;
|
||||||
|
|
||||||
|
if (strncmp(f->value, "default", 7)) { // not default
|
||||||
|
ret = av_dict_parse_string(&filter_options, f->value, "=", "@", 0); // parse option_name:option_value
|
||||||
|
if (ret) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Parsing of filter options for \"%s\" failed.\n", f->key);
|
||||||
|
return AVERROR(EIO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (av_log_get_level() >= AV_LOG_DEBUG) {
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "Creating filter %i: \"%s\":\n", i, f->key);
|
||||||
|
if (!filter_options) {
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "\tusing default options\n");
|
||||||
|
} else {
|
||||||
|
while ((o = av_dict_get(filter_options, "", o, AV_DICT_IGNORE_SUFFIX))) {
|
||||||
|
av_log(ctx, AV_LOG_DEBUG, "\t%s: %s\n", o->key, o->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->filters[i] = CFBridgingRetain(create_filter(ctx, f->key, filter_options));
|
||||||
|
if (!ctx->filters[i]) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "Could not create filter \"%s\".\n", f->key);
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "No filters specified.\n");
|
||||||
|
return AVERROR(EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create GPU context on OSX
|
||||||
|
const NSOpenGLPixelFormatAttribute attr[] = {
|
||||||
|
NSOpenGLPFAAccelerated,
|
||||||
|
NSOpenGLPFANoRecovery,
|
||||||
|
NSOpenGLPFAColorSize, 32,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
NSOpenGLPixelFormat *pixel_format = [[NSOpenGLPixelFormat alloc] initWithAttributes:(void *)&attr];
|
||||||
|
ctx->color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
||||||
|
ctx->glctx = CFBridgingRetain([CIContext contextWithCGLContext:CGLGetCurrentContext()
|
||||||
|
pixelFormat:[pixel_format CGLPixelFormatObj]
|
||||||
|
colorSpace:ctx->color_space
|
||||||
|
options:nil]);
|
||||||
|
|
||||||
|
if (!ctx->glctx) {
|
||||||
|
av_log(ctx, AV_LOG_ERROR, "CIContext not created.\n");
|
||||||
|
return AVERROR_EXTERNAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating an empty input image as input container for the context
|
||||||
|
ctx->input_image = CFBridgingRetain([CIImage emptyImage]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold int init_src(AVFilterContext *fctx)
|
||||||
|
{
|
||||||
|
CoreImageContext *ctx = fctx->priv;
|
||||||
|
|
||||||
|
ctx->is_video_source = 1;
|
||||||
|
ctx->time_base = av_inv_q(ctx->frame_rate);
|
||||||
|
ctx->pts = 0;
|
||||||
|
|
||||||
|
return init(fctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static av_cold void uninit(AVFilterContext *fctx)
|
||||||
|
{
|
||||||
|
#define SafeCFRelease(ptr) do { \
|
||||||
|
if (ptr) { \
|
||||||
|
CFRelease(ptr); \
|
||||||
|
ptr = NULL; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
CoreImageContext *ctx = fctx->priv;
|
||||||
|
|
||||||
|
SafeCFRelease(ctx->glctx);
|
||||||
|
SafeCFRelease(ctx->cgctx);
|
||||||
|
SafeCFRelease(ctx->color_space);
|
||||||
|
SafeCFRelease(ctx->input_image);
|
||||||
|
|
||||||
|
if (ctx->filters) {
|
||||||
|
for (int i = 0; i < ctx->num_filters; i++) {
|
||||||
|
SafeCFRelease(ctx->filters[i]);
|
||||||
|
}
|
||||||
|
av_freep(&ctx->filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
av_frame_free(&ctx->picref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const AVFilterPad vf_coreimage_inputs[] = {
|
||||||
|
{
|
||||||
|
.name = "default",
|
||||||
|
.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.filter_frame = filter_frame,
|
||||||
|
.config_props = config_input,
|
||||||
|
},
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const AVFilterPad vf_coreimage_outputs[] = {
|
||||||
|
{
|
||||||
|
.name = "default",
|
||||||
|
.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
},
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const AVFilterPad vsrc_coreimagesrc_outputs[] = {
|
||||||
|
{
|
||||||
|
.name = "default",
|
||||||
|
.type = AVMEDIA_TYPE_VIDEO,
|
||||||
|
.request_frame = request_frame,
|
||||||
|
.config_props = config_output,
|
||||||
|
},
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define OFFSET(x) offsetof(CoreImageContext, x)
|
||||||
|
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||||
|
|
||||||
|
#define GENERATOR_OPTIONS \
|
||||||
|
{"size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS}, \
|
||||||
|
{"s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "320x240"}, 0, 0, FLAGS}, \
|
||||||
|
{"rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, FLAGS}, \
|
||||||
|
{"r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, FLAGS}, \
|
||||||
|
{"duration", "set video duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = -1}, -1, INT64_MAX, FLAGS}, \
|
||||||
|
{"d", "set video duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = -1}, -1, INT64_MAX, FLAGS}, \
|
||||||
|
{"sar", "set video sample aspect ratio", OFFSET(sar), AV_OPT_TYPE_RATIONAL, {.dbl = 1}, 0, INT_MAX, FLAGS},
|
||||||
|
|
||||||
|
#define FILTER_OPTIONS \
|
||||||
|
{"list_filters", "list available filters", OFFSET(list_filters), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = FLAGS}, \
|
||||||
|
{"list_generators", "list available generators", OFFSET(list_generators), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, .flags = FLAGS}, \
|
||||||
|
{"filter", "names and options of filters to apply", OFFSET(filter_string), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS}, \
|
||||||
|
{"output_rect", "output rectangle within output image", OFFSET(output_rect), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS},
|
||||||
|
|
||||||
|
|
||||||
|
// definitions for coreimage video filter
|
||||||
|
static const AVOption coreimage_options[] = {
|
||||||
|
FILTER_OPTIONS
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
AVFILTER_DEFINE_CLASS(coreimage);
|
||||||
|
|
||||||
|
AVFilter ff_vf_coreimage = {
|
||||||
|
.name = "coreimage",
|
||||||
|
.description = NULL_IF_CONFIG_SMALL("Video filtering using CoreImage API."),
|
||||||
|
.init = init,
|
||||||
|
.uninit = uninit,
|
||||||
|
.priv_size = sizeof(CoreImageContext),
|
||||||
|
.priv_class = &coreimage_class,
|
||||||
|
.inputs = vf_coreimage_inputs,
|
||||||
|
.outputs = vf_coreimage_outputs,
|
||||||
|
.query_formats = query_formats,
|
||||||
|
};
|
||||||
|
|
||||||
|
// definitions for coreimagesrc video source
|
||||||
|
static const AVOption coreimagesrc_options[] = {
|
||||||
|
GENERATOR_OPTIONS
|
||||||
|
FILTER_OPTIONS
|
||||||
|
{ NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
AVFILTER_DEFINE_CLASS(coreimagesrc);
|
||||||
|
|
||||||
|
AVFilter ff_vsrc_coreimagesrc = {
|
||||||
|
.name = "coreimagesrc",
|
||||||
|
.description = NULL_IF_CONFIG_SMALL("Video source using image generators of CoreImage API."),
|
||||||
|
.init = init_src,
|
||||||
|
.uninit = uninit,
|
||||||
|
.priv_size = sizeof(CoreImageContext),
|
||||||
|
.priv_class = &coreimagesrc_class,
|
||||||
|
.inputs = NULL,
|
||||||
|
.outputs = vsrc_coreimagesrc_outputs,
|
||||||
|
.query_formats = query_formats_src,
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user