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 <steven@strobe.cc>
Signed-off-by: Stefano Sabatini <stefasab@gmail.com>
This commit is contained in:
Steven Robertson 2012-07-10 22:14:57 -07:00 committed by Stefano Sabatini
parent 8c2ce16f93
commit 82ecae8a70
12 changed files with 376 additions and 4 deletions

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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, \

View File

@ -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 <string.h>
#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 }
},
};

212
libavfilter/vf_alphamerge.c Normal file
View File

@ -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 <string.h>
#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 }
},
};

View File

@ -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(){

View File

@ -0,0 +1 @@
alphaextract_rgb b706818ea0d324e0d43adbaef9ab3470

View File

@ -0,0 +1 @@
alphaextract_yuv 430b779a379ad9c38f3b4c190f723cd4

View File

@ -0,0 +1 @@
alphamerge_rgb b706818ea0d324e0d43adbaef9ab3470

View File

@ -0,0 +1 @@
alphamerge_yuv 430b779a379ad9c38f3b4c190f723cd4