From 82ecae8a7069104698d491000b37629cd8d96fd8 Mon Sep 17 00:00:00 2001 From: Steven Robertson Date: Tue, 10 Jul 2012 22:14:57 -0700 Subject: [PATCH] lavfi: add alphaextract and alphamerge filters These filters are designed for storing and transmitting video sequences with alpha using higher-efficiency codecs such as x264 which don't natively support an alpha channel. 'alphaextract' takes an input stream with an alpha channel and returns a video containing just the alpha component as a grayscale value; 'alphamerge' takes an RGB or YUV stream and adds an alpha channel recovered from a second grayscale stream. Signed-off-by: Steven Robertson Signed-off-by: Stefano Sabatini --- Changelog | 1 + doc/filters.texi | 25 ++++ libavfilter/Makefile | 2 + libavfilter/allfilters.c | 2 + libavfilter/version.h | 2 +- libavfilter/vf_alphaextract.c | 117 +++++++++++++++++ libavfilter/vf_alphamerge.c | 212 +++++++++++++++++++++++++++++++ tests/lavfi-regression.sh | 15 ++- tests/ref/lavfi/alphaextract_rgb | 1 + tests/ref/lavfi/alphaextract_yuv | 1 + tests/ref/lavfi/alphamerge_rgb | 1 + tests/ref/lavfi/alphamerge_yuv | 1 + 12 files changed, 376 insertions(+), 4 deletions(-) create mode 100644 libavfilter/vf_alphaextract.c create mode 100644 libavfilter/vf_alphamerge.c create mode 100644 tests/ref/lavfi/alphaextract_rgb create mode 100644 tests/ref/lavfi/alphaextract_yuv create mode 100644 tests/ref/lavfi/alphamerge_rgb create mode 100644 tests/ref/lavfi/alphamerge_yuv diff --git a/Changelog b/Changelog index ab8e7045c5..44e647f3b6 100644 --- a/Changelog +++ b/Changelog @@ -34,6 +34,7 @@ version next: - ffmpeg -(no)stdin option - Opus decoder using libopus - caca output device using libcaca +- alphaextract and alphamerge filters version 0.11: diff --git a/doc/filters.texi b/doc/filters.texi index 4a6c092e59..2b5cc875f5 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -1101,6 +1101,31 @@ build. Below is a description of the currently available video filters. +@section alphaextract + +Extract the alpha component from the input as a grayscale video. This +is especially useful with the @var{alphamerge} filter. + +@section alphamerge + +Add or replace the alpha component of the primary input with the +grayscale value of a second input. This is intended for use with +@var{alphaextract} to allow the transmission or storage of frame +sequences that have alpha in a format that doesn't support an alpha +channel. + +For example, to reconstruct full frames from a normal YUV-encoded video +and a separate video created with @var{alphaextract}, you might use: +@example +movie=in_alpha.mkv [alpha]; [in][alpha] alphamerge [out] +@end example + +Since this filter is designed for reconstruction, it operates on frame +sequences without considering timestamps, and terminates when either +input reaches end of stream. This will cause problems if your encoding +pipeline drops frames. If you're trying to apply an image as an +overlay to a video stream, consider the @var{overlay} filter instead. + @section ass Draw ASS (Advanced Substation Alpha) subtitles on top of input video diff --git a/libavfilter/Makefile b/libavfilter/Makefile index b094f59a57..a177752352 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -76,6 +76,8 @@ OBJS-$(CONFIG_ABUFFERSINK_FILTER) += sink_buffer.o OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o OBJS-$(CONFIG_ASS_FILTER) += vf_ass.o +OBJS-$(CONFIG_ALPHAEXTRACT_FILTER) += vf_alphaextract.o +OBJS-$(CONFIG_ALPHAMERGE_FILTER) += vf_alphamerge.o OBJS-$(CONFIG_BBOX_FILTER) += bbox.o vf_bbox.o OBJS-$(CONFIG_BLACKDETECT_FILTER) += vf_blackdetect.o OBJS-$(CONFIG_BLACKFRAME_FILTER) += vf_blackframe.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 706405ebc0..aad453446c 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -64,6 +64,8 @@ void avfilter_register_all(void) REGISTER_FILTER (ABUFFERSINK, abuffersink, asink); REGISTER_FILTER (ANULLSINK, anullsink, asink); + REGISTER_FILTER (ALPHAEXTRACT, alphaextract, vf); + REGISTER_FILTER (ALPHAMERGE, alphamerge, vf); REGISTER_FILTER (ASS, ass, vf); REGISTER_FILTER (BBOX, bbox, vf); REGISTER_FILTER (BLACKDETECT, blackdetect, vf); diff --git a/libavfilter/version.h b/libavfilter/version.h index ea95cf39ce..96af8d6351 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -29,7 +29,7 @@ #include "libavutil/avutil.h" #define LIBAVFILTER_VERSION_MAJOR 3 -#define LIBAVFILTER_VERSION_MINOR 2 +#define LIBAVFILTER_VERSION_MINOR 3 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ diff --git a/libavfilter/vf_alphaextract.c b/libavfilter/vf_alphaextract.c new file mode 100644 index 0000000000..06cbb71315 --- /dev/null +++ b/libavfilter/vf_alphaextract.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012 Steven Robertson + * + * 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 + * simple channel-swapping filter to get at the alpha component + */ + +#include + +#include "libavutil/pixfmt.h" +#include "avfilter.h" +#include "drawutils.h" +#include "formats.h" +#include "video.h" + +enum { Y, U, V, A }; + +typedef struct { + int is_packed_rgb; + uint8_t rgba_map[4]; +} AlphaExtractContext; + +static int query_formats(AVFilterContext *ctx) +{ + enum PixelFormat in_fmts[] = { + PIX_FMT_YUVA444P, PIX_FMT_YUVA422P, PIX_FMT_YUVA420P, + PIX_FMT_RGBA, PIX_FMT_BGRA, PIX_FMT_ARGB, PIX_FMT_ABGR, + PIX_FMT_NONE + }; + enum PixelFormat out_fmts[] = { PIX_FMT_GRAY8, PIX_FMT_NONE }; + ff_formats_ref(ff_make_format_list(in_fmts), &ctx->inputs[0]->out_formats); + ff_formats_ref(ff_make_format_list(out_fmts), &ctx->outputs[0]->in_formats); + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + AlphaExtractContext *extract = inlink->dst->priv; + extract->is_packed_rgb = + ff_fill_rgba_map(extract->rgba_map, inlink->format) >= 0; + return 0; +} + +static void draw_slice(AVFilterLink *inlink, int y0, int h, int slice_dir) +{ + AlphaExtractContext *extract = inlink->dst->priv; + AVFilterBufferRef *cur_buf = inlink->cur_buf; + AVFilterBufferRef *out_buf = inlink->dst->outputs[0]->out_buf; + + if (extract->is_packed_rgb) { + int x, y; + uint8_t *pin, *pout; + for (y = y0; y < (y0 + h); y++) { + pin = cur_buf->data[0] + y * cur_buf->linesize[0] + extract->rgba_map[A]; + pout = out_buf->data[0] + y * out_buf->linesize[0]; + for (x = 0; x < out_buf->video->w; x++) { + *pout = *pin; + pout += 1; + pin += 4; + } + } + } else if (cur_buf->linesize[A] == out_buf->linesize[Y]) { + const int linesize = cur_buf->linesize[A]; + memcpy(out_buf->data[Y] + y0 * linesize, + cur_buf->data[A] + y0 * linesize, + linesize * h); + } else { + const int linesize = FFMIN(out_buf->linesize[Y], cur_buf->linesize[A]); + int y; + for (y = y0; y < (y0 + h); y++) { + memcpy(out_buf->data[Y] + y * out_buf->linesize[Y], + cur_buf->data[A] + y * cur_buf->linesize[A], + linesize); + } + } + ff_draw_slice(inlink->dst->outputs[0], y0, h, slice_dir); +} + +AVFilter avfilter_vf_alphaextract = { + .name = "alphaextract", + .description = NULL_IF_CONFIG_SMALL("Extract an alpha channel as a " + "grayscale image component."), + .priv_size = sizeof(AlphaExtractContext), + .query_formats = query_formats, + + .inputs = (const AVFilterPad[]) { + { .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input, + .draw_slice = draw_slice, + .min_perms = AV_PERM_READ }, + { .name = NULL } + }, + .outputs = (const AVFilterPad[]) { + { .name = "default", + .type = AVMEDIA_TYPE_VIDEO, }, + { .name = NULL } + }, +}; diff --git a/libavfilter/vf_alphamerge.c b/libavfilter/vf_alphamerge.c new file mode 100644 index 0000000000..67422a1df1 --- /dev/null +++ b/libavfilter/vf_alphamerge.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2012 Steven Robertson + * + * 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 + * copy an alpha component from another video's luma + */ + +#include + +#include "libavutil/pixfmt.h" +#include "avfilter.h" +#include "bufferqueue.h" +#include "drawutils.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +enum { Y, U, V, A }; + +typedef struct { + int frame_requested; + int is_packed_rgb; + uint8_t rgba_map[4]; + struct FFBufQueue queue_main; + struct FFBufQueue queue_alpha; +} AlphaMergeContext; + +static av_cold void uninit(AVFilterContext *ctx) +{ + AlphaMergeContext *merge = ctx->priv; + ff_bufqueue_discard_all(&merge->queue_main); + ff_bufqueue_discard_all(&merge->queue_alpha); +} + +static int query_formats(AVFilterContext *ctx) +{ + enum PixelFormat main_fmts[] = { + PIX_FMT_YUVA444P, PIX_FMT_YUVA422P, PIX_FMT_YUVA420P, + PIX_FMT_RGBA, PIX_FMT_BGRA, PIX_FMT_ARGB, PIX_FMT_ABGR, + PIX_FMT_NONE + }; + enum PixelFormat alpha_fmts[] = { PIX_FMT_GRAY8, PIX_FMT_NONE }; + AVFilterFormats *main_formats = ff_make_format_list(main_fmts); + AVFilterFormats *alpha_formats = ff_make_format_list(alpha_fmts); + ff_formats_ref(main_formats, &ctx->inputs[0]->out_formats); + ff_formats_ref(alpha_formats, &ctx->inputs[1]->out_formats); + ff_formats_ref(main_formats, &ctx->outputs[0]->in_formats); + return 0; +} + +static int config_input_main(AVFilterLink *inlink) +{ + AlphaMergeContext *merge = inlink->dst->priv; + merge->is_packed_rgb = + ff_fill_rgba_map(merge->rgba_map, inlink->format) >= 0; + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AVFilterLink *mainlink = ctx->inputs[0]; + AVFilterLink *alphalink = ctx->inputs[1]; + if (mainlink->w != alphalink->w || mainlink->h != alphalink->h) { + av_log(ctx, AV_LOG_ERROR, + "Input frame sizes do not match (%dx%d vs %dx%d).\n", + mainlink->w, mainlink->h, + alphalink->w, alphalink->h); + return AVERROR(EINVAL); + } + + outlink->w = mainlink->w; + outlink->h = mainlink->h; + outlink->time_base = mainlink->time_base; + outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; + outlink->frame_rate = mainlink->frame_rate; + return 0; +} + +static void start_frame(AVFilterLink *inlink, AVFilterBufferRef *picref) {} +static void draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir) {} + +static void draw_frame(AVFilterContext *ctx, + AVFilterBufferRef *main_buf, + AVFilterBufferRef *alpha_buf) +{ + AlphaMergeContext *merge = ctx->priv; + int h = main_buf->video->h; + + if (merge->is_packed_rgb) { + int x, y; + uint8_t *pin, *pout; + for (y = 0; y < h; y++) { + pin = alpha_buf->data[0] + y * alpha_buf->linesize[0]; + pout = main_buf->data[0] + y * main_buf->linesize[0] + merge->rgba_map[A]; + for (x = 0; x < main_buf->video->w; x++) { + *pout = *pin; + pin += 1; + pout += 4; + } + } + } else { + int y; + const int main_linesize = main_buf->linesize[A]; + const int alpha_linesize = alpha_buf->linesize[Y]; + for (y = 0; y < h && y < alpha_buf->video->h; y++) { + memcpy(main_buf->data[A] + y * main_linesize, + alpha_buf->data[Y] + y * alpha_linesize, + FFMIN(main_linesize, alpha_linesize)); + } + } + ff_draw_slice(ctx->outputs[0], 0, h, 1); +} + +static void end_frame(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + AlphaMergeContext *merge = ctx->priv; + + int is_alpha = (inlink == ctx->inputs[1]); + struct FFBufQueue *queue = + (is_alpha ? &merge->queue_alpha : &merge->queue_main); + ff_bufqueue_add(ctx, queue, inlink->cur_buf); + inlink->cur_buf = NULL; + + while (1) { + AVFilterBufferRef *main_buf, *alpha_buf; + + if (!ff_bufqueue_peek(&merge->queue_main, 0) || + !ff_bufqueue_peek(&merge->queue_alpha, 0)) break; + + main_buf = ff_bufqueue_get(&merge->queue_main); + alpha_buf = ff_bufqueue_get(&merge->queue_alpha); + + ctx->outputs[0]->out_buf = main_buf; + ff_start_frame(ctx->outputs[0], avfilter_ref_buffer(main_buf, ~0)); + merge->frame_requested = 0; + draw_frame(ctx, main_buf, alpha_buf); + ff_end_frame(ctx->outputs[0]); + avfilter_unref_buffer(alpha_buf); + } +} + +static int request_frame(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + AlphaMergeContext *merge = ctx->priv; + int in, ret; + + merge->frame_requested = 1; + while (merge->frame_requested) { + in = ff_bufqueue_peek(&merge->queue_main, 0) ? 0 : 1; + ret = ff_request_frame(ctx->inputs[in]); + if (ret < 0) + return ret; + } + return 0; +} + +AVFilter avfilter_vf_alphamerge = { + .name = "alphamerge", + .description = NULL_IF_CONFIG_SMALL("Copy the luma value of the second " + "input into the alpha channel of the first input."), + .uninit = uninit, + .priv_size = sizeof(AlphaMergeContext), + .query_formats = query_formats, + + .inputs = (const AVFilterPad[]) { + { .name = "main", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_input_main, + .get_video_buffer = ff_null_get_video_buffer, + .start_frame = start_frame, + .draw_slice = draw_slice, + .end_frame = end_frame, + .min_perms = AV_PERM_READ | AV_PERM_WRITE, + .rej_perms = AV_PERM_REUSE2 | AV_PERM_PRESERVE }, + { .name = "alpha", + .type = AVMEDIA_TYPE_VIDEO, + .start_frame = start_frame, + .draw_slice = draw_slice, + .end_frame = end_frame, + .min_perms = AV_PERM_READ, + .rej_perms = AV_PERM_REUSE2 }, + { .name = NULL } + }, + .outputs = (const AVFilterPad[]) { + { .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + .request_frame = request_frame }, + { .name = NULL } + }, +}; diff --git a/tests/lavfi-regression.sh b/tests/lavfi-regression.sh index dd5e2dac62..61040445bf 100755 --- a/tests/lavfi-regression.sh +++ b/tests/lavfi-regression.sh @@ -13,21 +13,25 @@ eval do_$test=y do_video_filter() { label=$1 - filters=$2 + filters="$2" shift 2 printf '%-20s' $label run_avconv $DEC_OPTS -f image2 -vcodec pgmyuv -i $raw_src \ $ENC_OPTS -vf "$filters" -vcodec rawvideo $* -f nut md5: } -do_lavfi() { - vfilters="slicify=random,$2" +do_lavfi_plain() { + vfilters="$2" if [ $test = $1 ] ; then do_video_filter $test "$vfilters" fi } +do_lavfi() { + do_lavfi_plain $1 "slicify=random,$2" +} + do_lavfi_colormatrix() { do_lavfi "${1}1" "$1=$4:$5,$1=$5:$3,$1=$3:$4,$1=$4:$3,$1=$3:$5,$1=$5:$2" do_lavfi "${1}2" "$1=$2:$3,$1=$3:$2,$1=$2:$4,$1=$4:$2,$1=$2:$5,$1=$5:$4" @@ -60,6 +64,11 @@ do_lavfi "vflip" "vflip" do_lavfi "vflip_crop" "vflip,crop=iw-100:ih-100:100:100" do_lavfi "vflip_vflip" "vflip,vflip" +do_lavfi_plain "alphamerge_rgb" "[in]slicify=random,format=bgra,split,alphamerge[out]" +do_lavfi_plain "alphamerge_yuv" "[in]slicify=random,format=yuv420p,split,alphamerge[out]" +do_lavfi_plain "alphaextract_rgb" "[in]slicify=random,format=bgra,split,alphamerge,slicify=random,split[o3][o4];[o4]alphaextract[alpha];[o3][alpha]alphamerge[out]" +do_lavfi_plain "alphaextract_yuv" "[in]slicify=random,format=yuv420p,split,alphamerge,slicify=random,split[o3][o4];[o4]alphaextract[alpha];[o3][alpha]alphamerge[out]" + do_lavfi_colormatrix "colormatrix" bt709 fcc bt601 smpte240m do_lavfi_pixfmts(){ diff --git a/tests/ref/lavfi/alphaextract_rgb b/tests/ref/lavfi/alphaextract_rgb new file mode 100644 index 0000000000..3b86986bae --- /dev/null +++ b/tests/ref/lavfi/alphaextract_rgb @@ -0,0 +1 @@ +alphaextract_rgb b706818ea0d324e0d43adbaef9ab3470 diff --git a/tests/ref/lavfi/alphaextract_yuv b/tests/ref/lavfi/alphaextract_yuv new file mode 100644 index 0000000000..7bfcf8629f --- /dev/null +++ b/tests/ref/lavfi/alphaextract_yuv @@ -0,0 +1 @@ +alphaextract_yuv 430b779a379ad9c38f3b4c190f723cd4 diff --git a/tests/ref/lavfi/alphamerge_rgb b/tests/ref/lavfi/alphamerge_rgb new file mode 100644 index 0000000000..6dc3b9a513 --- /dev/null +++ b/tests/ref/lavfi/alphamerge_rgb @@ -0,0 +1 @@ +alphamerge_rgb b706818ea0d324e0d43adbaef9ab3470 diff --git a/tests/ref/lavfi/alphamerge_yuv b/tests/ref/lavfi/alphamerge_yuv new file mode 100644 index 0000000000..ecd7bf8d9b --- /dev/null +++ b/tests/ref/lavfi/alphamerge_yuv @@ -0,0 +1 @@ +alphamerge_yuv 430b779a379ad9c38f3b4c190f723cd4