Bundling ffmpeg, initial attempt. This builds on MinGW; Linux is untested as of yet.
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2010 S.N. Hemanth Meenakshisundaram <[email protected]>
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
* Copyright (c) 2011 Mina Nagy Zaki
|
||||
*
|
||||
* 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
|
||||
* sample format and channel layout conversion audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
enum AVSampleFormat out_sample_fmt;
|
||||
int64_t out_chlayout;
|
||||
struct SwrContext *swr;
|
||||
char *format_str;
|
||||
char *channel_layout_str;
|
||||
} AConvertContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AConvertContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption aconvert_options[] = {
|
||||
{ "sample_fmt", "", OFFSET(format_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ "channel_layout", "", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(aconvert);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AConvertContext *aconvert = ctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
av_log(ctx, AV_LOG_WARNING, "This filter is deprecated, use aformat instead\n");
|
||||
|
||||
aconvert->out_sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
aconvert->out_chlayout = 0;
|
||||
|
||||
if (aconvert->format_str && strcmp(aconvert->format_str, "auto") &&
|
||||
(ret = ff_parse_sample_format(&aconvert->out_sample_fmt, aconvert->format_str, ctx)) < 0)
|
||||
return ret;
|
||||
if (aconvert->channel_layout_str && strcmp(aconvert->channel_layout_str, "auto"))
|
||||
return ff_parse_channel_layout(&aconvert->out_chlayout, NULL, aconvert->channel_layout_str, ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AConvertContext *aconvert = ctx->priv;
|
||||
swr_free(&aconvert->swr);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AConvertContext *aconvert = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
AVFilterChannelLayouts *layouts;
|
||||
|
||||
ff_formats_ref(ff_all_formats(AVMEDIA_TYPE_AUDIO),
|
||||
&inlink->out_formats);
|
||||
if (aconvert->out_sample_fmt != AV_SAMPLE_FMT_NONE) {
|
||||
formats = NULL;
|
||||
ff_add_format(&formats, aconvert->out_sample_fmt);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
} else
|
||||
ff_formats_ref(ff_all_formats(AVMEDIA_TYPE_AUDIO),
|
||||
&outlink->in_formats);
|
||||
|
||||
ff_channel_layouts_ref(ff_all_channel_layouts(),
|
||||
&inlink->out_channel_layouts);
|
||||
if (aconvert->out_chlayout != 0) {
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts, aconvert->out_chlayout);
|
||||
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
|
||||
} else
|
||||
ff_channel_layouts_ref(ff_all_channel_layouts(),
|
||||
&outlink->in_channel_layouts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
int ret;
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AConvertContext *aconvert = ctx->priv;
|
||||
char buf1[64], buf2[64];
|
||||
|
||||
/* if not specified in args, use the format and layout of the output */
|
||||
if (aconvert->out_sample_fmt == AV_SAMPLE_FMT_NONE)
|
||||
aconvert->out_sample_fmt = outlink->format;
|
||||
if (aconvert->out_chlayout == 0)
|
||||
aconvert->out_chlayout = outlink->channel_layout;
|
||||
|
||||
aconvert->swr = swr_alloc_set_opts(aconvert->swr,
|
||||
aconvert->out_chlayout, aconvert->out_sample_fmt, inlink->sample_rate,
|
||||
inlink->channel_layout, inlink->format, inlink->sample_rate,
|
||||
0, ctx);
|
||||
if (!aconvert->swr)
|
||||
return AVERROR(ENOMEM);
|
||||
ret = swr_init(aconvert->swr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
av_get_channel_layout_string(buf1, sizeof(buf1),
|
||||
-1, inlink ->channel_layout);
|
||||
av_get_channel_layout_string(buf2, sizeof(buf2),
|
||||
-1, outlink->channel_layout);
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"fmt:%s cl:%s -> fmt:%s cl:%s\n",
|
||||
av_get_sample_fmt_name(inlink ->format), buf1,
|
||||
av_get_sample_fmt_name(outlink->format), buf2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamplesref)
|
||||
{
|
||||
AConvertContext *aconvert = inlink->dst->priv;
|
||||
const int n = insamplesref->nb_samples;
|
||||
AVFilterLink *const outlink = inlink->dst->outputs[0];
|
||||
AVFrame *outsamplesref = ff_get_audio_buffer(outlink, n);
|
||||
int ret;
|
||||
|
||||
if (!outsamplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
swr_convert(aconvert->swr, outsamplesref->extended_data, n,
|
||||
(void *)insamplesref->extended_data, n);
|
||||
|
||||
av_frame_copy_props(outsamplesref, insamplesref);
|
||||
av_frame_set_channels(outsamplesref, outlink->channels);
|
||||
outsamplesref->channel_layout = outlink->channel_layout;
|
||||
|
||||
ret = ff_filter_frame(outlink, outsamplesref);
|
||||
av_frame_free(&insamplesref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad aconvert_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad aconvert_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aconvert = {
|
||||
.name = "aconvert",
|
||||
.description = NULL_IF_CONFIG_SMALL("Convert the input audio to sample_fmt:channel_layout."),
|
||||
.priv_size = sizeof(AConvertContext),
|
||||
.priv_class = &aconvert_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = aconvert_inputs,
|
||||
.outputs = aconvert_outputs,
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ChanDelay {
|
||||
int delay;
|
||||
unsigned delay_index;
|
||||
unsigned index;
|
||||
uint8_t *samples;
|
||||
} ChanDelay;
|
||||
|
||||
typedef struct AudioDelayContext {
|
||||
const AVClass *class;
|
||||
char *delays;
|
||||
ChanDelay *chandelay;
|
||||
int nb_delays;
|
||||
int block_align;
|
||||
unsigned max_delay;
|
||||
int64_t next_pts;
|
||||
|
||||
void (*delay_channel)(ChanDelay *d, int nb_samples,
|
||||
const uint8_t *src, uint8_t *dst);
|
||||
} AudioDelayContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AudioDelayContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption adelay_options[] = {
|
||||
{ "delays", "set list of delays for each channel", OFFSET(delays), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, A },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(adelay);
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterChannelLayouts *layouts;
|
||||
AVFilterFormats *formats;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_U8P, AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DELAY(name, type, fill) \
|
||||
static void delay_channel_## name ##p(ChanDelay *d, int nb_samples, \
|
||||
const uint8_t *ssrc, uint8_t *ddst) \
|
||||
{ \
|
||||
const type *src = (type *)ssrc; \
|
||||
type *dst = (type *)ddst; \
|
||||
type *samples = (type *)d->samples; \
|
||||
\
|
||||
while (nb_samples) { \
|
||||
if (d->delay_index < d->delay) { \
|
||||
const int len = FFMIN(nb_samples, d->delay - d->delay_index); \
|
||||
\
|
||||
memcpy(&samples[d->delay_index], src, len * sizeof(type)); \
|
||||
memset(dst, fill, len * sizeof(type)); \
|
||||
d->delay_index += len; \
|
||||
src += len; \
|
||||
dst += len; \
|
||||
nb_samples -= len; \
|
||||
} else { \
|
||||
*dst = samples[d->index]; \
|
||||
samples[d->index] = *src; \
|
||||
nb_samples--; \
|
||||
d->index++; \
|
||||
src++, dst++; \
|
||||
d->index = d->index >= d->delay ? 0 : d->index; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
DELAY(u8, uint8_t, 0x80)
|
||||
DELAY(s16, int16_t, 0)
|
||||
DELAY(s32, int32_t, 0)
|
||||
DELAY(flt, float, 0)
|
||||
DELAY(dbl, double, 0)
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AudioDelayContext *s = ctx->priv;
|
||||
char *p, *arg, *saveptr = NULL;
|
||||
int i;
|
||||
|
||||
s->chandelay = av_calloc(inlink->channels, sizeof(*s->chandelay));
|
||||
if (!s->chandelay)
|
||||
return AVERROR(ENOMEM);
|
||||
s->nb_delays = inlink->channels;
|
||||
s->block_align = av_get_bytes_per_sample(inlink->format);
|
||||
|
||||
p = s->delays;
|
||||
for (i = 0; i < s->nb_delays; i++) {
|
||||
ChanDelay *d = &s->chandelay[i];
|
||||
float delay;
|
||||
|
||||
if (!(arg = av_strtok(p, "|", &saveptr)))
|
||||
break;
|
||||
|
||||
p = NULL;
|
||||
sscanf(arg, "%f", &delay);
|
||||
|
||||
d->delay = delay * inlink->sample_rate / 1000.0;
|
||||
if (d->delay < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Delay must be non negative number.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < s->nb_delays; i++) {
|
||||
ChanDelay *d = &s->chandelay[i];
|
||||
|
||||
if (!d->delay)
|
||||
continue;
|
||||
|
||||
d->samples = av_malloc_array(d->delay, s->block_align);
|
||||
if (!d->samples)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
s->max_delay = FFMAX(s->max_delay, d->delay);
|
||||
}
|
||||
|
||||
if (!s->max_delay) {
|
||||
av_log(ctx, AV_LOG_ERROR, "At least one delay >0 must be specified.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_U8P : s->delay_channel = delay_channel_u8p ; break;
|
||||
case AV_SAMPLE_FMT_S16P: s->delay_channel = delay_channel_s16p; break;
|
||||
case AV_SAMPLE_FMT_S32P: s->delay_channel = delay_channel_s32p; break;
|
||||
case AV_SAMPLE_FMT_FLTP: s->delay_channel = delay_channel_fltp; break;
|
||||
case AV_SAMPLE_FMT_DBLP: s->delay_channel = delay_channel_dblp; break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AudioDelayContext *s = ctx->priv;
|
||||
AVFrame *out_frame;
|
||||
int i;
|
||||
|
||||
if (ctx->is_disabled || !s->delays)
|
||||
return ff_filter_frame(ctx->outputs[0], frame);
|
||||
|
||||
out_frame = ff_get_audio_buffer(inlink, frame->nb_samples);
|
||||
if (!out_frame)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_frame, frame);
|
||||
|
||||
for (i = 0; i < s->nb_delays; i++) {
|
||||
ChanDelay *d = &s->chandelay[i];
|
||||
const uint8_t *src = frame->extended_data[i];
|
||||
uint8_t *dst = out_frame->extended_data[i];
|
||||
|
||||
if (!d->delay)
|
||||
memcpy(dst, src, frame->nb_samples * s->block_align);
|
||||
else
|
||||
s->delay_channel(d, frame->nb_samples, src, dst);
|
||||
}
|
||||
|
||||
s->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, inlink->sample_rate}, inlink->time_base);
|
||||
av_frame_free(&frame);
|
||||
return ff_filter_frame(ctx->outputs[0], out_frame);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AudioDelayContext *s = ctx->priv;
|
||||
int ret;
|
||||
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
if (ret == AVERROR_EOF && !ctx->is_disabled && s->max_delay) {
|
||||
int nb_samples = FFMIN(s->max_delay, 2048);
|
||||
AVFrame *frame;
|
||||
|
||||
frame = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
s->max_delay -= nb_samples;
|
||||
|
||||
av_samples_set_silence(frame->extended_data, 0,
|
||||
frame->nb_samples,
|
||||
outlink->channels,
|
||||
frame->format);
|
||||
|
||||
frame->pts = s->next_pts;
|
||||
if (s->next_pts != AV_NOPTS_VALUE)
|
||||
s->next_pts += av_rescale_q(nb_samples, (AVRational){1, outlink->sample_rate}, outlink->time_base);
|
||||
|
||||
ret = filter_frame(ctx->inputs[0], frame);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AudioDelayContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->nb_delays; i++)
|
||||
av_free(s->chandelay[i].samples);
|
||||
av_freep(&s->chandelay);
|
||||
}
|
||||
|
||||
static const AVFilterPad adelay_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_input,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad adelay_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.request_frame = request_frame,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_adelay = {
|
||||
.name = "adelay",
|
||||
.description = NULL_IF_CONFIG_SMALL("Delay one or more audio channels."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioDelayContext),
|
||||
.priv_class = &adelay_class,
|
||||
.uninit = uninit,
|
||||
.inputs = adelay_inputs,
|
||||
.outputs = adelay_outputs,
|
||||
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
|
||||
};
|
||||
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct AudioEchoContext {
|
||||
const AVClass *class;
|
||||
float in_gain, out_gain;
|
||||
char *delays, *decays;
|
||||
float *delay, *decay;
|
||||
int nb_echoes;
|
||||
int delay_index;
|
||||
uint8_t **delayptrs;
|
||||
int max_samples, fade_out;
|
||||
int *samples;
|
||||
int64_t next_pts;
|
||||
|
||||
void (*echo_samples)(struct AudioEchoContext *ctx, uint8_t **delayptrs,
|
||||
uint8_t * const *src, uint8_t **dst,
|
||||
int nb_samples, int channels);
|
||||
} AudioEchoContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AudioEchoContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption aecho_options[] = {
|
||||
{ "in_gain", "set signal input gain", OFFSET(in_gain), AV_OPT_TYPE_FLOAT, {.dbl=0.6}, 0, 1, A },
|
||||
{ "out_gain", "set signal output gain", OFFSET(out_gain), AV_OPT_TYPE_FLOAT, {.dbl=0.3}, 0, 1, A },
|
||||
{ "delays", "set list of signal delays", OFFSET(delays), AV_OPT_TYPE_STRING, {.str="1000"}, 0, 0, A },
|
||||
{ "decays", "set list of signal decays", OFFSET(decays), AV_OPT_TYPE_STRING, {.str="0.5"}, 0, 0, A },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(aecho);
|
||||
|
||||
static void count_items(char *item_str, int *nb_items)
|
||||
{
|
||||
char *p;
|
||||
|
||||
*nb_items = 1;
|
||||
for (p = item_str; *p; p++) {
|
||||
if (*p == '|')
|
||||
(*nb_items)++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void fill_items(char *item_str, int *nb_items, float *items)
|
||||
{
|
||||
char *p, *saveptr = NULL;
|
||||
int i, new_nb_items = 0;
|
||||
|
||||
p = item_str;
|
||||
for (i = 0; i < *nb_items; i++) {
|
||||
char *tstr = av_strtok(p, "|", &saveptr);
|
||||
p = NULL;
|
||||
new_nb_items += sscanf(tstr, "%f", &items[i]) == 1;
|
||||
}
|
||||
|
||||
*nb_items = new_nb_items;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AudioEchoContext *s = ctx->priv;
|
||||
|
||||
av_freep(&s->delay);
|
||||
av_freep(&s->decay);
|
||||
av_freep(&s->samples);
|
||||
|
||||
if (s->delayptrs)
|
||||
av_freep(&s->delayptrs[0]);
|
||||
av_freep(&s->delayptrs);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AudioEchoContext *s = ctx->priv;
|
||||
int nb_delays, nb_decays, i;
|
||||
|
||||
if (!s->delays || !s->decays) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Missing delays and/or decays.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
count_items(s->delays, &nb_delays);
|
||||
count_items(s->decays, &nb_decays);
|
||||
|
||||
s->delay = av_realloc_f(s->delay, nb_delays, sizeof(*s->delay));
|
||||
s->decay = av_realloc_f(s->decay, nb_decays, sizeof(*s->decay));
|
||||
if (!s->delay || !s->decay)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
fill_items(s->delays, &nb_delays, s->delay);
|
||||
fill_items(s->decays, &nb_decays, s->decay);
|
||||
|
||||
if (nb_delays != nb_decays) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Number of delays %d differs from number of decays %d.\n", nb_delays, nb_decays);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
s->nb_echoes = nb_delays;
|
||||
if (!s->nb_echoes) {
|
||||
av_log(ctx, AV_LOG_ERROR, "At least one decay & delay must be set.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
s->samples = av_realloc_f(s->samples, nb_delays, sizeof(*s->samples));
|
||||
if (!s->samples)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < nb_delays; i++) {
|
||||
if (s->delay[i] <= 0 || s->delay[i] > 90000) {
|
||||
av_log(ctx, AV_LOG_ERROR, "delay[%d]: %f is out of allowed range: (0, 90000]\n", i, s->delay[i]);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (s->decay[i] <= 0 || s->decay[i] > 1) {
|
||||
av_log(ctx, AV_LOG_ERROR, "decay[%d]: %f is out of allowed range: (0, 1]\n", i, s->decay[i]);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
s->next_pts = AV_NOPTS_VALUE;
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG, "nb_echoes:%d\n", s->nb_echoes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterChannelLayouts *layouts;
|
||||
AVFilterFormats *formats;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MOD(a, b) (((a) >= (b)) ? (a) - (b) : (a))
|
||||
|
||||
#define ECHO(name, type, min, max) \
|
||||
static void echo_samples_## name ##p(AudioEchoContext *ctx, \
|
||||
uint8_t **delayptrs, \
|
||||
uint8_t * const *src, uint8_t **dst, \
|
||||
int nb_samples, int channels) \
|
||||
{ \
|
||||
const double out_gain = ctx->out_gain; \
|
||||
const double in_gain = ctx->in_gain; \
|
||||
const int nb_echoes = ctx->nb_echoes; \
|
||||
const int max_samples = ctx->max_samples; \
|
||||
int i, j, chan, av_uninit(index); \
|
||||
\
|
||||
av_assert1(channels > 0); /* would corrupt delay_index */ \
|
||||
\
|
||||
for (chan = 0; chan < channels; chan++) { \
|
||||
const type *s = (type *)src[chan]; \
|
||||
type *d = (type *)dst[chan]; \
|
||||
type *dbuf = (type *)delayptrs[chan]; \
|
||||
\
|
||||
index = ctx->delay_index; \
|
||||
for (i = 0; i < nb_samples; i++, s++, d++) { \
|
||||
double out, in; \
|
||||
\
|
||||
in = *s; \
|
||||
out = in * in_gain; \
|
||||
for (j = 0; j < nb_echoes; j++) { \
|
||||
int ix = index + max_samples - ctx->samples[j]; \
|
||||
ix = MOD(ix, max_samples); \
|
||||
out += dbuf[ix] * ctx->decay[j]; \
|
||||
} \
|
||||
out *= out_gain; \
|
||||
\
|
||||
*d = av_clipd(out, min, max); \
|
||||
dbuf[index] = in; \
|
||||
\
|
||||
index = MOD(index + 1, max_samples); \
|
||||
} \
|
||||
} \
|
||||
ctx->delay_index = index; \
|
||||
}
|
||||
|
||||
ECHO(dbl, double, -1.0, 1.0 )
|
||||
ECHO(flt, float, -1.0, 1.0 )
|
||||
ECHO(s16, int16_t, INT16_MIN, INT16_MAX)
|
||||
ECHO(s32, int32_t, INT32_MIN, INT32_MAX)
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AudioEchoContext *s = ctx->priv;
|
||||
float volume = 1.0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->nb_echoes; i++) {
|
||||
s->samples[i] = s->delay[i] * outlink->sample_rate / 1000.0;
|
||||
s->max_samples = FFMAX(s->max_samples, s->samples[i]);
|
||||
volume += s->decay[i];
|
||||
}
|
||||
|
||||
if (s->max_samples <= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Nothing to echo - missing delay samples.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->fade_out = s->max_samples;
|
||||
|
||||
if (volume * s->in_gain * s->out_gain > 1.0)
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"out_gain %f can cause saturation of output\n", s->out_gain);
|
||||
|
||||
switch (outlink->format) {
|
||||
case AV_SAMPLE_FMT_DBLP: s->echo_samples = echo_samples_dblp; break;
|
||||
case AV_SAMPLE_FMT_FLTP: s->echo_samples = echo_samples_fltp; break;
|
||||
case AV_SAMPLE_FMT_S16P: s->echo_samples = echo_samples_s16p; break;
|
||||
case AV_SAMPLE_FMT_S32P: s->echo_samples = echo_samples_s32p; break;
|
||||
}
|
||||
|
||||
|
||||
if (s->delayptrs)
|
||||
av_freep(&s->delayptrs[0]);
|
||||
av_freep(&s->delayptrs);
|
||||
|
||||
return av_samples_alloc_array_and_samples(&s->delayptrs, NULL,
|
||||
outlink->channels,
|
||||
s->max_samples,
|
||||
outlink->format, 0);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AudioEchoContext *s = ctx->priv;
|
||||
AVFrame *out_frame;
|
||||
|
||||
if (av_frame_is_writable(frame)) {
|
||||
out_frame = frame;
|
||||
} else {
|
||||
out_frame = ff_get_audio_buffer(inlink, frame->nb_samples);
|
||||
if (!out_frame)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_frame, frame);
|
||||
}
|
||||
|
||||
s->echo_samples(s, s->delayptrs, frame->extended_data, out_frame->extended_data,
|
||||
frame->nb_samples, inlink->channels);
|
||||
|
||||
if (frame != out_frame)
|
||||
av_frame_free(&frame);
|
||||
|
||||
s->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, inlink->sample_rate}, inlink->time_base);
|
||||
return ff_filter_frame(ctx->outputs[0], out_frame);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AudioEchoContext *s = ctx->priv;
|
||||
int ret;
|
||||
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
if (ret == AVERROR_EOF && !ctx->is_disabled && s->fade_out) {
|
||||
int nb_samples = FFMIN(s->fade_out, 2048);
|
||||
AVFrame *frame;
|
||||
|
||||
frame = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
s->fade_out -= nb_samples;
|
||||
|
||||
av_samples_set_silence(frame->extended_data, 0,
|
||||
frame->nb_samples,
|
||||
outlink->channels,
|
||||
frame->format);
|
||||
|
||||
s->echo_samples(s, s->delayptrs, frame->extended_data, frame->extended_data,
|
||||
frame->nb_samples, outlink->channels);
|
||||
|
||||
frame->pts = s->next_pts;
|
||||
if (s->next_pts != AV_NOPTS_VALUE)
|
||||
s->next_pts += av_rescale_q(nb_samples, (AVRational){1, outlink->sample_rate}, outlink->time_base);
|
||||
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad aecho_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad aecho_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.request_frame = request_frame,
|
||||
.config_props = config_output,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aecho = {
|
||||
.name = "aecho",
|
||||
.description = NULL_IF_CONFIG_SMALL("Add echoing to the audio."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioEchoContext),
|
||||
.priv_class = &aecho_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.inputs = aecho_inputs,
|
||||
.outputs = aecho_outputs,
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
* fade audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int type;
|
||||
int curve;
|
||||
int nb_samples;
|
||||
int64_t start_sample;
|
||||
int64_t duration;
|
||||
int64_t start_time;
|
||||
|
||||
void (*fade_samples)(uint8_t **dst, uint8_t * const *src,
|
||||
int nb_samples, int channels, int direction,
|
||||
int64_t start, int range, int curve);
|
||||
} AudioFadeContext;
|
||||
|
||||
enum CurveType { TRI, QSIN, ESIN, HSIN, LOG, PAR, QUA, CUB, SQU, CBR };
|
||||
|
||||
#define OFFSET(x) offsetof(AudioFadeContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption afade_options[] = {
|
||||
{ "type", "set the fade direction", OFFSET(type), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, FLAGS, "type" },
|
||||
{ "t", "set the fade direction", OFFSET(type), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, 1, FLAGS, "type" },
|
||||
{ "in", "fade-in", 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, FLAGS, "type" },
|
||||
{ "out", "fade-out", 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, FLAGS, "type" },
|
||||
{ "start_sample", "set number of first sample to start fading", OFFSET(start_sample), AV_OPT_TYPE_INT64, {.i64 = 0 }, 0, INT64_MAX, FLAGS },
|
||||
{ "ss", "set number of first sample to start fading", OFFSET(start_sample), AV_OPT_TYPE_INT64, {.i64 = 0 }, 0, INT64_MAX, FLAGS },
|
||||
{ "nb_samples", "set number of samples for fade duration", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 44100}, 1, INT32_MAX, FLAGS },
|
||||
{ "ns", "set number of samples for fade duration", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 44100}, 1, INT32_MAX, FLAGS },
|
||||
{ "start_time", "set time to start fading", OFFSET(start_time), AV_OPT_TYPE_DURATION, {.i64 = 0. }, 0, INT32_MAX, FLAGS },
|
||||
{ "st", "set time to start fading", OFFSET(start_time), AV_OPT_TYPE_DURATION, {.i64 = 0. }, 0, INT32_MAX, FLAGS },
|
||||
{ "duration", "set fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = 0. }, 0, INT32_MAX, FLAGS },
|
||||
{ "d", "set fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = 0. }, 0, INT32_MAX, FLAGS },
|
||||
{ "curve", "set fade curve type", OFFSET(curve), AV_OPT_TYPE_INT, {.i64 = TRI }, TRI, CBR, FLAGS, "curve" },
|
||||
{ "c", "set fade curve type", OFFSET(curve), AV_OPT_TYPE_INT, {.i64 = TRI }, TRI, CBR, FLAGS, "curve" },
|
||||
{ "tri", "linear slope", 0, AV_OPT_TYPE_CONST, {.i64 = TRI }, 0, 0, FLAGS, "curve" },
|
||||
{ "qsin", "quarter of sine wave", 0, AV_OPT_TYPE_CONST, {.i64 = QSIN }, 0, 0, FLAGS, "curve" },
|
||||
{ "esin", "exponential sine wave", 0, AV_OPT_TYPE_CONST, {.i64 = ESIN }, 0, 0, FLAGS, "curve" },
|
||||
{ "hsin", "half of sine wave", 0, AV_OPT_TYPE_CONST, {.i64 = HSIN }, 0, 0, FLAGS, "curve" },
|
||||
{ "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64 = LOG }, 0, 0, FLAGS, "curve" },
|
||||
{ "par", "inverted parabola", 0, AV_OPT_TYPE_CONST, {.i64 = PAR }, 0, 0, FLAGS, "curve" },
|
||||
{ "qua", "quadratic", 0, AV_OPT_TYPE_CONST, {.i64 = QUA }, 0, 0, FLAGS, "curve" },
|
||||
{ "cub", "cubic", 0, AV_OPT_TYPE_CONST, {.i64 = CUB }, 0, 0, FLAGS, "curve" },
|
||||
{ "squ", "square root", 0, AV_OPT_TYPE_CONST, {.i64 = SQU }, 0, 0, FLAGS, "curve" },
|
||||
{ "cbr", "cubic root", 0, AV_OPT_TYPE_CONST, {.i64 = CBR }, 0, 0, FLAGS, "curve" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(afade);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AudioFadeContext *s = ctx->priv;
|
||||
|
||||
if (INT64_MAX - s->nb_samples < s->start_sample)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP,
|
||||
AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static double fade_gain(int curve, int64_t index, int range)
|
||||
{
|
||||
double gain;
|
||||
|
||||
gain = FFMAX(0.0, FFMIN(1.0, 1.0 * index / range));
|
||||
|
||||
switch (curve) {
|
||||
case QSIN:
|
||||
gain = sin(gain * M_PI / 2.0);
|
||||
break;
|
||||
case ESIN:
|
||||
gain = 1.0 - cos(M_PI / 4.0 * (pow(2.0*gain - 1, 3) + 1));
|
||||
break;
|
||||
case HSIN:
|
||||
gain = (1.0 - cos(gain * M_PI)) / 2.0;
|
||||
break;
|
||||
case LOG:
|
||||
gain = pow(0.1, (1 - gain) * 5.0);
|
||||
break;
|
||||
case PAR:
|
||||
gain = (1 - (1 - gain) * (1 - gain));
|
||||
break;
|
||||
case QUA:
|
||||
gain *= gain;
|
||||
break;
|
||||
case CUB:
|
||||
gain = gain * gain * gain;
|
||||
break;
|
||||
case SQU:
|
||||
gain = sqrt(gain);
|
||||
break;
|
||||
case CBR:
|
||||
gain = cbrt(gain);
|
||||
break;
|
||||
}
|
||||
|
||||
return gain;
|
||||
}
|
||||
|
||||
#define FADE_PLANAR(name, type) \
|
||||
static void fade_samples_## name ##p(uint8_t **dst, uint8_t * const *src, \
|
||||
int nb_samples, int channels, int dir, \
|
||||
int64_t start, int range, int curve) \
|
||||
{ \
|
||||
int i, c; \
|
||||
\
|
||||
for (i = 0; i < nb_samples; i++) { \
|
||||
double gain = fade_gain(curve, start + i * dir, range); \
|
||||
for (c = 0; c < channels; c++) { \
|
||||
type *d = (type *)dst[c]; \
|
||||
const type *s = (type *)src[c]; \
|
||||
\
|
||||
d[i] = s[i] * gain; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
|
||||
#define FADE(name, type) \
|
||||
static void fade_samples_## name (uint8_t **dst, uint8_t * const *src, \
|
||||
int nb_samples, int channels, int dir, \
|
||||
int64_t start, int range, int curve) \
|
||||
{ \
|
||||
type *d = (type *)dst[0]; \
|
||||
const type *s = (type *)src[0]; \
|
||||
int i, c, k = 0; \
|
||||
\
|
||||
for (i = 0; i < nb_samples; i++) { \
|
||||
double gain = fade_gain(curve, start + i * dir, range); \
|
||||
for (c = 0; c < channels; c++, k++) \
|
||||
d[k] = s[k] * gain; \
|
||||
} \
|
||||
}
|
||||
|
||||
FADE_PLANAR(dbl, double)
|
||||
FADE_PLANAR(flt, float)
|
||||
FADE_PLANAR(s16, int16_t)
|
||||
FADE_PLANAR(s32, int32_t)
|
||||
|
||||
FADE(dbl, double)
|
||||
FADE(flt, float)
|
||||
FADE(s16, int16_t)
|
||||
FADE(s32, int32_t)
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AudioFadeContext *s = ctx->priv;
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_DBL: s->fade_samples = fade_samples_dbl; break;
|
||||
case AV_SAMPLE_FMT_DBLP: s->fade_samples = fade_samples_dblp; break;
|
||||
case AV_SAMPLE_FMT_FLT: s->fade_samples = fade_samples_flt; break;
|
||||
case AV_SAMPLE_FMT_FLTP: s->fade_samples = fade_samples_fltp; break;
|
||||
case AV_SAMPLE_FMT_S16: s->fade_samples = fade_samples_s16; break;
|
||||
case AV_SAMPLE_FMT_S16P: s->fade_samples = fade_samples_s16p; break;
|
||||
case AV_SAMPLE_FMT_S32: s->fade_samples = fade_samples_s32; break;
|
||||
case AV_SAMPLE_FMT_S32P: s->fade_samples = fade_samples_s32p; break;
|
||||
}
|
||||
|
||||
if (s->duration)
|
||||
s->nb_samples = av_rescale(s->duration, inlink->sample_rate, AV_TIME_BASE);
|
||||
if (s->start_time)
|
||||
s->start_sample = av_rescale(s->start_time, inlink->sample_rate, AV_TIME_BASE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AudioFadeContext *s = inlink->dst->priv;
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
int nb_samples = buf->nb_samples;
|
||||
AVFrame *out_buf;
|
||||
int64_t cur_sample = av_rescale_q(buf->pts, (AVRational){1, outlink->sample_rate}, outlink->time_base);
|
||||
|
||||
if ((!s->type && (s->start_sample + s->nb_samples < cur_sample)) ||
|
||||
( s->type && (cur_sample + s->nb_samples < s->start_sample)))
|
||||
return ff_filter_frame(outlink, buf);
|
||||
|
||||
if (av_frame_is_writable(buf)) {
|
||||
out_buf = buf;
|
||||
} else {
|
||||
out_buf = ff_get_audio_buffer(inlink, nb_samples);
|
||||
if (!out_buf)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_buf, buf);
|
||||
}
|
||||
|
||||
if ((!s->type && (cur_sample + nb_samples < s->start_sample)) ||
|
||||
( s->type && (s->start_sample + s->nb_samples < cur_sample))) {
|
||||
av_samples_set_silence(out_buf->extended_data, 0, nb_samples,
|
||||
av_frame_get_channels(out_buf), out_buf->format);
|
||||
} else {
|
||||
int64_t start;
|
||||
|
||||
if (!s->type)
|
||||
start = cur_sample - s->start_sample;
|
||||
else
|
||||
start = s->start_sample + s->nb_samples - cur_sample;
|
||||
|
||||
s->fade_samples(out_buf->extended_data, buf->extended_data,
|
||||
nb_samples, av_frame_get_channels(buf),
|
||||
s->type ? -1 : 1, start,
|
||||
s->nb_samples, s->curve);
|
||||
}
|
||||
|
||||
if (buf != out_buf)
|
||||
av_frame_free(&buf);
|
||||
|
||||
return ff_filter_frame(outlink, out_buf);
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_afade_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
.config_props = config_input,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_afade_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_afade = {
|
||||
.name = "afade",
|
||||
.description = NULL_IF_CONFIG_SMALL("Fade in/out input audio."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioFadeContext),
|
||||
.init = init,
|
||||
.inputs = avfilter_af_afade_inputs,
|
||||
.outputs = avfilter_af_afade_outputs,
|
||||
.priv_class = &afade_class,
|
||||
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Mina Nagy Zaki
|
||||
*
|
||||
* 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
|
||||
* format audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct AFormatContext {
|
||||
const AVClass *class;
|
||||
|
||||
AVFilterFormats *formats;
|
||||
AVFilterFormats *sample_rates;
|
||||
AVFilterChannelLayouts *channel_layouts;
|
||||
|
||||
char *formats_str;
|
||||
char *sample_rates_str;
|
||||
char *channel_layouts_str;
|
||||
} AFormatContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AFormatContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption aformat_options[] = {
|
||||
{ "sample_fmts", "A comma-separated list of sample formats.", OFFSET(formats_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ "sample_rates", "A comma-separated list of sample rates.", OFFSET(sample_rates_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ "channel_layouts", "A comma-separated list of channel layouts.", OFFSET(channel_layouts_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(aformat);
|
||||
|
||||
#define PARSE_FORMATS(str, type, list, add_to_list, get_fmt, none, desc) \
|
||||
do { \
|
||||
char *next, *cur = str, sep; \
|
||||
\
|
||||
if (str && strchr(str, ',')) { \
|
||||
av_log(ctx, AV_LOG_WARNING, "This syntax is deprecated, use '|' to "\
|
||||
"separate %s.\n", desc); \
|
||||
sep = ','; \
|
||||
} else \
|
||||
sep = '|'; \
|
||||
\
|
||||
while (cur) { \
|
||||
type fmt; \
|
||||
next = strchr(cur, sep); \
|
||||
if (next) \
|
||||
*next++ = 0; \
|
||||
\
|
||||
if ((fmt = get_fmt(cur)) == none) { \
|
||||
av_log(ctx, AV_LOG_ERROR, "Error parsing " desc ": %s.\n", cur);\
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
add_to_list(&list, fmt); \
|
||||
\
|
||||
cur = next; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int get_sample_rate(const char *samplerate)
|
||||
{
|
||||
int ret = strtol(samplerate, NULL, 0);
|
||||
return FFMAX(ret, 0);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AFormatContext *s = ctx->priv;
|
||||
|
||||
PARSE_FORMATS(s->formats_str, enum AVSampleFormat, s->formats,
|
||||
ff_add_format, av_get_sample_fmt, AV_SAMPLE_FMT_NONE, "sample format");
|
||||
PARSE_FORMATS(s->sample_rates_str, int, s->sample_rates, ff_add_format,
|
||||
get_sample_rate, 0, "sample rate");
|
||||
PARSE_FORMATS(s->channel_layouts_str, uint64_t, s->channel_layouts,
|
||||
ff_add_channel_layout, av_get_channel_layout, 0,
|
||||
"channel layout");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AFormatContext *s = ctx->priv;
|
||||
|
||||
ff_set_common_formats(ctx, s->formats ? s->formats :
|
||||
ff_all_formats(AVMEDIA_TYPE_AUDIO));
|
||||
ff_set_common_samplerates(ctx, s->sample_rates ? s->sample_rates :
|
||||
ff_all_samplerates());
|
||||
ff_set_common_channel_layouts(ctx, s->channel_layouts ? s->channel_layouts :
|
||||
ff_all_channel_counts());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_aformat_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_aformat_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aformat = {
|
||||
.name = "aformat",
|
||||
.description = NULL_IF_CONFIG_SMALL("Convert the input audio to one of the specified formats."),
|
||||
.init = init,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AFormatContext),
|
||||
.priv_class = &aformat_class,
|
||||
.inputs = avfilter_af_aformat_inputs,
|
||||
.outputs = avfilter_af_aformat_outputs,
|
||||
};
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Nicolas George <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* Audio merging filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libswresample/swresample.h" // only for SWR_CH_MAX
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "bufferqueue.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int nb_inputs;
|
||||
int route[SWR_CH_MAX]; /**< channels routing, see copy_samples */
|
||||
int bps;
|
||||
struct amerge_input {
|
||||
struct FFBufQueue queue;
|
||||
int nb_ch; /**< number of channels for the input */
|
||||
int nb_samples;
|
||||
int pos;
|
||||
} *in;
|
||||
} AMergeContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AMergeContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption amerge_options[] = {
|
||||
{ "inputs", "specify the number of inputs", OFFSET(nb_inputs),
|
||||
AV_OPT_TYPE_INT, { .i64 = 2 }, 2, SWR_CH_MAX, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(amerge);
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AMergeContext *am = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
if (am->in)
|
||||
ff_bufqueue_discard_all(&am->in[i].queue);
|
||||
if (ctx->input_pads)
|
||||
av_freep(&ctx->input_pads[i].name);
|
||||
}
|
||||
av_freep(&am->in);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AMergeContext *am = ctx->priv;
|
||||
int64_t inlayout[SWR_CH_MAX], outlayout = 0;
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
int i, overlap = 0, nb_ch = 0;
|
||||
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
if (!ctx->inputs[i]->in_channel_layouts ||
|
||||
!ctx->inputs[i]->in_channel_layouts->nb_channel_layouts) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"No channel layout for input %d\n", i + 1);
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
inlayout[i] = ctx->inputs[i]->in_channel_layouts->channel_layouts[0];
|
||||
if (ctx->inputs[i]->in_channel_layouts->nb_channel_layouts > 1) {
|
||||
char buf[256];
|
||||
av_get_channel_layout_string(buf, sizeof(buf), 0, inlayout[i]);
|
||||
av_log(ctx, AV_LOG_INFO, "Using \"%s\" for input %d\n", buf, i + 1);
|
||||
}
|
||||
am->in[i].nb_ch = av_get_channel_layout_nb_channels(inlayout[i]);
|
||||
if (outlayout & inlayout[i])
|
||||
overlap++;
|
||||
outlayout |= inlayout[i];
|
||||
nb_ch += am->in[i].nb_ch;
|
||||
}
|
||||
if (nb_ch > SWR_CH_MAX) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Too many channels (max %d)\n", SWR_CH_MAX);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (overlap) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Input channel layouts overlap: "
|
||||
"output layout will be determined by the number of distinct input channels\n");
|
||||
for (i = 0; i < nb_ch; i++)
|
||||
am->route[i] = i;
|
||||
outlayout = av_get_default_channel_layout(nb_ch);
|
||||
if (!outlayout)
|
||||
outlayout = ((int64_t)1 << nb_ch) - 1;
|
||||
} else {
|
||||
int *route[SWR_CH_MAX];
|
||||
int c, out_ch_number = 0;
|
||||
|
||||
route[0] = am->route;
|
||||
for (i = 1; i < am->nb_inputs; i++)
|
||||
route[i] = route[i - 1] + am->in[i - 1].nb_ch;
|
||||
for (c = 0; c < 64; c++)
|
||||
for (i = 0; i < am->nb_inputs; i++)
|
||||
if ((inlayout[i] >> c) & 1)
|
||||
*(route[i]++) = out_ch_number++;
|
||||
}
|
||||
formats = ff_make_format_list(ff_packed_sample_fmts_array);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts, inlayout[i]);
|
||||
ff_channel_layouts_ref(layouts, &ctx->inputs[i]->out_channel_layouts);
|
||||
}
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts, outlayout);
|
||||
ff_channel_layouts_ref(layouts, &ctx->outputs[0]->in_channel_layouts);
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AMergeContext *am = ctx->priv;
|
||||
AVBPrint bp;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < am->nb_inputs; i++) {
|
||||
if (ctx->inputs[i]->sample_rate != ctx->inputs[0]->sample_rate) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Inputs must have the same sample rate "
|
||||
"%d for in%d vs %d\n",
|
||||
ctx->inputs[i]->sample_rate, i, ctx->inputs[0]->sample_rate);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
am->bps = av_get_bytes_per_sample(ctx->outputs[0]->format);
|
||||
outlink->sample_rate = ctx->inputs[0]->sample_rate;
|
||||
outlink->time_base = ctx->inputs[0]->time_base;
|
||||
|
||||
av_bprint_init(&bp, 0, 1);
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
av_bprintf(&bp, "%sin%d:", i ? " + " : "", i);
|
||||
av_bprint_channel_layout(&bp, -1, ctx->inputs[i]->channel_layout);
|
||||
}
|
||||
av_bprintf(&bp, " -> out:");
|
||||
av_bprint_channel_layout(&bp, -1, ctx->outputs[0]->channel_layout);
|
||||
av_log(ctx, AV_LOG_VERBOSE, "%s\n", bp.str);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AMergeContext *am = ctx->priv;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < am->nb_inputs; i++)
|
||||
if (!am->in[i].nb_samples)
|
||||
if ((ret = ff_request_frame(ctx->inputs[i])) < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy samples from several input streams to one output stream.
|
||||
* @param nb_inputs number of inputs
|
||||
* @param in inputs; used only for the nb_ch field;
|
||||
* @param route routing values;
|
||||
* input channel i goes to output channel route[i];
|
||||
* i < in[0].nb_ch are the channels from the first output;
|
||||
* i >= in[0].nb_ch are the channels from the second output
|
||||
* @param ins pointer to the samples of each inputs, in packed format;
|
||||
* will be left at the end of the copied samples
|
||||
* @param outs pointer to the samples of the output, in packet format;
|
||||
* must point to a buffer big enough;
|
||||
* will be left at the end of the copied samples
|
||||
* @param ns number of samples to copy
|
||||
* @param bps bytes per sample
|
||||
*/
|
||||
static inline void copy_samples(int nb_inputs, struct amerge_input in[],
|
||||
int *route, uint8_t *ins[],
|
||||
uint8_t **outs, int ns, int bps)
|
||||
{
|
||||
int *route_cur;
|
||||
int i, c, nb_ch = 0;
|
||||
|
||||
for (i = 0; i < nb_inputs; i++)
|
||||
nb_ch += in[i].nb_ch;
|
||||
while (ns--) {
|
||||
route_cur = route;
|
||||
for (i = 0; i < nb_inputs; i++) {
|
||||
for (c = 0; c < in[i].nb_ch; c++) {
|
||||
memcpy((*outs) + bps * *(route_cur++), ins[i], bps);
|
||||
ins[i] += bps;
|
||||
}
|
||||
}
|
||||
*outs += nb_ch * bps;
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AMergeContext *am = ctx->priv;
|
||||
AVFilterLink *const outlink = ctx->outputs[0];
|
||||
int input_number;
|
||||
int nb_samples, ns, i;
|
||||
AVFrame *outbuf, *inbuf[SWR_CH_MAX];
|
||||
uint8_t *ins[SWR_CH_MAX], *outs;
|
||||
|
||||
for (input_number = 0; input_number < am->nb_inputs; input_number++)
|
||||
if (inlink == ctx->inputs[input_number])
|
||||
break;
|
||||
av_assert1(input_number < am->nb_inputs);
|
||||
if (ff_bufqueue_is_full(&am->in[input_number].queue)) {
|
||||
av_frame_free(&insamples);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
ff_bufqueue_add(ctx, &am->in[input_number].queue, av_frame_clone(insamples));
|
||||
am->in[input_number].nb_samples += insamples->nb_samples;
|
||||
av_frame_free(&insamples);
|
||||
nb_samples = am->in[0].nb_samples;
|
||||
for (i = 1; i < am->nb_inputs; i++)
|
||||
nb_samples = FFMIN(nb_samples, am->in[i].nb_samples);
|
||||
if (!nb_samples)
|
||||
return 0;
|
||||
|
||||
outbuf = ff_get_audio_buffer(ctx->outputs[0], nb_samples);
|
||||
if (!outbuf)
|
||||
return AVERROR(ENOMEM);
|
||||
outs = outbuf->data[0];
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
inbuf[i] = ff_bufqueue_peek(&am->in[i].queue, 0);
|
||||
ins[i] = inbuf[i]->data[0] +
|
||||
am->in[i].pos * am->in[i].nb_ch * am->bps;
|
||||
}
|
||||
av_frame_copy_props(outbuf, inbuf[0]);
|
||||
outbuf->pts = inbuf[0]->pts == AV_NOPTS_VALUE ? AV_NOPTS_VALUE :
|
||||
inbuf[0]->pts +
|
||||
av_rescale_q(am->in[0].pos,
|
||||
(AVRational){ 1, ctx->inputs[0]->sample_rate },
|
||||
ctx->outputs[0]->time_base);
|
||||
|
||||
outbuf->nb_samples = nb_samples;
|
||||
outbuf->channel_layout = outlink->channel_layout;
|
||||
av_frame_set_channels(outbuf, outlink->channels);
|
||||
|
||||
while (nb_samples) {
|
||||
ns = nb_samples;
|
||||
for (i = 0; i < am->nb_inputs; i++)
|
||||
ns = FFMIN(ns, inbuf[i]->nb_samples - am->in[i].pos);
|
||||
/* Unroll the most common sample formats: speed +~350% for the loop,
|
||||
+~13% overall (including two common decoders) */
|
||||
switch (am->bps) {
|
||||
case 1:
|
||||
copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 1);
|
||||
break;
|
||||
case 2:
|
||||
copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 2);
|
||||
break;
|
||||
case 4:
|
||||
copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, 4);
|
||||
break;
|
||||
default:
|
||||
copy_samples(am->nb_inputs, am->in, am->route, ins, &outs, ns, am->bps);
|
||||
break;
|
||||
}
|
||||
|
||||
nb_samples -= ns;
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
am->in[i].nb_samples -= ns;
|
||||
am->in[i].pos += ns;
|
||||
if (am->in[i].pos == inbuf[i]->nb_samples) {
|
||||
am->in[i].pos = 0;
|
||||
av_frame_free(&inbuf[i]);
|
||||
ff_bufqueue_get(&am->in[i].queue);
|
||||
inbuf[i] = ff_bufqueue_peek(&am->in[i].queue, 0);
|
||||
ins[i] = inbuf[i] ? inbuf[i]->data[0] : NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ff_filter_frame(ctx->outputs[0], outbuf);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AMergeContext *am = ctx->priv;
|
||||
int i;
|
||||
|
||||
am->in = av_calloc(am->nb_inputs, sizeof(*am->in));
|
||||
if (!am->in)
|
||||
return AVERROR(ENOMEM);
|
||||
for (i = 0; i < am->nb_inputs; i++) {
|
||||
char *name = av_asprintf("in%d", i);
|
||||
AVFilterPad pad = {
|
||||
.name = name,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
};
|
||||
if (!name)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_insert_inpad(ctx, i, &pad);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad amerge_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_amerge = {
|
||||
.name = "amerge",
|
||||
.description = NULL_IF_CONFIG_SMALL("Merge two or more audio streams into "
|
||||
"a single multi-channel stream."),
|
||||
.priv_size = sizeof(AMergeContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = NULL,
|
||||
.outputs = amerge_outputs,
|
||||
.priv_class = &amerge_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* Audio Mix Filter
|
||||
* Copyright (c) 2012 Justin Ruggles <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* Audio Mix Filter
|
||||
*
|
||||
* Mixes audio from multiple sources into a single output. The channel layout,
|
||||
* sample rate, and sample format will be the same for all inputs and the
|
||||
* output.
|
||||
*/
|
||||
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/audio_fifo.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/float_dsp.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define INPUT_OFF 0 /**< input has reached EOF */
|
||||
#define INPUT_ON 1 /**< input is active */
|
||||
#define INPUT_INACTIVE 2 /**< input is on, but is currently inactive */
|
||||
|
||||
#define DURATION_LONGEST 0
|
||||
#define DURATION_SHORTEST 1
|
||||
#define DURATION_FIRST 2
|
||||
|
||||
|
||||
typedef struct FrameInfo {
|
||||
int nb_samples;
|
||||
int64_t pts;
|
||||
struct FrameInfo *next;
|
||||
} FrameInfo;
|
||||
|
||||
/**
|
||||
* Linked list used to store timestamps and frame sizes of all frames in the
|
||||
* FIFO for the first input.
|
||||
*
|
||||
* This is needed to keep timestamps synchronized for the case where multiple
|
||||
* input frames are pushed to the filter for processing before a frame is
|
||||
* requested by the output link.
|
||||
*/
|
||||
typedef struct FrameList {
|
||||
int nb_frames;
|
||||
int nb_samples;
|
||||
FrameInfo *list;
|
||||
FrameInfo *end;
|
||||
} FrameList;
|
||||
|
||||
static void frame_list_clear(FrameList *frame_list)
|
||||
{
|
||||
if (frame_list) {
|
||||
while (frame_list->list) {
|
||||
FrameInfo *info = frame_list->list;
|
||||
frame_list->list = info->next;
|
||||
av_free(info);
|
||||
}
|
||||
frame_list->nb_frames = 0;
|
||||
frame_list->nb_samples = 0;
|
||||
frame_list->end = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int frame_list_next_frame_size(FrameList *frame_list)
|
||||
{
|
||||
if (!frame_list->list)
|
||||
return 0;
|
||||
return frame_list->list->nb_samples;
|
||||
}
|
||||
|
||||
static int64_t frame_list_next_pts(FrameList *frame_list)
|
||||
{
|
||||
if (!frame_list->list)
|
||||
return AV_NOPTS_VALUE;
|
||||
return frame_list->list->pts;
|
||||
}
|
||||
|
||||
static void frame_list_remove_samples(FrameList *frame_list, int nb_samples)
|
||||
{
|
||||
if (nb_samples >= frame_list->nb_samples) {
|
||||
frame_list_clear(frame_list);
|
||||
} else {
|
||||
int samples = nb_samples;
|
||||
while (samples > 0) {
|
||||
FrameInfo *info = frame_list->list;
|
||||
av_assert0(info != NULL);
|
||||
if (info->nb_samples <= samples) {
|
||||
samples -= info->nb_samples;
|
||||
frame_list->list = info->next;
|
||||
if (!frame_list->list)
|
||||
frame_list->end = NULL;
|
||||
frame_list->nb_frames--;
|
||||
frame_list->nb_samples -= info->nb_samples;
|
||||
av_free(info);
|
||||
} else {
|
||||
info->nb_samples -= samples;
|
||||
info->pts += samples;
|
||||
frame_list->nb_samples -= samples;
|
||||
samples = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int frame_list_add_frame(FrameList *frame_list, int nb_samples, int64_t pts)
|
||||
{
|
||||
FrameInfo *info = av_malloc(sizeof(*info));
|
||||
if (!info)
|
||||
return AVERROR(ENOMEM);
|
||||
info->nb_samples = nb_samples;
|
||||
info->pts = pts;
|
||||
info->next = NULL;
|
||||
|
||||
if (!frame_list->list) {
|
||||
frame_list->list = info;
|
||||
frame_list->end = info;
|
||||
} else {
|
||||
av_assert0(frame_list->end != NULL);
|
||||
frame_list->end->next = info;
|
||||
frame_list->end = info;
|
||||
}
|
||||
frame_list->nb_frames++;
|
||||
frame_list->nb_samples += nb_samples;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
typedef struct MixContext {
|
||||
const AVClass *class; /**< class for AVOptions */
|
||||
AVFloatDSPContext fdsp;
|
||||
|
||||
int nb_inputs; /**< number of inputs */
|
||||
int active_inputs; /**< number of input currently active */
|
||||
int duration_mode; /**< mode for determining duration */
|
||||
float dropout_transition; /**< transition time when an input drops out */
|
||||
|
||||
int nb_channels; /**< number of channels */
|
||||
int sample_rate; /**< sample rate */
|
||||
int planar;
|
||||
AVAudioFifo **fifos; /**< audio fifo for each input */
|
||||
uint8_t *input_state; /**< current state of each input */
|
||||
float *input_scale; /**< mixing scale factor for each input */
|
||||
float scale_norm; /**< normalization factor for all inputs */
|
||||
int64_t next_pts; /**< calculated pts for next output frame */
|
||||
FrameList *frame_list; /**< list of frame info for the first input */
|
||||
} MixContext;
|
||||
|
||||
#define OFFSET(x) offsetof(MixContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption amix_options[] = {
|
||||
{ "inputs", "Number of inputs.",
|
||||
OFFSET(nb_inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, 32, A|F },
|
||||
{ "duration", "How to determine the end-of-stream.",
|
||||
OFFSET(duration_mode), AV_OPT_TYPE_INT, { .i64 = DURATION_LONGEST }, 0, 2, A|F, "duration" },
|
||||
{ "longest", "Duration of longest input.", 0, AV_OPT_TYPE_CONST, { .i64 = DURATION_LONGEST }, INT_MIN, INT_MAX, A|F, "duration" },
|
||||
{ "shortest", "Duration of shortest input.", 0, AV_OPT_TYPE_CONST, { .i64 = DURATION_SHORTEST }, INT_MIN, INT_MAX, A|F, "duration" },
|
||||
{ "first", "Duration of first input.", 0, AV_OPT_TYPE_CONST, { .i64 = DURATION_FIRST }, INT_MIN, INT_MAX, A|F, "duration" },
|
||||
{ "dropout_transition", "Transition time, in seconds, for volume "
|
||||
"renormalization when an input stream ends.",
|
||||
OFFSET(dropout_transition), AV_OPT_TYPE_FLOAT, { .dbl = 2.0 }, 0, INT_MAX, A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(amix);
|
||||
|
||||
/**
|
||||
* Update the scaling factors to apply to each input during mixing.
|
||||
*
|
||||
* This balances the full volume range between active inputs and handles
|
||||
* volume transitions when EOF is encountered on an input but mixing continues
|
||||
* with the remaining inputs.
|
||||
*/
|
||||
static void calculate_scales(MixContext *s, int nb_samples)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (s->scale_norm > s->active_inputs) {
|
||||
s->scale_norm -= nb_samples / (s->dropout_transition * s->sample_rate);
|
||||
s->scale_norm = FFMAX(s->scale_norm, s->active_inputs);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
if (s->input_state[i] == INPUT_ON)
|
||||
s->input_scale[i] = 1.0f / s->scale_norm;
|
||||
else
|
||||
s->input_scale[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
MixContext *s = ctx->priv;
|
||||
int i;
|
||||
char buf[64];
|
||||
|
||||
s->planar = av_sample_fmt_is_planar(outlink->format);
|
||||
s->sample_rate = outlink->sample_rate;
|
||||
outlink->time_base = (AVRational){ 1, outlink->sample_rate };
|
||||
s->next_pts = AV_NOPTS_VALUE;
|
||||
|
||||
s->frame_list = av_mallocz(sizeof(*s->frame_list));
|
||||
if (!s->frame_list)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
s->fifos = av_mallocz(s->nb_inputs * sizeof(*s->fifos));
|
||||
if (!s->fifos)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
s->nb_channels = av_get_channel_layout_nb_channels(outlink->channel_layout);
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
s->fifos[i] = av_audio_fifo_alloc(outlink->format, s->nb_channels, 1024);
|
||||
if (!s->fifos[i])
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
s->input_state = av_malloc(s->nb_inputs);
|
||||
if (!s->input_state)
|
||||
return AVERROR(ENOMEM);
|
||||
memset(s->input_state, INPUT_ON, s->nb_inputs);
|
||||
s->active_inputs = s->nb_inputs;
|
||||
|
||||
s->input_scale = av_mallocz(s->nb_inputs * sizeof(*s->input_scale));
|
||||
if (!s->input_scale)
|
||||
return AVERROR(ENOMEM);
|
||||
s->scale_norm = s->active_inputs;
|
||||
calculate_scales(s, 0);
|
||||
|
||||
av_get_channel_layout_string(buf, sizeof(buf), -1, outlink->channel_layout);
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"inputs:%d fmt:%s srate:%d cl:%s\n", s->nb_inputs,
|
||||
av_get_sample_fmt_name(outlink->format), outlink->sample_rate, buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read samples from the input FIFOs, mix, and write to the output link.
|
||||
*/
|
||||
static int output_frame(AVFilterLink *outlink, int nb_samples)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
MixContext *s = ctx->priv;
|
||||
AVFrame *out_buf, *in_buf;
|
||||
int i;
|
||||
|
||||
calculate_scales(s, nb_samples);
|
||||
|
||||
out_buf = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!out_buf)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
in_buf = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!in_buf) {
|
||||
av_frame_free(&out_buf);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
if (s->input_state[i] == INPUT_ON) {
|
||||
int planes, plane_size, p;
|
||||
|
||||
av_audio_fifo_read(s->fifos[i], (void **)in_buf->extended_data,
|
||||
nb_samples);
|
||||
|
||||
planes = s->planar ? s->nb_channels : 1;
|
||||
plane_size = nb_samples * (s->planar ? 1 : s->nb_channels);
|
||||
plane_size = FFALIGN(plane_size, 16);
|
||||
|
||||
for (p = 0; p < planes; p++) {
|
||||
s->fdsp.vector_fmac_scalar((float *)out_buf->extended_data[p],
|
||||
(float *) in_buf->extended_data[p],
|
||||
s->input_scale[i], plane_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
av_frame_free(&in_buf);
|
||||
|
||||
out_buf->pts = s->next_pts;
|
||||
if (s->next_pts != AV_NOPTS_VALUE)
|
||||
s->next_pts += nb_samples;
|
||||
|
||||
return ff_filter_frame(outlink, out_buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the smallest number of samples available in the input FIFOs other
|
||||
* than that of the first input.
|
||||
*/
|
||||
static int get_available_samples(MixContext *s)
|
||||
{
|
||||
int i;
|
||||
int available_samples = INT_MAX;
|
||||
|
||||
av_assert0(s->nb_inputs > 1);
|
||||
|
||||
for (i = 1; i < s->nb_inputs; i++) {
|
||||
int nb_samples;
|
||||
if (s->input_state[i] == INPUT_OFF)
|
||||
continue;
|
||||
nb_samples = av_audio_fifo_size(s->fifos[i]);
|
||||
available_samples = FFMIN(available_samples, nb_samples);
|
||||
}
|
||||
if (available_samples == INT_MAX)
|
||||
return 0;
|
||||
return available_samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests a frame, if needed, from each input link other than the first.
|
||||
*/
|
||||
static int request_samples(AVFilterContext *ctx, int min_samples)
|
||||
{
|
||||
MixContext *s = ctx->priv;
|
||||
int i, ret;
|
||||
|
||||
av_assert0(s->nb_inputs > 1);
|
||||
|
||||
for (i = 1; i < s->nb_inputs; i++) {
|
||||
ret = 0;
|
||||
if (s->input_state[i] == INPUT_OFF)
|
||||
continue;
|
||||
while (!ret && av_audio_fifo_size(s->fifos[i]) < min_samples)
|
||||
ret = ff_request_frame(ctx->inputs[i]);
|
||||
if (ret == AVERROR_EOF) {
|
||||
if (av_audio_fifo_size(s->fifos[i]) == 0) {
|
||||
s->input_state[i] = INPUT_OFF;
|
||||
continue;
|
||||
}
|
||||
} else if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of active inputs and determines EOF based on the
|
||||
* duration option.
|
||||
*
|
||||
* @return 0 if mixing should continue, or AVERROR_EOF if mixing should stop.
|
||||
*/
|
||||
static int calc_active_inputs(MixContext *s)
|
||||
{
|
||||
int i;
|
||||
int active_inputs = 0;
|
||||
for (i = 0; i < s->nb_inputs; i++)
|
||||
active_inputs += !!(s->input_state[i] != INPUT_OFF);
|
||||
s->active_inputs = active_inputs;
|
||||
|
||||
if (!active_inputs ||
|
||||
(s->duration_mode == DURATION_FIRST && s->input_state[0] == INPUT_OFF) ||
|
||||
(s->duration_mode == DURATION_SHORTEST && active_inputs != s->nb_inputs))
|
||||
return AVERROR_EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
MixContext *s = ctx->priv;
|
||||
int ret;
|
||||
int wanted_samples, available_samples;
|
||||
|
||||
ret = calc_active_inputs(s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (s->input_state[0] == INPUT_OFF) {
|
||||
ret = request_samples(ctx, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = calc_active_inputs(s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
available_samples = get_available_samples(s);
|
||||
if (!available_samples)
|
||||
return AVERROR(EAGAIN);
|
||||
|
||||
return output_frame(outlink, available_samples);
|
||||
}
|
||||
|
||||
if (s->frame_list->nb_frames == 0) {
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
if (ret == AVERROR_EOF) {
|
||||
s->input_state[0] = INPUT_OFF;
|
||||
if (s->nb_inputs == 1)
|
||||
return AVERROR_EOF;
|
||||
else
|
||||
return AVERROR(EAGAIN);
|
||||
} else if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
av_assert0(s->frame_list->nb_frames > 0);
|
||||
|
||||
wanted_samples = frame_list_next_frame_size(s->frame_list);
|
||||
|
||||
if (s->active_inputs > 1) {
|
||||
ret = request_samples(ctx, wanted_samples);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = calc_active_inputs(s);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s->active_inputs > 1) {
|
||||
available_samples = get_available_samples(s);
|
||||
if (!available_samples)
|
||||
return AVERROR(EAGAIN);
|
||||
available_samples = FFMIN(available_samples, wanted_samples);
|
||||
} else {
|
||||
available_samples = wanted_samples;
|
||||
}
|
||||
|
||||
s->next_pts = frame_list_next_pts(s->frame_list);
|
||||
frame_list_remove_samples(s->frame_list, available_samples);
|
||||
|
||||
return output_frame(outlink, available_samples);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
MixContext *s = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++)
|
||||
if (ctx->inputs[i] == inlink)
|
||||
break;
|
||||
if (i >= ctx->nb_inputs) {
|
||||
av_log(ctx, AV_LOG_ERROR, "unknown input link\n");
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
int64_t pts = av_rescale_q(buf->pts, inlink->time_base,
|
||||
outlink->time_base);
|
||||
ret = frame_list_add_frame(s->frame_list, buf->nb_samples, pts);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = av_audio_fifo_write(s->fifos[i], (void **)buf->extended_data,
|
||||
buf->nb_samples);
|
||||
|
||||
fail:
|
||||
av_frame_free(&buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
MixContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
char name[32];
|
||||
AVFilterPad pad = { 0 };
|
||||
|
||||
snprintf(name, sizeof(name), "input%d", i);
|
||||
pad.type = AVMEDIA_TYPE_AUDIO;
|
||||
pad.name = av_strdup(name);
|
||||
pad.filter_frame = filter_frame;
|
||||
|
||||
ff_insert_inpad(ctx, i, &pad);
|
||||
}
|
||||
|
||||
avpriv_float_dsp_init(&s->fdsp, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
int i;
|
||||
MixContext *s = ctx->priv;
|
||||
|
||||
if (s->fifos) {
|
||||
for (i = 0; i < s->nb_inputs; i++)
|
||||
av_audio_fifo_free(s->fifos[i]);
|
||||
av_freep(&s->fifos);
|
||||
}
|
||||
frame_list_clear(s->frame_list);
|
||||
av_freep(&s->frame_list);
|
||||
av_freep(&s->input_state);
|
||||
av_freep(&s->input_scale);
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++)
|
||||
av_freep(&ctx->input_pads[i].name);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
ff_add_format(&formats, AV_SAMPLE_FMT_FLT);
|
||||
ff_add_format(&formats, AV_SAMPLE_FMT_FLTP);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
ff_set_common_channel_layouts(ctx, ff_all_channel_layouts());
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_amix_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_amix = {
|
||||
.name = "amix",
|
||||
.description = NULL_IF_CONFIG_SMALL("Audio mixing."),
|
||||
.priv_size = sizeof(MixContext),
|
||||
.priv_class = &amix_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = NULL,
|
||||
.outputs = avfilter_af_amix_outputs,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2010 S.N. Hemanth Meenakshisundaram <[email protected]>
|
||||
* 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
|
||||
* null audio filter
|
||||
*/
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "libavutil/internal.h"
|
||||
|
||||
static const AVFilterPad avfilter_af_anull_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_anull_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_anull = {
|
||||
.name = "anull",
|
||||
.description = NULL_IF_CONFIG_SMALL("Pass the source unchanged to the output."),
|
||||
.query_formats = ff_query_formats_all,
|
||||
.inputs = avfilter_af_anull_inputs,
|
||||
.outputs = avfilter_af_anull_outputs,
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Michael Niedermayer
|
||||
*
|
||||
* 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
|
||||
* audio pad filter.
|
||||
*
|
||||
* Based on af_aresample.c
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int64_t next_pts;
|
||||
|
||||
int packet_size;
|
||||
int64_t pad_len;
|
||||
int64_t whole_len;
|
||||
} APadContext;
|
||||
|
||||
#define OFFSET(x) offsetof(APadContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption apad_options[] = {
|
||||
{ "packet_size", "set silence packet size", OFFSET(packet_size), AV_OPT_TYPE_INT, { .i64 = 4096 }, 0, INT_MAX, A },
|
||||
{ "pad_len", "number of samples of silence to add", OFFSET(pad_len), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, A },
|
||||
{ "whole_len", "target number of samples in the audio stream", OFFSET(whole_len), AV_OPT_TYPE_INT64, { .i64 = 0 }, 0, INT64_MAX, A },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(apad);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
APadContext *apad = ctx->priv;
|
||||
|
||||
apad->next_pts = AV_NOPTS_VALUE;
|
||||
if (apad->whole_len && apad->pad_len) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Both whole and pad length are set, this is not possible\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
APadContext *apad = ctx->priv;
|
||||
|
||||
if (apad->whole_len)
|
||||
apad->whole_len -= frame->nb_samples;
|
||||
|
||||
apad->next_pts = frame->pts + av_rescale_q(frame->nb_samples, (AVRational){1, inlink->sample_rate}, inlink->time_base);
|
||||
return ff_filter_frame(ctx->outputs[0], frame);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
APadContext *apad = ctx->priv;
|
||||
int ret;
|
||||
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
if (ret == AVERROR_EOF && !ctx->is_disabled) {
|
||||
int n_out = apad->packet_size;
|
||||
AVFrame *outsamplesref;
|
||||
|
||||
if (apad->whole_len > 0) {
|
||||
apad->pad_len = apad->whole_len;
|
||||
apad->whole_len = 0;
|
||||
}
|
||||
if (apad->pad_len > 0) {
|
||||
n_out = FFMIN(n_out, apad->pad_len);
|
||||
apad->pad_len -= n_out;
|
||||
}
|
||||
|
||||
if(!n_out)
|
||||
return AVERROR_EOF;
|
||||
|
||||
outsamplesref = ff_get_audio_buffer(outlink, n_out);
|
||||
if (!outsamplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_assert0(outsamplesref->sample_rate == outlink->sample_rate);
|
||||
av_assert0(outsamplesref->nb_samples == n_out);
|
||||
|
||||
av_samples_set_silence(outsamplesref->extended_data, 0,
|
||||
n_out,
|
||||
av_frame_get_channels(outsamplesref),
|
||||
outsamplesref->format);
|
||||
|
||||
outsamplesref->pts = apad->next_pts;
|
||||
if (apad->next_pts != AV_NOPTS_VALUE)
|
||||
apad->next_pts += av_rescale_q(n_out, (AVRational){1, outlink->sample_rate}, outlink->time_base);
|
||||
|
||||
return ff_filter_frame(outlink, outsamplesref);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad apad_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad apad_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.request_frame = request_frame,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_apad = {
|
||||
.name = "apad",
|
||||
.description = NULL_IF_CONFIG_SMALL("Pad audio with silence."),
|
||||
.init = init,
|
||||
.priv_size = sizeof(APadContext),
|
||||
.inputs = apad_inputs,
|
||||
.outputs = apad_outputs,
|
||||
.priv_class = &apad_class,
|
||||
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
|
||||
};
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
* phaser audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
enum WaveType {
|
||||
WAVE_SIN,
|
||||
WAVE_TRI,
|
||||
WAVE_NB,
|
||||
};
|
||||
|
||||
typedef struct AudioPhaserContext {
|
||||
const AVClass *class;
|
||||
double in_gain, out_gain;
|
||||
double delay;
|
||||
double decay;
|
||||
double speed;
|
||||
|
||||
enum WaveType type;
|
||||
|
||||
int delay_buffer_length;
|
||||
double *delay_buffer;
|
||||
|
||||
int modulation_buffer_length;
|
||||
int32_t *modulation_buffer;
|
||||
|
||||
int delay_pos, modulation_pos;
|
||||
|
||||
void (*phaser)(struct AudioPhaserContext *p,
|
||||
uint8_t * const *src, uint8_t **dst,
|
||||
int nb_samples, int channels);
|
||||
} AudioPhaserContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AudioPhaserContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption aphaser_options[] = {
|
||||
{ "in_gain", "set input gain", OFFSET(in_gain), AV_OPT_TYPE_DOUBLE, {.dbl=.4}, 0, 1, FLAGS },
|
||||
{ "out_gain", "set output gain", OFFSET(out_gain), AV_OPT_TYPE_DOUBLE, {.dbl=.74}, 0, 1e9, FLAGS },
|
||||
{ "delay", "set delay in milliseconds", OFFSET(delay), AV_OPT_TYPE_DOUBLE, {.dbl=3.}, 0, 5, FLAGS },
|
||||
{ "decay", "set decay", OFFSET(decay), AV_OPT_TYPE_DOUBLE, {.dbl=.4}, 0, .99, FLAGS },
|
||||
{ "speed", "set modulation speed", OFFSET(speed), AV_OPT_TYPE_DOUBLE, {.dbl=.5}, .1, 2, FLAGS },
|
||||
{ "type", "set modulation type", OFFSET(type), AV_OPT_TYPE_INT, {.i64=WAVE_TRI}, 0, WAVE_NB-1, FLAGS, "type" },
|
||||
{ "triangular", NULL, 0, AV_OPT_TYPE_CONST, {.i64=WAVE_TRI}, 0, 0, FLAGS, "type" },
|
||||
{ "t", NULL, 0, AV_OPT_TYPE_CONST, {.i64=WAVE_TRI}, 0, 0, FLAGS, "type" },
|
||||
{ "sinusoidal", NULL, 0, AV_OPT_TYPE_CONST, {.i64=WAVE_SIN}, 0, 0, FLAGS, "type" },
|
||||
{ "s", NULL, 0, AV_OPT_TYPE_CONST, {.i64=WAVE_SIN}, 0, 0, FLAGS, "type" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(aphaser);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AudioPhaserContext *p = ctx->priv;
|
||||
|
||||
if (p->in_gain > (1 - p->decay * p->decay))
|
||||
av_log(ctx, AV_LOG_WARNING, "in_gain may cause clipping\n");
|
||||
if (p->in_gain / (1 - p->decay) > 1 / p->out_gain)
|
||||
av_log(ctx, AV_LOG_WARNING, "out_gain may cause clipping\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_FLTP,
|
||||
AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void generate_wave_table(enum WaveType wave_type, enum AVSampleFormat sample_fmt,
|
||||
void *table, int table_size,
|
||||
double min, double max, double phase)
|
||||
{
|
||||
uint32_t i, phase_offset = phase / M_PI / 2 * table_size + 0.5;
|
||||
|
||||
for (i = 0; i < table_size; i++) {
|
||||
uint32_t point = (i + phase_offset) % table_size;
|
||||
double d;
|
||||
|
||||
switch (wave_type) {
|
||||
case WAVE_SIN:
|
||||
d = (sin((double)point / table_size * 2 * M_PI) + 1) / 2;
|
||||
break;
|
||||
case WAVE_TRI:
|
||||
d = (double)point * 2 / table_size;
|
||||
switch (4 * point / table_size) {
|
||||
case 0: d = d + 0.5; break;
|
||||
case 1:
|
||||
case 2: d = 1.5 - d; break;
|
||||
case 3: d = d - 1.5; break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
|
||||
d = d * (max - min) + min;
|
||||
switch (sample_fmt) {
|
||||
case AV_SAMPLE_FMT_FLT: {
|
||||
float *fp = (float *)table;
|
||||
*fp++ = (float)d;
|
||||
table = fp;
|
||||
continue; }
|
||||
case AV_SAMPLE_FMT_DBL: {
|
||||
double *dp = (double *)table;
|
||||
*dp++ = d;
|
||||
table = dp;
|
||||
continue; }
|
||||
}
|
||||
|
||||
d += d < 0 ? -0.5 : 0.5;
|
||||
switch (sample_fmt) {
|
||||
case AV_SAMPLE_FMT_S16: {
|
||||
int16_t *sp = table;
|
||||
*sp++ = (int16_t)d;
|
||||
table = sp;
|
||||
continue; }
|
||||
case AV_SAMPLE_FMT_S32: {
|
||||
int32_t *ip = table;
|
||||
*ip++ = (int32_t)d;
|
||||
table = ip;
|
||||
continue; }
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MOD(a, b) (((a) >= (b)) ? (a) - (b) : (a))
|
||||
|
||||
#define PHASER_PLANAR(name, type) \
|
||||
static void phaser_## name ##p(AudioPhaserContext *p, \
|
||||
uint8_t * const *src, uint8_t **dst, \
|
||||
int nb_samples, int channels) \
|
||||
{ \
|
||||
int i, c, delay_pos, modulation_pos; \
|
||||
\
|
||||
av_assert0(channels > 0); \
|
||||
for (c = 0; c < channels; c++) { \
|
||||
type *s = (type *)src[c]; \
|
||||
type *d = (type *)dst[c]; \
|
||||
double *buffer = p->delay_buffer + \
|
||||
c * p->delay_buffer_length; \
|
||||
\
|
||||
delay_pos = p->delay_pos; \
|
||||
modulation_pos = p->modulation_pos; \
|
||||
\
|
||||
for (i = 0; i < nb_samples; i++, s++, d++) { \
|
||||
double v = *s * p->in_gain + buffer[ \
|
||||
MOD(delay_pos + p->modulation_buffer[ \
|
||||
modulation_pos], \
|
||||
p->delay_buffer_length)] * p->decay; \
|
||||
\
|
||||
modulation_pos = MOD(modulation_pos + 1, \
|
||||
p->modulation_buffer_length); \
|
||||
delay_pos = MOD(delay_pos + 1, p->delay_buffer_length); \
|
||||
buffer[delay_pos] = v; \
|
||||
\
|
||||
*d = v * p->out_gain; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
p->delay_pos = delay_pos; \
|
||||
p->modulation_pos = modulation_pos; \
|
||||
}
|
||||
|
||||
#define PHASER(name, type) \
|
||||
static void phaser_## name (AudioPhaserContext *p, \
|
||||
uint8_t * const *src, uint8_t **dst, \
|
||||
int nb_samples, int channels) \
|
||||
{ \
|
||||
int i, c, delay_pos, modulation_pos; \
|
||||
type *s = (type *)src[0]; \
|
||||
type *d = (type *)dst[0]; \
|
||||
double *buffer = p->delay_buffer; \
|
||||
\
|
||||
delay_pos = p->delay_pos; \
|
||||
modulation_pos = p->modulation_pos; \
|
||||
\
|
||||
for (i = 0; i < nb_samples; i++) { \
|
||||
int pos = MOD(delay_pos + p->modulation_buffer[modulation_pos], \
|
||||
p->delay_buffer_length) * channels; \
|
||||
int npos; \
|
||||
\
|
||||
delay_pos = MOD(delay_pos + 1, p->delay_buffer_length); \
|
||||
npos = delay_pos * channels; \
|
||||
for (c = 0; c < channels; c++, s++, d++) { \
|
||||
double v = *s * p->in_gain + buffer[pos + c] * p->decay; \
|
||||
\
|
||||
buffer[npos + c] = v; \
|
||||
\
|
||||
*d = v * p->out_gain; \
|
||||
} \
|
||||
\
|
||||
modulation_pos = MOD(modulation_pos + 1, \
|
||||
p->modulation_buffer_length); \
|
||||
} \
|
||||
\
|
||||
p->delay_pos = delay_pos; \
|
||||
p->modulation_pos = modulation_pos; \
|
||||
}
|
||||
|
||||
PHASER_PLANAR(dbl, double)
|
||||
PHASER_PLANAR(flt, float)
|
||||
PHASER_PLANAR(s16, int16_t)
|
||||
PHASER_PLANAR(s32, int32_t)
|
||||
|
||||
PHASER(dbl, double)
|
||||
PHASER(flt, float)
|
||||
PHASER(s16, int16_t)
|
||||
PHASER(s32, int32_t)
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AudioPhaserContext *p = outlink->src->priv;
|
||||
AVFilterLink *inlink = outlink->src->inputs[0];
|
||||
|
||||
p->delay_buffer_length = p->delay * 0.001 * inlink->sample_rate + 0.5;
|
||||
p->delay_buffer = av_calloc(p->delay_buffer_length, sizeof(*p->delay_buffer) * inlink->channels);
|
||||
p->modulation_buffer_length = inlink->sample_rate / p->speed + 0.5;
|
||||
p->modulation_buffer = av_malloc(p->modulation_buffer_length * sizeof(*p->modulation_buffer));
|
||||
|
||||
if (!p->modulation_buffer || !p->delay_buffer)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
generate_wave_table(p->type, AV_SAMPLE_FMT_S32,
|
||||
p->modulation_buffer, p->modulation_buffer_length,
|
||||
1., p->delay_buffer_length, M_PI / 2.0);
|
||||
|
||||
p->delay_pos = p->modulation_pos = 0;
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_DBL: p->phaser = phaser_dbl; break;
|
||||
case AV_SAMPLE_FMT_DBLP: p->phaser = phaser_dblp; break;
|
||||
case AV_SAMPLE_FMT_FLT: p->phaser = phaser_flt; break;
|
||||
case AV_SAMPLE_FMT_FLTP: p->phaser = phaser_fltp; break;
|
||||
case AV_SAMPLE_FMT_S16: p->phaser = phaser_s16; break;
|
||||
case AV_SAMPLE_FMT_S16P: p->phaser = phaser_s16p; break;
|
||||
case AV_SAMPLE_FMT_S32: p->phaser = phaser_s32; break;
|
||||
case AV_SAMPLE_FMT_S32P: p->phaser = phaser_s32p; break;
|
||||
default: av_assert0(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *inbuf)
|
||||
{
|
||||
AudioPhaserContext *p = inlink->dst->priv;
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
AVFrame *outbuf;
|
||||
|
||||
if (av_frame_is_writable(inbuf)) {
|
||||
outbuf = inbuf;
|
||||
} else {
|
||||
outbuf = ff_get_audio_buffer(inlink, inbuf->nb_samples);
|
||||
if (!outbuf)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(outbuf, inbuf);
|
||||
}
|
||||
|
||||
p->phaser(p, inbuf->extended_data, outbuf->extended_data,
|
||||
outbuf->nb_samples, av_frame_get_channels(outbuf));
|
||||
|
||||
if (inbuf != outbuf)
|
||||
av_frame_free(&inbuf);
|
||||
|
||||
return ff_filter_frame(outlink, outbuf);
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AudioPhaserContext *p = ctx->priv;
|
||||
|
||||
av_freep(&p->delay_buffer);
|
||||
av_freep(&p->modulation_buffer);
|
||||
}
|
||||
|
||||
static const AVFilterPad aphaser_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad aphaser_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aphaser = {
|
||||
.name = "aphaser",
|
||||
.description = NULL_IF_CONFIG_SMALL("Add a phasing effect to the audio."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioPhaserContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.inputs = aphaser_inputs,
|
||||
.outputs = aphaser_outputs,
|
||||
.priv_class = &aphaser_class,
|
||||
};
|
||||
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
* Copyright (c) 2011 Mina Nagy Zaki
|
||||
*
|
||||
* 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
|
||||
* resampling audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int sample_rate_arg;
|
||||
double ratio;
|
||||
struct SwrContext *swr;
|
||||
int64_t next_pts;
|
||||
int req_fullfilled;
|
||||
} AResampleContext;
|
||||
|
||||
static av_cold int init_dict(AVFilterContext *ctx, AVDictionary **opts)
|
||||
{
|
||||
AResampleContext *aresample = ctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
aresample->next_pts = AV_NOPTS_VALUE;
|
||||
aresample->swr = swr_alloc();
|
||||
if (!aresample->swr) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
AVDictionaryEntry *e = NULL;
|
||||
|
||||
while ((e = av_dict_get(*opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
|
||||
if ((ret = av_opt_set(aresample->swr, e->key, e->value, 0)) < 0)
|
||||
goto end;
|
||||
}
|
||||
av_dict_free(opts);
|
||||
}
|
||||
if (aresample->sample_rate_arg > 0)
|
||||
av_opt_set_int(aresample->swr, "osr", aresample->sample_rate_arg, 0);
|
||||
end:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AResampleContext *aresample = ctx->priv;
|
||||
swr_free(&aresample->swr);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AResampleContext *aresample = ctx->priv;
|
||||
int out_rate = av_get_int(aresample->swr, "osr", NULL);
|
||||
uint64_t out_layout = av_get_int(aresample->swr, "ocl", NULL);
|
||||
enum AVSampleFormat out_format = av_get_int(aresample->swr, "osf", NULL);
|
||||
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
AVFilterFormats *in_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO);
|
||||
AVFilterFormats *out_formats;
|
||||
AVFilterFormats *in_samplerates = ff_all_samplerates();
|
||||
AVFilterFormats *out_samplerates;
|
||||
AVFilterChannelLayouts *in_layouts = ff_all_channel_counts();
|
||||
AVFilterChannelLayouts *out_layouts;
|
||||
|
||||
ff_formats_ref (in_formats, &inlink->out_formats);
|
||||
ff_formats_ref (in_samplerates, &inlink->out_samplerates);
|
||||
ff_channel_layouts_ref(in_layouts, &inlink->out_channel_layouts);
|
||||
|
||||
if(out_rate > 0) {
|
||||
out_samplerates = ff_make_format_list((int[]){ out_rate, -1 });
|
||||
} else {
|
||||
out_samplerates = ff_all_samplerates();
|
||||
}
|
||||
ff_formats_ref(out_samplerates, &outlink->in_samplerates);
|
||||
|
||||
if(out_format != AV_SAMPLE_FMT_NONE) {
|
||||
out_formats = ff_make_format_list((int[]){ out_format, -1 });
|
||||
} else
|
||||
out_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO);
|
||||
ff_formats_ref(out_formats, &outlink->in_formats);
|
||||
|
||||
if(out_layout) {
|
||||
out_layouts = avfilter_make_format64_list((int64_t[]){ out_layout, -1 });
|
||||
} else
|
||||
out_layouts = ff_all_channel_counts();
|
||||
ff_channel_layouts_ref(out_layouts, &outlink->in_channel_layouts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
int ret;
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AResampleContext *aresample = ctx->priv;
|
||||
int out_rate;
|
||||
uint64_t out_layout;
|
||||
enum AVSampleFormat out_format;
|
||||
char inchl_buf[128], outchl_buf[128];
|
||||
|
||||
aresample->swr = swr_alloc_set_opts(aresample->swr,
|
||||
outlink->channel_layout, outlink->format, outlink->sample_rate,
|
||||
inlink->channel_layout, inlink->format, inlink->sample_rate,
|
||||
0, ctx);
|
||||
if (!aresample->swr)
|
||||
return AVERROR(ENOMEM);
|
||||
if (!inlink->channel_layout)
|
||||
av_opt_set_int(aresample->swr, "ich", inlink->channels, 0);
|
||||
if (!outlink->channel_layout)
|
||||
av_opt_set_int(aresample->swr, "och", outlink->channels, 0);
|
||||
|
||||
ret = swr_init(aresample->swr);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
out_rate = av_get_int(aresample->swr, "osr", NULL);
|
||||
out_layout = av_get_int(aresample->swr, "ocl", NULL);
|
||||
out_format = av_get_int(aresample->swr, "osf", NULL);
|
||||
outlink->time_base = (AVRational) {1, out_rate};
|
||||
|
||||
av_assert0(outlink->sample_rate == out_rate);
|
||||
av_assert0(outlink->channel_layout == out_layout || !outlink->channel_layout);
|
||||
av_assert0(outlink->format == out_format);
|
||||
|
||||
aresample->ratio = (double)outlink->sample_rate / inlink->sample_rate;
|
||||
|
||||
av_get_channel_layout_string(inchl_buf, sizeof(inchl_buf), inlink ->channels, inlink ->channel_layout);
|
||||
av_get_channel_layout_string(outchl_buf, sizeof(outchl_buf), outlink->channels, outlink->channel_layout);
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "ch:%d chl:%s fmt:%s r:%dHz -> ch:%d chl:%s fmt:%s r:%dHz\n",
|
||||
inlink ->channels, inchl_buf, av_get_sample_fmt_name(inlink->format), inlink->sample_rate,
|
||||
outlink->channels, outchl_buf, av_get_sample_fmt_name(outlink->format), outlink->sample_rate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamplesref)
|
||||
{
|
||||
AResampleContext *aresample = inlink->dst->priv;
|
||||
const int n_in = insamplesref->nb_samples;
|
||||
int n_out = n_in * aresample->ratio * 2 + 256;
|
||||
AVFilterLink *const outlink = inlink->dst->outputs[0];
|
||||
AVFrame *outsamplesref = ff_get_audio_buffer(outlink, n_out);
|
||||
int ret;
|
||||
|
||||
if(!outsamplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_frame_copy_props(outsamplesref, insamplesref);
|
||||
outsamplesref->format = outlink->format;
|
||||
av_frame_set_channels(outsamplesref, outlink->channels);
|
||||
outsamplesref->channel_layout = outlink->channel_layout;
|
||||
outsamplesref->sample_rate = outlink->sample_rate;
|
||||
|
||||
if(insamplesref->pts != AV_NOPTS_VALUE) {
|
||||
int64_t inpts = av_rescale(insamplesref->pts, inlink->time_base.num * (int64_t)outlink->sample_rate * inlink->sample_rate, inlink->time_base.den);
|
||||
int64_t outpts= swr_next_pts(aresample->swr, inpts);
|
||||
aresample->next_pts =
|
||||
outsamplesref->pts = ROUNDED_DIV(outpts, inlink->sample_rate);
|
||||
} else {
|
||||
outsamplesref->pts = AV_NOPTS_VALUE;
|
||||
}
|
||||
n_out = swr_convert(aresample->swr, outsamplesref->extended_data, n_out,
|
||||
(void *)insamplesref->extended_data, n_in);
|
||||
if (n_out <= 0) {
|
||||
av_frame_free(&outsamplesref);
|
||||
av_frame_free(&insamplesref);
|
||||
return 0;
|
||||
}
|
||||
|
||||
outsamplesref->nb_samples = n_out;
|
||||
|
||||
ret = ff_filter_frame(outlink, outsamplesref);
|
||||
aresample->req_fullfilled= 1;
|
||||
av_frame_free(&insamplesref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AResampleContext *aresample = ctx->priv;
|
||||
AVFilterLink *const inlink = outlink->src->inputs[0];
|
||||
int ret;
|
||||
|
||||
aresample->req_fullfilled = 0;
|
||||
do{
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
}while(!aresample->req_fullfilled && ret>=0);
|
||||
|
||||
if (ret == AVERROR_EOF) {
|
||||
AVFrame *outsamplesref;
|
||||
int n_out = 4096;
|
||||
|
||||
outsamplesref = ff_get_audio_buffer(outlink, n_out);
|
||||
if (!outsamplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
n_out = swr_convert(aresample->swr, outsamplesref->extended_data, n_out, 0, 0);
|
||||
if (n_out <= 0) {
|
||||
av_frame_free(&outsamplesref);
|
||||
return (n_out == 0) ? AVERROR_EOF : n_out;
|
||||
}
|
||||
|
||||
outsamplesref->sample_rate = outlink->sample_rate;
|
||||
outsamplesref->nb_samples = n_out;
|
||||
#if 0
|
||||
outsamplesref->pts = aresample->next_pts;
|
||||
if(aresample->next_pts != AV_NOPTS_VALUE)
|
||||
aresample->next_pts += av_rescale_q(n_out, (AVRational){1 ,outlink->sample_rate}, outlink->time_base);
|
||||
#else
|
||||
outsamplesref->pts = swr_next_pts(aresample->swr, INT64_MIN);
|
||||
outsamplesref->pts = ROUNDED_DIV(outsamplesref->pts, inlink->sample_rate);
|
||||
#endif
|
||||
|
||||
return ff_filter_frame(outlink, outsamplesref);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVClass *resample_child_class_next(const AVClass *prev)
|
||||
{
|
||||
return prev ? NULL : swr_get_class();
|
||||
}
|
||||
|
||||
static void *resample_child_next(void *obj, void *prev)
|
||||
{
|
||||
AResampleContext *s = obj;
|
||||
return prev ? NULL : s->swr;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(AResampleContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption options[] = {
|
||||
{"sample_rate", NULL, OFFSET(sample_rate_arg), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, FLAGS },
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static const AVClass aresample_class = {
|
||||
.class_name = "aresample",
|
||||
.item_name = av_default_item_name,
|
||||
.option = options,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
.child_class_next = resample_child_class_next,
|
||||
.child_next = resample_child_next,
|
||||
};
|
||||
|
||||
static const AVFilterPad aresample_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad aresample_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aresample = {
|
||||
.name = "aresample",
|
||||
.description = NULL_IF_CONFIG_SMALL("Resample audio data."),
|
||||
.init_dict = init_dict,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AResampleContext),
|
||||
.priv_class = &aresample_class,
|
||||
.inputs = aresample_inputs,
|
||||
.outputs = aresample_outputs,
|
||||
};
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Andrey Utkin
|
||||
* Copyright (c) 2012 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* Filter that changes number of samples on single output operation
|
||||
*/
|
||||
|
||||
#include "libavutil/audio_fifo.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
#include "formats.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int nb_out_samples; ///< how many samples to output
|
||||
AVAudioFifo *fifo; ///< samples are queued here
|
||||
int64_t next_out_pts;
|
||||
int pad;
|
||||
} ASNSContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ASNSContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption asetnsamples_options[] = {
|
||||
{ "nb_out_samples", "set the number of per-frame output samples", OFFSET(nb_out_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "n", "set the number of per-frame output samples", OFFSET(nb_out_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "pad", "pad last frame with zeros", OFFSET(pad), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS },
|
||||
{ "p", "pad last frame with zeros", OFFSET(pad), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(asetnsamples);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ASNSContext *asns = ctx->priv;
|
||||
|
||||
asns->next_out_pts = AV_NOPTS_VALUE;
|
||||
av_log(ctx, AV_LOG_VERBOSE, "nb_out_samples:%d pad:%d\n", asns->nb_out_samples, asns->pad);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ASNSContext *asns = ctx->priv;
|
||||
av_audio_fifo_free(asns->fifo);
|
||||
}
|
||||
|
||||
static int config_props_output(AVFilterLink *outlink)
|
||||
{
|
||||
ASNSContext *asns = outlink->src->priv;
|
||||
|
||||
asns->fifo = av_audio_fifo_alloc(outlink->format, outlink->channels, asns->nb_out_samples);
|
||||
if (!asns->fifo)
|
||||
return AVERROR(ENOMEM);
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int push_samples(AVFilterLink *outlink)
|
||||
{
|
||||
ASNSContext *asns = outlink->src->priv;
|
||||
AVFrame *outsamples = NULL;
|
||||
int ret, nb_out_samples, nb_pad_samples;
|
||||
|
||||
if (asns->pad) {
|
||||
nb_out_samples = av_audio_fifo_size(asns->fifo) ? asns->nb_out_samples : 0;
|
||||
nb_pad_samples = nb_out_samples - FFMIN(nb_out_samples, av_audio_fifo_size(asns->fifo));
|
||||
} else {
|
||||
nb_out_samples = FFMIN(asns->nb_out_samples, av_audio_fifo_size(asns->fifo));
|
||||
nb_pad_samples = 0;
|
||||
}
|
||||
|
||||
if (!nb_out_samples)
|
||||
return 0;
|
||||
|
||||
outsamples = ff_get_audio_buffer(outlink, nb_out_samples);
|
||||
if (!outsamples)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_audio_fifo_read(asns->fifo,
|
||||
(void **)outsamples->extended_data, nb_out_samples);
|
||||
|
||||
if (nb_pad_samples)
|
||||
av_samples_set_silence(outsamples->extended_data, nb_out_samples - nb_pad_samples,
|
||||
nb_pad_samples, outlink->channels,
|
||||
outlink->format);
|
||||
outsamples->nb_samples = nb_out_samples;
|
||||
outsamples->channel_layout = outlink->channel_layout;
|
||||
outsamples->sample_rate = outlink->sample_rate;
|
||||
outsamples->pts = asns->next_out_pts;
|
||||
|
||||
if (asns->next_out_pts != AV_NOPTS_VALUE)
|
||||
asns->next_out_pts += nb_out_samples;
|
||||
|
||||
ret = ff_filter_frame(outlink, outsamples);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return nb_out_samples;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ASNSContext *asns = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
int ret;
|
||||
int nb_samples = insamples->nb_samples;
|
||||
|
||||
if (av_audio_fifo_space(asns->fifo) < nb_samples) {
|
||||
av_log(ctx, AV_LOG_DEBUG, "No space for %d samples, stretching audio fifo\n", nb_samples);
|
||||
ret = av_audio_fifo_realloc(asns->fifo, av_audio_fifo_size(asns->fifo) + nb_samples);
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Stretching audio fifo failed, discarded %d samples\n", nb_samples);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
av_audio_fifo_write(asns->fifo, (void **)insamples->extended_data, nb_samples);
|
||||
if (asns->next_out_pts == AV_NOPTS_VALUE)
|
||||
asns->next_out_pts = insamples->pts;
|
||||
av_frame_free(&insamples);
|
||||
|
||||
while (av_audio_fifo_size(asns->fifo) >= asns->nb_out_samples)
|
||||
push_samples(outlink);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterLink *inlink = outlink->src->inputs[0];
|
||||
int ret;
|
||||
|
||||
ret = ff_request_frame(inlink);
|
||||
if (ret == AVERROR_EOF) {
|
||||
ret = push_samples(outlink);
|
||||
return ret < 0 ? ret : ret > 0 ? 0 : AVERROR_EOF;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad asetnsamples_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad asetnsamples_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.request_frame = request_frame,
|
||||
.config_props = config_props_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_asetnsamples = {
|
||||
.name = "asetnsamples",
|
||||
.description = NULL_IF_CONFIG_SMALL("Set the number of samples for each output audio frames."),
|
||||
.priv_size = sizeof(ASNSContext),
|
||||
.priv_class = &asetnsamples_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.inputs = asetnsamples_inputs,
|
||||
.outputs = asetnsamples_outputs,
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int sample_rate;
|
||||
int rescale_pts;
|
||||
} ASetRateContext;
|
||||
|
||||
#define CONTEXT ASetRateContext
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
#define OPT_GENERIC(name, field, def, min, max, descr, type, deffield, ...) \
|
||||
{ name, descr, offsetof(CONTEXT, field), AV_OPT_TYPE_ ## type, \
|
||||
{ .deffield = def }, min, max, FLAGS, __VA_ARGS__ }
|
||||
|
||||
#define OPT_INT(name, field, def, min, max, descr, ...) \
|
||||
OPT_GENERIC(name, field, def, min, max, descr, INT, i64, __VA_ARGS__)
|
||||
|
||||
static const AVOption asetrate_options[] = {
|
||||
OPT_INT("sample_rate", sample_rate, 44100, 1, INT_MAX, "set the sample rate"),
|
||||
OPT_INT("r", sample_rate, 44100, 1, INT_MAX, "set the sample rate"),
|
||||
{NULL},
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(asetrate);
|
||||
|
||||
static av_cold int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
ASetRateContext *sr = ctx->priv;
|
||||
int sample_rates[] = { sr->sample_rate, -1 };
|
||||
|
||||
ff_formats_ref(ff_make_format_list(sample_rates),
|
||||
&ctx->outputs[0]->in_samplerates);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int config_props(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
ASetRateContext *sr = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVRational intb = ctx->inputs[0]->time_base;
|
||||
int inrate = inlink->sample_rate;
|
||||
|
||||
if (intb.num == 1 && intb.den == inrate) {
|
||||
outlink->time_base.num = 1;
|
||||
outlink->time_base.den = outlink->sample_rate;
|
||||
} else {
|
||||
outlink->time_base = intb;
|
||||
sr->rescale_pts = 1;
|
||||
if (av_q2d(intb) > 1.0 / FFMAX(inrate, outlink->sample_rate))
|
||||
av_log(ctx, AV_LOG_WARNING, "Time base is inaccurate\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ASetRateContext *sr = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
frame->sample_rate = outlink->sample_rate;
|
||||
if (sr->rescale_pts)
|
||||
frame->pts = av_rescale(frame->pts, inlink->sample_rate,
|
||||
outlink->sample_rate);
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
|
||||
static const AVFilterPad asetrate_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad asetrate_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_asetrate = {
|
||||
.name = "asetrate",
|
||||
.description = NULL_IF_CONFIG_SMALL("Change the sample rate without "
|
||||
"altering the data."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(ASetRateContext),
|
||||
.inputs = asetrate_inputs,
|
||||
.outputs = asetrate_outputs,
|
||||
.priv_class = &asetrate_class,
|
||||
};
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* filter for showing textual audio frame information
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "libavutil/adler32.h"
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct AShowInfoContext {
|
||||
/**
|
||||
* Scratch space for individual plane checksums for planar audio
|
||||
*/
|
||||
uint32_t *plane_checksums;
|
||||
} AShowInfoContext;
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AShowInfoContext *s = ctx->priv;
|
||||
av_freep(&s->plane_checksums);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AShowInfoContext *s = ctx->priv;
|
||||
char chlayout_str[128];
|
||||
uint32_t checksum = 0;
|
||||
int channels = inlink->channels;
|
||||
int planar = av_sample_fmt_is_planar(buf->format);
|
||||
int block_align = av_get_bytes_per_sample(buf->format) * (planar ? 1 : channels);
|
||||
int data_size = buf->nb_samples * block_align;
|
||||
int planes = planar ? channels : 1;
|
||||
int i;
|
||||
void *tmp_ptr = av_realloc(s->plane_checksums, channels * sizeof(*s->plane_checksums));
|
||||
|
||||
if (!tmp_ptr)
|
||||
return AVERROR(ENOMEM);
|
||||
s->plane_checksums = tmp_ptr;
|
||||
|
||||
for (i = 0; i < planes; i++) {
|
||||
uint8_t *data = buf->extended_data[i];
|
||||
|
||||
s->plane_checksums[i] = av_adler32_update(0, data, data_size);
|
||||
checksum = i ? av_adler32_update(checksum, data, data_size) :
|
||||
s->plane_checksums[0];
|
||||
}
|
||||
|
||||
av_get_channel_layout_string(chlayout_str, sizeof(chlayout_str), -1,
|
||||
buf->channel_layout);
|
||||
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"n:%"PRId64" pts:%s pts_time:%s pos:%"PRId64" "
|
||||
"fmt:%s channels:%d chlayout:%s rate:%d nb_samples:%d "
|
||||
"checksum:%08X ",
|
||||
inlink->frame_count,
|
||||
av_ts2str(buf->pts), av_ts2timestr(buf->pts, &inlink->time_base),
|
||||
av_frame_get_pkt_pos(buf),
|
||||
av_get_sample_fmt_name(buf->format), av_frame_get_channels(buf), chlayout_str,
|
||||
buf->sample_rate, buf->nb_samples,
|
||||
checksum);
|
||||
|
||||
av_log(ctx, AV_LOG_INFO, "plane_checksums: [ ");
|
||||
for (i = 0; i < planes; i++)
|
||||
av_log(ctx, AV_LOG_INFO, "%08X ", s->plane_checksums[i]);
|
||||
av_log(ctx, AV_LOG_INFO, "]\n");
|
||||
|
||||
return ff_filter_frame(inlink->dst->outputs[0], buf);
|
||||
}
|
||||
|
||||
static const AVFilterPad inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_ashowinfo = {
|
||||
.name = "ashowinfo",
|
||||
.description = NULL_IF_CONFIG_SMALL("Show textual information for each audio frame."),
|
||||
.priv_size = sizeof(AShowInfoContext),
|
||||
.uninit = uninit,
|
||||
.inputs = inputs,
|
||||
.outputs = outputs,
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Rob Sykes <[email protected]>
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ChannelStats {
|
||||
double last;
|
||||
double sigma_x, sigma_x2;
|
||||
double avg_sigma_x2, min_sigma_x2, max_sigma_x2;
|
||||
double min, max;
|
||||
double min_run, max_run;
|
||||
double min_runs, max_runs;
|
||||
uint64_t min_count, max_count;
|
||||
uint64_t nb_samples;
|
||||
} ChannelStats;
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
ChannelStats *chstats;
|
||||
int nb_channels;
|
||||
uint64_t tc_samples;
|
||||
double time_constant;
|
||||
double mult;
|
||||
} AudioStatsContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AudioStatsContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption astats_options[] = {
|
||||
{ "length", "set the window length", OFFSET(time_constant), AV_OPT_TYPE_DOUBLE, {.dbl=.05}, .01, 10, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(astats);
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AudioStatsContext *s = outlink->src->priv;
|
||||
int c;
|
||||
|
||||
s->chstats = av_calloc(sizeof(*s->chstats), outlink->channels);
|
||||
if (!s->chstats)
|
||||
return AVERROR(ENOMEM);
|
||||
s->nb_channels = outlink->channels;
|
||||
s->mult = exp((-1 / s->time_constant / outlink->sample_rate));
|
||||
s->tc_samples = 5 * s->time_constant * outlink->sample_rate + .5;
|
||||
|
||||
for (c = 0; c < s->nb_channels; c++) {
|
||||
ChannelStats *p = &s->chstats[c];
|
||||
|
||||
p->min = p->min_sigma_x2 = DBL_MAX;
|
||||
p->max = p->max_sigma_x2 = DBL_MIN;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void update_stat(AudioStatsContext *s, ChannelStats *p, double d)
|
||||
{
|
||||
if (d < p->min) {
|
||||
p->min = d;
|
||||
p->min_run = 1;
|
||||
p->min_runs = 0;
|
||||
p->min_count = 1;
|
||||
} else if (d == p->min) {
|
||||
p->min_count++;
|
||||
p->min_run = d == p->last ? p->min_run + 1 : 1;
|
||||
} else if (p->last == p->min) {
|
||||
p->min_runs += p->min_run * p->min_run;
|
||||
}
|
||||
|
||||
if (d > p->max) {
|
||||
p->max = d;
|
||||
p->max_run = 1;
|
||||
p->max_runs = 0;
|
||||
p->max_count = 1;
|
||||
} else if (d == p->max) {
|
||||
p->max_count++;
|
||||
p->max_run = d == p->last ? p->max_run + 1 : 1;
|
||||
} else if (p->last == p->max) {
|
||||
p->max_runs += p->max_run * p->max_run;
|
||||
}
|
||||
|
||||
p->sigma_x += d;
|
||||
p->sigma_x2 += d * d;
|
||||
p->avg_sigma_x2 = p->avg_sigma_x2 * s->mult + (1.0 - s->mult) * d * d;
|
||||
p->last = d;
|
||||
|
||||
if (p->nb_samples >= s->tc_samples) {
|
||||
p->max_sigma_x2 = FFMAX(p->max_sigma_x2, p->avg_sigma_x2);
|
||||
p->min_sigma_x2 = FFMIN(p->min_sigma_x2, p->avg_sigma_x2);
|
||||
}
|
||||
p->nb_samples++;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AudioStatsContext *s = inlink->dst->priv;
|
||||
const int channels = s->nb_channels;
|
||||
const double *src;
|
||||
int i, c;
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_DBLP:
|
||||
for (c = 0; c < channels; c++) {
|
||||
ChannelStats *p = &s->chstats[c];
|
||||
src = (const double *)buf->extended_data[c];
|
||||
|
||||
for (i = 0; i < buf->nb_samples; i++, src++)
|
||||
update_stat(s, p, *src);
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
src = (const double *)buf->extended_data[0];
|
||||
|
||||
for (i = 0; i < buf->nb_samples; i++) {
|
||||
for (c = 0; c < channels; c++, src++)
|
||||
update_stat(s, &s->chstats[c], *src);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return ff_filter_frame(inlink->dst->outputs[0], buf);
|
||||
}
|
||||
|
||||
#define LINEAR_TO_DB(x) (log10(x) * 20)
|
||||
|
||||
static void print_stats(AVFilterContext *ctx)
|
||||
{
|
||||
AudioStatsContext *s = ctx->priv;
|
||||
uint64_t min_count = 0, max_count = 0, nb_samples = 0;
|
||||
double min_runs = 0, max_runs = 0,
|
||||
min = DBL_MAX, max = DBL_MIN,
|
||||
max_sigma_x = 0,
|
||||
sigma_x = 0,
|
||||
sigma_x2 = 0,
|
||||
min_sigma_x2 = DBL_MAX,
|
||||
max_sigma_x2 = DBL_MIN;
|
||||
int c;
|
||||
|
||||
for (c = 0; c < s->nb_channels; c++) {
|
||||
ChannelStats *p = &s->chstats[c];
|
||||
|
||||
if (p->nb_samples < s->tc_samples)
|
||||
p->min_sigma_x2 = p->max_sigma_x2 = p->sigma_x2 / p->nb_samples;
|
||||
|
||||
min = FFMIN(min, p->min);
|
||||
max = FFMAX(max, p->max);
|
||||
min_sigma_x2 = FFMIN(min_sigma_x2, p->min_sigma_x2);
|
||||
max_sigma_x2 = FFMAX(max_sigma_x2, p->max_sigma_x2);
|
||||
sigma_x += p->sigma_x;
|
||||
sigma_x2 += p->sigma_x2;
|
||||
min_count += p->min_count;
|
||||
max_count += p->max_count;
|
||||
min_runs += p->min_runs;
|
||||
max_runs += p->max_runs;
|
||||
nb_samples += p->nb_samples;
|
||||
if (fabs(p->sigma_x) > fabs(max_sigma_x))
|
||||
max_sigma_x = p->sigma_x;
|
||||
|
||||
av_log(ctx, AV_LOG_INFO, "Channel: %d\n", c + 1);
|
||||
av_log(ctx, AV_LOG_INFO, "DC offset: %f\n", p->sigma_x / p->nb_samples);
|
||||
av_log(ctx, AV_LOG_INFO, "Min level: %f\n", p->min);
|
||||
av_log(ctx, AV_LOG_INFO, "Max level: %f\n", p->max);
|
||||
av_log(ctx, AV_LOG_INFO, "Peak level dB: %f\n", LINEAR_TO_DB(FFMAX(-p->min, p->max)));
|
||||
av_log(ctx, AV_LOG_INFO, "RMS level dB: %f\n", LINEAR_TO_DB(sqrt(p->sigma_x2 / p->nb_samples)));
|
||||
av_log(ctx, AV_LOG_INFO, "RMS peak dB: %f\n", LINEAR_TO_DB(sqrt(p->max_sigma_x2)));
|
||||
if (p->min_sigma_x2 != 1)
|
||||
av_log(ctx, AV_LOG_INFO, "RMS trough dB: %f\n",LINEAR_TO_DB(sqrt(p->min_sigma_x2)));
|
||||
av_log(ctx, AV_LOG_INFO, "Crest factor: %f\n", p->sigma_x2 ? FFMAX(-p->min, p->max) / sqrt(p->sigma_x2 / p->nb_samples) : 1);
|
||||
av_log(ctx, AV_LOG_INFO, "Flat factor: %f\n", LINEAR_TO_DB((p->min_runs + p->max_runs) / (p->min_count + p->max_count)));
|
||||
av_log(ctx, AV_LOG_INFO, "Peak count: %"PRId64"\n", p->min_count + p->max_count);
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_INFO, "Overall\n");
|
||||
av_log(ctx, AV_LOG_INFO, "DC offset: %f\n", max_sigma_x / (nb_samples / s->nb_channels));
|
||||
av_log(ctx, AV_LOG_INFO, "Min level: %f\n", min);
|
||||
av_log(ctx, AV_LOG_INFO, "Max level: %f\n", max);
|
||||
av_log(ctx, AV_LOG_INFO, "Peak level dB: %f\n", LINEAR_TO_DB(FFMAX(-min, max)));
|
||||
av_log(ctx, AV_LOG_INFO, "RMS level dB: %f\n", LINEAR_TO_DB(sqrt(sigma_x2 / nb_samples)));
|
||||
av_log(ctx, AV_LOG_INFO, "RMS peak dB: %f\n", LINEAR_TO_DB(sqrt(max_sigma_x2)));
|
||||
if (min_sigma_x2 != 1)
|
||||
av_log(ctx, AV_LOG_INFO, "RMS trough dB: %f\n", LINEAR_TO_DB(sqrt(min_sigma_x2)));
|
||||
av_log(ctx, AV_LOG_INFO, "Flat factor: %f\n", LINEAR_TO_DB((min_runs + max_runs) / (min_count + max_count)));
|
||||
av_log(ctx, AV_LOG_INFO, "Peak count: %f\n", (min_count + max_count) / (double)s->nb_channels);
|
||||
av_log(ctx, AV_LOG_INFO, "Number of samples: %"PRId64"\n", nb_samples / s->nb_channels);
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AudioStatsContext *s = ctx->priv;
|
||||
|
||||
print_stats(ctx);
|
||||
av_freep(&s->chstats);
|
||||
}
|
||||
|
||||
static const AVFilterPad astats_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad astats_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_astats = {
|
||||
.name = "astats",
|
||||
.description = NULL_IF_CONFIG_SMALL("Show time domain statistics about audio frames."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioStatsContext),
|
||||
.priv_class = &astats_class,
|
||||
.uninit = uninit,
|
||||
.inputs = astats_inputs,
|
||||
.outputs = astats_outputs,
|
||||
};
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Nicolas George <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* Stream (de)synchronization filter
|
||||
*/
|
||||
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define QUEUE_SIZE 16
|
||||
|
||||
static const char * const var_names[] = {
|
||||
"b1", "b2",
|
||||
"s1", "s2",
|
||||
"t1", "t2",
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_B1, VAR_B2,
|
||||
VAR_S1, VAR_S2,
|
||||
VAR_T1, VAR_T2,
|
||||
VAR_NB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
AVExpr *expr;
|
||||
char *expr_str;
|
||||
double var_values[VAR_NB];
|
||||
struct buf_queue {
|
||||
AVFrame *buf[QUEUE_SIZE];
|
||||
unsigned tail, nb;
|
||||
/* buf[tail] is the oldest,
|
||||
buf[(tail + nb) % QUEUE_SIZE] is where the next is added */
|
||||
} queue[2];
|
||||
int req[2];
|
||||
int next_out;
|
||||
int eof; /* bitmask, one bit for each stream */
|
||||
} AStreamSyncContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AStreamSyncContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption astreamsync_options[] = {
|
||||
{ "expr", "set stream selection expression", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "t1-t2" }, .flags = FLAGS },
|
||||
{ "e", "set stream selection expression", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "t1-t2" }, .flags = FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(astreamsync);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
int r, i;
|
||||
|
||||
r = av_expr_parse(&as->expr, as->expr_str, var_names,
|
||||
NULL, NULL, NULL, NULL, 0, ctx);
|
||||
if (r < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error in expression \"%s\"\n", as->expr_str);
|
||||
return r;
|
||||
}
|
||||
for (i = 0; i < 42; i++)
|
||||
av_expr_eval(as->expr, as->var_values, NULL); /* exercize prng */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
int i;
|
||||
AVFilterFormats *formats, *rates;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
formats = ctx->inputs[i]->in_formats;
|
||||
ff_formats_ref(formats, &ctx->inputs[i]->out_formats);
|
||||
ff_formats_ref(formats, &ctx->outputs[i]->in_formats);
|
||||
rates = ff_all_samplerates();
|
||||
ff_formats_ref(rates, &ctx->inputs[i]->out_samplerates);
|
||||
ff_formats_ref(rates, &ctx->outputs[i]->in_samplerates);
|
||||
layouts = ctx->inputs[i]->in_channel_layouts;
|
||||
ff_channel_layouts_ref(layouts, &ctx->inputs[i]->out_channel_layouts);
|
||||
ff_channel_layouts_ref(layouts, &ctx->outputs[i]->in_channel_layouts);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
int id = outlink == ctx->outputs[1];
|
||||
|
||||
outlink->sample_rate = ctx->inputs[id]->sample_rate;
|
||||
outlink->time_base = ctx->inputs[id]->time_base;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_out(AVFilterContext *ctx, int out_id)
|
||||
{
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
struct buf_queue *queue = &as->queue[out_id];
|
||||
AVFrame *buf = queue->buf[queue->tail];
|
||||
int ret;
|
||||
|
||||
queue->buf[queue->tail] = NULL;
|
||||
as->var_values[VAR_B1 + out_id]++;
|
||||
as->var_values[VAR_S1 + out_id] += buf->nb_samples;
|
||||
if (buf->pts != AV_NOPTS_VALUE)
|
||||
as->var_values[VAR_T1 + out_id] =
|
||||
av_q2d(ctx->outputs[out_id]->time_base) * buf->pts;
|
||||
as->var_values[VAR_T1 + out_id] += buf->nb_samples /
|
||||
(double)ctx->inputs[out_id]->sample_rate;
|
||||
ret = ff_filter_frame(ctx->outputs[out_id], buf);
|
||||
queue->nb--;
|
||||
queue->tail = (queue->tail + 1) % QUEUE_SIZE;
|
||||
if (as->req[out_id])
|
||||
as->req[out_id]--;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void send_next(AVFilterContext *ctx)
|
||||
{
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
int i;
|
||||
|
||||
while (1) {
|
||||
if (!as->queue[as->next_out].nb)
|
||||
break;
|
||||
send_out(ctx, as->next_out);
|
||||
if (!as->eof)
|
||||
as->next_out = av_expr_eval(as->expr, as->var_values, NULL) >= 0;
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
if (as->queue[i].nb == QUEUE_SIZE)
|
||||
send_out(ctx, i);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
int id = outlink == ctx->outputs[1];
|
||||
|
||||
as->req[id]++;
|
||||
while (as->req[id] && !(as->eof & (1 << id))) {
|
||||
if (as->queue[as->next_out].nb) {
|
||||
send_next(ctx);
|
||||
} else {
|
||||
as->eof |= 1 << as->next_out;
|
||||
ff_request_frame(ctx->inputs[as->next_out]);
|
||||
if (as->eof & (1 << as->next_out))
|
||||
as->next_out = !as->next_out;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
int id = inlink == ctx->inputs[1];
|
||||
|
||||
as->queue[id].buf[(as->queue[id].tail + as->queue[id].nb++) % QUEUE_SIZE] =
|
||||
insamples;
|
||||
as->eof &= ~(1 << id);
|
||||
send_next(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AStreamSyncContext *as = ctx->priv;
|
||||
|
||||
av_expr_free(as->expr);
|
||||
as->expr = NULL;
|
||||
}
|
||||
|
||||
static const AVFilterPad astreamsync_inputs[] = {
|
||||
{
|
||||
.name = "in1",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},{
|
||||
.name = "in2",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad astreamsync_outputs[] = {
|
||||
{
|
||||
.name = "out1",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},{
|
||||
.name = "out2",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_astreamsync = {
|
||||
.name = "astreamsync",
|
||||
.description = NULL_IF_CONFIG_SMALL("Copy two streams of audio data "
|
||||
"in a configurable order."),
|
||||
.priv_size = sizeof(AStreamSyncContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = astreamsync_inputs,
|
||||
.outputs = astreamsync_outputs,
|
||||
.priv_class = &astreamsync_class,
|
||||
};
|
||||
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavresample/avresample.h"
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/audio_fifo.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ASyncContext {
|
||||
const AVClass *class;
|
||||
|
||||
AVAudioResampleContext *avr;
|
||||
int64_t pts; ///< timestamp in samples of the first sample in fifo
|
||||
int min_delta; ///< pad/trim min threshold in samples
|
||||
int first_frame; ///< 1 until filter_frame() has processed at least 1 frame with a pts != AV_NOPTS_VALUE
|
||||
int64_t first_pts; ///< user-specified first expected pts, in samples
|
||||
int comp; ///< current resample compensation
|
||||
|
||||
/* options */
|
||||
int resample;
|
||||
float min_delta_sec;
|
||||
int max_comp;
|
||||
|
||||
/* set by filter_frame() to signal an output frame to request_frame() */
|
||||
int got_output;
|
||||
} ASyncContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ASyncContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption asyncts_options[] = {
|
||||
{ "compensate", "Stretch/squeeze the data to make it match the timestamps", OFFSET(resample), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, A|F },
|
||||
{ "min_delta", "Minimum difference between timestamps and audio data "
|
||||
"(in seconds) to trigger padding/trimmin the data.", OFFSET(min_delta_sec), AV_OPT_TYPE_FLOAT, { .dbl = 0.1 }, 0, INT_MAX, A|F },
|
||||
{ "max_comp", "Maximum compensation in samples per second.", OFFSET(max_comp), AV_OPT_TYPE_INT, { .i64 = 500 }, 0, INT_MAX, A|F },
|
||||
{ "first_pts", "Assume the first pts should be this value.", OFFSET(first_pts), AV_OPT_TYPE_INT64, { .i64 = AV_NOPTS_VALUE }, INT64_MIN, INT64_MAX, A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(asyncts);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ASyncContext *s = ctx->priv;
|
||||
|
||||
s->pts = AV_NOPTS_VALUE;
|
||||
s->first_frame = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ASyncContext *s = ctx->priv;
|
||||
|
||||
if (s->avr) {
|
||||
avresample_close(s->avr);
|
||||
avresample_free(&s->avr);
|
||||
}
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *link)
|
||||
{
|
||||
ASyncContext *s = link->src->priv;
|
||||
int ret;
|
||||
|
||||
s->min_delta = s->min_delta_sec * link->sample_rate;
|
||||
link->time_base = (AVRational){1, link->sample_rate};
|
||||
|
||||
s->avr = avresample_alloc_context();
|
||||
if (!s->avr)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_opt_set_int(s->avr, "in_channel_layout", link->channel_layout, 0);
|
||||
av_opt_set_int(s->avr, "out_channel_layout", link->channel_layout, 0);
|
||||
av_opt_set_int(s->avr, "in_sample_fmt", link->format, 0);
|
||||
av_opt_set_int(s->avr, "out_sample_fmt", link->format, 0);
|
||||
av_opt_set_int(s->avr, "in_sample_rate", link->sample_rate, 0);
|
||||
av_opt_set_int(s->avr, "out_sample_rate", link->sample_rate, 0);
|
||||
|
||||
if (s->resample)
|
||||
av_opt_set_int(s->avr, "force_resampling", 1, 0);
|
||||
|
||||
if ((ret = avresample_open(s->avr)) < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get amount of data currently buffered, in samples */
|
||||
static int64_t get_delay(ASyncContext *s)
|
||||
{
|
||||
return avresample_available(s->avr) + avresample_get_delay(s->avr);
|
||||
}
|
||||
|
||||
static void handle_trimming(AVFilterContext *ctx)
|
||||
{
|
||||
ASyncContext *s = ctx->priv;
|
||||
|
||||
if (s->pts < s->first_pts) {
|
||||
int delta = FFMIN(s->first_pts - s->pts, avresample_available(s->avr));
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Trimming %d samples from start\n",
|
||||
delta);
|
||||
avresample_read(s->avr, NULL, delta);
|
||||
s->pts += delta;
|
||||
} else if (s->first_frame)
|
||||
s->pts = s->first_pts;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *link)
|
||||
{
|
||||
AVFilterContext *ctx = link->src;
|
||||
ASyncContext *s = ctx->priv;
|
||||
int ret = 0;
|
||||
int nb_samples;
|
||||
|
||||
s->got_output = 0;
|
||||
while (ret >= 0 && !s->got_output)
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
/* flush the fifo */
|
||||
if (ret == AVERROR_EOF) {
|
||||
if (s->first_pts != AV_NOPTS_VALUE)
|
||||
handle_trimming(ctx);
|
||||
|
||||
if (nb_samples = get_delay(s)) {
|
||||
AVFrame *buf = ff_get_audio_buffer(link, nb_samples);
|
||||
if (!buf)
|
||||
return AVERROR(ENOMEM);
|
||||
ret = avresample_convert(s->avr, buf->extended_data,
|
||||
buf->linesize[0], nb_samples, NULL, 0, 0);
|
||||
if (ret <= 0) {
|
||||
av_frame_free(&buf);
|
||||
return (ret < 0) ? ret : AVERROR_EOF;
|
||||
}
|
||||
|
||||
buf->pts = s->pts;
|
||||
return ff_filter_frame(link, buf);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int write_to_fifo(ASyncContext *s, AVFrame *buf)
|
||||
{
|
||||
int ret = avresample_convert(s->avr, NULL, 0, 0, buf->extended_data,
|
||||
buf->linesize[0], buf->nb_samples);
|
||||
av_frame_free(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ASyncContext *s = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
int nb_channels = av_get_channel_layout_nb_channels(buf->channel_layout);
|
||||
int64_t pts = (buf->pts == AV_NOPTS_VALUE) ? buf->pts :
|
||||
av_rescale_q(buf->pts, inlink->time_base, outlink->time_base);
|
||||
int out_size, ret;
|
||||
int64_t delta;
|
||||
int64_t new_pts;
|
||||
|
||||
/* buffer data until we get the next timestamp */
|
||||
if (s->pts == AV_NOPTS_VALUE || pts == AV_NOPTS_VALUE) {
|
||||
if (pts != AV_NOPTS_VALUE) {
|
||||
s->pts = pts - get_delay(s);
|
||||
}
|
||||
return write_to_fifo(s, buf);
|
||||
}
|
||||
|
||||
if (s->first_pts != AV_NOPTS_VALUE) {
|
||||
handle_trimming(ctx);
|
||||
if (!avresample_available(s->avr))
|
||||
return write_to_fifo(s, buf);
|
||||
}
|
||||
|
||||
/* when we have two timestamps, compute how many samples would we have
|
||||
* to add/remove to get proper sync between data and timestamps */
|
||||
delta = pts - s->pts - get_delay(s);
|
||||
out_size = avresample_available(s->avr);
|
||||
|
||||
if (labs(delta) > s->min_delta ||
|
||||
(s->first_frame && delta && s->first_pts != AV_NOPTS_VALUE)) {
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Discontinuity - %"PRId64" samples.\n", delta);
|
||||
out_size = av_clipl_int32((int64_t)out_size + delta);
|
||||
} else {
|
||||
if (s->resample) {
|
||||
// adjust the compensation if delta is non-zero
|
||||
int delay = get_delay(s);
|
||||
int comp = s->comp + av_clip(delta * inlink->sample_rate / delay,
|
||||
-s->max_comp, s->max_comp);
|
||||
if (comp != s->comp) {
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Compensating %d samples per second.\n", comp);
|
||||
if (avresample_set_compensation(s->avr, comp, inlink->sample_rate) == 0) {
|
||||
s->comp = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
// adjust PTS to avoid monotonicity errors with input PTS jitter
|
||||
pts -= delta;
|
||||
delta = 0;
|
||||
}
|
||||
|
||||
if (out_size > 0) {
|
||||
AVFrame *buf_out = ff_get_audio_buffer(outlink, out_size);
|
||||
if (!buf_out) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (s->first_frame && delta > 0) {
|
||||
int planar = av_sample_fmt_is_planar(buf_out->format);
|
||||
int planes = planar ? nb_channels : 1;
|
||||
int block_size = av_get_bytes_per_sample(buf_out->format) *
|
||||
(planar ? 1 : nb_channels);
|
||||
|
||||
int ch;
|
||||
|
||||
av_samples_set_silence(buf_out->extended_data, 0, delta,
|
||||
nb_channels, buf->format);
|
||||
|
||||
for (ch = 0; ch < planes; ch++)
|
||||
buf_out->extended_data[ch] += delta * block_size;
|
||||
|
||||
avresample_read(s->avr, buf_out->extended_data, out_size);
|
||||
|
||||
for (ch = 0; ch < planes; ch++)
|
||||
buf_out->extended_data[ch] -= delta * block_size;
|
||||
} else {
|
||||
avresample_read(s->avr, buf_out->extended_data, out_size);
|
||||
|
||||
if (delta > 0) {
|
||||
av_samples_set_silence(buf_out->extended_data, out_size - delta,
|
||||
delta, nb_channels, buf->format);
|
||||
}
|
||||
}
|
||||
buf_out->pts = s->pts;
|
||||
ret = ff_filter_frame(outlink, buf_out);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
s->got_output = 1;
|
||||
} else if (avresample_available(s->avr)) {
|
||||
av_log(ctx, AV_LOG_WARNING, "Non-monotonous timestamps, dropping "
|
||||
"whole buffer.\n");
|
||||
}
|
||||
|
||||
/* drain any remaining buffered data */
|
||||
avresample_read(s->avr, NULL, avresample_available(s->avr));
|
||||
|
||||
new_pts = pts - avresample_get_delay(s->avr);
|
||||
/* check for s->pts monotonicity */
|
||||
if (new_pts > s->pts) {
|
||||
s->pts = new_pts;
|
||||
ret = avresample_convert(s->avr, NULL, 0, 0, buf->extended_data,
|
||||
buf->linesize[0], buf->nb_samples);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_WARNING, "Non-monotonous timestamps, dropping "
|
||||
"whole buffer.\n");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
s->first_frame = 0;
|
||||
fail:
|
||||
av_frame_free(&buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_asyncts_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_asyncts_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
.request_frame = request_frame
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_asyncts = {
|
||||
.name = "asyncts",
|
||||
.description = NULL_IF_CONFIG_SMALL("Sync audio data to timestamps"),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(ASyncContext),
|
||||
.priv_class = &asyncts_class,
|
||||
.inputs = avfilter_af_asyncts_inputs,
|
||||
.outputs = avfilter_af_asyncts_outputs,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
* Copyright (c) 2006-2008 Rob Sykes <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/*
|
||||
* 2-pole filters designed by Robert Bristow-Johnson <[email protected]>
|
||||
* see http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
||||
*
|
||||
* 1-pole filters based on code (c) 2000 Chris Bagwell <[email protected]>
|
||||
* Algorithms: Recursive single pole low/high pass filter
|
||||
* Reference: The Scientist and Engineer's Guide to Digital Signal Processing
|
||||
*
|
||||
* low-pass: output[N] = input[N] * A + output[N-1] * B
|
||||
* X = exp(-2.0 * pi * Fc)
|
||||
* A = 1 - X
|
||||
* B = X
|
||||
* Fc = cutoff freq / sample rate
|
||||
*
|
||||
* Mimics an RC low-pass filter:
|
||||
*
|
||||
* ---/\/\/\/\----------->
|
||||
* |
|
||||
* --- C
|
||||
* ---
|
||||
* |
|
||||
* |
|
||||
* V
|
||||
*
|
||||
* high-pass: output[N] = A0 * input[N] + A1 * input[N-1] + B1 * output[N-1]
|
||||
* X = exp(-2.0 * pi * Fc)
|
||||
* A0 = (1 + X) / 2
|
||||
* A1 = -(1 + X) / 2
|
||||
* B1 = X
|
||||
* Fc = cutoff freq / sample rate
|
||||
*
|
||||
* Mimics an RC high-pass filter:
|
||||
*
|
||||
* || C
|
||||
* ----||--------->
|
||||
* || |
|
||||
* <
|
||||
* > R
|
||||
* <
|
||||
* |
|
||||
* V
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
enum FilterType {
|
||||
biquad,
|
||||
equalizer,
|
||||
bass,
|
||||
treble,
|
||||
band,
|
||||
bandpass,
|
||||
bandreject,
|
||||
allpass,
|
||||
highpass,
|
||||
lowpass,
|
||||
};
|
||||
|
||||
enum WidthType {
|
||||
NONE,
|
||||
HERTZ,
|
||||
OCTAVE,
|
||||
QFACTOR,
|
||||
SLOPE,
|
||||
};
|
||||
|
||||
typedef struct ChanCache {
|
||||
double i1, i2;
|
||||
double o1, o2;
|
||||
} ChanCache;
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
|
||||
enum FilterType filter_type;
|
||||
enum WidthType width_type;
|
||||
int poles;
|
||||
int csg;
|
||||
|
||||
double gain;
|
||||
double frequency;
|
||||
double width;
|
||||
|
||||
double a0, a1, a2;
|
||||
double b0, b1, b2;
|
||||
|
||||
ChanCache *cache;
|
||||
|
||||
void (*filter)(const void *ibuf, void *obuf, int len,
|
||||
double *i1, double *i2, double *o1, double *o2,
|
||||
double b0, double b1, double b2, double a1, double a2);
|
||||
} BiquadsContext;
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
BiquadsContext *p = ctx->priv;
|
||||
|
||||
if (p->filter_type != biquad) {
|
||||
if (p->frequency <= 0 || p->width <= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid frequency %f and/or width %f <= 0\n",
|
||||
p->frequency, p->width);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLTP,
|
||||
AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BIQUAD_FILTER(name, type, min, max) \
|
||||
static void biquad_## name (const void *input, void *output, int len, \
|
||||
double *in1, double *in2, \
|
||||
double *out1, double *out2, \
|
||||
double b0, double b1, double b2, \
|
||||
double a1, double a2) \
|
||||
{ \
|
||||
const type *ibuf = input; \
|
||||
type *obuf = output; \
|
||||
double i1 = *in1; \
|
||||
double i2 = *in2; \
|
||||
double o1 = *out1; \
|
||||
double o2 = *out2; \
|
||||
int i; \
|
||||
a1 = -a1; \
|
||||
a2 = -a2; \
|
||||
\
|
||||
for (i = 0; i+1 < len; i++) { \
|
||||
o2 = i2 * b2 + i1 * b1 + ibuf[i] * b0 + o2 * a2 + o1 * a1; \
|
||||
i2 = ibuf[i]; \
|
||||
if (o2 < min) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = min; \
|
||||
} else if (o2 > max) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = max; \
|
||||
} else { \
|
||||
obuf[i] = o2; \
|
||||
} \
|
||||
i++; \
|
||||
o1 = i1 * b2 + i2 * b1 + ibuf[i] * b0 + o1 * a2 + o2 * a1; \
|
||||
i1 = ibuf[i]; \
|
||||
if (o1 < min) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = min; \
|
||||
} else if (o1 > max) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = max; \
|
||||
} else { \
|
||||
obuf[i] = o1; \
|
||||
} \
|
||||
} \
|
||||
if (i < len) { \
|
||||
double o0 = ibuf[i] * b0 + i1 * b1 + i2 * b2 + o1 * a1 + o2 * a2; \
|
||||
i2 = i1; \
|
||||
i1 = ibuf[i]; \
|
||||
o2 = o1; \
|
||||
o1 = o0; \
|
||||
if (o0 < min) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = min; \
|
||||
} else if (o0 > max) { \
|
||||
av_log(NULL, AV_LOG_WARNING, "clipping\n"); \
|
||||
obuf[i] = max; \
|
||||
} else { \
|
||||
obuf[i] = o0; \
|
||||
} \
|
||||
} \
|
||||
*in1 = i1; \
|
||||
*in2 = i2; \
|
||||
*out1 = o1; \
|
||||
*out2 = o2; \
|
||||
}
|
||||
|
||||
BIQUAD_FILTER(s16, int16_t, INT16_MIN, INT16_MAX)
|
||||
BIQUAD_FILTER(s32, int32_t, INT32_MIN, INT32_MAX)
|
||||
BIQUAD_FILTER(flt, float, -1., 1.)
|
||||
BIQUAD_FILTER(dbl, double, -1., 1.)
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
BiquadsContext *p = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
double A = exp(p->gain / 40 * log(10.));
|
||||
double w0 = 2 * M_PI * p->frequency / inlink->sample_rate;
|
||||
double alpha;
|
||||
|
||||
if (w0 > M_PI) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Invalid frequency %f. Frequency must be less than half the sample-rate %d.\n",
|
||||
p->frequency, inlink->sample_rate);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
switch (p->width_type) {
|
||||
case NONE:
|
||||
alpha = 0.0;
|
||||
break;
|
||||
case HERTZ:
|
||||
alpha = sin(w0) / (2 * p->frequency / p->width);
|
||||
break;
|
||||
case OCTAVE:
|
||||
alpha = sin(w0) * sinh(log(2.) / 2 * p->width * w0 / sin(w0));
|
||||
break;
|
||||
case QFACTOR:
|
||||
alpha = sin(w0) / (2 * p->width);
|
||||
break;
|
||||
case SLOPE:
|
||||
alpha = sin(w0) / 2 * sqrt((A + 1 / A) * (1 / p->width - 1) + 2);
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
|
||||
switch (p->filter_type) {
|
||||
case biquad:
|
||||
break;
|
||||
case equalizer:
|
||||
p->a0 = 1 + alpha / A;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha / A;
|
||||
p->b0 = 1 + alpha * A;
|
||||
p->b1 = -2 * cos(w0);
|
||||
p->b2 = 1 - alpha * A;
|
||||
break;
|
||||
case bass:
|
||||
p->a0 = (A + 1) + (A - 1) * cos(w0) + 2 * sqrt(A) * alpha;
|
||||
p->a1 = -2 * ((A - 1) + (A + 1) * cos(w0));
|
||||
p->a2 = (A + 1) + (A - 1) * cos(w0) - 2 * sqrt(A) * alpha;
|
||||
p->b0 = A * ((A + 1) - (A - 1) * cos(w0) + 2 * sqrt(A) * alpha);
|
||||
p->b1 = 2 * A * ((A - 1) - (A + 1) * cos(w0));
|
||||
p->b2 = A * ((A + 1) - (A - 1) * cos(w0) - 2 * sqrt(A) * alpha);
|
||||
break;
|
||||
case treble:
|
||||
p->a0 = (A + 1) - (A - 1) * cos(w0) + 2 * sqrt(A) * alpha;
|
||||
p->a1 = 2 * ((A - 1) - (A + 1) * cos(w0));
|
||||
p->a2 = (A + 1) - (A - 1) * cos(w0) - 2 * sqrt(A) * alpha;
|
||||
p->b0 = A * ((A + 1) + (A - 1) * cos(w0) + 2 * sqrt(A) * alpha);
|
||||
p->b1 =-2 * A * ((A - 1) + (A + 1) * cos(w0));
|
||||
p->b2 = A * ((A + 1) + (A - 1) * cos(w0) - 2 * sqrt(A) * alpha);
|
||||
break;
|
||||
case bandpass:
|
||||
if (p->csg) {
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = sin(w0) / 2;
|
||||
p->b1 = 0;
|
||||
p->b2 = -sin(w0) / 2;
|
||||
} else {
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = alpha;
|
||||
p->b1 = 0;
|
||||
p->b2 = -alpha;
|
||||
}
|
||||
break;
|
||||
case bandreject:
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = 1;
|
||||
p->b1 = -2 * cos(w0);
|
||||
p->b2 = 1;
|
||||
break;
|
||||
case lowpass:
|
||||
if (p->poles == 1) {
|
||||
p->a0 = 1;
|
||||
p->a1 = -exp(-w0);
|
||||
p->a2 = 0;
|
||||
p->b0 = 1 + p->a1;
|
||||
p->b1 = 0;
|
||||
p->b2 = 0;
|
||||
} else {
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = (1 - cos(w0)) / 2;
|
||||
p->b1 = 1 - cos(w0);
|
||||
p->b2 = (1 - cos(w0)) / 2;
|
||||
}
|
||||
break;
|
||||
case highpass:
|
||||
if (p->poles == 1) {
|
||||
p->a0 = 1;
|
||||
p->a1 = -exp(-w0);
|
||||
p->a2 = 0;
|
||||
p->b0 = (1 - p->a1) / 2;
|
||||
p->b1 = -p->b0;
|
||||
p->b2 = 0;
|
||||
} else {
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = (1 + cos(w0)) / 2;
|
||||
p->b1 = -(1 + cos(w0));
|
||||
p->b2 = (1 + cos(w0)) / 2;
|
||||
}
|
||||
break;
|
||||
case allpass:
|
||||
p->a0 = 1 + alpha;
|
||||
p->a1 = -2 * cos(w0);
|
||||
p->a2 = 1 - alpha;
|
||||
p->b0 = 1 - alpha;
|
||||
p->b1 = -2 * cos(w0);
|
||||
p->b2 = 1 + alpha;
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
|
||||
p->a1 /= p->a0;
|
||||
p->a2 /= p->a0;
|
||||
p->b0 /= p->a0;
|
||||
p->b1 /= p->a0;
|
||||
p->b2 /= p->a0;
|
||||
|
||||
p->cache = av_realloc_f(p->cache, sizeof(ChanCache), inlink->channels);
|
||||
if (!p->cache)
|
||||
return AVERROR(ENOMEM);
|
||||
memset(p->cache, 0, sizeof(ChanCache) * inlink->channels);
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_S16P: p->filter = biquad_s16; break;
|
||||
case AV_SAMPLE_FMT_S32P: p->filter = biquad_s32; break;
|
||||
case AV_SAMPLE_FMT_FLTP: p->filter = biquad_flt; break;
|
||||
case AV_SAMPLE_FMT_DBLP: p->filter = biquad_dbl; break;
|
||||
default: av_assert0(0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
BiquadsContext *p = inlink->dst->priv;
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
AVFrame *out_buf;
|
||||
int nb_samples = buf->nb_samples;
|
||||
int ch;
|
||||
|
||||
if (av_frame_is_writable(buf)) {
|
||||
out_buf = buf;
|
||||
} else {
|
||||
out_buf = ff_get_audio_buffer(inlink, nb_samples);
|
||||
if (!out_buf)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_buf, buf);
|
||||
}
|
||||
|
||||
for (ch = 0; ch < av_frame_get_channels(buf); ch++)
|
||||
p->filter(buf->extended_data[ch],
|
||||
out_buf->extended_data[ch], nb_samples,
|
||||
&p->cache[ch].i1, &p->cache[ch].i2,
|
||||
&p->cache[ch].o1, &p->cache[ch].o2,
|
||||
p->b0, p->b1, p->b2, p->a1, p->a2);
|
||||
|
||||
if (buf != out_buf)
|
||||
av_frame_free(&buf);
|
||||
|
||||
return ff_filter_frame(outlink, out_buf);
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
BiquadsContext *p = ctx->priv;
|
||||
|
||||
av_freep(&p->cache);
|
||||
}
|
||||
|
||||
static const AVFilterPad inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define OFFSET(x) offsetof(BiquadsContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
#define DEFINE_BIQUAD_FILTER(name_, description_) \
|
||||
AVFILTER_DEFINE_CLASS(name_); \
|
||||
static av_cold int name_##_init(AVFilterContext *ctx) \
|
||||
{ \
|
||||
BiquadsContext *p = ctx->priv; \
|
||||
p->class = &name_##_class; \
|
||||
p->filter_type = name_; \
|
||||
return init(ctx); \
|
||||
} \
|
||||
\
|
||||
AVFilter avfilter_af_##name_ = { \
|
||||
.name = #name_, \
|
||||
.description = NULL_IF_CONFIG_SMALL(description_), \
|
||||
.priv_size = sizeof(BiquadsContext), \
|
||||
.init = name_##_init, \
|
||||
.uninit = uninit, \
|
||||
.query_formats = query_formats, \
|
||||
.inputs = inputs, \
|
||||
.outputs = outputs, \
|
||||
.priv_class = &name_##_class, \
|
||||
}
|
||||
|
||||
#if CONFIG_EQUALIZER_FILTER
|
||||
static const AVOption equalizer_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 999, FLAGS},
|
||||
{"w", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0, 999, FLAGS},
|
||||
{"gain", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{"g", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(equalizer, "Apply two-pole peaking equalization (EQ) filter.");
|
||||
#endif /* CONFIG_EQUALIZER_FILTER */
|
||||
#if CONFIG_BASS_FILTER
|
||||
static const AVOption bass_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=100}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=100}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set shelf transition steep", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 99999, FLAGS},
|
||||
{"w", "set shelf transition steep", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 99999, FLAGS},
|
||||
{"gain", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{"g", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(bass, "Boost or cut lower frequencies.");
|
||||
#endif /* CONFIG_BASS_FILTER */
|
||||
#if CONFIG_TREBLE_FILTER
|
||||
static const AVOption treble_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set shelf transition steep", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 99999, FLAGS},
|
||||
{"w", "set shelf transition steep", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 99999, FLAGS},
|
||||
{"gain", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{"g", "set gain", OFFSET(gain), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(treble, "Boost or cut upper frequencies.");
|
||||
#endif /* CONFIG_TREBLE_FILTER */
|
||||
#if CONFIG_BANDPASS_FILTER
|
||||
static const AVOption bandpass_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 999, FLAGS},
|
||||
{"w", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 999, FLAGS},
|
||||
{"csg", "use constant skirt gain", OFFSET(csg), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(bandpass, "Apply a two-pole Butterworth band-pass filter.");
|
||||
#endif /* CONFIG_BANDPASS_FILTER */
|
||||
#if CONFIG_BANDREJECT_FILTER
|
||||
static const AVOption bandreject_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 999, FLAGS},
|
||||
{"w", "set band-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.5}, 0, 999, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(bandreject, "Apply a two-pole Butterworth band-reject filter.");
|
||||
#endif /* CONFIG_BANDREJECT_FILTER */
|
||||
#if CONFIG_LOWPASS_FILTER
|
||||
static const AVOption lowpass_options[] = {
|
||||
{"frequency", "set frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=500}, 0, 999999, FLAGS},
|
||||
{"f", "set frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=500}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.707}, 0, 99999, FLAGS},
|
||||
{"w", "set width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.707}, 0, 99999, FLAGS},
|
||||
{"poles", "set number of poles", OFFSET(poles), AV_OPT_TYPE_INT, {.i64=2}, 1, 2, FLAGS},
|
||||
{"p", "set number of poles", OFFSET(poles), AV_OPT_TYPE_INT, {.i64=2}, 1, 2, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(lowpass, "Apply a low-pass filter with 3dB point frequency.");
|
||||
#endif /* CONFIG_LOWPASS_FILTER */
|
||||
#if CONFIG_HIGHPASS_FILTER
|
||||
static const AVOption highpass_options[] = {
|
||||
{"frequency", "set frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"f", "set frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=QFACTOR}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.707}, 0, 99999, FLAGS},
|
||||
{"w", "set width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=0.707}, 0, 99999, FLAGS},
|
||||
{"poles", "set number of poles", OFFSET(poles), AV_OPT_TYPE_INT, {.i64=2}, 1, 2, FLAGS},
|
||||
{"p", "set number of poles", OFFSET(poles), AV_OPT_TYPE_INT, {.i64=2}, 1, 2, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(highpass, "Apply a high-pass filter with 3dB point frequency.");
|
||||
#endif /* CONFIG_HIGHPASS_FILTER */
|
||||
#if CONFIG_ALLPASS_FILTER
|
||||
static const AVOption allpass_options[] = {
|
||||
{"frequency", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"f", "set central frequency", OFFSET(frequency), AV_OPT_TYPE_DOUBLE, {.dbl=3000}, 0, 999999, FLAGS},
|
||||
{"width_type", "set filter-width type", OFFSET(width_type), AV_OPT_TYPE_INT, {.i64=HERTZ}, HERTZ, SLOPE, FLAGS, "width_type"},
|
||||
{"h", "Hz", 0, AV_OPT_TYPE_CONST, {.i64=HERTZ}, 0, 0, FLAGS, "width_type"},
|
||||
{"q", "Q-Factor", 0, AV_OPT_TYPE_CONST, {.i64=QFACTOR}, 0, 0, FLAGS, "width_type"},
|
||||
{"o", "octave", 0, AV_OPT_TYPE_CONST, {.i64=OCTAVE}, 0, 0, FLAGS, "width_type"},
|
||||
{"s", "slope", 0, AV_OPT_TYPE_CONST, {.i64=SLOPE}, 0, 0, FLAGS, "width_type"},
|
||||
{"width", "set filter-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=707.1}, 0, 99999, FLAGS},
|
||||
{"w", "set filter-width", OFFSET(width), AV_OPT_TYPE_DOUBLE, {.dbl=707.1}, 0, 99999, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(allpass, "Apply a two-pole all-pass filter.");
|
||||
#endif /* CONFIG_ALLPASS_FILTER */
|
||||
#if CONFIG_BIQUAD_FILTER
|
||||
static const AVOption biquad_options[] = {
|
||||
{"a0", NULL, OFFSET(a0), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{"a1", NULL, OFFSET(a1), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{"a2", NULL, OFFSET(a2), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{"b0", NULL, OFFSET(b0), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{"b1", NULL, OFFSET(b1), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{"b2", NULL, OFFSET(b2), AV_OPT_TYPE_DOUBLE, {.dbl=1}, INT16_MIN, INT16_MAX, FLAGS},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
DEFINE_BIQUAD_FILTER(biquad, "Apply a biquad IIR filter with the given coefficients.");
|
||||
#endif /* CONFIG_BIQUAD_FILTER */
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Google, Inc.
|
||||
*
|
||||
* 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
|
||||
* audio channel mapping filter
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
struct ChannelMap {
|
||||
uint64_t in_channel;
|
||||
uint64_t out_channel;
|
||||
int in_channel_idx;
|
||||
int out_channel_idx;
|
||||
};
|
||||
|
||||
enum MappingMode {
|
||||
MAP_NONE,
|
||||
MAP_ONE_INT,
|
||||
MAP_ONE_STR,
|
||||
MAP_PAIR_INT_INT,
|
||||
MAP_PAIR_INT_STR,
|
||||
MAP_PAIR_STR_INT,
|
||||
MAP_PAIR_STR_STR
|
||||
};
|
||||
|
||||
#define MAX_CH 64
|
||||
typedef struct ChannelMapContext {
|
||||
const AVClass *class;
|
||||
AVFilterChannelLayouts *channel_layouts;
|
||||
char *mapping_str;
|
||||
char *channel_layout_str;
|
||||
uint64_t output_layout;
|
||||
struct ChannelMap map[MAX_CH];
|
||||
int nch;
|
||||
enum MappingMode mode;
|
||||
} ChannelMapContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ChannelMapContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption channelmap_options[] = {
|
||||
{ "map", "A comma-separated list of input channel numbers in output order.",
|
||||
OFFSET(mapping_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ "channel_layout", "Output channel layout.",
|
||||
OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(channelmap);
|
||||
|
||||
static char* split(char *message, char delim) {
|
||||
char *next = strchr(message, delim);
|
||||
if (next)
|
||||
*next++ = '\0';
|
||||
return next;
|
||||
}
|
||||
|
||||
static int get_channel_idx(char **map, int *ch, char delim, int max_ch)
|
||||
{
|
||||
char *next = split(*map, delim);
|
||||
int len;
|
||||
int n = 0;
|
||||
if (!next && delim == '-')
|
||||
return AVERROR(EINVAL);
|
||||
len = strlen(*map);
|
||||
sscanf(*map, "%d%n", ch, &n);
|
||||
if (n != len)
|
||||
return AVERROR(EINVAL);
|
||||
if (*ch < 0 || *ch > max_ch)
|
||||
return AVERROR(EINVAL);
|
||||
*map = next;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_channel(char **map, uint64_t *ch, char delim)
|
||||
{
|
||||
char *next = split(*map, delim);
|
||||
if (!next && delim == '-')
|
||||
return AVERROR(EINVAL);
|
||||
*ch = av_get_channel_layout(*map);
|
||||
if (av_get_channel_layout_nb_channels(*ch) != 1)
|
||||
return AVERROR(EINVAL);
|
||||
*map = next;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int channelmap_init(AVFilterContext *ctx)
|
||||
{
|
||||
ChannelMapContext *s = ctx->priv;
|
||||
char *mapping, separator = '|';
|
||||
int map_entries = 0;
|
||||
char buf[256];
|
||||
enum MappingMode mode;
|
||||
uint64_t out_ch_mask = 0;
|
||||
int i;
|
||||
|
||||
mapping = s->mapping_str;
|
||||
|
||||
if (!mapping) {
|
||||
mode = MAP_NONE;
|
||||
} else {
|
||||
char *dash = strchr(mapping, '-');
|
||||
if (!dash) { // short mapping
|
||||
if (av_isdigit(*mapping))
|
||||
mode = MAP_ONE_INT;
|
||||
else
|
||||
mode = MAP_ONE_STR;
|
||||
} else if (av_isdigit(*mapping)) {
|
||||
if (av_isdigit(*(dash+1)))
|
||||
mode = MAP_PAIR_INT_INT;
|
||||
else
|
||||
mode = MAP_PAIR_INT_STR;
|
||||
} else {
|
||||
if (av_isdigit(*(dash+1)))
|
||||
mode = MAP_PAIR_STR_INT;
|
||||
else
|
||||
mode = MAP_PAIR_STR_STR;
|
||||
}
|
||||
#if FF_API_OLD_FILTER_OPTS
|
||||
if (strchr(mapping, ',')) {
|
||||
av_log(ctx, AV_LOG_WARNING, "This syntax is deprecated, use "
|
||||
"'|' to separate the mappings.\n");
|
||||
separator = ',';
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mode != MAP_NONE) {
|
||||
char *sep = mapping;
|
||||
map_entries = 1;
|
||||
while ((sep = strchr(sep, separator))) {
|
||||
if (*++sep) // Allow trailing comma
|
||||
map_entries++;
|
||||
}
|
||||
}
|
||||
|
||||
if (map_entries > MAX_CH) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Too many channels mapped: '%d'.\n", map_entries);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
for (i = 0; i < map_entries; i++) {
|
||||
int in_ch_idx = -1, out_ch_idx = -1;
|
||||
uint64_t in_ch = 0, out_ch = 0;
|
||||
static const char err[] = "Failed to parse channel map\n";
|
||||
switch (mode) {
|
||||
case MAP_ONE_INT:
|
||||
if (get_channel_idx(&mapping, &in_ch_idx, separator, MAX_CH) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel_idx = in_ch_idx;
|
||||
s->map[i].out_channel_idx = i;
|
||||
break;
|
||||
case MAP_ONE_STR:
|
||||
if (!get_channel(&mapping, &in_ch, separator)) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel = in_ch;
|
||||
s->map[i].out_channel_idx = i;
|
||||
break;
|
||||
case MAP_PAIR_INT_INT:
|
||||
if (get_channel_idx(&mapping, &in_ch_idx, '-', MAX_CH) < 0 ||
|
||||
get_channel_idx(&mapping, &out_ch_idx, separator, MAX_CH) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel_idx = in_ch_idx;
|
||||
s->map[i].out_channel_idx = out_ch_idx;
|
||||
break;
|
||||
case MAP_PAIR_INT_STR:
|
||||
if (get_channel_idx(&mapping, &in_ch_idx, '-', MAX_CH) < 0 ||
|
||||
get_channel(&mapping, &out_ch, separator) < 0 ||
|
||||
out_ch & out_ch_mask) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel_idx = in_ch_idx;
|
||||
s->map[i].out_channel = out_ch;
|
||||
out_ch_mask |= out_ch;
|
||||
break;
|
||||
case MAP_PAIR_STR_INT:
|
||||
if (get_channel(&mapping, &in_ch, '-') < 0 ||
|
||||
get_channel_idx(&mapping, &out_ch_idx, separator, MAX_CH) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel = in_ch;
|
||||
s->map[i].out_channel_idx = out_ch_idx;
|
||||
break;
|
||||
case MAP_PAIR_STR_STR:
|
||||
if (get_channel(&mapping, &in_ch, '-') < 0 ||
|
||||
get_channel(&mapping, &out_ch, separator) < 0 ||
|
||||
out_ch & out_ch_mask) {
|
||||
av_log(ctx, AV_LOG_ERROR, err);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->map[i].in_channel = in_ch;
|
||||
s->map[i].out_channel = out_ch;
|
||||
out_ch_mask |= out_ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->mode = mode;
|
||||
s->nch = map_entries;
|
||||
s->output_layout = out_ch_mask ? out_ch_mask :
|
||||
av_get_default_channel_layout(map_entries);
|
||||
|
||||
if (s->channel_layout_str) {
|
||||
uint64_t fmt;
|
||||
if ((fmt = av_get_channel_layout(s->channel_layout_str)) == 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout: '%s'.\n",
|
||||
s->channel_layout_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (mode == MAP_NONE) {
|
||||
int i;
|
||||
s->nch = av_get_channel_layout_nb_channels(fmt);
|
||||
for (i = 0; i < s->nch; i++) {
|
||||
s->map[i].in_channel_idx = i;
|
||||
s->map[i].out_channel_idx = i;
|
||||
}
|
||||
} else if (out_ch_mask && out_ch_mask != fmt) {
|
||||
av_get_channel_layout_string(buf, sizeof(buf), 0, out_ch_mask);
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Output channel layout '%s' does not match the list of channel mapped: '%s'.\n",
|
||||
s->channel_layout_str, buf);
|
||||
return AVERROR(EINVAL);
|
||||
} else if (s->nch != av_get_channel_layout_nb_channels(fmt)) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Output channel layout %s does not match the number of channels mapped %d.\n",
|
||||
s->channel_layout_str, s->nch);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
s->output_layout = fmt;
|
||||
}
|
||||
if (!s->output_layout) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Output channel layout is not set and "
|
||||
"cannot be guessed from the maps.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
ff_add_channel_layout(&s->channel_layouts, s->output_layout);
|
||||
|
||||
if (mode == MAP_PAIR_INT_STR || mode == MAP_PAIR_STR_STR) {
|
||||
for (i = 0; i < s->nch; i++) {
|
||||
s->map[i].out_channel_idx = av_get_channel_layout_channel_index(
|
||||
s->output_layout, s->map[i].out_channel);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int channelmap_query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
ChannelMapContext *s = ctx->priv;
|
||||
|
||||
ff_set_common_formats(ctx, ff_planar_sample_fmts());
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
ff_channel_layouts_ref(ff_all_channel_layouts(), &ctx->inputs[0]->out_channel_layouts);
|
||||
ff_channel_layouts_ref(s->channel_layouts, &ctx->outputs[0]->in_channel_layouts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int channelmap_filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
const ChannelMapContext *s = ctx->priv;
|
||||
const int nch_in = av_get_channel_layout_nb_channels(inlink->channel_layout);
|
||||
const int nch_out = s->nch;
|
||||
int ch;
|
||||
uint8_t *source_planes[MAX_CH];
|
||||
|
||||
memcpy(source_planes, buf->extended_data,
|
||||
nch_in * sizeof(source_planes[0]));
|
||||
|
||||
if (nch_out > nch_in) {
|
||||
if (nch_out > FF_ARRAY_ELEMS(buf->data)) {
|
||||
uint8_t **new_extended_data =
|
||||
av_mallocz(nch_out * sizeof(*buf->extended_data));
|
||||
if (!new_extended_data) {
|
||||
av_frame_free(&buf);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
if (buf->extended_data == buf->data) {
|
||||
buf->extended_data = new_extended_data;
|
||||
} else {
|
||||
av_free(buf->extended_data);
|
||||
buf->extended_data = new_extended_data;
|
||||
}
|
||||
} else if (buf->extended_data != buf->data) {
|
||||
av_free(buf->extended_data);
|
||||
buf->extended_data = buf->data;
|
||||
}
|
||||
}
|
||||
|
||||
for (ch = 0; ch < nch_out; ch++) {
|
||||
buf->extended_data[s->map[ch].out_channel_idx] =
|
||||
source_planes[s->map[ch].in_channel_idx];
|
||||
}
|
||||
|
||||
if (buf->data != buf->extended_data)
|
||||
memcpy(buf->data, buf->extended_data,
|
||||
FFMIN(FF_ARRAY_ELEMS(buf->data), nch_out) * sizeof(buf->data[0]));
|
||||
|
||||
return ff_filter_frame(outlink, buf);
|
||||
}
|
||||
|
||||
static int channelmap_config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ChannelMapContext *s = ctx->priv;
|
||||
int nb_channels = av_get_channel_layout_nb_channels(inlink->channel_layout);
|
||||
int i, err = 0;
|
||||
const char *channel_name;
|
||||
char layout_name[256];
|
||||
|
||||
for (i = 0; i < s->nch; i++) {
|
||||
struct ChannelMap *m = &s->map[i];
|
||||
|
||||
if (s->mode == MAP_PAIR_STR_INT || s->mode == MAP_PAIR_STR_STR) {
|
||||
m->in_channel_idx = av_get_channel_layout_channel_index(
|
||||
inlink->channel_layout, m->in_channel);
|
||||
}
|
||||
|
||||
if (m->in_channel_idx < 0 || m->in_channel_idx >= nb_channels) {
|
||||
av_get_channel_layout_string(layout_name, sizeof(layout_name),
|
||||
0, inlink->channel_layout);
|
||||
if (m->in_channel) {
|
||||
channel_name = av_get_channel_name(m->in_channel);
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"input channel '%s' not available from input layout '%s'\n",
|
||||
channel_name, layout_name);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"input channel #%d not available from input layout '%s'\n",
|
||||
m->in_channel_idx, layout_name);
|
||||
}
|
||||
err = AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_channelmap_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = channelmap_filter_frame,
|
||||
.config_props = channelmap_config_input,
|
||||
.needs_writable = 1,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_channelmap_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_channelmap = {
|
||||
.name = "channelmap",
|
||||
.description = NULL_IF_CONFIG_SMALL("Remap audio channels."),
|
||||
.init = channelmap_init,
|
||||
.query_formats = channelmap_query_formats,
|
||||
.priv_size = sizeof(ChannelMapContext),
|
||||
.priv_class = &channelmap_class,
|
||||
.inputs = avfilter_af_channelmap_inputs,
|
||||
.outputs = avfilter_af_channelmap_outputs,
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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
|
||||
* Channel split filter
|
||||
*
|
||||
* Split an audio stream into per-channel streams.
|
||||
*/
|
||||
|
||||
#include "libavutil/attributes.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ChannelSplitContext {
|
||||
const AVClass *class;
|
||||
|
||||
uint64_t channel_layout;
|
||||
char *channel_layout_str;
|
||||
} ChannelSplitContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ChannelSplitContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption channelsplit_options[] = {
|
||||
{ "channel_layout", "Input channel layout.", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, { .str = "stereo" }, .flags = A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(channelsplit);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ChannelSplitContext *s = ctx->priv;
|
||||
int nb_channels;
|
||||
int ret = 0, i;
|
||||
|
||||
if (!(s->channel_layout = av_get_channel_layout(s->channel_layout_str))) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout '%s'.\n",
|
||||
s->channel_layout_str);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nb_channels = av_get_channel_layout_nb_channels(s->channel_layout);
|
||||
for (i = 0; i < nb_channels; i++) {
|
||||
uint64_t channel = av_channel_layout_extract_channel(s->channel_layout, i);
|
||||
AVFilterPad pad = { 0 };
|
||||
|
||||
pad.type = AVMEDIA_TYPE_AUDIO;
|
||||
pad.name = av_get_channel_name(channel);
|
||||
|
||||
ff_insert_outpad(ctx, i, &pad);
|
||||
}
|
||||
|
||||
fail:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
ChannelSplitContext *s = ctx->priv;
|
||||
AVFilterChannelLayouts *in_layouts = NULL;
|
||||
int i;
|
||||
|
||||
ff_set_common_formats (ctx, ff_planar_sample_fmts());
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
|
||||
ff_add_channel_layout(&in_layouts, s->channel_layout);
|
||||
ff_channel_layouts_ref(in_layouts, &ctx->inputs[0]->out_channel_layouts);
|
||||
|
||||
for (i = 0; i < ctx->nb_outputs; i++) {
|
||||
AVFilterChannelLayouts *out_layouts = NULL;
|
||||
uint64_t channel = av_channel_layout_extract_channel(s->channel_layout, i);
|
||||
|
||||
ff_add_channel_layout(&out_layouts, channel);
|
||||
ff_channel_layouts_ref(out_layouts, &ctx->outputs[i]->in_channel_layouts);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < ctx->nb_outputs; i++) {
|
||||
AVFrame *buf_out = av_frame_clone(buf);
|
||||
|
||||
if (!buf_out) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
break;
|
||||
}
|
||||
|
||||
buf_out->data[0] = buf_out->extended_data[0] = buf_out->extended_data[i];
|
||||
buf_out->channel_layout =
|
||||
av_channel_layout_extract_channel(buf->channel_layout, i);
|
||||
av_frame_set_channels(buf_out, 1);
|
||||
|
||||
ret = ff_filter_frame(ctx->outputs[i], buf_out);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
av_frame_free(&buf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_channelsplit_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_channelsplit = {
|
||||
.name = "channelsplit",
|
||||
.description = NULL_IF_CONFIG_SMALL("Split audio into per-channel streams."),
|
||||
.priv_size = sizeof(ChannelSplitContext),
|
||||
.priv_class = &channelsplit_class,
|
||||
.init = init,
|
||||
.query_formats = query_formats,
|
||||
.inputs = avfilter_af_channelsplit_inputs,
|
||||
.outputs = NULL,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
|
||||
};
|
||||
@@ -0,0 +1,518 @@
|
||||
/*
|
||||
* Copyright (c) 1999 Chris Bagwell
|
||||
* Copyright (c) 1999 Nick Bailey
|
||||
* Copyright (c) 2007 Rob Sykes <[email protected]>
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ChanParam {
|
||||
double attack;
|
||||
double decay;
|
||||
double volume;
|
||||
} ChanParam;
|
||||
|
||||
typedef struct CompandSegment {
|
||||
double x, y;
|
||||
double a, b;
|
||||
} CompandSegment;
|
||||
|
||||
typedef struct CompandContext {
|
||||
const AVClass *class;
|
||||
char *attacks, *decays, *points;
|
||||
CompandSegment *segments;
|
||||
ChanParam *channels;
|
||||
double in_min_lin;
|
||||
double out_min_lin;
|
||||
double curve_dB;
|
||||
double gain_dB;
|
||||
double initial_volume;
|
||||
double delay;
|
||||
uint8_t **delayptrs;
|
||||
int delay_samples;
|
||||
int delay_count;
|
||||
int delay_index;
|
||||
int64_t pts;
|
||||
|
||||
int (*compand)(AVFilterContext *ctx, AVFrame *frame);
|
||||
} CompandContext;
|
||||
|
||||
#define OFFSET(x) offsetof(CompandContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption compand_options[] = {
|
||||
{ "attacks", "set time over which increase of volume is determined", OFFSET(attacks), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, A },
|
||||
{ "decays", "set time over which decrease of volume is determined", OFFSET(decays), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, A },
|
||||
{ "points", "set points of transfer function", OFFSET(points), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, A },
|
||||
{ "soft-knee", "set soft-knee", OFFSET(curve_dB), AV_OPT_TYPE_DOUBLE, {.dbl=0.01}, 0.01, 900, A },
|
||||
{ "gain", "set output gain", OFFSET(gain_dB), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 900, A },
|
||||
{ "volume", "set initial volume", OFFSET(initial_volume), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -900, 0, A },
|
||||
{ "delay", "set delay for samples before sending them to volume adjuster", OFFSET(delay), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 20, A },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(compand);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
CompandContext *s = ctx->priv;
|
||||
|
||||
if (!s->attacks || !s->decays || !s->points) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Missing attacks and/or decays and/or points.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
CompandContext *s = ctx->priv;
|
||||
|
||||
av_freep(&s->channels);
|
||||
av_freep(&s->segments);
|
||||
if (s->delayptrs)
|
||||
av_freep(&s->delayptrs[0]);
|
||||
av_freep(&s->delayptrs);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterChannelLayouts *layouts;
|
||||
AVFilterFormats *formats;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void count_items(char *item_str, int *nb_items)
|
||||
{
|
||||
char *p;
|
||||
|
||||
*nb_items = 1;
|
||||
for (p = item_str; *p; p++) {
|
||||
if (*p == ' ')
|
||||
(*nb_items)++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void update_volume(ChanParam *cp, double in)
|
||||
{
|
||||
double delta = in - cp->volume;
|
||||
|
||||
if (delta > 0.0)
|
||||
cp->volume += delta * cp->attack;
|
||||
else
|
||||
cp->volume += delta * cp->decay;
|
||||
}
|
||||
|
||||
static double get_volume(CompandContext *s, double in_lin)
|
||||
{
|
||||
CompandSegment *cs;
|
||||
double in_log, out_log;
|
||||
int i;
|
||||
|
||||
if (in_lin < s->in_min_lin)
|
||||
return s->out_min_lin;
|
||||
|
||||
in_log = log(in_lin);
|
||||
|
||||
for (i = 1;; i++)
|
||||
if (in_log <= s->segments[i + 1].x)
|
||||
break;
|
||||
|
||||
cs = &s->segments[i];
|
||||
in_log -= cs->x;
|
||||
out_log = cs->y + in_log * (cs->a * in_log + cs->b);
|
||||
|
||||
return exp(out_log);
|
||||
}
|
||||
|
||||
static int compand_nodelay(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
CompandContext *s = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
const int channels = inlink->channels;
|
||||
const int nb_samples = frame->nb_samples;
|
||||
AVFrame *out_frame;
|
||||
int chan, i;
|
||||
|
||||
if (av_frame_is_writable(frame)) {
|
||||
out_frame = frame;
|
||||
} else {
|
||||
out_frame = ff_get_audio_buffer(inlink, nb_samples);
|
||||
if (!out_frame)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_frame, frame);
|
||||
}
|
||||
|
||||
for (chan = 0; chan < channels; chan++) {
|
||||
const double *src = (double *)frame->extended_data[chan];
|
||||
double *dst = (double *)out_frame->extended_data[chan];
|
||||
ChanParam *cp = &s->channels[chan];
|
||||
|
||||
for (i = 0; i < nb_samples; i++) {
|
||||
update_volume(cp, fabs(src[i]));
|
||||
|
||||
dst[i] = av_clipd(src[i] * get_volume(s, cp->volume), -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame != out_frame)
|
||||
av_frame_free(&frame);
|
||||
|
||||
return ff_filter_frame(ctx->outputs[0], out_frame);
|
||||
}
|
||||
|
||||
#define MOD(a, b) (((a) >= (b)) ? (a) - (b) : (a))
|
||||
|
||||
static int compand_delay(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
CompandContext *s = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
const int channels = inlink->channels;
|
||||
const int nb_samples = frame->nb_samples;
|
||||
int chan, i, av_uninit(dindex), oindex, av_uninit(count);
|
||||
AVFrame *out_frame = NULL;
|
||||
|
||||
av_assert1(channels > 0); /* would corrupt delay_count and delay_index */
|
||||
|
||||
for (chan = 0; chan < channels; chan++) {
|
||||
const double *src = (double *)frame->extended_data[chan];
|
||||
double *dbuf = (double *)s->delayptrs[chan];
|
||||
ChanParam *cp = &s->channels[chan];
|
||||
double *dst;
|
||||
|
||||
count = s->delay_count;
|
||||
dindex = s->delay_index;
|
||||
for (i = 0, oindex = 0; i < nb_samples; i++) {
|
||||
const double in = src[i];
|
||||
update_volume(cp, fabs(in));
|
||||
|
||||
if (count >= s->delay_samples) {
|
||||
if (!out_frame) {
|
||||
out_frame = ff_get_audio_buffer(inlink, nb_samples - i);
|
||||
if (!out_frame)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_frame, frame);
|
||||
out_frame->pts = s->pts;
|
||||
s->pts += av_rescale_q(nb_samples - i, (AVRational){1, inlink->sample_rate}, inlink->time_base);
|
||||
}
|
||||
|
||||
dst = (double *)out_frame->extended_data[chan];
|
||||
dst[oindex++] = av_clipd(dbuf[dindex] * get_volume(s, cp->volume), -1, 1);
|
||||
} else {
|
||||
count++;
|
||||
}
|
||||
|
||||
dbuf[dindex] = in;
|
||||
dindex = MOD(dindex + 1, s->delay_samples);
|
||||
}
|
||||
}
|
||||
|
||||
s->delay_count = count;
|
||||
s->delay_index = dindex;
|
||||
|
||||
av_frame_free(&frame);
|
||||
return out_frame ? ff_filter_frame(ctx->outputs[0], out_frame) : 0;
|
||||
}
|
||||
|
||||
static int compand_drain(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
CompandContext *s = ctx->priv;
|
||||
const int channels = outlink->channels;
|
||||
int chan, i, dindex;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
frame = ff_get_audio_buffer(outlink, FFMIN(2048, s->delay_count));
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
frame->pts = s->pts;
|
||||
s->pts += av_rescale_q(frame->nb_samples, (AVRational){1, outlink->sample_rate}, outlink->time_base);
|
||||
|
||||
for (chan = 0; chan < channels; chan++) {
|
||||
double *dbuf = (double *)s->delayptrs[chan];
|
||||
double *dst = (double *)frame->extended_data[chan];
|
||||
ChanParam *cp = &s->channels[chan];
|
||||
|
||||
dindex = s->delay_index;
|
||||
for (i = 0; i < frame->nb_samples; i++) {
|
||||
dst[i] = av_clipd(dbuf[dindex] * get_volume(s, cp->volume), -1, 1);
|
||||
dindex = MOD(dindex + 1, s->delay_samples);
|
||||
}
|
||||
}
|
||||
s->delay_count -= frame->nb_samples;
|
||||
s->delay_index = dindex;
|
||||
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
CompandContext *s = ctx->priv;
|
||||
const int sample_rate = outlink->sample_rate;
|
||||
double radius = s->curve_dB * M_LN10 / 20;
|
||||
int nb_attacks, nb_decays, nb_points;
|
||||
char *p, *saveptr = NULL;
|
||||
int new_nb_items, num;
|
||||
int i;
|
||||
|
||||
count_items(s->attacks, &nb_attacks);
|
||||
count_items(s->decays, &nb_decays);
|
||||
count_items(s->points, &nb_points);
|
||||
|
||||
if ((nb_attacks > outlink->channels) || (nb_decays > outlink->channels)) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Number of attacks/decays bigger than number of channels.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
uninit(ctx);
|
||||
|
||||
s->channels = av_mallocz_array(outlink->channels, sizeof(*s->channels));
|
||||
s->segments = av_mallocz_array((nb_points + 4) * 2, sizeof(*s->segments));
|
||||
|
||||
if (!s->channels || !s->segments)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
p = s->attacks;
|
||||
for (i = 0, new_nb_items = 0; i < nb_attacks; i++) {
|
||||
char *tstr = av_strtok(p, " ", &saveptr);
|
||||
p = NULL;
|
||||
new_nb_items += sscanf(tstr, "%lf", &s->channels[i].attack) == 1;
|
||||
if (s->channels[i].attack < 0)
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
nb_attacks = new_nb_items;
|
||||
|
||||
p = s->decays;
|
||||
for (i = 0, new_nb_items = 0; i < nb_decays; i++) {
|
||||
char *tstr = av_strtok(p, " ", &saveptr);
|
||||
p = NULL;
|
||||
new_nb_items += sscanf(tstr, "%lf", &s->channels[i].decay) == 1;
|
||||
if (s->channels[i].decay < 0)
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
nb_decays = new_nb_items;
|
||||
|
||||
if (nb_attacks != nb_decays) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Number of attacks %d differs from number of decays %d.\n", nb_attacks, nb_decays);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
#define S(x) s->segments[2 * ((x) + 1)]
|
||||
p = s->points;
|
||||
for (i = 0, new_nb_items = 0; i < nb_points; i++) {
|
||||
char *tstr = av_strtok(p, " ", &saveptr);
|
||||
p = NULL;
|
||||
if (sscanf(tstr, "%lf/%lf", &S(i).x, &S(i).y) != 2) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid and/or missing input/output value.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (i && S(i - 1).x > S(i).x) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Transfer function input values must be increasing.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
S(i).y -= S(i).x;
|
||||
av_log(ctx, AV_LOG_DEBUG, "%d: x=%f y=%f\n", i, S(i).x, S(i).y);
|
||||
new_nb_items++;
|
||||
}
|
||||
num = new_nb_items;
|
||||
|
||||
/* Add 0,0 if necessary */
|
||||
if (num == 0 || S(num - 1).x)
|
||||
num++;
|
||||
|
||||
#undef S
|
||||
#define S(x) s->segments[2 * (x)]
|
||||
/* Add a tail off segment at the start */
|
||||
S(0).x = S(1).x - 2 * s->curve_dB;
|
||||
S(0).y = S(1).y;
|
||||
num++;
|
||||
|
||||
/* Join adjacent colinear segments */
|
||||
for (i = 2; i < num; i++) {
|
||||
double g1 = (S(i - 1).y - S(i - 2).y) * (S(i - 0).x - S(i - 1).x);
|
||||
double g2 = (S(i - 0).y - S(i - 1).y) * (S(i - 1).x - S(i - 2).x);
|
||||
int j;
|
||||
|
||||
if (fabs(g1 - g2))
|
||||
continue;
|
||||
num--;
|
||||
for (j = --i; j < num; j++)
|
||||
S(j) = S(j + 1);
|
||||
}
|
||||
|
||||
for (i = 0; !i || s->segments[i - 2].x; i += 2) {
|
||||
s->segments[i].y += s->gain_dB;
|
||||
s->segments[i].x *= M_LN10 / 20;
|
||||
s->segments[i].y *= M_LN10 / 20;
|
||||
}
|
||||
|
||||
#define L(x) s->segments[i - (x)]
|
||||
for (i = 4; s->segments[i - 2].x; i += 2) {
|
||||
double x, y, cx, cy, in1, in2, out1, out2, theta, len, r;
|
||||
|
||||
L(4).a = 0;
|
||||
L(4).b = (L(2).y - L(4).y) / (L(2).x - L(4).x);
|
||||
|
||||
L(2).a = 0;
|
||||
L(2).b = (L(0).y - L(2).y) / (L(0).x - L(2).x);
|
||||
|
||||
theta = atan2(L(2).y - L(4).y, L(2).x - L(4).x);
|
||||
len = sqrt(pow(L(2).x - L(4).x, 2.) + pow(L(2).y - L(4).y, 2.));
|
||||
r = FFMIN(radius, len);
|
||||
L(3).x = L(2).x - r * cos(theta);
|
||||
L(3).y = L(2).y - r * sin(theta);
|
||||
|
||||
theta = atan2(L(0).y - L(2).y, L(0).x - L(2).x);
|
||||
len = sqrt(pow(L(0).x - L(2).x, 2.) + pow(L(0).y - L(2).y, 2.));
|
||||
r = FFMIN(radius, len / 2);
|
||||
x = L(2).x + r * cos(theta);
|
||||
y = L(2).y + r * sin(theta);
|
||||
|
||||
cx = (L(3).x + L(2).x + x) / 3;
|
||||
cy = (L(3).y + L(2).y + y) / 3;
|
||||
|
||||
L(2).x = x;
|
||||
L(2).y = y;
|
||||
|
||||
in1 = cx - L(3).x;
|
||||
out1 = cy - L(3).y;
|
||||
in2 = L(2).x - L(3).x;
|
||||
out2 = L(2).y - L(3).y;
|
||||
L(3).a = (out2 / in2 - out1 / in1) / (in2-in1);
|
||||
L(3).b = out1 / in1 - L(3).a * in1;
|
||||
}
|
||||
L(3).x = 0;
|
||||
L(3).y = L(2).y;
|
||||
|
||||
s->in_min_lin = exp(s->segments[1].x);
|
||||
s->out_min_lin = exp(s->segments[1].y);
|
||||
|
||||
for (i = 0; i < outlink->channels; i++) {
|
||||
ChanParam *cp = &s->channels[i];
|
||||
|
||||
if (cp->attack > 1.0 / sample_rate)
|
||||
cp->attack = 1.0 - exp(-1.0 / (sample_rate * cp->attack));
|
||||
else
|
||||
cp->attack = 1.0;
|
||||
if (cp->decay > 1.0 / sample_rate)
|
||||
cp->decay = 1.0 - exp(-1.0 / (sample_rate * cp->decay));
|
||||
else
|
||||
cp->decay = 1.0;
|
||||
cp->volume = pow(10.0, s->initial_volume / 20);
|
||||
}
|
||||
|
||||
s->delay_samples = s->delay * sample_rate;
|
||||
if (s->delay_samples > 0) {
|
||||
int ret;
|
||||
if ((ret = av_samples_alloc_array_and_samples(&s->delayptrs, NULL,
|
||||
outlink->channels,
|
||||
s->delay_samples,
|
||||
outlink->format, 0)) < 0)
|
||||
return ret;
|
||||
s->compand = compand_delay;
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
} else {
|
||||
s->compand = compand_nodelay;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
CompandContext *s = ctx->priv;
|
||||
|
||||
return s->compand(ctx, frame);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
CompandContext *s = ctx->priv;
|
||||
int ret;
|
||||
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
if (ret == AVERROR_EOF && !ctx->is_disabled && s->delay_count)
|
||||
ret = compand_drain(outlink);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad compand_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad compand_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.request_frame = request_frame,
|
||||
.config_props = config_output,
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_compand = {
|
||||
.name = "compand",
|
||||
.description = NULL_IF_CONFIG_SMALL("Compress or expand audio dynamic range."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(CompandContext),
|
||||
.priv_class = &compand_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.inputs = compand_inputs,
|
||||
.outputs = compand_outputs,
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Mina Nagy Zaki
|
||||
* Copyright (c) 2000 Edward Beingessner And Sundry Contributors.
|
||||
* This source code is freely redistributable and may be used for any purpose.
|
||||
* This copyright notice must be maintained. Edward Beingessner And Sundry
|
||||
* Contributors are not responsible for the consequences of using this
|
||||
* software.
|
||||
*
|
||||
* 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
|
||||
* Stereo Widening Effect. Adds audio cues to move stereo image in
|
||||
* front of the listener. Adapted from the libsox earwax effect.
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "formats.h"
|
||||
|
||||
#define NUMTAPS 64
|
||||
|
||||
static const int8_t filt[NUMTAPS] = {
|
||||
/* 30° 330° */
|
||||
4, -6, /* 32 tap stereo FIR filter. */
|
||||
4, -11, /* One side filters as if the */
|
||||
-1, -5, /* signal was from 30 degrees */
|
||||
3, 3, /* from the ear, the other as */
|
||||
-2, 5, /* if 330 degrees. */
|
||||
-5, 0,
|
||||
9, 1,
|
||||
6, 3, /* Input */
|
||||
-4, -1, /* Left Right */
|
||||
-5, -3, /* __________ __________ */
|
||||
-2, -5, /* | | | | */
|
||||
-7, 1, /* .---| Hh,0(f) | | Hh,0(f) |---. */
|
||||
6, -7, /* / |__________| |__________| \ */
|
||||
30, -29, /* / \ / \ */
|
||||
12, -3, /* / X \ */
|
||||
-11, 4, /* / / \ \ */
|
||||
-3, 7, /* ____V_____ __________V V__________ _____V____ */
|
||||
-20, 23, /* | | | | | | | | */
|
||||
2, 0, /* | Hh,30(f) | | Hh,330(f)| | Hh,330(f)| | Hh,30(f) | */
|
||||
1, -6, /* |__________| |__________| |__________| |__________| */
|
||||
-14, -5, /* \ ___ / \ ___ / */
|
||||
15, -18, /* \ / \ / _____ \ / \ / */
|
||||
6, 7, /* `->| + |<--' / \ `-->| + |<-' */
|
||||
15, -10, /* \___/ _/ \_ \___/ */
|
||||
-14, 22, /* \ / \ / \ / */
|
||||
-7, -2, /* `--->| | | |<---' */
|
||||
-4, 9, /* \_/ \_/ */
|
||||
6, -12, /* */
|
||||
6, -6, /* Headphones */
|
||||
0, -11,
|
||||
0, -5,
|
||||
4, 0};
|
||||
|
||||
typedef struct {
|
||||
int16_t taps[NUMTAPS * 2];
|
||||
} EarwaxContext;
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
static const int sample_rates[] = { 44100, -1 };
|
||||
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layout = NULL;
|
||||
|
||||
ff_add_format(&formats, AV_SAMPLE_FMT_S16);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO);
|
||||
ff_set_common_channel_layouts(ctx, layout);
|
||||
ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//FIXME: replace with DSPContext.scalarproduct_int16
|
||||
static inline int16_t *scalarproduct(const int16_t *in, const int16_t *endin, int16_t *out)
|
||||
{
|
||||
int32_t sample;
|
||||
int16_t j;
|
||||
|
||||
while (in < endin) {
|
||||
sample = 0;
|
||||
for (j = 0; j < NUMTAPS; j++)
|
||||
sample += in[j] * filt[j];
|
||||
*out = av_clip_int16(sample >> 6);
|
||||
out++;
|
||||
in++;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
int16_t *taps, *endin, *in, *out;
|
||||
AVFrame *outsamples = ff_get_audio_buffer(inlink, insamples->nb_samples);
|
||||
int len;
|
||||
|
||||
if (!outsamples) {
|
||||
av_frame_free(&insamples);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
av_frame_copy_props(outsamples, insamples);
|
||||
|
||||
taps = ((EarwaxContext *)inlink->dst->priv)->taps;
|
||||
out = (int16_t *)outsamples->data[0];
|
||||
in = (int16_t *)insamples ->data[0];
|
||||
|
||||
len = FFMIN(NUMTAPS, 2*insamples->nb_samples);
|
||||
// copy part of new input and process with saved input
|
||||
memcpy(taps+NUMTAPS, in, len * sizeof(*taps));
|
||||
out = scalarproduct(taps, taps + len, out);
|
||||
|
||||
// process current input
|
||||
if (2*insamples->nb_samples >= NUMTAPS ){
|
||||
endin = in + insamples->nb_samples * 2 - NUMTAPS;
|
||||
scalarproduct(in, endin, out);
|
||||
|
||||
// save part of input for next round
|
||||
memcpy(taps, endin, NUMTAPS * sizeof(*taps));
|
||||
} else
|
||||
memmove(taps, taps + 2*insamples->nb_samples, NUMTAPS * sizeof(*taps));
|
||||
|
||||
av_frame_free(&insamples);
|
||||
return ff_filter_frame(outlink, outsamples);
|
||||
}
|
||||
|
||||
static const AVFilterPad earwax_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad earwax_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_earwax = {
|
||||
.name = "earwax",
|
||||
.description = NULL_IF_CONFIG_SMALL("Widen the stereo image."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(EarwaxContext),
|
||||
.inputs = earwax_inputs,
|
||||
.outputs = earwax_outputs,
|
||||
};
|
||||
@@ -0,0 +1,516 @@
|
||||
/*
|
||||
* 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
|
||||
* Audio join filter
|
||||
*
|
||||
* Join multiple audio inputs as different channels in
|
||||
* a single output
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ChannelMap {
|
||||
int input; ///< input stream index
|
||||
int in_channel_idx; ///< index of in_channel in the input stream data
|
||||
uint64_t in_channel; ///< layout describing the input channel
|
||||
uint64_t out_channel; ///< layout describing the output channel
|
||||
} ChannelMap;
|
||||
|
||||
typedef struct JoinContext {
|
||||
const AVClass *class;
|
||||
|
||||
int inputs;
|
||||
char *map;
|
||||
char *channel_layout_str;
|
||||
uint64_t channel_layout;
|
||||
|
||||
int nb_channels;
|
||||
ChannelMap *channels;
|
||||
|
||||
/**
|
||||
* Temporary storage for input frames, until we get one on each input.
|
||||
*/
|
||||
AVFrame **input_frames;
|
||||
|
||||
/**
|
||||
* Temporary storage for buffer references, for assembling the output frame.
|
||||
*/
|
||||
AVBufferRef **buffers;
|
||||
} JoinContext;
|
||||
|
||||
#define OFFSET(x) offsetof(JoinContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption join_options[] = {
|
||||
{ "inputs", "Number of input streams.", OFFSET(inputs), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, A|F },
|
||||
{ "channel_layout", "Channel layout of the "
|
||||
"output stream.", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, A|F },
|
||||
{ "map", "A comma-separated list of channels maps in the format "
|
||||
"'input_stream.input_channel-output_channel.",
|
||||
OFFSET(map), AV_OPT_TYPE_STRING, .flags = A|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(join);
|
||||
|
||||
static int filter_frame(AVFilterLink *link, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = link->dst;
|
||||
JoinContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++)
|
||||
if (link == ctx->inputs[i])
|
||||
break;
|
||||
av_assert0(i < ctx->nb_inputs);
|
||||
av_assert0(!s->input_frames[i]);
|
||||
s->input_frames[i] = frame;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_maps(AVFilterContext *ctx)
|
||||
{
|
||||
JoinContext *s = ctx->priv;
|
||||
char separator = '|';
|
||||
char *cur = s->map;
|
||||
|
||||
#if FF_API_OLD_FILTER_OPTS
|
||||
if (cur && strchr(cur, ',')) {
|
||||
av_log(ctx, AV_LOG_WARNING, "This syntax is deprecated, use '|' to "
|
||||
"separate the mappings.\n");
|
||||
separator = ',';
|
||||
}
|
||||
#endif
|
||||
|
||||
while (cur && *cur) {
|
||||
char *sep, *next, *p;
|
||||
uint64_t in_channel = 0, out_channel = 0;
|
||||
int input_idx, out_ch_idx, in_ch_idx;
|
||||
|
||||
next = strchr(cur, separator);
|
||||
if (next)
|
||||
*next++ = 0;
|
||||
|
||||
/* split the map into input and output parts */
|
||||
if (!(sep = strchr(cur, '-'))) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Missing separator '-' in channel "
|
||||
"map '%s'\n", cur);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
*sep++ = 0;
|
||||
|
||||
#define PARSE_CHANNEL(str, var, inout) \
|
||||
if (!(var = av_get_channel_layout(str))) { \
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid " inout " channel: %s.\n", str);\
|
||||
return AVERROR(EINVAL); \
|
||||
} \
|
||||
if (av_get_channel_layout_nb_channels(var) != 1) { \
|
||||
av_log(ctx, AV_LOG_ERROR, "Channel map describes more than one " \
|
||||
inout " channel.\n"); \
|
||||
return AVERROR(EINVAL); \
|
||||
}
|
||||
|
||||
/* parse output channel */
|
||||
PARSE_CHANNEL(sep, out_channel, "output");
|
||||
if (!(out_channel & s->channel_layout)) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Output channel '%s' is not present in "
|
||||
"requested channel layout.\n", sep);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
out_ch_idx = av_get_channel_layout_channel_index(s->channel_layout,
|
||||
out_channel);
|
||||
if (s->channels[out_ch_idx].input >= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Multiple maps for output channel "
|
||||
"'%s'.\n", sep);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
/* parse input channel */
|
||||
input_idx = strtol(cur, &cur, 0);
|
||||
if (input_idx < 0 || input_idx >= s->inputs) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid input stream index: %d.\n",
|
||||
input_idx);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (*cur)
|
||||
cur++;
|
||||
|
||||
in_ch_idx = strtol(cur, &p, 0);
|
||||
if (p == cur) {
|
||||
/* channel specifier is not a number,
|
||||
* try to parse as channel name */
|
||||
PARSE_CHANNEL(cur, in_channel, "input");
|
||||
}
|
||||
|
||||
s->channels[out_ch_idx].input = input_idx;
|
||||
if (in_channel)
|
||||
s->channels[out_ch_idx].in_channel = in_channel;
|
||||
else
|
||||
s->channels[out_ch_idx].in_channel_idx = in_ch_idx;
|
||||
|
||||
cur = next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int join_init(AVFilterContext *ctx)
|
||||
{
|
||||
JoinContext *s = ctx->priv;
|
||||
int ret, i;
|
||||
|
||||
if (!(s->channel_layout = av_get_channel_layout(s->channel_layout_str))) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout '%s'.\n",
|
||||
s->channel_layout_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
s->nb_channels = av_get_channel_layout_nb_channels(s->channel_layout);
|
||||
s->channels = av_mallocz(sizeof(*s->channels) * s->nb_channels);
|
||||
s->buffers = av_mallocz(sizeof(*s->buffers) * s->nb_channels);
|
||||
s->input_frames = av_mallocz(sizeof(*s->input_frames) * s->inputs);
|
||||
if (!s->channels || !s->buffers|| !s->input_frames)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
s->channels[i].out_channel = av_channel_layout_extract_channel(s->channel_layout, i);
|
||||
s->channels[i].input = -1;
|
||||
}
|
||||
|
||||
if ((ret = parse_maps(ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < s->inputs; i++) {
|
||||
char name[32];
|
||||
AVFilterPad pad = { 0 };
|
||||
|
||||
snprintf(name, sizeof(name), "input%d", i);
|
||||
pad.type = AVMEDIA_TYPE_AUDIO;
|
||||
pad.name = av_strdup(name);
|
||||
pad.filter_frame = filter_frame;
|
||||
|
||||
pad.needs_fifo = 1;
|
||||
|
||||
ff_insert_inpad(ctx, i, &pad);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void join_uninit(AVFilterContext *ctx)
|
||||
{
|
||||
JoinContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
av_freep(&ctx->input_pads[i].name);
|
||||
av_frame_free(&s->input_frames[i]);
|
||||
}
|
||||
|
||||
av_freep(&s->channels);
|
||||
av_freep(&s->buffers);
|
||||
av_freep(&s->input_frames);
|
||||
}
|
||||
|
||||
static int join_query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
JoinContext *s = ctx->priv;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
int i;
|
||||
|
||||
ff_add_channel_layout(&layouts, s->channel_layout);
|
||||
ff_channel_layouts_ref(layouts, &ctx->outputs[0]->in_channel_layouts);
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++)
|
||||
ff_channel_layouts_ref(ff_all_channel_layouts(),
|
||||
&ctx->inputs[i]->out_channel_layouts);
|
||||
|
||||
ff_set_common_formats (ctx, ff_planar_sample_fmts());
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void guess_map_matching(AVFilterContext *ctx, ChannelMap *ch,
|
||||
uint64_t *inputs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
AVFilterLink *link = ctx->inputs[i];
|
||||
|
||||
if (ch->out_channel & link->channel_layout &&
|
||||
!(ch->out_channel & inputs[i])) {
|
||||
ch->input = i;
|
||||
ch->in_channel = ch->out_channel;
|
||||
inputs[i] |= ch->out_channel;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void guess_map_any(AVFilterContext *ctx, ChannelMap *ch,
|
||||
uint64_t *inputs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
AVFilterLink *link = ctx->inputs[i];
|
||||
|
||||
if ((inputs[i] & link->channel_layout) != link->channel_layout) {
|
||||
uint64_t unused = link->channel_layout & ~inputs[i];
|
||||
|
||||
ch->input = i;
|
||||
ch->in_channel = av_channel_layout_extract_channel(unused, 0);
|
||||
inputs[i] |= ch->in_channel;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int join_config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
JoinContext *s = ctx->priv;
|
||||
uint64_t *inputs; // nth element tracks which channels are used from nth input
|
||||
int i, ret = 0;
|
||||
|
||||
/* initialize inputs to user-specified mappings */
|
||||
if (!(inputs = av_mallocz(sizeof(*inputs) * ctx->nb_inputs)))
|
||||
return AVERROR(ENOMEM);
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
ChannelMap *ch = &s->channels[i];
|
||||
AVFilterLink *inlink;
|
||||
|
||||
if (ch->input < 0)
|
||||
continue;
|
||||
|
||||
inlink = ctx->inputs[ch->input];
|
||||
|
||||
if (!ch->in_channel)
|
||||
ch->in_channel = av_channel_layout_extract_channel(inlink->channel_layout,
|
||||
ch->in_channel_idx);
|
||||
|
||||
if (!(ch->in_channel & inlink->channel_layout)) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Requested channel %s is not present in "
|
||||
"input stream #%d.\n", av_get_channel_name(ch->in_channel),
|
||||
ch->input);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
inputs[ch->input] |= ch->in_channel;
|
||||
}
|
||||
|
||||
/* guess channel maps when not explicitly defined */
|
||||
/* first try unused matching channels */
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
ChannelMap *ch = &s->channels[i];
|
||||
|
||||
if (ch->input < 0)
|
||||
guess_map_matching(ctx, ch, inputs);
|
||||
}
|
||||
|
||||
/* if the above failed, try to find _any_ unused input channel */
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
ChannelMap *ch = &s->channels[i];
|
||||
|
||||
if (ch->input < 0)
|
||||
guess_map_any(ctx, ch, inputs);
|
||||
|
||||
if (ch->input < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Could not find input channel for "
|
||||
"output channel '%s'.\n",
|
||||
av_get_channel_name(ch->out_channel));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ch->in_channel_idx = av_get_channel_layout_channel_index(ctx->inputs[ch->input]->channel_layout,
|
||||
ch->in_channel);
|
||||
}
|
||||
|
||||
/* print mappings */
|
||||
av_log(ctx, AV_LOG_VERBOSE, "mappings: ");
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
ChannelMap *ch = &s->channels[i];
|
||||
av_log(ctx, AV_LOG_VERBOSE, "%d.%s => %s ", ch->input,
|
||||
av_get_channel_name(ch->in_channel),
|
||||
av_get_channel_name(ch->out_channel));
|
||||
}
|
||||
av_log(ctx, AV_LOG_VERBOSE, "\n");
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
if (!inputs[i])
|
||||
av_log(ctx, AV_LOG_WARNING, "No channels are used from input "
|
||||
"stream %d.\n", i);
|
||||
}
|
||||
|
||||
fail:
|
||||
av_freep(&inputs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int join_request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
JoinContext *s = ctx->priv;
|
||||
AVFrame *frame;
|
||||
int linesize = INT_MAX;
|
||||
int nb_samples = 0;
|
||||
int nb_buffers = 0;
|
||||
int i, j, ret;
|
||||
|
||||
/* get a frame on each input */
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
AVFilterLink *inlink = ctx->inputs[i];
|
||||
|
||||
if (!s->input_frames[i] &&
|
||||
(ret = ff_request_frame(inlink)) < 0)
|
||||
return ret;
|
||||
|
||||
/* request the same number of samples on all inputs */
|
||||
if (i == 0) {
|
||||
nb_samples = s->input_frames[0]->nb_samples;
|
||||
|
||||
for (j = 1; !i && j < ctx->nb_inputs; j++)
|
||||
ctx->inputs[j]->request_samples = nb_samples;
|
||||
}
|
||||
}
|
||||
|
||||
/* setup the output frame */
|
||||
frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
if (s->nb_channels > FF_ARRAY_ELEMS(frame->data)) {
|
||||
frame->extended_data = av_mallocz(s->nb_channels *
|
||||
sizeof(*frame->extended_data));
|
||||
if (!frame->extended_data) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy the data pointers */
|
||||
for (i = 0; i < s->nb_channels; i++) {
|
||||
ChannelMap *ch = &s->channels[i];
|
||||
AVFrame *cur = s->input_frames[ch->input];
|
||||
AVBufferRef *buf;
|
||||
|
||||
frame->extended_data[i] = cur->extended_data[ch->in_channel_idx];
|
||||
linesize = FFMIN(linesize, cur->linesize[0]);
|
||||
|
||||
/* add the buffer where this plan is stored to the list if it's
|
||||
* not already there */
|
||||
buf = av_frame_get_plane_buffer(cur, ch->in_channel_idx);
|
||||
if (!buf) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
for (j = 0; j < nb_buffers; j++)
|
||||
if (s->buffers[j]->buffer == buf->buffer)
|
||||
break;
|
||||
if (j == i)
|
||||
s->buffers[nb_buffers++] = buf;
|
||||
}
|
||||
|
||||
/* create references to the buffers we copied to output */
|
||||
if (nb_buffers > FF_ARRAY_ELEMS(frame->buf)) {
|
||||
frame->nb_extended_buf = nb_buffers - FF_ARRAY_ELEMS(frame->buf);
|
||||
frame->extended_buf = av_mallocz(sizeof(*frame->extended_buf) *
|
||||
frame->nb_extended_buf);
|
||||
if (!frame->extended_buf) {
|
||||
frame->nb_extended_buf = 0;
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < FFMIN(FF_ARRAY_ELEMS(frame->buf), nb_buffers); i++) {
|
||||
frame->buf[i] = av_buffer_ref(s->buffers[i]);
|
||||
if (!frame->buf[i]) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < frame->nb_extended_buf; i++) {
|
||||
frame->extended_buf[i] = av_buffer_ref(s->buffers[i +
|
||||
FF_ARRAY_ELEMS(frame->buf)]);
|
||||
if (!frame->extended_buf[i]) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
frame->nb_samples = nb_samples;
|
||||
frame->channel_layout = outlink->channel_layout;
|
||||
av_frame_set_channels(frame, outlink->channels);
|
||||
frame->format = outlink->format;
|
||||
frame->sample_rate = outlink->sample_rate;
|
||||
frame->pts = s->input_frames[0]->pts;
|
||||
frame->linesize[0] = linesize;
|
||||
if (frame->data != frame->extended_data) {
|
||||
memcpy(frame->data, frame->extended_data, sizeof(*frame->data) *
|
||||
FFMIN(FF_ARRAY_ELEMS(frame->data), s->nb_channels));
|
||||
}
|
||||
|
||||
ret = ff_filter_frame(outlink, frame);
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++)
|
||||
av_frame_free(&s->input_frames[i]);
|
||||
|
||||
return ret;
|
||||
|
||||
fail:
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_join_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = join_config_output,
|
||||
.request_frame = join_request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_join = {
|
||||
.name = "join",
|
||||
.description = NULL_IF_CONFIG_SMALL("Join multiple audio streams into "
|
||||
"multi-channel output."),
|
||||
.priv_size = sizeof(JoinContext),
|
||||
.priv_class = &join_class,
|
||||
.init = join_init,
|
||||
.uninit = join_uninit,
|
||||
.query_formats = join_query_formats,
|
||||
.inputs = NULL,
|
||||
.outputs = avfilter_af_join_outputs,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
@@ -0,0 +1,703 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
* Copyright (c) 2011 Mina Nagy Zaki
|
||||
*
|
||||
* 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
|
||||
* LADSPA wrapper
|
||||
*/
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <ladspa.h>
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct LADSPAContext {
|
||||
const AVClass *class;
|
||||
char *dl_name;
|
||||
char *plugin;
|
||||
char *options;
|
||||
void *dl_handle;
|
||||
|
||||
unsigned long nb_inputs;
|
||||
unsigned long *ipmap; /* map input number to port number */
|
||||
|
||||
unsigned long nb_inputcontrols;
|
||||
unsigned long *icmap; /* map input control number to port number */
|
||||
LADSPA_Data *ictlv; /* input controls values */
|
||||
|
||||
unsigned long nb_outputs;
|
||||
unsigned long *opmap; /* map output number to port number */
|
||||
|
||||
unsigned long nb_outputcontrols;
|
||||
unsigned long *ocmap; /* map output control number to port number */
|
||||
LADSPA_Data *octlv; /* output controls values */
|
||||
|
||||
const LADSPA_Descriptor *desc;
|
||||
int *ctl_needs_value;
|
||||
int nb_handles;
|
||||
LADSPA_Handle *handles;
|
||||
|
||||
int sample_rate;
|
||||
int nb_samples;
|
||||
int64_t pts;
|
||||
int64_t duration;
|
||||
} LADSPAContext;
|
||||
|
||||
#define OFFSET(x) offsetof(LADSPAContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption ladspa_options[] = {
|
||||
{ "file", "set library name or full path", OFFSET(dl_name), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "f", "set library name or full path", OFFSET(dl_name), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "plugin", "set plugin name", OFFSET(plugin), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "p", "set plugin name", OFFSET(plugin), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "controls", "set plugin options", OFFSET(options), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "c", "set plugin options", OFFSET(options), AV_OPT_TYPE_STRING, .flags = FLAGS },
|
||||
{ "sample_rate", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100}, 1, INT32_MAX, FLAGS },
|
||||
{ "s", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100}, 1, INT32_MAX, FLAGS },
|
||||
{ "nb_samples", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "n", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64=1024}, 1, INT_MAX, FLAGS },
|
||||
{ "duration", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=-1}, -1, INT64_MAX, FLAGS },
|
||||
{ "d", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=-1}, -1, INT64_MAX, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(ladspa);
|
||||
|
||||
static void print_ctl_info(AVFilterContext *ctx, int level,
|
||||
LADSPAContext *s, int ctl, unsigned long *map,
|
||||
LADSPA_Data *values, int print)
|
||||
{
|
||||
const LADSPA_PortRangeHint *h = s->desc->PortRangeHints + map[ctl];
|
||||
|
||||
av_log(ctx, level, "c%i: %s [", ctl, s->desc->PortNames[map[ctl]]);
|
||||
|
||||
if (LADSPA_IS_HINT_TOGGLED(h->HintDescriptor)) {
|
||||
av_log(ctx, level, "toggled (1 or 0)");
|
||||
|
||||
if (LADSPA_IS_HINT_HAS_DEFAULT(h->HintDescriptor))
|
||||
av_log(ctx, level, " (default %i)", (int)values[ctl]);
|
||||
} else {
|
||||
if (LADSPA_IS_HINT_INTEGER(h->HintDescriptor)) {
|
||||
av_log(ctx, level, "<int>");
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor))
|
||||
av_log(ctx, level, ", min: %i", (int)h->LowerBound);
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor))
|
||||
av_log(ctx, level, ", max: %i", (int)h->UpperBound);
|
||||
|
||||
if (print)
|
||||
av_log(ctx, level, " (value %d)", (int)values[ctl]);
|
||||
else if (LADSPA_IS_HINT_HAS_DEFAULT(h->HintDescriptor))
|
||||
av_log(ctx, level, " (default %d)", (int)values[ctl]);
|
||||
} else {
|
||||
av_log(ctx, level, "<float>");
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor))
|
||||
av_log(ctx, level, ", min: %f", h->LowerBound);
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor))
|
||||
av_log(ctx, level, ", max: %f", h->UpperBound);
|
||||
|
||||
if (print)
|
||||
av_log(ctx, level, " (value %f)", values[ctl]);
|
||||
else if (LADSPA_IS_HINT_HAS_DEFAULT(h->HintDescriptor))
|
||||
av_log(ctx, level, " (default %f)", values[ctl]);
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_SAMPLE_RATE(h->HintDescriptor))
|
||||
av_log(ctx, level, ", multiple of sample rate");
|
||||
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor))
|
||||
av_log(ctx, level, ", logarithmic scale");
|
||||
}
|
||||
|
||||
av_log(ctx, level, "]\n");
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
LADSPAContext *s = ctx->priv;
|
||||
AVFrame *out;
|
||||
int i, h;
|
||||
|
||||
if (!s->nb_outputs ||
|
||||
(av_frame_is_writable(in) && s->nb_inputs == s->nb_outputs &&
|
||||
!(s->desc->Properties & LADSPA_PROPERTY_INPLACE_BROKEN))) {
|
||||
out = in;
|
||||
} else {
|
||||
out = ff_get_audio_buffer(ctx->outputs[0], in->nb_samples);
|
||||
if (!out) {
|
||||
av_frame_free(&in);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
av_frame_copy_props(out, in);
|
||||
}
|
||||
|
||||
for (h = 0; h < s->nb_handles; h++) {
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
s->desc->connect_port(s->handles[h], s->ipmap[i],
|
||||
(LADSPA_Data*)in->extended_data[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->nb_outputs; i++) {
|
||||
s->desc->connect_port(s->handles[h], s->opmap[i],
|
||||
(LADSPA_Data*)out->extended_data[i]);
|
||||
}
|
||||
|
||||
s->desc->run(s->handles[h], in->nb_samples);
|
||||
}
|
||||
|
||||
for (i = 0; i < s->nb_outputcontrols; i++)
|
||||
print_ctl_info(ctx, AV_LOG_VERBOSE, s, i, s->ocmap, s->octlv, 1);
|
||||
|
||||
if (out != in)
|
||||
av_frame_free(&in);
|
||||
|
||||
return ff_filter_frame(ctx->outputs[0], out);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
LADSPAContext *s = ctx->priv;
|
||||
AVFrame *out;
|
||||
int64_t t;
|
||||
int i;
|
||||
|
||||
if (ctx->nb_inputs)
|
||||
return ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
t = av_rescale(s->pts, AV_TIME_BASE, s->sample_rate);
|
||||
if (s->duration >= 0 && t >= s->duration)
|
||||
return AVERROR_EOF;
|
||||
|
||||
out = ff_get_audio_buffer(outlink, s->nb_samples);
|
||||
if (!out)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < s->nb_outputs; i++)
|
||||
s->desc->connect_port(s->handles[0], s->opmap[i],
|
||||
(LADSPA_Data*)out->extended_data[i]);
|
||||
|
||||
s->desc->run(s->handles[0], s->nb_samples);
|
||||
|
||||
for (i = 0; i < s->nb_outputcontrols; i++)
|
||||
print_ctl_info(ctx, AV_LOG_INFO, s, i, s->ocmap, s->octlv, 1);
|
||||
|
||||
out->sample_rate = s->sample_rate;
|
||||
out->pts = s->pts;
|
||||
s->pts += s->nb_samples;
|
||||
|
||||
return ff_filter_frame(outlink, out);
|
||||
}
|
||||
|
||||
static void set_default_ctl_value(LADSPAContext *s, int ctl,
|
||||
unsigned long *map, LADSPA_Data *values)
|
||||
{
|
||||
const LADSPA_PortRangeHint *h = s->desc->PortRangeHints + map[ctl];
|
||||
const LADSPA_Data lower = h->LowerBound;
|
||||
const LADSPA_Data upper = h->UpperBound;
|
||||
|
||||
if (LADSPA_IS_HINT_DEFAULT_MINIMUM(h->HintDescriptor)) {
|
||||
values[ctl] = lower;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(h->HintDescriptor)) {
|
||||
values[ctl] = upper;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_0(h->HintDescriptor)) {
|
||||
values[ctl] = 0.0;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_1(h->HintDescriptor)) {
|
||||
values[ctl] = 1.0;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_100(h->HintDescriptor)) {
|
||||
values[ctl] = 100.0;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_440(h->HintDescriptor)) {
|
||||
values[ctl] = 440.0;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_LOW(h->HintDescriptor)) {
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor))
|
||||
values[ctl] = exp(log(lower) * 0.75 + log(upper) * 0.25);
|
||||
else
|
||||
values[ctl] = lower * 0.75 + upper * 0.25;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(h->HintDescriptor)) {
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor))
|
||||
values[ctl] = exp(log(lower) * 0.5 + log(upper) * 0.5);
|
||||
else
|
||||
values[ctl] = lower * 0.5 + upper * 0.5;
|
||||
} else if (LADSPA_IS_HINT_DEFAULT_HIGH(h->HintDescriptor)) {
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(h->HintDescriptor))
|
||||
values[ctl] = exp(log(lower) * 0.25 + log(upper) * 0.75);
|
||||
else
|
||||
values[ctl] = lower * 0.25 + upper * 0.75;
|
||||
}
|
||||
}
|
||||
|
||||
static int connect_ports(AVFilterContext *ctx, AVFilterLink *link)
|
||||
{
|
||||
LADSPAContext *s = ctx->priv;
|
||||
int i, j;
|
||||
|
||||
s->nb_handles = s->nb_inputs == 1 && s->nb_outputs == 1 ? link->channels : 1;
|
||||
s->handles = av_calloc(s->nb_handles, sizeof(*s->handles));
|
||||
if (!s->handles)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < s->nb_handles; i++) {
|
||||
s->handles[i] = s->desc->instantiate(s->desc, link->sample_rate);
|
||||
if (!s->handles[i]) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Could not instantiate plugin.\n");
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
// Connect the input control ports
|
||||
for (j = 0; j < s->nb_inputcontrols; j++)
|
||||
s->desc->connect_port(s->handles[i], s->icmap[j], s->ictlv + j);
|
||||
|
||||
// Connect the output control ports
|
||||
for (j = 0; j < s->nb_outputcontrols; j++)
|
||||
s->desc->connect_port(s->handles[i], s->ocmap[j], &s->octlv[j]);
|
||||
|
||||
if (s->desc->activate)
|
||||
s->desc->activate(s->handles[i]);
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG, "handles: %d\n", s->nb_handles);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
|
||||
return connect_ports(ctx, inlink);
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
int ret;
|
||||
|
||||
if (ctx->nb_inputs) {
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
|
||||
outlink->format = inlink->format;
|
||||
outlink->sample_rate = inlink->sample_rate;
|
||||
|
||||
ret = 0;
|
||||
} else {
|
||||
LADSPAContext *s = ctx->priv;
|
||||
|
||||
outlink->sample_rate = s->sample_rate;
|
||||
outlink->time_base = (AVRational){1, s->sample_rate};
|
||||
|
||||
ret = connect_ports(ctx, outlink);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void count_ports(const LADSPA_Descriptor *desc,
|
||||
unsigned long *nb_inputs, unsigned long *nb_outputs)
|
||||
{
|
||||
LADSPA_PortDescriptor pd;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < desc->PortCount; i++) {
|
||||
pd = desc->PortDescriptors[i];
|
||||
|
||||
if (LADSPA_IS_PORT_AUDIO(pd)) {
|
||||
if (LADSPA_IS_PORT_INPUT(pd)) {
|
||||
(*nb_inputs)++;
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(pd)) {
|
||||
(*nb_outputs)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void *try_load(const char *dir, const char *soname)
|
||||
{
|
||||
char *path = av_asprintf("%s/%s.so", dir, soname);
|
||||
void *ret = NULL;
|
||||
|
||||
if (path) {
|
||||
ret = dlopen(path, RTLD_LOCAL|RTLD_NOW);
|
||||
av_free(path);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int set_control(AVFilterContext *ctx, unsigned long port, LADSPA_Data value)
|
||||
{
|
||||
LADSPAContext *s = ctx->priv;
|
||||
const char *label = s->desc->Label;
|
||||
LADSPA_PortRangeHint *h = (LADSPA_PortRangeHint *)s->desc->PortRangeHints +
|
||||
s->icmap[port];
|
||||
|
||||
if (port >= s->nb_inputcontrols) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Control c%ld is out of range [0 - %lu].\n",
|
||||
port, s->nb_inputcontrols);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(h->HintDescriptor) &&
|
||||
value < h->LowerBound) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"%s: input control c%ld is below lower boundary of %0.4f.\n",
|
||||
label, port, h->LowerBound);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(h->HintDescriptor) &&
|
||||
value > h->UpperBound) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"%s: input control c%ld is above upper boundary of %0.4f.\n",
|
||||
label, port, h->UpperBound);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
s->ictlv[port] = value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
LADSPAContext *s = ctx->priv;
|
||||
LADSPA_Descriptor_Function descriptor_fn;
|
||||
const LADSPA_Descriptor *desc;
|
||||
LADSPA_PortDescriptor pd;
|
||||
AVFilterPad pad = { NULL };
|
||||
char *p, *arg, *saveptr = NULL;
|
||||
unsigned long nb_ports;
|
||||
int i;
|
||||
|
||||
if (!s->dl_name) {
|
||||
av_log(ctx, AV_LOG_ERROR, "No plugin name provided\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (s->dl_name[0] == '/' || s->dl_name[0] == '.') {
|
||||
// argument is a path
|
||||
s->dl_handle = dlopen(s->dl_name, RTLD_LOCAL|RTLD_NOW);
|
||||
} else {
|
||||
// argument is a shared object name
|
||||
char *paths = av_strdup(getenv("LADSPA_PATH"));
|
||||
const char *separator = ":";
|
||||
|
||||
if (paths) {
|
||||
p = paths;
|
||||
while ((arg = av_strtok(p, separator, &saveptr)) && !s->dl_handle) {
|
||||
s->dl_handle = try_load(arg, s->dl_name);
|
||||
p = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
av_free(paths);
|
||||
if (!s->dl_handle && (paths = av_asprintf("%s/.ladspa/lib", getenv("HOME")))) {
|
||||
s->dl_handle = try_load(paths, s->dl_name);
|
||||
av_free(paths);
|
||||
}
|
||||
|
||||
if (!s->dl_handle)
|
||||
s->dl_handle = try_load("/usr/local/lib/ladspa", s->dl_name);
|
||||
|
||||
if (!s->dl_handle)
|
||||
s->dl_handle = try_load("/usr/lib/ladspa", s->dl_name);
|
||||
}
|
||||
if (!s->dl_handle) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Failed to load '%s'\n", s->dl_name);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
descriptor_fn = dlsym(s->dl_handle, "ladspa_descriptor");
|
||||
if (!descriptor_fn) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Could not find ladspa_descriptor: %s\n", dlerror());
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
// Find the requested plugin, or list plugins
|
||||
if (!s->plugin) {
|
||||
av_log(ctx, AV_LOG_INFO, "The '%s' library contains the following plugins:\n", s->dl_name);
|
||||
av_log(ctx, AV_LOG_INFO, "I = Input Channels\n");
|
||||
av_log(ctx, AV_LOG_INFO, "O = Output Channels\n");
|
||||
av_log(ctx, AV_LOG_INFO, "I:O %-25s %s\n", "Plugin", "Description");
|
||||
av_log(ctx, AV_LOG_INFO, "\n");
|
||||
for (i = 0; desc = descriptor_fn(i); i++) {
|
||||
unsigned long inputs = 0, outputs = 0;
|
||||
|
||||
count_ports(desc, &inputs, &outputs);
|
||||
av_log(ctx, AV_LOG_INFO, "%lu:%lu %-25s %s\n", inputs, outputs, desc->Label,
|
||||
av_x_if_null(desc->Name, "?"));
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Maker: %s\n", av_x_if_null(desc->Maker, "?"));
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Copyright: %s\n", av_x_if_null(desc->Copyright, "?"));
|
||||
}
|
||||
return AVERROR_EXIT;
|
||||
} else {
|
||||
for (i = 0;; i++) {
|
||||
desc = descriptor_fn(i);
|
||||
if (!desc) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Could not find plugin: %s\n", s->plugin);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (desc->Label && !strcmp(desc->Label, s->plugin))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
s->desc = desc;
|
||||
nb_ports = desc->PortCount;
|
||||
|
||||
s->ipmap = av_calloc(nb_ports, sizeof(*s->ipmap));
|
||||
s->opmap = av_calloc(nb_ports, sizeof(*s->opmap));
|
||||
s->icmap = av_calloc(nb_ports, sizeof(*s->icmap));
|
||||
s->ocmap = av_calloc(nb_ports, sizeof(*s->ocmap));
|
||||
s->ictlv = av_calloc(nb_ports, sizeof(*s->ictlv));
|
||||
s->octlv = av_calloc(nb_ports, sizeof(*s->octlv));
|
||||
s->ctl_needs_value = av_calloc(nb_ports, sizeof(*s->ctl_needs_value));
|
||||
if (!s->ipmap || !s->opmap || !s->icmap ||
|
||||
!s->ocmap || !s->ictlv || !s->octlv || !s->ctl_needs_value)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < nb_ports; i++) {
|
||||
pd = desc->PortDescriptors[i];
|
||||
|
||||
if (LADSPA_IS_PORT_AUDIO(pd)) {
|
||||
if (LADSPA_IS_PORT_INPUT(pd)) {
|
||||
s->ipmap[s->nb_inputs] = i;
|
||||
s->nb_inputs++;
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(pd)) {
|
||||
s->opmap[s->nb_outputs] = i;
|
||||
s->nb_outputs++;
|
||||
}
|
||||
} else if (LADSPA_IS_PORT_CONTROL(pd)) {
|
||||
if (LADSPA_IS_PORT_INPUT(pd)) {
|
||||
s->icmap[s->nb_inputcontrols] = i;
|
||||
|
||||
if (LADSPA_IS_HINT_HAS_DEFAULT(desc->PortRangeHints[i].HintDescriptor))
|
||||
set_default_ctl_value(s, s->nb_inputcontrols, s->icmap, s->ictlv);
|
||||
else
|
||||
s->ctl_needs_value[s->nb_inputcontrols] = 1;
|
||||
|
||||
s->nb_inputcontrols++;
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(pd)) {
|
||||
s->ocmap[s->nb_outputcontrols] = i;
|
||||
s->nb_outputcontrols++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List Control Ports if "help" is specified
|
||||
if (s->options && !strcmp(s->options, "help")) {
|
||||
if (!s->nb_inputcontrols) {
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"The '%s' plugin does not have any input controls.\n",
|
||||
desc->Label);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_INFO,
|
||||
"The '%s' plugin has the following input controls:\n",
|
||||
desc->Label);
|
||||
for (i = 0; i < s->nb_inputcontrols; i++)
|
||||
print_ctl_info(ctx, AV_LOG_INFO, s, i, s->icmap, s->ictlv, 0);
|
||||
}
|
||||
return AVERROR_EXIT;
|
||||
}
|
||||
|
||||
// Parse control parameters
|
||||
p = s->options;
|
||||
while (s->options) {
|
||||
LADSPA_Data val;
|
||||
int ret;
|
||||
|
||||
if (!(arg = av_strtok(p, "|", &saveptr)))
|
||||
break;
|
||||
p = NULL;
|
||||
|
||||
if (sscanf(arg, "c%d=%f", &i, &val) != 2) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid syntax.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if ((ret = set_control(ctx, i, val)) < 0)
|
||||
return ret;
|
||||
s->ctl_needs_value[i] = 0;
|
||||
}
|
||||
|
||||
// Check if any controls are not set
|
||||
for (i = 0; i < s->nb_inputcontrols; i++) {
|
||||
if (s->ctl_needs_value[i]) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Control c%d must be set.\n", i);
|
||||
print_ctl_info(ctx, AV_LOG_ERROR, s, i, s->icmap, s->ictlv, 0);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
pad.type = AVMEDIA_TYPE_AUDIO;
|
||||
|
||||
if (s->nb_inputs) {
|
||||
pad.name = av_asprintf("in0:%s%lu", desc->Label, s->nb_inputs);
|
||||
if (!pad.name)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
pad.filter_frame = filter_frame;
|
||||
pad.config_props = config_input;
|
||||
if (ff_insert_inpad(ctx, ctx->nb_inputs, &pad) < 0) {
|
||||
av_freep(&pad.name);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG, "ports: %lu\n", nb_ports);
|
||||
av_log(ctx, AV_LOG_DEBUG, "inputs: %lu outputs: %lu\n",
|
||||
s->nb_inputs, s->nb_outputs);
|
||||
av_log(ctx, AV_LOG_DEBUG, "input controls: %lu output controls: %lu\n",
|
||||
s->nb_inputcontrols, s->nb_outputcontrols);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
LADSPAContext *s = ctx->priv;
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE };
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
if (s->nb_inputs) {
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
} else {
|
||||
int sample_rates[] = { s->sample_rate, -1 };
|
||||
|
||||
ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates));
|
||||
}
|
||||
|
||||
if (s->nb_inputs == 1 && s->nb_outputs == 1) {
|
||||
// We will instantiate multiple LADSPA_Handle, one over each channel
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
} else {
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
if (s->nb_inputs >= 1) {
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
int64_t inlayout = FF_COUNT2LAYOUT(s->nb_inputs);
|
||||
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts, inlayout);
|
||||
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
|
||||
|
||||
if (!s->nb_outputs)
|
||||
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
|
||||
}
|
||||
|
||||
if (s->nb_outputs >= 1) {
|
||||
int64_t outlayout = FF_COUNT2LAYOUT(s->nb_outputs);
|
||||
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts, outlayout);
|
||||
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
LADSPAContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->nb_handles; i++) {
|
||||
if (s->desc->deactivate)
|
||||
s->desc->deactivate(s->handles[i]);
|
||||
if (s->desc->cleanup)
|
||||
s->desc->cleanup(s->handles[i]);
|
||||
}
|
||||
|
||||
if (s->dl_handle)
|
||||
dlclose(s->dl_handle);
|
||||
|
||||
av_freep(&s->ipmap);
|
||||
av_freep(&s->opmap);
|
||||
av_freep(&s->icmap);
|
||||
av_freep(&s->ocmap);
|
||||
av_freep(&s->ictlv);
|
||||
av_freep(&s->octlv);
|
||||
av_freep(&s->handles);
|
||||
av_freep(&s->ctl_needs_value);
|
||||
|
||||
if (ctx->nb_inputs)
|
||||
av_freep(&ctx->input_pads[0].name);
|
||||
}
|
||||
|
||||
static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
|
||||
char *res, int res_len, int flags)
|
||||
{
|
||||
LADSPA_Data value;
|
||||
unsigned long port;
|
||||
|
||||
if (sscanf(cmd, "c%ld", &port) + sscanf(args, "%f", &value) != 2)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
return set_control(ctx, port, value);
|
||||
}
|
||||
|
||||
static const AVFilterPad ladspa_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_ladspa = {
|
||||
.name = "ladspa",
|
||||
.description = NULL_IF_CONFIG_SMALL("Apply LADSPA effect."),
|
||||
.priv_size = sizeof(LADSPAContext),
|
||||
.priv_class = &ladspa_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.process_command = process_command,
|
||||
.inputs = 0,
|
||||
.outputs = ladspa_outputs,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright (c) 2002 Anders Johansson <[email protected]>
|
||||
* Copyright (c) 2011 Clément Bœsch <u pkh me>
|
||||
* Copyright (c) 2011 Nicolas George <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* Audio panning filter (channels mixing)
|
||||
* Original code written by Anders Johansson for MPlayer,
|
||||
* reimplemented for FFmpeg.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define MAX_CHANNELS 63
|
||||
|
||||
typedef struct PanContext {
|
||||
const AVClass *class;
|
||||
char *args;
|
||||
int64_t out_channel_layout;
|
||||
double gain[MAX_CHANNELS][MAX_CHANNELS];
|
||||
int64_t need_renorm;
|
||||
int need_renumber;
|
||||
int nb_output_channels;
|
||||
|
||||
int pure_gains;
|
||||
/* channel mapping specific */
|
||||
int channel_map[SWR_CH_MAX];
|
||||
struct SwrContext *swr;
|
||||
} PanContext;
|
||||
|
||||
static void skip_spaces(char **arg)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
sscanf(*arg, " %n", &len);
|
||||
*arg += len;
|
||||
}
|
||||
|
||||
static int parse_channel_name(char **arg, int *rchannel, int *rnamed)
|
||||
{
|
||||
char buf[8];
|
||||
int len, i, channel_id = 0;
|
||||
int64_t layout, layout0;
|
||||
|
||||
skip_spaces(arg);
|
||||
/* try to parse a channel name, e.g. "FL" */
|
||||
if (sscanf(*arg, "%7[A-Z]%n", buf, &len)) {
|
||||
layout0 = layout = av_get_channel_layout(buf);
|
||||
/* channel_id <- first set bit in layout */
|
||||
for (i = 32; i > 0; i >>= 1) {
|
||||
if (layout >= (int64_t)1 << i) {
|
||||
channel_id += i;
|
||||
layout >>= i;
|
||||
}
|
||||
}
|
||||
/* reject layouts that are not a single channel */
|
||||
if (channel_id >= MAX_CHANNELS || layout0 != (int64_t)1 << channel_id)
|
||||
return AVERROR(EINVAL);
|
||||
*rchannel = channel_id;
|
||||
*rnamed = 1;
|
||||
*arg += len;
|
||||
return 0;
|
||||
}
|
||||
/* try to parse a channel number, e.g. "c2" */
|
||||
if (sscanf(*arg, "c%d%n", &channel_id, &len) &&
|
||||
channel_id >= 0 && channel_id < MAX_CHANNELS) {
|
||||
*rchannel = channel_id;
|
||||
*rnamed = 0;
|
||||
*arg += len;
|
||||
return 0;
|
||||
}
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
PanContext *const pan = ctx->priv;
|
||||
char *arg, *arg0, *tokenizer, *args = av_strdup(pan->args);
|
||||
int out_ch_id, in_ch_id, len, named, ret;
|
||||
int nb_in_channels[2] = { 0, 0 }; // number of unnamed and named input channels
|
||||
double gain;
|
||||
|
||||
if (!pan->args) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"pan filter needs a channel layout and a set "
|
||||
"of channels definitions as parameter\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (!args)
|
||||
return AVERROR(ENOMEM);
|
||||
arg = av_strtok(args, "|", &tokenizer);
|
||||
ret = ff_parse_channel_layout(&pan->out_channel_layout,
|
||||
&pan->nb_output_channels, arg, ctx);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
/* parse channel specifications */
|
||||
while ((arg = arg0 = av_strtok(NULL, "|", &tokenizer))) {
|
||||
/* channel name */
|
||||
if (parse_channel_name(&arg, &out_ch_id, &named)) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Expected out channel name, got \"%.8s\"\n", arg);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
if (named) {
|
||||
if (!((pan->out_channel_layout >> out_ch_id) & 1)) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Channel \"%.8s\" does not exist in the chosen layout\n", arg0);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
/* get the channel number in the output channel layout:
|
||||
* out_channel_layout & ((1 << out_ch_id) - 1) are all the
|
||||
* channels that come before out_ch_id,
|
||||
* so their count is the index of out_ch_id */
|
||||
out_ch_id = av_get_channel_layout_nb_channels(pan->out_channel_layout & (((int64_t)1 << out_ch_id) - 1));
|
||||
}
|
||||
if (out_ch_id < 0 || out_ch_id >= pan->nb_output_channels) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Invalid out channel name \"%.8s\"\n", arg0);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
skip_spaces(&arg);
|
||||
if (*arg == '=') {
|
||||
arg++;
|
||||
} else if (*arg == '<') {
|
||||
pan->need_renorm |= (int64_t)1 << out_ch_id;
|
||||
arg++;
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Syntax error after channel name in \"%.8s\"\n", arg0);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
/* gains */
|
||||
while (1) {
|
||||
gain = 1;
|
||||
if (sscanf(arg, "%lf%n *%n", &gain, &len, &len))
|
||||
arg += len;
|
||||
if (parse_channel_name(&arg, &in_ch_id, &named)){
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Expected in channel name, got \"%.8s\"\n", arg);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
nb_in_channels[named]++;
|
||||
if (nb_in_channels[!named]) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Can not mix named and numbered channels\n");
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
pan->gain[out_ch_id][in_ch_id] = gain;
|
||||
skip_spaces(&arg);
|
||||
if (!*arg)
|
||||
break;
|
||||
if (*arg != '+') {
|
||||
av_log(ctx, AV_LOG_ERROR, "Syntax error near \"%.8s\"\n", arg);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
arg++;
|
||||
}
|
||||
}
|
||||
pan->need_renumber = !!nb_in_channels[1];
|
||||
|
||||
ret = 0;
|
||||
fail:
|
||||
av_free(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int are_gains_pure(const PanContext *pan)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < MAX_CHANNELS; i++) {
|
||||
int nb_gain = 0;
|
||||
|
||||
for (j = 0; j < MAX_CHANNELS; j++) {
|
||||
double gain = pan->gain[i][j];
|
||||
|
||||
/* channel mapping is effective only if 0% or 100% of a channel is
|
||||
* selected... */
|
||||
if (gain != 0. && gain != 1.)
|
||||
return 0;
|
||||
/* ...and if the output channel is only composed of one input */
|
||||
if (gain && nb_gain++)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
PanContext *pan = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
|
||||
pan->pure_gains = are_gains_pure(pan);
|
||||
/* libswr supports any sample and packing formats */
|
||||
ff_set_common_formats(ctx, ff_all_formats(AVMEDIA_TYPE_AUDIO));
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
// inlink supports any channel layout
|
||||
layouts = ff_all_channel_counts();
|
||||
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
|
||||
|
||||
// outlink supports only requested output channel layout
|
||||
layouts = NULL;
|
||||
ff_add_channel_layout(&layouts,
|
||||
pan->out_channel_layout ? pan->out_channel_layout :
|
||||
FF_COUNT2LAYOUT(pan->nb_output_channels));
|
||||
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *link)
|
||||
{
|
||||
AVFilterContext *ctx = link->dst;
|
||||
PanContext *pan = ctx->priv;
|
||||
char buf[1024], *cur;
|
||||
int i, j, k, r;
|
||||
double t;
|
||||
|
||||
if (pan->need_renumber) {
|
||||
// input channels were given by their name: renumber them
|
||||
for (i = j = 0; i < MAX_CHANNELS; i++) {
|
||||
if ((link->channel_layout >> i) & 1) {
|
||||
for (k = 0; k < pan->nb_output_channels; k++)
|
||||
pan->gain[k][j] = pan->gain[k][i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sanity check; can't be done in query_formats since the inlink
|
||||
// channel layout is unknown at that time
|
||||
if (link->channels > SWR_CH_MAX ||
|
||||
pan->nb_output_channels > SWR_CH_MAX) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"libswresample support a maximum of %d channels. "
|
||||
"Feel free to ask for a higher limit.\n", SWR_CH_MAX);
|
||||
return AVERROR_PATCHWELCOME;
|
||||
}
|
||||
|
||||
// init libswresample context
|
||||
pan->swr = swr_alloc_set_opts(pan->swr,
|
||||
pan->out_channel_layout, link->format, link->sample_rate,
|
||||
link->channel_layout, link->format, link->sample_rate,
|
||||
0, ctx);
|
||||
if (!pan->swr)
|
||||
return AVERROR(ENOMEM);
|
||||
if (!link->channel_layout)
|
||||
av_opt_set_int(pan->swr, "ich", link->channels, 0);
|
||||
if (!pan->out_channel_layout)
|
||||
av_opt_set_int(pan->swr, "och", pan->nb_output_channels, 0);
|
||||
|
||||
// gains are pure, init the channel mapping
|
||||
if (pan->pure_gains) {
|
||||
|
||||
// get channel map from the pure gains
|
||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||
int ch_id = -1;
|
||||
for (j = 0; j < link->channels; j++) {
|
||||
if (pan->gain[i][j]) {
|
||||
ch_id = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pan->channel_map[i] = ch_id;
|
||||
}
|
||||
|
||||
av_opt_set_int(pan->swr, "icl", pan->out_channel_layout, 0);
|
||||
av_opt_set_int(pan->swr, "uch", pan->nb_output_channels, 0);
|
||||
swr_set_channel_mapping(pan->swr, pan->channel_map);
|
||||
} else {
|
||||
// renormalize
|
||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||
if (!((pan->need_renorm >> i) & 1))
|
||||
continue;
|
||||
t = 0;
|
||||
for (j = 0; j < link->channels; j++)
|
||||
t += pan->gain[i][j];
|
||||
if (t > -1E-5 && t < 1E-5) {
|
||||
// t is almost 0 but not exactly, this is probably a mistake
|
||||
if (t)
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Degenerate coefficients while renormalizing\n");
|
||||
continue;
|
||||
}
|
||||
for (j = 0; j < link->channels; j++)
|
||||
pan->gain[i][j] /= t;
|
||||
}
|
||||
av_opt_set_int(pan->swr, "icl", link->channel_layout, 0);
|
||||
av_opt_set_int(pan->swr, "ocl", pan->out_channel_layout, 0);
|
||||
swr_set_matrix(pan->swr, pan->gain[0], pan->gain[1] - pan->gain[0]);
|
||||
}
|
||||
|
||||
r = swr_init(pan->swr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
// summary
|
||||
for (i = 0; i < pan->nb_output_channels; i++) {
|
||||
cur = buf;
|
||||
for (j = 0; j < link->channels; j++) {
|
||||
r = snprintf(cur, buf + sizeof(buf) - cur, "%s%.3g i%d",
|
||||
j ? " + " : "", pan->gain[i][j], j);
|
||||
cur += FFMIN(buf + sizeof(buf) - cur, r);
|
||||
}
|
||||
av_log(ctx, AV_LOG_VERBOSE, "o%d = %s\n", i, buf);
|
||||
}
|
||||
// add channel mapping summary if possible
|
||||
if (pan->pure_gains) {
|
||||
av_log(ctx, AV_LOG_INFO, "Pure channel mapping detected:");
|
||||
for (i = 0; i < pan->nb_output_channels; i++)
|
||||
if (pan->channel_map[i] < 0)
|
||||
av_log(ctx, AV_LOG_INFO, " M");
|
||||
else
|
||||
av_log(ctx, AV_LOG_INFO, " %d", pan->channel_map[i]);
|
||||
av_log(ctx, AV_LOG_INFO, "\n");
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
int ret;
|
||||
int n = insamples->nb_samples;
|
||||
AVFilterLink *const outlink = inlink->dst->outputs[0];
|
||||
AVFrame *outsamples = ff_get_audio_buffer(outlink, n);
|
||||
PanContext *pan = inlink->dst->priv;
|
||||
|
||||
if (!outsamples)
|
||||
return AVERROR(ENOMEM);
|
||||
swr_convert(pan->swr, outsamples->data, n, (void *)insamples->data, n);
|
||||
av_frame_copy_props(outsamples, insamples);
|
||||
outsamples->channel_layout = outlink->channel_layout;
|
||||
av_frame_set_channels(outsamples, outlink->channels);
|
||||
|
||||
ret = ff_filter_frame(outlink, outsamples);
|
||||
av_frame_free(&insamples);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
PanContext *pan = ctx->priv;
|
||||
swr_free(&pan->swr);
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(PanContext, x)
|
||||
|
||||
static const AVOption pan_options[] = {
|
||||
{ "args", NULL, OFFSET(args), AV_OPT_TYPE_STRING, { .str = NULL }, CHAR_MIN, CHAR_MAX, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(pan);
|
||||
|
||||
static const AVFilterPad pan_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad pan_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_pan = {
|
||||
.name = "pan",
|
||||
.description = NULL_IF_CONFIG_SMALL("Remix channels with coefficients (panning)."),
|
||||
.priv_size = sizeof(PanContext),
|
||||
.priv_class = &pan_class,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = pan_inputs,
|
||||
.outputs = pan_outputs,
|
||||
};
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* Copyright (c) 1998 - 2009 Conifer Software
|
||||
*
|
||||
* 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
|
||||
* ReplayGain scanner
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define HISTOGRAM_SLOTS 12000
|
||||
#define BUTTER_ORDER 2
|
||||
#define YULE_ORDER 10
|
||||
|
||||
typedef struct ReplayGainFreqInfo {
|
||||
int sample_rate;
|
||||
double BYule[YULE_ORDER + 1];
|
||||
double AYule[YULE_ORDER + 1];
|
||||
double BButter[BUTTER_ORDER + 1];
|
||||
double AButter[BUTTER_ORDER + 1];
|
||||
} ReplayGainFreqInfo;
|
||||
|
||||
static const ReplayGainFreqInfo freqinfos[] =
|
||||
{
|
||||
{
|
||||
192000,
|
||||
{ 0.01184742123123, -0.04631092400086, 0.06584226961238,
|
||||
-0.02165588522478, -0.05656260778952, 0.08607493592760,
|
||||
-0.03375544339786, -0.04216579932754, 0.06416711490648,
|
||||
-0.03444708260844, 0.00697275872241 },
|
||||
{ 1.00000000000000, -5.24727318348167, 10.60821585192244,
|
||||
-8.74127665810413, -1.33906071371683, 8.07972882096606,
|
||||
-5.46179918950847, 0.54318070652536, 0.87450969224280,
|
||||
-0.34656083539754, 0.03034796843589 },
|
||||
{ 0.99653501465135, -1.99307002930271, 0.99653501465135 },
|
||||
{ 1.00000000000000, -1.99305802314321, 0.99308203546221 },
|
||||
},
|
||||
{
|
||||
176400,
|
||||
{ 0.00268568524529, -0.00852379426080, 0.00852704191347,
|
||||
0.00146116310295, -0.00950855828762, 0.00625449515499,
|
||||
0.00116183868722, -0.00362461417136, 0.00203961000134,
|
||||
-0.00050664587933, 0.00004327455427 },
|
||||
{ 1.00000000000000, -5.57512782763045, 12.44291056065794,
|
||||
-12.87462799681221, 3.08554846961576, 6.62493459880692,
|
||||
-7.07662766313248, 2.51175542736441, 0.06731510802735,
|
||||
-0.24567753819213, 0.03961404162376 },
|
||||
{ 0.99622916581118, -1.99245833162236, 0.99622916581118 },
|
||||
{ 1.00000000000000, -1.99244411238133, 0.99247255086339 },
|
||||
},
|
||||
{
|
||||
144000,
|
||||
{ 0.00639682359450, -0.02556437970955, 0.04230854400938,
|
||||
-0.03722462201267, 0.01718514827295, 0.00610592243009,
|
||||
-0.03065965747365, 0.04345745003539, -0.03298592681309,
|
||||
0.01320937236809, -0.00220304127757 },
|
||||
{ 1.00000000000000, -6.14814623523425, 15.80002457141566,
|
||||
-20.78487587686937, 11.98848552310315, 3.36462015062606,
|
||||
-10.22419868359470, 6.65599702146473, -1.67141861110485,
|
||||
-0.05417956536718, 0.07374767867406 },
|
||||
{ 0.99538268958706, -1.99076537917413, 0.99538268958706 },
|
||||
{ 1.00000000000000, -1.99074405950505, 0.99078669884321 },
|
||||
},
|
||||
{
|
||||
128000,
|
||||
{ 0.00553120584305, -0.02112620545016, 0.03549076243117,
|
||||
-0.03362498312306, 0.01425867248183, 0.01344686928787,
|
||||
-0.03392770787836, 0.03464136459530, -0.02039116051549,
|
||||
0.00667420794705, -0.00093763762995 },
|
||||
{ 1.00000000000000, -6.14581710839925, 16.04785903675838,
|
||||
-22.19089131407749, 15.24756471580286, -0.52001440400238,
|
||||
-8.00488641699940, 6.60916094768855, -2.37856022810923,
|
||||
0.33106947986101, 0.00459820832036 },
|
||||
{ 0.99480702681278, -1.98961405362557, 0.99480702681278 },
|
||||
{ 1.00000000000000, -1.98958708647324, 0.98964102077790 },
|
||||
},
|
||||
{
|
||||
112000,
|
||||
{ 0.00528778718259, -0.01893240907245, 0.03185982561867,
|
||||
-0.02926260297838, 0.00715743034072, 0.01985743355827,
|
||||
-0.03222614850941, 0.02565681978192, -0.01210662313473,
|
||||
0.00325436284541, -0.00044173593001 },
|
||||
{ 1.00000000000000, -6.24932108456288, 17.42344320538476,
|
||||
-27.86819709054896, 26.79087344681326,-13.43711081485123,
|
||||
-0.66023612948173, 6.03658091814935, -4.24926577030310,
|
||||
1.40829268709186, -0.19480852628112 },
|
||||
{ 0.99406737810867, -1.98813475621734, 0.99406737810867 },
|
||||
{ 1.00000000000000, -1.98809955990514, 0.98816995252954 },
|
||||
},
|
||||
{
|
||||
96000,
|
||||
{ 0.00588138296683, -0.01613559730421, 0.02184798954216,
|
||||
-0.01742490405317, 0.00464635643780, 0.01117772513205,
|
||||
-0.02123865824368, 0.01959354413350, -0.01079720643523,
|
||||
0.00352183686289, -0.00063124341421 },
|
||||
{ 1.00000000000000, -5.97808823642008, 16.21362507964068,
|
||||
-25.72923730652599, 25.40470663139513,-14.66166287771134,
|
||||
2.81597484359752, 2.51447125969733, -2.23575306985286,
|
||||
0.75788151036791, -0.10078025199029 },
|
||||
{ 0.99308203517541, -1.98616407035082, 0.99308203517541 },
|
||||
{ 1.00000000000000, -1.98611621154089, 0.98621192916075 },
|
||||
},
|
||||
{
|
||||
88200,
|
||||
{ 0.02667482047416, -0.11377479336097, 0.23063167910965,
|
||||
-0.30726477945593, 0.33188520686529, -0.33862680249063,
|
||||
0.31807161531340, -0.23730796929880, 0.12273894790371,
|
||||
-0.03840017967282, 0.00549673387936 },
|
||||
{ 1.00000000000000, -6.31836451657302, 18.31351310801799,
|
||||
-31.88210014815921, 36.53792146976740,-28.23393036467559,
|
||||
14.24725258227189, -4.04670980012854, 0.18865757280515,
|
||||
0.25420333563908, -0.06012333531065 },
|
||||
{ 0.99247255046129, -1.98494510092259, 0.99247255046129 },
|
||||
{ 1.00000000000000, -1.98488843762335, 0.98500176422183 },
|
||||
},
|
||||
{
|
||||
64000,
|
||||
{ 0.02613056568174, -0.08128786488109, 0.14937282347325,
|
||||
-0.21695711675126, 0.25010286673402, -0.23162283619278,
|
||||
0.17424041833052, -0.10299599216680, 0.04258696481981,
|
||||
-0.00977952936493, 0.00105325558889 },
|
||||
{ 1.00000000000000, -5.73625477092119, 16.15249794355035,
|
||||
-29.68654912464508, 39.55706155674083,-39.82524556246253,
|
||||
30.50605345013009,-17.43051772821245, 7.05154573908017,
|
||||
-1.80783839720514, 0.22127840210813 },
|
||||
{ 0.98964101933472, -1.97928203866944, 0.98964101933472 },
|
||||
{ 1.00000000000000, -1.97917472731009, 0.97938935002880 },
|
||||
},
|
||||
{
|
||||
56000,
|
||||
{ 0.03144914734085, -0.06151729206963, 0.08066788708145,
|
||||
-0.09737939921516, 0.08943210803999, -0.06989984672010,
|
||||
0.04926972841044, -0.03161257848451, 0.01456837493506,
|
||||
-0.00316015108496, 0.00132807215875 },
|
||||
{ 1.00000000000000, -4.87377313090032, 12.03922160140209,
|
||||
-20.10151118381395, 25.10388534415171,-24.29065560815903,
|
||||
18.27158469090663,-10.45249552560593, 4.30319491872003,
|
||||
-1.13716992070185, 0.14510733527035 },
|
||||
{ 0.98816995007392, -1.97633990014784, 0.98816995007392 },
|
||||
{ 1.00000000000000, -1.97619994516973, 0.97647985512594 },
|
||||
},
|
||||
{
|
||||
48000,
|
||||
{ 0.03857599435200, -0.02160367184185, -0.00123395316851,
|
||||
-0.00009291677959, -0.01655260341619, 0.02161526843274,
|
||||
-0.02074045215285, 0.00594298065125, 0.00306428023191,
|
||||
0.00012025322027, 0.00288463683916 },
|
||||
{ 1.00000000000000, -3.84664617118067, 7.81501653005538,
|
||||
-11.34170355132042, 13.05504219327545,-12.28759895145294,
|
||||
9.48293806319790, -5.87257861775999, 2.75465861874613,
|
||||
-0.86984376593551, 0.13919314567432 },
|
||||
{ 0.98621192462708, -1.97242384925416, 0.98621192462708 },
|
||||
{ 1.00000000000000, -1.97223372919527, 0.97261396931306 },
|
||||
},
|
||||
{
|
||||
44100,
|
||||
{ 0.05418656406430, -0.02911007808948, -0.00848709379851,
|
||||
-0.00851165645469, -0.00834990904936, 0.02245293253339,
|
||||
-0.02596338512915, 0.01624864962975, -0.00240879051584,
|
||||
0.00674613682247, -0.00187763777362 },
|
||||
{ 1.00000000000000, -3.47845948550071, 6.36317777566148,
|
||||
-8.54751527471874, 9.47693607801280, -8.81498681370155,
|
||||
6.85401540936998, -4.39470996079559, 2.19611684890774,
|
||||
-0.75104302451432, 0.13149317958808 },
|
||||
{ 0.98500175787242, -1.97000351574484, 0.98500175787242 },
|
||||
{ 1.00000000000000, -1.96977855582618, 0.97022847566350 },
|
||||
},
|
||||
{
|
||||
37800,
|
||||
{ 0.08717879977844, -0.01000374016172, -0.06265852122368,
|
||||
-0.01119328800950, -0.00114279372960, 0.02081333954769,
|
||||
-0.01603261863207, 0.01936763028546, 0.00760044736442,
|
||||
-0.00303979112271, -0.00075088605788 },
|
||||
{ 1.00000000000000, -2.62816311472146, 3.53734535817992,
|
||||
-3.81003448678921, 3.91291636730132, -3.53518605896288,
|
||||
2.71356866157873, -1.86723311846592, 1.12075382367659,
|
||||
-0.48574086886890, 0.11330544663849 },
|
||||
{ 0.98252400815195, -1.96504801630391, 0.98252400815195 },
|
||||
{ 1.00000000000000, -1.96474258269041, 0.96535344991740 },
|
||||
},
|
||||
{
|
||||
32000,
|
||||
{ 0.15457299681924, -0.09331049056315, -0.06247880153653,
|
||||
0.02163541888798, -0.05588393329856, 0.04781476674921,
|
||||
0.00222312597743, 0.03174092540049, -0.01390589421898,
|
||||
0.00651420667831, -0.00881362733839 },
|
||||
{ 1.00000000000000, -2.37898834973084, 2.84868151156327,
|
||||
-2.64577170229825, 2.23697657451713, -1.67148153367602,
|
||||
1.00595954808547, -0.45953458054983, 0.16378164858596,
|
||||
-0.05032077717131, 0.02347897407020 },
|
||||
{ 0.97938932735214, -1.95877865470428, 0.97938932735214 },
|
||||
{ 1.00000000000000, -1.95835380975398, 0.95920349965459 },
|
||||
},
|
||||
{
|
||||
24000,
|
||||
{ 0.30296907319327, -0.22613988682123, -0.08587323730772,
|
||||
0.03282930172664, -0.00915702933434, -0.02364141202522,
|
||||
-0.00584456039913, 0.06276101321749, -0.00000828086748,
|
||||
0.00205861885564, -0.02950134983287 },
|
||||
{ 1.00000000000000, -1.61273165137247, 1.07977492259970,
|
||||
-0.25656257754070, -0.16276719120440, -0.22638893773906,
|
||||
0.39120800788284, -0.22138138954925, 0.04500235387352,
|
||||
0.02005851806501, 0.00302439095741 },
|
||||
{ 0.97531843204928, -1.95063686409857, 0.97531843204928 },
|
||||
{ 1.00000000000000, -1.95002759149878, 0.95124613669835 },
|
||||
},
|
||||
{
|
||||
22050,
|
||||
{ 0.33642304856132, -0.25572241425570, -0.11828570177555,
|
||||
0.11921148675203, -0.07834489609479, -0.00469977914380,
|
||||
-0.00589500224440, 0.05724228140351, 0.00832043980773,
|
||||
-0.01635381384540, -0.01760176568150 },
|
||||
{ 1.00000000000000, -1.49858979367799, 0.87350271418188,
|
||||
0.12205022308084, -0.80774944671438, 0.47854794562326,
|
||||
-0.12453458140019, -0.04067510197014, 0.08333755284107,
|
||||
-0.04237348025746, 0.02977207319925 },
|
||||
{ 0.97316523498161, -1.94633046996323, 0.97316523498161 },
|
||||
{ 1.00000000000000, -1.94561023566527, 0.94705070426118 },
|
||||
},
|
||||
{
|
||||
18900,
|
||||
{ 0.38524531015142, -0.27682212062067, -0.09980181488805,
|
||||
0.09951486755646, -0.08934020156622, -0.00322369330199,
|
||||
-0.00110329090689, 0.03784509844682, 0.01683906213303,
|
||||
-0.01147039862572, -0.01941767987192 },
|
||||
{ 1.00000000000000, -1.29708918404534, 0.90399339674203,
|
||||
-0.29613799017877, -0.42326645916207, 0.37934887402200,
|
||||
-0.37919795944938, 0.23410283284785, -0.03892971758879,
|
||||
0.00403009552351, 0.03640166626278 },
|
||||
{ 0.96535326815829, -1.93070653631658, 0.96535326815829 },
|
||||
{ 1.00000000000000, -1.92950577983524, 0.93190729279793 },
|
||||
},
|
||||
{
|
||||
16000,
|
||||
{ 0.44915256608450, -0.14351757464547, -0.22784394429749,
|
||||
-0.01419140100551, 0.04078262797139, -0.12398163381748,
|
||||
0.04097565135648, 0.10478503600251, -0.01863887810927,
|
||||
-0.03193428438915, 0.00541907748707 },
|
||||
{ 1.00000000000000, -0.62820619233671, 0.29661783706366,
|
||||
-0.37256372942400, 0.00213767857124, -0.42029820170918,
|
||||
0.22199650564824, 0.00613424350682, 0.06747620744683,
|
||||
0.05784820375801, 0.03222754072173 },
|
||||
{ 0.96454515552826, -1.92909031105652, 0.96454515552826 },
|
||||
{ 1.00000000000000, -1.92783286977036, 0.93034775234268 },
|
||||
},
|
||||
{
|
||||
12000,
|
||||
{ 0.56619470757641, -0.75464456939302, 0.16242137742230,
|
||||
0.16744243493672, -0.18901604199609, 0.30931782841830,
|
||||
-0.27562961986224, 0.00647310677246, 0.08647503780351,
|
||||
-0.03788984554840, -0.00588215443421 },
|
||||
{ 1.00000000000000, -1.04800335126349, 0.29156311971249,
|
||||
-0.26806001042947, 0.00819999645858, 0.45054734505008,
|
||||
-0.33032403314006, 0.06739368333110, -0.04784254229033,
|
||||
0.01639907836189, 0.01807364323573 },
|
||||
{ 0.96009142950541, -1.92018285901082, 0.96009142950541 },
|
||||
{ 1.00000000000000, -1.91858953033784, 0.92177618768381 },
|
||||
},
|
||||
{
|
||||
11025,
|
||||
{ 0.58100494960553, -0.53174909058578, -0.14289799034253,
|
||||
0.17520704835522, 0.02377945217615, 0.15558449135573,
|
||||
-0.25344790059353, 0.01628462406333, 0.06920467763959,
|
||||
-0.03721611395801, -0.00749618797172 },
|
||||
{ 1.00000000000000, -0.51035327095184, -0.31863563325245,
|
||||
-0.20256413484477, 0.14728154134330, 0.38952639978999,
|
||||
-0.23313271880868, -0.05246019024463, -0.02505961724053,
|
||||
0.02442357316099, 0.01818801111503 },
|
||||
{ 0.95856916599601, -1.91713833199203, 0.95856916599601 },
|
||||
{ 1.00000000000000, -1.91542108074780, 0.91885558323625 },
|
||||
},
|
||||
{
|
||||
8000,
|
||||
{ 0.53648789255105, -0.42163034350696, -0.00275953611929,
|
||||
0.04267842219415, -0.10214864179676, 0.14590772289388,
|
||||
-0.02459864859345, -0.11202315195388, -0.04060034127000,
|
||||
0.04788665548180, -0.02217936801134 },
|
||||
{ 1.00000000000000, -0.25049871956020, -0.43193942311114,
|
||||
-0.03424681017675, -0.04678328784242, 0.26408300200955,
|
||||
0.15113130533216, -0.17556493366449, -0.18823009262115,
|
||||
0.05477720428674, 0.04704409688120 },
|
||||
{ 0.94597685600279, -1.89195371200558, 0.94597685600279 },
|
||||
{ 1.00000000000000, -1.88903307939452, 0.89487434461664 },
|
||||
},
|
||||
};
|
||||
|
||||
typedef struct ReplayGainContext {
|
||||
uint32_t histogram[HISTOGRAM_SLOTS];
|
||||
float peak;
|
||||
int yule_hist_i, butter_hist_i;
|
||||
const double *yule_coeff_a;
|
||||
const double *yule_coeff_b;
|
||||
const double *butter_coeff_a;
|
||||
const double *butter_coeff_b;
|
||||
float yule_hist_a[256];
|
||||
float yule_hist_b[256];
|
||||
float butter_hist_a[256];
|
||||
float butter_hist_b[256];
|
||||
} ReplayGainContext;
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layout = NULL;
|
||||
int i;
|
||||
|
||||
ff_add_format(&formats, AV_SAMPLE_FMT_FLT);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO);
|
||||
ff_set_common_channel_layouts(ctx, layout);
|
||||
|
||||
formats = NULL;
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(freqinfos); i++)
|
||||
ff_add_format(&formats, freqinfos[i].sample_rate);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ReplayGainContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(freqinfos); i++) {
|
||||
if (freqinfos[i].sample_rate == inlink->sample_rate)
|
||||
break;
|
||||
}
|
||||
av_assert0(i < FF_ARRAY_ELEMS(freqinfos));
|
||||
|
||||
s->yule_coeff_a = freqinfos[i].AYule;
|
||||
s->yule_coeff_b = freqinfos[i].BYule;
|
||||
s->butter_coeff_a = freqinfos[i].AButter;
|
||||
s->butter_coeff_b = freqinfos[i].BButter;
|
||||
|
||||
s->yule_hist_i = 20;
|
||||
s->butter_hist_i = 4;
|
||||
inlink->partial_buf_size =
|
||||
inlink->min_samples =
|
||||
inlink->max_samples = inlink->sample_rate / 20;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update largest absolute sample value.
|
||||
*/
|
||||
static void calc_stereo_peak(const float *samples, int nb_samples,
|
||||
float *peak_p)
|
||||
{
|
||||
float peak = 0.0;
|
||||
|
||||
while (nb_samples--) {
|
||||
if (samples[0] > peak)
|
||||
peak = samples[0];
|
||||
else if (-samples[0] > peak)
|
||||
peak = -samples[0];
|
||||
|
||||
if (samples[1] > peak)
|
||||
peak = samples[1];
|
||||
else if (-samples[1] > peak)
|
||||
peak = -samples[1];
|
||||
|
||||
samples += 2;
|
||||
}
|
||||
|
||||
*peak_p = FFMAX(peak, *peak_p);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate stereo RMS level. Minimum value is about -100 dB for
|
||||
* digital silence. The 90 dB offset is to compensate for the
|
||||
* normalized float range and 3 dB is for stereo samples.
|
||||
*/
|
||||
static double calc_stereo_rms(const float *samples, int nb_samples)
|
||||
{
|
||||
int count = nb_samples;
|
||||
double sum = 1e-16;
|
||||
|
||||
while (count--) {
|
||||
sum += samples[0] * samples[0] + samples[1] * samples[1];
|
||||
samples += 2;
|
||||
}
|
||||
|
||||
return 10 * log10 (sum / nb_samples) + 90.0 - 3.0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimized implementation of 2nd-order IIR stereo filter.
|
||||
*/
|
||||
static void butter_filter_stereo_samples(ReplayGainContext *s,
|
||||
float *samples, int nb_samples)
|
||||
{
|
||||
const double *coeff_a = s->butter_coeff_a;
|
||||
const double *coeff_b = s->butter_coeff_b;
|
||||
float *hist_a = s->butter_hist_a;
|
||||
float *hist_b = s->butter_hist_b;
|
||||
double left, right;
|
||||
int i, j;
|
||||
|
||||
i = s->butter_hist_i;
|
||||
|
||||
// If filter history is very small magnitude, clear it completely
|
||||
// to prevent denormals from rattling around in there forever
|
||||
// (slowing us down).
|
||||
|
||||
for (j = -4; j < 0; ++j)
|
||||
if (fabs(hist_a[i + j]) > 1e-10 || fabs(hist_b[i + j]) > 1e-10)
|
||||
break;
|
||||
|
||||
if (!j) {
|
||||
memset(s->butter_hist_a, 0, sizeof(s->butter_hist_a));
|
||||
memset(s->butter_hist_b, 0, sizeof(s->butter_hist_b));
|
||||
}
|
||||
|
||||
while (nb_samples--) {
|
||||
left = (hist_b[i ] = samples[0]) * coeff_b[0];
|
||||
right = (hist_b[i + 1] = samples[1]) * coeff_b[0];
|
||||
left += hist_b[i - 2] * coeff_b[1] - hist_a[i - 2] * coeff_a[1];
|
||||
right += hist_b[i - 1] * coeff_b[1] - hist_a[i - 1] * coeff_a[1];
|
||||
left += hist_b[i - 4] * coeff_b[2] - hist_a[i - 4] * coeff_a[2];
|
||||
right += hist_b[i - 3] * coeff_b[2] - hist_a[i - 3] * coeff_a[2];
|
||||
samples[0] = hist_a[i ] = (float) left;
|
||||
samples[1] = hist_a[i + 1] = (float) right;
|
||||
samples += 2;
|
||||
|
||||
if ((i += 2) == 256) {
|
||||
memcpy(hist_a, hist_a + 252, sizeof(*hist_a) * 4);
|
||||
memcpy(hist_b, hist_b + 252, sizeof(*hist_b) * 4);
|
||||
i = 4;
|
||||
}
|
||||
}
|
||||
|
||||
s->butter_hist_i = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimized implementation of 10th-order IIR stereo filter.
|
||||
*/
|
||||
static void yule_filter_stereo_samples(ReplayGainContext *s, const float *src,
|
||||
float *dst, int nb_samples)
|
||||
{
|
||||
const double *coeff_a = s->yule_coeff_a;
|
||||
const double *coeff_b = s->yule_coeff_b;
|
||||
float *hist_a = s->yule_hist_a;
|
||||
float *hist_b = s->yule_hist_b;
|
||||
double left, right;
|
||||
int i, j;
|
||||
|
||||
i = s->yule_hist_i;
|
||||
|
||||
// If filter history is very small magnitude, clear it completely to
|
||||
// prevent denormals from rattling around in there forever
|
||||
// (slowing us down).
|
||||
|
||||
for (j = -20; j < 0; ++j)
|
||||
if (fabs(hist_a[i + j]) > 1e-10 || fabs(hist_b[i + j]) > 1e-10)
|
||||
break;
|
||||
|
||||
if (!j) {
|
||||
memset(s->yule_hist_a, 0, sizeof(s->yule_hist_a));
|
||||
memset(s->yule_hist_b, 0, sizeof(s->yule_hist_b));
|
||||
}
|
||||
|
||||
while (nb_samples--) {
|
||||
left = (hist_b[i] = src[0]) * coeff_b[0];
|
||||
right = (hist_b[i + 1] = src[1]) * coeff_b[0];
|
||||
left += hist_b[i - 2] * coeff_b[ 1] - hist_a[i - 2] * coeff_a[1 ];
|
||||
right += hist_b[i - 1] * coeff_b[ 1] - hist_a[i - 1] * coeff_a[1 ];
|
||||
left += hist_b[i - 4] * coeff_b[ 2] - hist_a[i - 4] * coeff_a[2 ];
|
||||
right += hist_b[i - 3] * coeff_b[ 2] - hist_a[i - 3] * coeff_a[2 ];
|
||||
left += hist_b[i - 6] * coeff_b[ 3] - hist_a[i - 6] * coeff_a[3 ];
|
||||
right += hist_b[i - 5] * coeff_b[ 3] - hist_a[i - 5] * coeff_a[3 ];
|
||||
left += hist_b[i - 8] * coeff_b[ 4] - hist_a[i - 8] * coeff_a[4 ];
|
||||
right += hist_b[i - 7] * coeff_b[ 4] - hist_a[i - 7] * coeff_a[4 ];
|
||||
left += hist_b[i - 10] * coeff_b[ 5] - hist_a[i - 10] * coeff_a[5 ];
|
||||
right += hist_b[i - 9] * coeff_b[ 5] - hist_a[i - 9] * coeff_a[5 ];
|
||||
left += hist_b[i - 12] * coeff_b[ 6] - hist_a[i - 12] * coeff_a[6 ];
|
||||
right += hist_b[i - 11] * coeff_b[ 6] - hist_a[i - 11] * coeff_a[6 ];
|
||||
left += hist_b[i - 14] * coeff_b[ 7] - hist_a[i - 14] * coeff_a[7 ];
|
||||
right += hist_b[i - 13] * coeff_b[ 7] - hist_a[i - 13] * coeff_a[7 ];
|
||||
left += hist_b[i - 16] * coeff_b[ 8] - hist_a[i - 16] * coeff_a[8 ];
|
||||
right += hist_b[i - 15] * coeff_b[ 8] - hist_a[i - 15] * coeff_a[8 ];
|
||||
left += hist_b[i - 18] * coeff_b[ 9] - hist_a[i - 18] * coeff_a[9 ];
|
||||
right += hist_b[i - 17] * coeff_b[ 9] - hist_a[i - 17] * coeff_a[9 ];
|
||||
left += hist_b[i - 20] * coeff_b[10] - hist_a[i - 20] * coeff_a[10];
|
||||
right += hist_b[i - 19] * coeff_b[10] - hist_a[i - 19] * coeff_a[10];
|
||||
dst[0] = hist_a[i ] = (float)left;
|
||||
dst[1] = hist_a[i + 1] = (float)right;
|
||||
src += 2;
|
||||
dst += 2;
|
||||
|
||||
if ((i += 2) == 256) {
|
||||
memcpy(hist_a, hist_a + 236, sizeof(*hist_a) * 20);
|
||||
memcpy(hist_b, hist_b + 236, sizeof(*hist_b) * 20);
|
||||
i = 20;
|
||||
}
|
||||
}
|
||||
|
||||
s->yule_hist_i = i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the ReplayGain value from the specified loudness histogram;
|
||||
* clip to -24 / +64 dB.
|
||||
*/
|
||||
static float calc_replaygain(uint32_t *histogram)
|
||||
{
|
||||
uint32_t loud_count = 0, total_windows = 0;
|
||||
float gain;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < HISTOGRAM_SLOTS; i++)
|
||||
total_windows += histogram [i];
|
||||
|
||||
while (i--)
|
||||
if ((loud_count += histogram [i]) * 20 >= total_windows)
|
||||
break;
|
||||
|
||||
gain = (float)(64.54 - i / 100.0);
|
||||
|
||||
return av_clipf(gain, -24.0, 64.0);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
ReplayGainContext *s = ctx->priv;
|
||||
uint32_t level;
|
||||
AVFrame *out;
|
||||
|
||||
out = ff_get_audio_buffer(inlink, in->nb_samples);
|
||||
if (!out) {
|
||||
av_frame_free(&in);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
calc_stereo_peak((float *)in->data[0],
|
||||
in->nb_samples, &s->peak);
|
||||
yule_filter_stereo_samples(s, (const float *)in->data[0],
|
||||
(float *)out->data[0],
|
||||
out->nb_samples);
|
||||
butter_filter_stereo_samples(s, (float *)out->data[0],
|
||||
out->nb_samples);
|
||||
level = (uint32_t)floor(100 * calc_stereo_rms((float *)out->data[0],
|
||||
out->nb_samples));
|
||||
level = av_clip(level, 0, HISTOGRAM_SLOTS - 1);
|
||||
|
||||
s->histogram[level]++;
|
||||
|
||||
av_frame_free(&out);
|
||||
return ff_filter_frame(outlink, in);
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ReplayGainContext *s = ctx->priv;
|
||||
float gain = calc_replaygain(s->histogram);
|
||||
|
||||
av_log(ctx, AV_LOG_INFO, "track_gain = %+.2f dB\n", gain);
|
||||
av_log(ctx, AV_LOG_INFO, "track_peak = %.6f\n", s->peak);
|
||||
}
|
||||
|
||||
static const AVFilterPad replaygain_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
.config_props = config_input,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad replaygain_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_replaygain = {
|
||||
.name = "replaygain",
|
||||
.description = NULL_IF_CONFIG_SMALL("ReplayGain scanner."),
|
||||
.query_formats = query_formats,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(ReplayGainContext),
|
||||
.inputs = replaygain_inputs,
|
||||
.outputs = replaygain_outputs,
|
||||
};
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* 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
|
||||
* sample format and channel layout conversion audio filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "libavresample/avresample.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct ResampleContext {
|
||||
const AVClass *class;
|
||||
AVAudioResampleContext *avr;
|
||||
AVDictionary *options;
|
||||
|
||||
int64_t next_pts;
|
||||
|
||||
/* set by filter_frame() to signal an output frame to request_frame() */
|
||||
int got_output;
|
||||
} ResampleContext;
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx, AVDictionary **opts)
|
||||
{
|
||||
ResampleContext *s = ctx->priv;
|
||||
const AVClass *avr_class = avresample_get_class();
|
||||
AVDictionaryEntry *e = NULL;
|
||||
|
||||
while ((e = av_dict_get(*opts, "", e, AV_DICT_IGNORE_SUFFIX))) {
|
||||
if (av_opt_find(&avr_class, e->key, NULL, 0,
|
||||
AV_OPT_SEARCH_FAKE_OBJ | AV_OPT_SEARCH_CHILDREN))
|
||||
av_dict_set(&s->options, e->key, e->value, 0);
|
||||
}
|
||||
|
||||
e = NULL;
|
||||
while ((e = av_dict_get(s->options, "", e, AV_DICT_IGNORE_SUFFIX)))
|
||||
av_dict_set(opts, e->key, NULL, 0);
|
||||
|
||||
/* do not allow the user to override basic format options */
|
||||
av_dict_set(&s->options, "in_channel_layout", NULL, 0);
|
||||
av_dict_set(&s->options, "out_channel_layout", NULL, 0);
|
||||
av_dict_set(&s->options, "in_sample_fmt", NULL, 0);
|
||||
av_dict_set(&s->options, "out_sample_fmt", NULL, 0);
|
||||
av_dict_set(&s->options, "in_sample_rate", NULL, 0);
|
||||
av_dict_set(&s->options, "out_sample_rate", NULL, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ResampleContext *s = ctx->priv;
|
||||
|
||||
if (s->avr) {
|
||||
avresample_close(s->avr);
|
||||
avresample_free(&s->avr);
|
||||
}
|
||||
av_dict_free(&s->options);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
AVFilterFormats *in_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO);
|
||||
AVFilterFormats *out_formats = ff_all_formats(AVMEDIA_TYPE_AUDIO);
|
||||
AVFilterFormats *in_samplerates = ff_all_samplerates();
|
||||
AVFilterFormats *out_samplerates = ff_all_samplerates();
|
||||
AVFilterChannelLayouts *in_layouts = ff_all_channel_layouts();
|
||||
AVFilterChannelLayouts *out_layouts = ff_all_channel_layouts();
|
||||
|
||||
ff_formats_ref(in_formats, &inlink->out_formats);
|
||||
ff_formats_ref(out_formats, &outlink->in_formats);
|
||||
|
||||
ff_formats_ref(in_samplerates, &inlink->out_samplerates);
|
||||
ff_formats_ref(out_samplerates, &outlink->in_samplerates);
|
||||
|
||||
ff_channel_layouts_ref(in_layouts, &inlink->out_channel_layouts);
|
||||
ff_channel_layouts_ref(out_layouts, &outlink->in_channel_layouts);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
ResampleContext *s = ctx->priv;
|
||||
char buf1[64], buf2[64];
|
||||
int ret;
|
||||
|
||||
if (s->avr) {
|
||||
avresample_close(s->avr);
|
||||
avresample_free(&s->avr);
|
||||
}
|
||||
|
||||
if (inlink->channel_layout == outlink->channel_layout &&
|
||||
inlink->sample_rate == outlink->sample_rate &&
|
||||
(inlink->format == outlink->format ||
|
||||
(av_get_channel_layout_nb_channels(inlink->channel_layout) == 1 &&
|
||||
av_get_channel_layout_nb_channels(outlink->channel_layout) == 1 &&
|
||||
av_get_planar_sample_fmt(inlink->format) ==
|
||||
av_get_planar_sample_fmt(outlink->format))))
|
||||
return 0;
|
||||
|
||||
if (!(s->avr = avresample_alloc_context()))
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if (s->options) {
|
||||
AVDictionaryEntry *e = NULL;
|
||||
while ((e = av_dict_get(s->options, "", e, AV_DICT_IGNORE_SUFFIX)))
|
||||
av_log(ctx, AV_LOG_VERBOSE, "lavr option: %s=%s\n", e->key, e->value);
|
||||
|
||||
av_opt_set_dict(s->avr, &s->options);
|
||||
}
|
||||
|
||||
av_opt_set_int(s->avr, "in_channel_layout", inlink ->channel_layout, 0);
|
||||
av_opt_set_int(s->avr, "out_channel_layout", outlink->channel_layout, 0);
|
||||
av_opt_set_int(s->avr, "in_sample_fmt", inlink ->format, 0);
|
||||
av_opt_set_int(s->avr, "out_sample_fmt", outlink->format, 0);
|
||||
av_opt_set_int(s->avr, "in_sample_rate", inlink ->sample_rate, 0);
|
||||
av_opt_set_int(s->avr, "out_sample_rate", outlink->sample_rate, 0);
|
||||
|
||||
if ((ret = avresample_open(s->avr)) < 0)
|
||||
return ret;
|
||||
|
||||
outlink->time_base = (AVRational){ 1, outlink->sample_rate };
|
||||
s->next_pts = AV_NOPTS_VALUE;
|
||||
|
||||
av_get_channel_layout_string(buf1, sizeof(buf1),
|
||||
-1, inlink ->channel_layout);
|
||||
av_get_channel_layout_string(buf2, sizeof(buf2),
|
||||
-1, outlink->channel_layout);
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"fmt:%s srate:%d cl:%s -> fmt:%s srate:%d cl:%s\n",
|
||||
av_get_sample_fmt_name(inlink ->format), inlink ->sample_rate, buf1,
|
||||
av_get_sample_fmt_name(outlink->format), outlink->sample_rate, buf2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
ResampleContext *s = ctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
s->got_output = 0;
|
||||
while (ret >= 0 && !s->got_output)
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
|
||||
/* flush the lavr delay buffer */
|
||||
if (ret == AVERROR_EOF && s->avr) {
|
||||
AVFrame *frame;
|
||||
int nb_samples = av_rescale_rnd(avresample_get_delay(s->avr),
|
||||
outlink->sample_rate,
|
||||
ctx->inputs[0]->sample_rate,
|
||||
AV_ROUND_UP);
|
||||
|
||||
if (!nb_samples)
|
||||
return ret;
|
||||
|
||||
frame = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ret = avresample_convert(s->avr, frame->extended_data,
|
||||
frame->linesize[0], nb_samples,
|
||||
NULL, 0, 0);
|
||||
if (ret <= 0) {
|
||||
av_frame_free(&frame);
|
||||
return (ret == 0) ? AVERROR_EOF : ret;
|
||||
}
|
||||
|
||||
frame->pts = s->next_pts;
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *in)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ResampleContext *s = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
int ret;
|
||||
|
||||
if (s->avr) {
|
||||
AVFrame *out;
|
||||
int delay, nb_samples;
|
||||
|
||||
/* maximum possible samples lavr can output */
|
||||
delay = avresample_get_delay(s->avr);
|
||||
nb_samples = av_rescale_rnd(in->nb_samples + delay,
|
||||
outlink->sample_rate, inlink->sample_rate,
|
||||
AV_ROUND_UP);
|
||||
|
||||
out = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!out) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = avresample_convert(s->avr, out->extended_data, out->linesize[0],
|
||||
nb_samples, in->extended_data, in->linesize[0],
|
||||
in->nb_samples);
|
||||
if (ret <= 0) {
|
||||
av_frame_free(&out);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
av_assert0(!avresample_available(s->avr));
|
||||
|
||||
if (s->next_pts == AV_NOPTS_VALUE) {
|
||||
if (in->pts == AV_NOPTS_VALUE) {
|
||||
av_log(ctx, AV_LOG_WARNING, "First timestamp is missing, "
|
||||
"assuming 0.\n");
|
||||
s->next_pts = 0;
|
||||
} else
|
||||
s->next_pts = av_rescale_q(in->pts, inlink->time_base,
|
||||
outlink->time_base);
|
||||
}
|
||||
|
||||
if (ret > 0) {
|
||||
out->nb_samples = ret;
|
||||
if (in->pts != AV_NOPTS_VALUE) {
|
||||
out->pts = av_rescale_q(in->pts, inlink->time_base,
|
||||
outlink->time_base) -
|
||||
av_rescale(delay, outlink->sample_rate,
|
||||
inlink->sample_rate);
|
||||
} else
|
||||
out->pts = s->next_pts;
|
||||
|
||||
s->next_pts = out->pts + out->nb_samples;
|
||||
|
||||
ret = ff_filter_frame(outlink, out);
|
||||
s->got_output = 1;
|
||||
}
|
||||
|
||||
fail:
|
||||
av_frame_free(&in);
|
||||
} else {
|
||||
in->format = outlink->format;
|
||||
ret = ff_filter_frame(outlink, in);
|
||||
s->got_output = 1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVClass *resample_child_class_next(const AVClass *prev)
|
||||
{
|
||||
return prev ? NULL : avresample_get_class();
|
||||
}
|
||||
|
||||
static void *resample_child_next(void *obj, void *prev)
|
||||
{
|
||||
ResampleContext *s = obj;
|
||||
return prev ? NULL : s->avr;
|
||||
}
|
||||
|
||||
static const AVClass resample_class = {
|
||||
.class_name = "resample",
|
||||
.item_name = av_default_item_name,
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
.child_class_next = resample_child_class_next,
|
||||
.child_next = resample_child_next,
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_resample_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_resample_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_resample = {
|
||||
.name = "resample",
|
||||
.description = NULL_IF_CONFIG_SMALL("Audio resampling and conversion."),
|
||||
.priv_size = sizeof(ResampleContext),
|
||||
.priv_class = &resample_class,
|
||||
.init_dict = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = avfilter_af_resample_inputs,
|
||||
.outputs = avfilter_af_resample_outputs,
|
||||
};
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Clément Bœsch <u pkh me>
|
||||
*
|
||||
* 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
|
||||
* Audio silence detector
|
||||
*/
|
||||
|
||||
#include <float.h> /* DBL_MAX */
|
||||
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
#include "audio.h"
|
||||
#include "formats.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct SilenceDetectContext {
|
||||
const AVClass *class;
|
||||
double noise; ///< noise amplitude ratio
|
||||
double duration; ///< minimum duration of silence until notification
|
||||
int64_t nb_null_samples; ///< current number of continuous zero samples
|
||||
int64_t start; ///< if silence is detected, this value contains the time of the first zero sample
|
||||
int last_sample_rate; ///< last sample rate to check for sample rate changes
|
||||
|
||||
void (*silencedetect)(struct SilenceDetectContext *s, AVFrame *insamples,
|
||||
int nb_samples, int64_t nb_samples_notify,
|
||||
AVRational time_base);
|
||||
} SilenceDetectContext;
|
||||
|
||||
#define OFFSET(x) offsetof(SilenceDetectContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_AUDIO_PARAM
|
||||
static const AVOption silencedetect_options[] = {
|
||||
{ "n", "set noise tolerance", OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl=0.001}, 0, DBL_MAX, FLAGS },
|
||||
{ "noise", "set noise tolerance", OFFSET(noise), AV_OPT_TYPE_DOUBLE, {.dbl=0.001}, 0, DBL_MAX, FLAGS },
|
||||
{ "d", "set minimum duration in seconds", OFFSET(duration), AV_OPT_TYPE_DOUBLE, {.dbl=2.}, 0, 24*60*60, FLAGS },
|
||||
{ "duration", "set minimum duration in seconds", OFFSET(duration), AV_OPT_TYPE_DOUBLE, {.dbl=2.}, 0, 24*60*60, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(silencedetect);
|
||||
|
||||
static char *get_metadata_val(AVFrame *insamples, const char *key)
|
||||
{
|
||||
AVDictionaryEntry *e = av_dict_get(insamples->metadata, key, NULL, 0);
|
||||
return e && e->value ? e->value : NULL;
|
||||
}
|
||||
|
||||
static av_always_inline void update(SilenceDetectContext *s, AVFrame *insamples,
|
||||
int is_silence, int64_t nb_samples_notify,
|
||||
AVRational time_base)
|
||||
{
|
||||
if (is_silence) {
|
||||
if (!s->start) {
|
||||
s->nb_null_samples++;
|
||||
if (s->nb_null_samples >= nb_samples_notify) {
|
||||
s->start = insamples->pts - (int64_t)(s->duration / av_q2d(time_base) + .5);
|
||||
av_dict_set(&insamples->metadata, "lavfi.silence_start",
|
||||
av_ts2timestr(s->start, &time_base), 0);
|
||||
av_log(s, AV_LOG_INFO, "silence_start: %s\n",
|
||||
get_metadata_val(insamples, "lavfi.silence_start"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (s->start) {
|
||||
av_dict_set(&insamples->metadata, "lavfi.silence_end",
|
||||
av_ts2timestr(insamples->pts, &time_base), 0);
|
||||
av_dict_set(&insamples->metadata, "lavfi.silence_duration",
|
||||
av_ts2timestr(insamples->pts - s->start, &time_base), 0);
|
||||
av_log(s, AV_LOG_INFO,
|
||||
"silence_end: %s | silence_duration: %s\n",
|
||||
get_metadata_val(insamples, "lavfi.silence_end"),
|
||||
get_metadata_val(insamples, "lavfi.silence_duration"));
|
||||
}
|
||||
s->nb_null_samples = s->start = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define SILENCE_DETECT(name, type) \
|
||||
static void silencedetect_##name(SilenceDetectContext *s, AVFrame *insamples, \
|
||||
int nb_samples, int64_t nb_samples_notify, \
|
||||
AVRational time_base) \
|
||||
{ \
|
||||
const type *p = (const type *)insamples->data[0]; \
|
||||
const type noise = s->noise; \
|
||||
int i; \
|
||||
\
|
||||
for (i = 0; i < nb_samples; i++, p++) \
|
||||
update(s, insamples, *p < noise && *p > -noise, \
|
||||
nb_samples_notify, time_base); \
|
||||
}
|
||||
|
||||
SILENCE_DETECT(dbl, double)
|
||||
SILENCE_DETECT(flt, float)
|
||||
SILENCE_DETECT(s32, int32_t)
|
||||
SILENCE_DETECT(s16, int16_t)
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
SilenceDetectContext *s = ctx->priv;
|
||||
|
||||
switch (inlink->format) {
|
||||
case AV_SAMPLE_FMT_DBL: s->silencedetect = silencedetect_dbl; break;
|
||||
case AV_SAMPLE_FMT_FLT: s->silencedetect = silencedetect_flt; break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
s->noise *= INT32_MAX;
|
||||
s->silencedetect = silencedetect_s32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
s->noise *= INT16_MAX;
|
||||
s->silencedetect = silencedetect_s16;
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
SilenceDetectContext *s = inlink->dst->priv;
|
||||
const int nb_channels = inlink->channels;
|
||||
const int srate = inlink->sample_rate;
|
||||
const int nb_samples = insamples->nb_samples * nb_channels;
|
||||
const int64_t nb_samples_notify = srate * s->duration * nb_channels;
|
||||
|
||||
// scale number of null samples to the new sample rate
|
||||
if (s->last_sample_rate && s->last_sample_rate != srate)
|
||||
s->nb_null_samples = srate * s->nb_null_samples / s->last_sample_rate;
|
||||
s->last_sample_rate = srate;
|
||||
|
||||
// TODO: document metadata
|
||||
s->silencedetect(s, insamples, nb_samples, nb_samples_notify,
|
||||
inlink->time_base);
|
||||
|
||||
return ff_filter_frame(inlink->dst->outputs[0], insamples);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_DBL,
|
||||
AV_SAMPLE_FMT_FLT,
|
||||
AV_SAMPLE_FMT_S32,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad silencedetect_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_input,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad silencedetect_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_silencedetect = {
|
||||
.name = "silencedetect",
|
||||
.description = NULL_IF_CONFIG_SMALL("Detect silence."),
|
||||
.priv_size = sizeof(SilenceDetectContext),
|
||||
.query_formats = query_formats,
|
||||
.inputs = silencedetect_inputs,
|
||||
.outputs = silencedetect_outputs,
|
||||
.priv_class = &silencedetect_class,
|
||||
};
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
* Copyright (c) 2012 Justin Ruggles <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* audio volume filter
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/float_dsp.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
#include "af_volume.h"
|
||||
|
||||
static const char *precision_str[] = {
|
||||
"fixed", "float", "double"
|
||||
};
|
||||
|
||||
#define OFFSET(x) offsetof(VolumeContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption volume_options[] = {
|
||||
{ "volume", "set volume adjustment",
|
||||
OFFSET(volume), AV_OPT_TYPE_DOUBLE, { .dbl = 1.0 }, 0, 0x7fffff, A|F },
|
||||
{ "precision", "select mathematical precision",
|
||||
OFFSET(precision), AV_OPT_TYPE_INT, { .i64 = PRECISION_FLOAT }, PRECISION_FIXED, PRECISION_DOUBLE, A|F, "precision" },
|
||||
{ "fixed", "select 8-bit fixed-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FIXED }, INT_MIN, INT_MAX, A|F, "precision" },
|
||||
{ "float", "select 32-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_FLOAT }, INT_MIN, INT_MAX, A|F, "precision" },
|
||||
{ "double", "select 64-bit floating-point", 0, AV_OPT_TYPE_CONST, { .i64 = PRECISION_DOUBLE }, INT_MIN, INT_MAX, A|F, "precision" },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(volume);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
VolumeContext *vol = ctx->priv;
|
||||
|
||||
if (vol->precision == PRECISION_FIXED) {
|
||||
vol->volume_i = (int)(vol->volume * 256 + 0.5);
|
||||
vol->volume = vol->volume_i / 256.0;
|
||||
av_log(ctx, AV_LOG_VERBOSE, "volume:(%d/256)(%f)(%1.2fdB) precision:fixed\n",
|
||||
vol->volume_i, vol->volume, 20.0*log(vol->volume)/M_LN10);
|
||||
} else {
|
||||
av_log(ctx, AV_LOG_VERBOSE, "volume:(%f)(%1.2fdB) precision:%s\n",
|
||||
vol->volume, 20.0*log(vol->volume)/M_LN10,
|
||||
precision_str[vol->precision]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
VolumeContext *vol = ctx->priv;
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
static const enum AVSampleFormat sample_fmts[][7] = {
|
||||
[PRECISION_FIXED] = {
|
||||
AV_SAMPLE_FMT_U8,
|
||||
AV_SAMPLE_FMT_U8P,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_S32,
|
||||
AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
},
|
||||
[PRECISION_FLOAT] = {
|
||||
AV_SAMPLE_FMT_FLT,
|
||||
AV_SAMPLE_FMT_FLTP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
},
|
||||
[PRECISION_DOUBLE] = {
|
||||
AV_SAMPLE_FMT_DBL,
|
||||
AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
}
|
||||
};
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
|
||||
formats = ff_make_format_list(sample_fmts[vol->precision]);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void scale_samples_u8(uint8_t *dst, const uint8_t *src,
|
||||
int nb_samples, int volume)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
dst[i] = av_clip_uint8(((((int64_t)src[i] - 128) * volume + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
static inline void scale_samples_u8_small(uint8_t *dst, const uint8_t *src,
|
||||
int nb_samples, int volume)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
dst[i] = av_clip_uint8((((src[i] - 128) * volume + 128) >> 8) + 128);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s16(uint8_t *dst, const uint8_t *src,
|
||||
int nb_samples, int volume)
|
||||
{
|
||||
int i;
|
||||
int16_t *smp_dst = (int16_t *)dst;
|
||||
const int16_t *smp_src = (const int16_t *)src;
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clip_int16(((int64_t)smp_src[i] * volume + 128) >> 8);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s16_small(uint8_t *dst, const uint8_t *src,
|
||||
int nb_samples, int volume)
|
||||
{
|
||||
int i;
|
||||
int16_t *smp_dst = (int16_t *)dst;
|
||||
const int16_t *smp_src = (const int16_t *)src;
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clip_int16((smp_src[i] * volume + 128) >> 8);
|
||||
}
|
||||
|
||||
static inline void scale_samples_s32(uint8_t *dst, const uint8_t *src,
|
||||
int nb_samples, int volume)
|
||||
{
|
||||
int i;
|
||||
int32_t *smp_dst = (int32_t *)dst;
|
||||
const int32_t *smp_src = (const int32_t *)src;
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
smp_dst[i] = av_clipl_int32((((int64_t)smp_src[i] * volume + 128) >> 8));
|
||||
}
|
||||
|
||||
static av_cold void volume_init(VolumeContext *vol)
|
||||
{
|
||||
vol->samples_align = 1;
|
||||
|
||||
switch (av_get_packed_sample_fmt(vol->sample_fmt)) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
if (vol->volume_i < 0x1000000)
|
||||
vol->scale_samples = scale_samples_u8_small;
|
||||
else
|
||||
vol->scale_samples = scale_samples_u8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
if (vol->volume_i < 0x10000)
|
||||
vol->scale_samples = scale_samples_s16_small;
|
||||
else
|
||||
vol->scale_samples = scale_samples_s16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
vol->scale_samples = scale_samples_s32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
avpriv_float_dsp_init(&vol->fdsp, 0);
|
||||
vol->samples_align = 4;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
avpriv_float_dsp_init(&vol->fdsp, 0);
|
||||
vol->samples_align = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ARCH_X86)
|
||||
ff_volume_init_x86(vol);
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
VolumeContext *vol = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
|
||||
vol->sample_fmt = inlink->format;
|
||||
vol->channels = av_get_channel_layout_nb_channels(inlink->channel_layout);
|
||||
vol->planes = av_sample_fmt_is_planar(inlink->format) ? vol->channels : 1;
|
||||
|
||||
volume_init(vol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
VolumeContext *vol = inlink->dst->priv;
|
||||
AVFilterLink *outlink = inlink->dst->outputs[0];
|
||||
int nb_samples = buf->nb_samples;
|
||||
AVFrame *out_buf;
|
||||
|
||||
if (vol->volume == 1.0 || vol->volume_i == 256)
|
||||
return ff_filter_frame(outlink, buf);
|
||||
|
||||
/* do volume scaling in-place if input buffer is writable */
|
||||
if (av_frame_is_writable(buf)) {
|
||||
out_buf = buf;
|
||||
} else {
|
||||
out_buf = ff_get_audio_buffer(inlink, nb_samples);
|
||||
if (!out_buf)
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_copy_props(out_buf, buf);
|
||||
}
|
||||
|
||||
if (vol->precision != PRECISION_FIXED || vol->volume_i > 0) {
|
||||
int p, plane_samples;
|
||||
|
||||
if (av_sample_fmt_is_planar(buf->format))
|
||||
plane_samples = FFALIGN(nb_samples, vol->samples_align);
|
||||
else
|
||||
plane_samples = FFALIGN(nb_samples * vol->channels, vol->samples_align);
|
||||
|
||||
if (vol->precision == PRECISION_FIXED) {
|
||||
for (p = 0; p < vol->planes; p++) {
|
||||
vol->scale_samples(out_buf->extended_data[p],
|
||||
buf->extended_data[p], plane_samples,
|
||||
vol->volume_i);
|
||||
}
|
||||
} else if (av_get_packed_sample_fmt(vol->sample_fmt) == AV_SAMPLE_FMT_FLT) {
|
||||
for (p = 0; p < vol->planes; p++) {
|
||||
vol->fdsp.vector_fmul_scalar((float *)out_buf->extended_data[p],
|
||||
(const float *)buf->extended_data[p],
|
||||
vol->volume, plane_samples);
|
||||
}
|
||||
} else {
|
||||
for (p = 0; p < vol->planes; p++) {
|
||||
vol->fdsp.vector_dmul_scalar((double *)out_buf->extended_data[p],
|
||||
(const double *)buf->extended_data[p],
|
||||
vol->volume, plane_samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (buf != out_buf)
|
||||
av_frame_free(&buf);
|
||||
|
||||
return ff_filter_frame(outlink, out_buf);
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_volume_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_volume_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_volume = {
|
||||
.name = "volume",
|
||||
.description = NULL_IF_CONFIG_SMALL("Change input volume."),
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(VolumeContext),
|
||||
.priv_class = &volume_class,
|
||||
.init = init,
|
||||
.inputs = avfilter_af_volume_inputs,
|
||||
.outputs = avfilter_af_volume_outputs,
|
||||
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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
|
||||
* audio volume filter
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_AF_VOLUME_H
|
||||
#define AVFILTER_AF_VOLUME_H
|
||||
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/float_dsp.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
enum PrecisionType {
|
||||
PRECISION_FIXED = 0,
|
||||
PRECISION_FLOAT,
|
||||
PRECISION_DOUBLE,
|
||||
};
|
||||
|
||||
typedef struct VolumeContext {
|
||||
const AVClass *class;
|
||||
AVFloatDSPContext fdsp;
|
||||
enum PrecisionType precision;
|
||||
double volume;
|
||||
int volume_i;
|
||||
int channels;
|
||||
int planes;
|
||||
enum AVSampleFormat sample_fmt;
|
||||
|
||||
void (*scale_samples)(uint8_t *dst, const uint8_t *src, int nb_samples,
|
||||
int volume);
|
||||
int samples_align;
|
||||
} VolumeContext;
|
||||
|
||||
void ff_volume_init_x86(VolumeContext *vol);
|
||||
|
||||
#endif /* AVFILTER_AF_VOLUME_H */
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
/**
|
||||
* Number of samples at each PCM value.
|
||||
* histogram[0x8000 + i] is the number of samples at value i.
|
||||
* The extra element is there for symmetry.
|
||||
*/
|
||||
uint64_t histogram[0x10001];
|
||||
} VolDetectContext;
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_S16,
|
||||
AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
AVFilterFormats *formats;
|
||||
|
||||
if (!(formats = ff_make_format_list(sample_fmts)))
|
||||
return AVERROR(ENOMEM);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *samples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
VolDetectContext *vd = ctx->priv;
|
||||
int64_t layout = samples->channel_layout;
|
||||
int nb_samples = samples->nb_samples;
|
||||
int nb_channels = av_get_channel_layout_nb_channels(layout);
|
||||
int nb_planes = nb_channels;
|
||||
int plane, i;
|
||||
int16_t *pcm;
|
||||
|
||||
if (!av_sample_fmt_is_planar(samples->format)) {
|
||||
nb_samples *= nb_channels;
|
||||
nb_planes = 1;
|
||||
}
|
||||
for (plane = 0; plane < nb_planes; plane++) {
|
||||
pcm = (int16_t *)samples->extended_data[plane];
|
||||
for (i = 0; i < nb_samples; i++)
|
||||
vd->histogram[pcm[i] + 0x8000]++;
|
||||
}
|
||||
|
||||
return ff_filter_frame(inlink->dst->outputs[0], samples);
|
||||
}
|
||||
|
||||
#define MAX_DB 91
|
||||
|
||||
static inline double logdb(uint64_t v)
|
||||
{
|
||||
double d = v / (double)(0x8000 * 0x8000);
|
||||
if (!v)
|
||||
return MAX_DB;
|
||||
return log(d) * -4.3429448190325182765112891891660508229; /* -10/log(10) */
|
||||
}
|
||||
|
||||
static void print_stats(AVFilterContext *ctx)
|
||||
{
|
||||
VolDetectContext *vd = ctx->priv;
|
||||
int i, max_volume, shift;
|
||||
uint64_t nb_samples = 0, power = 0, nb_samples_shift = 0, sum = 0;
|
||||
uint64_t histdb[MAX_DB + 1] = { 0 };
|
||||
|
||||
for (i = 0; i < 0x10000; i++)
|
||||
nb_samples += vd->histogram[i];
|
||||
av_log(ctx, AV_LOG_INFO, "n_samples: %"PRId64"\n", nb_samples);
|
||||
if (!nb_samples)
|
||||
return;
|
||||
|
||||
/* If nb_samples > 1<<34, there is a risk of overflow in the
|
||||
multiplication or the sum: shift all histogram values to avoid that.
|
||||
The total number of samples must be recomputed to avoid rounding
|
||||
errors. */
|
||||
shift = av_log2(nb_samples >> 33);
|
||||
for (i = 0; i < 0x10000; i++) {
|
||||
nb_samples_shift += vd->histogram[i] >> shift;
|
||||
power += (i - 0x8000) * (i - 0x8000) * (vd->histogram[i] >> shift);
|
||||
}
|
||||
if (!nb_samples_shift)
|
||||
return;
|
||||
power = (power + nb_samples_shift / 2) / nb_samples_shift;
|
||||
av_assert0(power <= 0x8000 * 0x8000);
|
||||
av_log(ctx, AV_LOG_INFO, "mean_volume: %.1f dB\n", -logdb(power));
|
||||
|
||||
max_volume = 0x8000;
|
||||
while (max_volume > 0 && !vd->histogram[0x8000 + max_volume] &&
|
||||
!vd->histogram[0x8000 - max_volume])
|
||||
max_volume--;
|
||||
av_log(ctx, AV_LOG_INFO, "max_volume: %.1f dB\n", -logdb(max_volume * max_volume));
|
||||
|
||||
for (i = 0; i < 0x10000; i++)
|
||||
histdb[(int)logdb((i - 0x8000) * (i - 0x8000))] += vd->histogram[i];
|
||||
for (i = 0; i <= MAX_DB && !histdb[i]; i++);
|
||||
for (; i <= MAX_DB && sum < nb_samples / 1000; i++) {
|
||||
av_log(ctx, AV_LOG_INFO, "histogram_%ddb: %"PRId64"\n", i, histdb[i]);
|
||||
sum += histdb[i];
|
||||
}
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
print_stats(ctx);
|
||||
}
|
||||
|
||||
static const AVFilterPad volumedetect_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad volumedetect_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_volumedetect = {
|
||||
.name = "volumedetect",
|
||||
.description = NULL_IF_CONFIG_SMALL("Detect audio volume."),
|
||||
.priv_size = sizeof(VolDetectContext),
|
||||
.query_formats = query_formats,
|
||||
.uninit = uninit,
|
||||
.inputs = volumedetect_inputs,
|
||||
.outputs = volumedetect_outputs,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
AV_CH_FRONT_CENTER,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER,
|
||||
AV_CH_FRONT_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_LEFT|AV_CH_BACK_RIGHT|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_CENTER|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_LOW_FREQUENCY|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
AV_CH_FRONT_LEFT|AV_CH_FRONT_RIGHT|AV_CH_FRONT_CENTER|AV_CH_BACK_CENTER|AV_CH_SIDE_LEFT|AV_CH_SIDE_RIGHT|AV_CH_STEREO_LEFT|AV_CH_STEREO_RIGHT,
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* filter registration
|
||||
* Copyright (c) 2008 Vitor Sessak
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "avfilter.h"
|
||||
#include "config.h"
|
||||
#include "opencl_allkernels.h"
|
||||
|
||||
|
||||
#define REGISTER_FILTER(X, x, y) \
|
||||
{ \
|
||||
extern AVFilter avfilter_##y##_##x; \
|
||||
if (CONFIG_##X##_FILTER) \
|
||||
avfilter_register(&avfilter_##y##_##x); \
|
||||
}
|
||||
|
||||
#define REGISTER_FILTER_UNCONDITIONAL(x) \
|
||||
{ \
|
||||
extern AVFilter avfilter_##x; \
|
||||
avfilter_register(&avfilter_##x); \
|
||||
}
|
||||
|
||||
void avfilter_register_all(void)
|
||||
{
|
||||
static int initialized;
|
||||
|
||||
if (initialized)
|
||||
return;
|
||||
initialized = 1;
|
||||
|
||||
#if FF_API_ACONVERT_FILTER
|
||||
REGISTER_FILTER(ACONVERT, aconvert, af);
|
||||
#endif
|
||||
REGISTER_FILTER(ADELAY, adelay, af);
|
||||
REGISTER_FILTER(AECHO, aecho, af);
|
||||
REGISTER_FILTER(AFADE, afade, af);
|
||||
REGISTER_FILTER(AFORMAT, aformat, af);
|
||||
REGISTER_FILTER(AINTERLEAVE, ainterleave, af);
|
||||
REGISTER_FILTER(ALLPASS, allpass, af);
|
||||
REGISTER_FILTER(AMERGE, amerge, af);
|
||||
REGISTER_FILTER(AMIX, amix, af);
|
||||
REGISTER_FILTER(ANULL, anull, af);
|
||||
REGISTER_FILTER(APAD, apad, af);
|
||||
REGISTER_FILTER(APERMS, aperms, af);
|
||||
REGISTER_FILTER(APHASER, aphaser, af);
|
||||
REGISTER_FILTER(ARESAMPLE, aresample, af);
|
||||
REGISTER_FILTER(ASELECT, aselect, af);
|
||||
REGISTER_FILTER(ASENDCMD, asendcmd, af);
|
||||
REGISTER_FILTER(ASETNSAMPLES, asetnsamples, af);
|
||||
REGISTER_FILTER(ASETPTS, asetpts, af);
|
||||
REGISTER_FILTER(ASETRATE, asetrate, af);
|
||||
REGISTER_FILTER(ASETTB, asettb, af);
|
||||
REGISTER_FILTER(ASHOWINFO, ashowinfo, af);
|
||||
REGISTER_FILTER(ASPLIT, asplit, af);
|
||||
REGISTER_FILTER(ASTATS, astats, af);
|
||||
REGISTER_FILTER(ASTREAMSYNC, astreamsync, af);
|
||||
REGISTER_FILTER(ASYNCTS, asyncts, af);
|
||||
REGISTER_FILTER(ATEMPO, atempo, af);
|
||||
REGISTER_FILTER(ATRIM, atrim, af);
|
||||
REGISTER_FILTER(AZMQ, azmq, af);
|
||||
REGISTER_FILTER(BANDPASS, bandpass, af);
|
||||
REGISTER_FILTER(BANDREJECT, bandreject, af);
|
||||
REGISTER_FILTER(BASS, bass, af);
|
||||
REGISTER_FILTER(BIQUAD, biquad, af);
|
||||
REGISTER_FILTER(CHANNELMAP, channelmap, af);
|
||||
REGISTER_FILTER(CHANNELSPLIT, channelsplit, af);
|
||||
REGISTER_FILTER(COMPAND, compand, af);
|
||||
REGISTER_FILTER(EARWAX, earwax, af);
|
||||
REGISTER_FILTER(EBUR128, ebur128, af);
|
||||
REGISTER_FILTER(EQUALIZER, equalizer, af);
|
||||
REGISTER_FILTER(HIGHPASS, highpass, af);
|
||||
REGISTER_FILTER(JOIN, join, af);
|
||||
REGISTER_FILTER(LADSPA, ladspa, af);
|
||||
REGISTER_FILTER(LOWPASS, lowpass, af);
|
||||
REGISTER_FILTER(PAN, pan, af);
|
||||
REGISTER_FILTER(REPLAYGAIN, replaygain, af);
|
||||
REGISTER_FILTER(RESAMPLE, resample, af);
|
||||
REGISTER_FILTER(SILENCEDETECT, silencedetect, af);
|
||||
REGISTER_FILTER(TREBLE, treble, af);
|
||||
REGISTER_FILTER(VOLUME, volume, af);
|
||||
REGISTER_FILTER(VOLUMEDETECT, volumedetect, af);
|
||||
|
||||
REGISTER_FILTER(AEVALSRC, aevalsrc, asrc);
|
||||
REGISTER_FILTER(ANULLSRC, anullsrc, asrc);
|
||||
REGISTER_FILTER(FLITE, flite, asrc);
|
||||
REGISTER_FILTER(SINE, sine, asrc);
|
||||
|
||||
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);
|
||||
REGISTER_FILTER(BLACKFRAME, blackframe, vf);
|
||||
REGISTER_FILTER(BLEND, blend, vf);
|
||||
REGISTER_FILTER(BOXBLUR, boxblur, vf);
|
||||
REGISTER_FILTER(COLORBALANCE, colorbalance, vf);
|
||||
REGISTER_FILTER(COLORCHANNELMIXER, colorchannelmixer, vf);
|
||||
REGISTER_FILTER(COLORMATRIX, colormatrix, vf);
|
||||
REGISTER_FILTER(COPY, copy, vf);
|
||||
REGISTER_FILTER(CROP, crop, vf);
|
||||
REGISTER_FILTER(CROPDETECT, cropdetect, vf);
|
||||
REGISTER_FILTER(CURVES, curves, vf);
|
||||
REGISTER_FILTER(DCTDNOIZ, dctdnoiz, vf);
|
||||
REGISTER_FILTER(DECIMATE, decimate, vf);
|
||||
REGISTER_FILTER(DELOGO, delogo, vf);
|
||||
REGISTER_FILTER(DESHAKE, deshake, vf);
|
||||
REGISTER_FILTER(DRAWBOX, drawbox, vf);
|
||||
REGISTER_FILTER(DRAWGRID, drawgrid, vf);
|
||||
REGISTER_FILTER(DRAWTEXT, drawtext, vf);
|
||||
REGISTER_FILTER(EDGEDETECT, edgedetect, vf);
|
||||
REGISTER_FILTER(EXTRACTPLANES, extractplanes, vf);
|
||||
REGISTER_FILTER(FADE, fade, vf);
|
||||
REGISTER_FILTER(FIELD, field, vf);
|
||||
REGISTER_FILTER(FIELDMATCH, fieldmatch, vf);
|
||||
REGISTER_FILTER(FIELDORDER, fieldorder, vf);
|
||||
REGISTER_FILTER(FORMAT, format, vf);
|
||||
REGISTER_FILTER(FPS, fps, vf);
|
||||
REGISTER_FILTER(FRAMESTEP, framestep, vf);
|
||||
REGISTER_FILTER(FREI0R, frei0r, vf);
|
||||
REGISTER_FILTER(GEQ, geq, vf);
|
||||
REGISTER_FILTER(GRADFUN, gradfun, vf);
|
||||
REGISTER_FILTER(HALDCLUT, haldclut, vf);
|
||||
REGISTER_FILTER(HFLIP, hflip, vf);
|
||||
REGISTER_FILTER(HISTEQ, histeq, vf);
|
||||
REGISTER_FILTER(HISTOGRAM, histogram, vf);
|
||||
REGISTER_FILTER(HQDN3D, hqdn3d, vf);
|
||||
REGISTER_FILTER(HUE, hue, vf);
|
||||
REGISTER_FILTER(IDET, idet, vf);
|
||||
REGISTER_FILTER(IL, il, vf);
|
||||
REGISTER_FILTER(INTERLACE, interlace, vf);
|
||||
REGISTER_FILTER(INTERLEAVE, interleave, vf);
|
||||
REGISTER_FILTER(KERNDEINT, kerndeint, vf);
|
||||
REGISTER_FILTER(LUT3D, lut3d, vf);
|
||||
REGISTER_FILTER(LUT, lut, vf);
|
||||
REGISTER_FILTER(LUTRGB, lutrgb, vf);
|
||||
REGISTER_FILTER(LUTYUV, lutyuv, vf);
|
||||
REGISTER_FILTER(MCDEINT, mcdeint, vf);
|
||||
REGISTER_FILTER(MERGEPLANES, mergeplanes, vf);
|
||||
REGISTER_FILTER(MP, mp, vf);
|
||||
REGISTER_FILTER(MPDECIMATE, mpdecimate, vf);
|
||||
REGISTER_FILTER(NEGATE, negate, vf);
|
||||
REGISTER_FILTER(NOFORMAT, noformat, vf);
|
||||
REGISTER_FILTER(NOISE, noise, vf);
|
||||
REGISTER_FILTER(NULL, null, vf);
|
||||
REGISTER_FILTER(OCV, ocv, vf);
|
||||
REGISTER_FILTER(OVERLAY, overlay, vf);
|
||||
REGISTER_FILTER(OWDENOISE, owdenoise, vf);
|
||||
REGISTER_FILTER(PAD, pad, vf);
|
||||
REGISTER_FILTER(PERMS, perms, vf);
|
||||
REGISTER_FILTER(PERSPECTIVE, perspective, vf);
|
||||
REGISTER_FILTER(PHASE, phase, vf);
|
||||
REGISTER_FILTER(PIXDESCTEST, pixdesctest, vf);
|
||||
REGISTER_FILTER(PP, pp, vf);
|
||||
REGISTER_FILTER(PSNR, psnr, vf);
|
||||
REGISTER_FILTER(PULLUP, pullup, vf);
|
||||
REGISTER_FILTER(REMOVELOGO, removelogo, vf);
|
||||
REGISTER_FILTER(ROTATE, rotate, vf);
|
||||
REGISTER_FILTER(SAB, sab, vf);
|
||||
REGISTER_FILTER(SCALE, scale, vf);
|
||||
REGISTER_FILTER(SELECT, select, vf);
|
||||
REGISTER_FILTER(SENDCMD, sendcmd, vf);
|
||||
REGISTER_FILTER(SEPARATEFIELDS, separatefields, vf);
|
||||
REGISTER_FILTER(SETDAR, setdar, vf);
|
||||
REGISTER_FILTER(SETFIELD, setfield, vf);
|
||||
REGISTER_FILTER(SETPTS, setpts, vf);
|
||||
REGISTER_FILTER(SETSAR, setsar, vf);
|
||||
REGISTER_FILTER(SETTB, settb, vf);
|
||||
REGISTER_FILTER(SHOWINFO, showinfo, vf);
|
||||
REGISTER_FILTER(SMARTBLUR, smartblur, vf);
|
||||
REGISTER_FILTER(SPLIT, split, vf);
|
||||
REGISTER_FILTER(SPP, spp, vf);
|
||||
REGISTER_FILTER(STEREO3D, stereo3d, vf);
|
||||
REGISTER_FILTER(SUBTITLES, subtitles, vf);
|
||||
REGISTER_FILTER(SUPER2XSAI, super2xsai, vf);
|
||||
REGISTER_FILTER(SWAPUV, swapuv, vf);
|
||||
REGISTER_FILTER(TELECINE, telecine, vf);
|
||||
REGISTER_FILTER(THUMBNAIL, thumbnail, vf);
|
||||
REGISTER_FILTER(TILE, tile, vf);
|
||||
REGISTER_FILTER(TINTERLACE, tinterlace, vf);
|
||||
REGISTER_FILTER(TRANSPOSE, transpose, vf);
|
||||
REGISTER_FILTER(TRIM, trim, vf);
|
||||
REGISTER_FILTER(UNSHARP, unsharp, vf);
|
||||
REGISTER_FILTER(VFLIP, vflip, vf);
|
||||
REGISTER_FILTER(VIDSTABDETECT, vidstabdetect, vf);
|
||||
REGISTER_FILTER(VIDSTABTRANSFORM, vidstabtransform, vf);
|
||||
REGISTER_FILTER(VIGNETTE, vignette, vf);
|
||||
REGISTER_FILTER(W3FDIF, w3fdif, vf);
|
||||
REGISTER_FILTER(YADIF, yadif, vf);
|
||||
REGISTER_FILTER(ZMQ, zmq, vf);
|
||||
|
||||
REGISTER_FILTER(CELLAUTO, cellauto, vsrc);
|
||||
REGISTER_FILTER(COLOR, color, vsrc);
|
||||
REGISTER_FILTER(FREI0R, frei0r_src, vsrc);
|
||||
REGISTER_FILTER(HALDCLUTSRC, haldclutsrc, vsrc);
|
||||
REGISTER_FILTER(LIFE, life, vsrc);
|
||||
REGISTER_FILTER(MANDELBROT, mandelbrot, vsrc);
|
||||
REGISTER_FILTER(MPTESTSRC, mptestsrc, vsrc);
|
||||
REGISTER_FILTER(NULLSRC, nullsrc, vsrc);
|
||||
REGISTER_FILTER(RGBTESTSRC, rgbtestsrc, vsrc);
|
||||
REGISTER_FILTER(SMPTEBARS, smptebars, vsrc);
|
||||
REGISTER_FILTER(SMPTEHDBARS, smptehdbars, vsrc);
|
||||
REGISTER_FILTER(TESTSRC, testsrc, vsrc);
|
||||
|
||||
REGISTER_FILTER(NULLSINK, nullsink, vsink);
|
||||
|
||||
/* multimedia filters */
|
||||
REGISTER_FILTER(AVECTORSCOPE, avectorscope, avf);
|
||||
REGISTER_FILTER(CONCAT, concat, avf);
|
||||
REGISTER_FILTER(SHOWSPECTRUM, showspectrum, avf);
|
||||
REGISTER_FILTER(SHOWWAVES, showwaves, avf);
|
||||
|
||||
/* multimedia sources */
|
||||
REGISTER_FILTER(AMOVIE, amovie, avsrc);
|
||||
REGISTER_FILTER(MOVIE, movie, avsrc);
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
REGISTER_FILTER_UNCONDITIONAL(vsink_ffbuffersink);
|
||||
REGISTER_FILTER_UNCONDITIONAL(asink_ffabuffersink);
|
||||
#endif
|
||||
|
||||
/* those filters are part of public or internal API => registered
|
||||
* unconditionally */
|
||||
REGISTER_FILTER_UNCONDITIONAL(asrc_abuffer);
|
||||
REGISTER_FILTER_UNCONDITIONAL(vsrc_buffer);
|
||||
REGISTER_FILTER_UNCONDITIONAL(asink_abuffer);
|
||||
REGISTER_FILTER_UNCONDITIONAL(vsink_buffer);
|
||||
REGISTER_FILTER_UNCONDITIONAL(af_afifo);
|
||||
REGISTER_FILTER_UNCONDITIONAL(vf_fifo);
|
||||
ff_opencl_register_filter_kernel_code_all();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2010 S.N. Hemanth Meenakshisundaram <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/internal.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
static int null_filter_frame(AVFilterLink *link, AVFrame *frame)
|
||||
{
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_asink_anullsink_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = null_filter_frame,
|
||||
},
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFilter avfilter_asink_anullsink = {
|
||||
.name = "anullsink",
|
||||
.description = NULL_IF_CONFIG_SMALL("Do absolutely nothing with the input audio."),
|
||||
|
||||
.priv_size = 0,
|
||||
|
||||
.inputs = avfilter_asink_anullsink_inputs,
|
||||
.outputs = NULL,
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_ASRC_ABUFFER_H
|
||||
#define AVFILTER_ASRC_ABUFFER_H
|
||||
|
||||
#include "avfilter.h"
|
||||
|
||||
/**
|
||||
* @file
|
||||
* memory buffer source for audio
|
||||
*
|
||||
* @deprecated use buffersrc.h instead.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Queue an audio buffer to the audio buffer source.
|
||||
*
|
||||
* @param abuffersrc audio source buffer context
|
||||
* @param data pointers to the samples planes
|
||||
* @param linesize linesizes of each audio buffer plane
|
||||
* @param nb_samples number of samples per channel
|
||||
* @param sample_fmt sample format of the audio data
|
||||
* @param ch_layout channel layout of the audio data
|
||||
* @param planar flag to indicate if audio data is planar or packed
|
||||
* @param pts presentation timestamp of the audio buffer
|
||||
* @param flags unused
|
||||
*
|
||||
* @deprecated use av_buffersrc_add_ref() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_asrc_buffer_add_samples(AVFilterContext *abuffersrc,
|
||||
uint8_t *data[8], int linesize[8],
|
||||
int nb_samples, int sample_rate,
|
||||
int sample_fmt, int64_t ch_layout, int planar,
|
||||
int64_t pts, int av_unused flags);
|
||||
|
||||
/**
|
||||
* Queue an audio buffer to the audio buffer source.
|
||||
*
|
||||
* This is similar to av_asrc_buffer_add_samples(), but the samples
|
||||
* are stored in a buffer with known size.
|
||||
*
|
||||
* @param abuffersrc audio source buffer context
|
||||
* @param buf pointer to the samples data, packed is assumed
|
||||
* @param size the size in bytes of the buffer, it must contain an
|
||||
* integer number of samples
|
||||
* @param sample_fmt sample format of the audio data
|
||||
* @param ch_layout channel layout of the audio data
|
||||
* @param pts presentation timestamp of the audio buffer
|
||||
* @param flags unused
|
||||
*
|
||||
* @deprecated use av_buffersrc_add_ref() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_asrc_buffer_add_buffer(AVFilterContext *abuffersrc,
|
||||
uint8_t *buf, int buf_size,
|
||||
int sample_rate,
|
||||
int sample_fmt, int64_t ch_layout, int planar,
|
||||
int64_t pts, int av_unused flags);
|
||||
|
||||
/**
|
||||
* Queue an audio buffer to the audio buffer source.
|
||||
*
|
||||
* @param abuffersrc audio source buffer context
|
||||
* @param samplesref buffer ref to queue
|
||||
* @param flags unused
|
||||
*
|
||||
* @deprecated use av_buffersrc_add_ref() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_asrc_buffer_add_audio_buffer_ref(AVFilterContext *abuffersrc,
|
||||
AVFilterBufferRef *samplesref,
|
||||
int av_unused flags);
|
||||
|
||||
#endif /* AVFILTER_ASRC_ABUFFER_H */
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* eval audio source
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "internal.h"
|
||||
|
||||
static const char * const var_names[] = {
|
||||
"n", ///< number of frame
|
||||
"t", ///< timestamp expressed in seconds
|
||||
"s", ///< sample rate
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_N,
|
||||
VAR_T,
|
||||
VAR_S,
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *sample_rate_str;
|
||||
int sample_rate;
|
||||
int64_t chlayout;
|
||||
char *chlayout_str;
|
||||
int nb_channels;
|
||||
int64_t pts;
|
||||
AVExpr **expr;
|
||||
char *exprs;
|
||||
int nb_samples; ///< number of samples per requested frame
|
||||
int64_t duration;
|
||||
uint64_t n;
|
||||
double var_values[VAR_VARS_NB];
|
||||
} EvalContext;
|
||||
|
||||
#define OFFSET(x) offsetof(EvalContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption aevalsrc_options[]= {
|
||||
{ "exprs", "set the '|'-separated list of channels expressions", OFFSET(exprs), AV_OPT_TYPE_STRING, {.str = NULL}, .flags = FLAGS },
|
||||
{ "nb_samples", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 1024}, 0, INT_MAX, FLAGS },
|
||||
{ "n", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 1024}, 0, INT_MAX, FLAGS },
|
||||
{ "sample_rate", "set the sample rate", OFFSET(sample_rate_str), AV_OPT_TYPE_STRING, {.str = "44100"}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ "s", "set the sample rate", OFFSET(sample_rate_str), AV_OPT_TYPE_STRING, {.str = "44100"}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ "duration", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = -1}, -1, INT64_MAX, FLAGS },
|
||||
{ "d", "set audio duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64 = -1}, -1, INT64_MAX, FLAGS },
|
||||
{ "channel_layout", "set channel layout", OFFSET(chlayout_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ "c", "set channel layout", OFFSET(chlayout_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(aevalsrc);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
EvalContext *eval = ctx->priv;
|
||||
char *args1 = av_strdup(eval->exprs);
|
||||
char *expr, *buf;
|
||||
int ret;
|
||||
|
||||
if (!args1) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Channels expressions list is empty\n");
|
||||
ret = eval->exprs ? AVERROR(ENOMEM) : AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* parse expressions */
|
||||
buf = args1;
|
||||
while (expr = av_strtok(buf, "|", &buf)) {
|
||||
if (!av_dynarray2_add((void **)&eval->expr, &eval->nb_channels, sizeof(*eval->expr), NULL)) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
ret = av_expr_parse(&eval->expr[eval->nb_channels - 1], expr, var_names,
|
||||
NULL, NULL, NULL, NULL, 0, ctx);
|
||||
if (ret < 0)
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (eval->chlayout_str) {
|
||||
int n;
|
||||
ret = ff_parse_channel_layout(&eval->chlayout, NULL, eval->chlayout_str, ctx);
|
||||
if (ret < 0)
|
||||
goto end;
|
||||
|
||||
n = av_get_channel_layout_nb_channels(eval->chlayout);
|
||||
if (n != eval->nb_channels) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Mismatch between the specified number of channels '%d' "
|
||||
"and the number of channels '%d' in the specified channel layout '%s'\n",
|
||||
eval->nb_channels, n, eval->chlayout_str);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
} else {
|
||||
/* guess channel layout from nb expressions/channels */
|
||||
eval->chlayout = av_get_default_channel_layout(eval->nb_channels);
|
||||
if (!eval->chlayout && eval->nb_channels <= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid number of channels '%d' provided\n",
|
||||
eval->nb_channels);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ret = ff_parse_sample_rate(&eval->sample_rate, eval->sample_rate_str, ctx)))
|
||||
goto end;
|
||||
eval->n = 0;
|
||||
|
||||
end:
|
||||
av_free(args1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
EvalContext *eval = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < eval->nb_channels; i++) {
|
||||
av_expr_free(eval->expr[i]);
|
||||
eval->expr[i] = NULL;
|
||||
}
|
||||
av_freep(&eval->expr);
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *outlink)
|
||||
{
|
||||
EvalContext *eval = outlink->src->priv;
|
||||
char buf[128];
|
||||
|
||||
outlink->time_base = (AVRational){1, eval->sample_rate};
|
||||
outlink->sample_rate = eval->sample_rate;
|
||||
|
||||
eval->var_values[VAR_S] = eval->sample_rate;
|
||||
|
||||
av_get_channel_layout_string(buf, sizeof(buf), 0, eval->chlayout);
|
||||
|
||||
av_log(outlink->src, AV_LOG_VERBOSE,
|
||||
"sample_rate:%d chlayout:%s duration:%"PRId64"\n",
|
||||
eval->sample_rate, buf, eval->duration);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
EvalContext *eval = ctx->priv;
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_DBLP, AV_SAMPLE_FMT_NONE };
|
||||
int64_t chlayouts[] = { eval->chlayout ? eval->chlayout : FF_COUNT2LAYOUT(eval->nb_channels) , -1 };
|
||||
int sample_rates[] = { eval->sample_rate, -1 };
|
||||
|
||||
ff_set_common_formats (ctx, ff_make_format_list(sample_fmts));
|
||||
ff_set_common_channel_layouts(ctx, avfilter_make_format64_list(chlayouts));
|
||||
ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
EvalContext *eval = outlink->src->priv;
|
||||
AVFrame *samplesref;
|
||||
int i, j;
|
||||
int64_t t = av_rescale(eval->n, AV_TIME_BASE, eval->sample_rate);
|
||||
|
||||
if (eval->duration >= 0 && t >= eval->duration)
|
||||
return AVERROR_EOF;
|
||||
|
||||
samplesref = ff_get_audio_buffer(outlink, eval->nb_samples);
|
||||
if (!samplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
/* evaluate expression for each single sample and for each channel */
|
||||
for (i = 0; i < eval->nb_samples; i++, eval->n++) {
|
||||
eval->var_values[VAR_N] = eval->n;
|
||||
eval->var_values[VAR_T] = eval->var_values[VAR_N] * (double)1/eval->sample_rate;
|
||||
|
||||
for (j = 0; j < eval->nb_channels; j++) {
|
||||
*((double *) samplesref->extended_data[j] + i) =
|
||||
av_expr_eval(eval->expr[j], eval->var_values, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
samplesref->pts = eval->pts;
|
||||
samplesref->sample_rate = eval->sample_rate;
|
||||
eval->pts += eval->nb_samples;
|
||||
|
||||
return ff_filter_frame(outlink, samplesref);
|
||||
}
|
||||
|
||||
static const AVFilterPad aevalsrc_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asrc_aevalsrc = {
|
||||
.name = "aevalsrc",
|
||||
.description = NULL_IF_CONFIG_SMALL("Generate an audio signal generated by an expression."),
|
||||
.query_formats = query_formats,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(EvalContext),
|
||||
.inputs = NULL,
|
||||
.outputs = aevalsrc_outputs,
|
||||
.priv_class = &aevalsrc_class,
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2010 S.N. Hemanth Meenakshisundaram <smeenaks ucsd edu>
|
||||
* Copyright 2010 Stefano Sabatini <stefano.sabatini-lala poste it>
|
||||
*
|
||||
* 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
|
||||
* null audio source
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *channel_layout_str;
|
||||
uint64_t channel_layout;
|
||||
char *sample_rate_str;
|
||||
int sample_rate;
|
||||
int nb_samples; ///< number of samples per requested frame
|
||||
int64_t pts;
|
||||
} ANullContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ANullContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption anullsrc_options[]= {
|
||||
{ "channel_layout", "set channel_layout", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, FLAGS },
|
||||
{ "cl", "set channel_layout", OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, FLAGS },
|
||||
{ "sample_rate", "set sample rate", OFFSET(sample_rate_str) , AV_OPT_TYPE_STRING, {.str = "44100"}, 0, 0, FLAGS },
|
||||
{ "r", "set sample rate", OFFSET(sample_rate_str) , AV_OPT_TYPE_STRING, {.str = "44100"}, 0, 0, FLAGS },
|
||||
{ "nb_samples", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 1024}, 0, INT_MAX, FLAGS },
|
||||
{ "n", "set the number of samples per requested frame", OFFSET(nb_samples), AV_OPT_TYPE_INT, {.i64 = 1024}, 0, INT_MAX, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(anullsrc);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ANullContext *null = ctx->priv;
|
||||
int ret;
|
||||
|
||||
if ((ret = ff_parse_sample_rate(&null->sample_rate,
|
||||
null->sample_rate_str, ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
if ((ret = ff_parse_channel_layout(&null->channel_layout, NULL,
|
||||
null->channel_layout_str, ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
ANullContext *null = ctx->priv;
|
||||
int64_t chlayouts[] = { null->channel_layout, -1 };
|
||||
int sample_rates[] = { null->sample_rate, -1 };
|
||||
|
||||
ff_set_common_formats (ctx, ff_all_formats(AVMEDIA_TYPE_AUDIO));
|
||||
ff_set_common_channel_layouts(ctx, avfilter_make_format64_list(chlayouts));
|
||||
ff_set_common_samplerates (ctx, ff_make_format_list(sample_rates));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *outlink)
|
||||
{
|
||||
ANullContext *null = outlink->src->priv;
|
||||
char buf[128];
|
||||
|
||||
av_get_channel_layout_string(buf, sizeof(buf), 0, null->channel_layout);
|
||||
av_log(outlink->src, AV_LOG_VERBOSE,
|
||||
"sample_rate:%d channel_layout:'%s' nb_samples:%d\n",
|
||||
null->sample_rate, buf, null->nb_samples);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
int ret;
|
||||
ANullContext *null = outlink->src->priv;
|
||||
AVFrame *samplesref;
|
||||
|
||||
samplesref = ff_get_audio_buffer(outlink, null->nb_samples);
|
||||
if (!samplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
samplesref->pts = null->pts;
|
||||
samplesref->channel_layout = null->channel_layout;
|
||||
samplesref->sample_rate = outlink->sample_rate;
|
||||
|
||||
ret = ff_filter_frame(outlink, av_frame_clone(samplesref));
|
||||
av_frame_free(&samplesref);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
null->pts += null->nb_samples;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_asrc_anullsrc_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asrc_anullsrc = {
|
||||
.name = "anullsrc",
|
||||
.description = NULL_IF_CONFIG_SMALL("Null audio source, return empty audio frames."),
|
||||
.init = init,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(ANullContext),
|
||||
.inputs = NULL,
|
||||
.outputs = avfilter_asrc_anullsrc_outputs,
|
||||
.priv_class = &anullsrc_class,
|
||||
};
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* flite voice synth source
|
||||
*/
|
||||
|
||||
#include <flite/flite.h>
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/file.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *voice_str;
|
||||
char *textfile;
|
||||
char *text;
|
||||
cst_wave *wave;
|
||||
int16_t *wave_samples;
|
||||
int wave_nb_samples;
|
||||
int list_voices;
|
||||
cst_voice *voice;
|
||||
struct voice_entry *voice_entry;
|
||||
int64_t pts;
|
||||
int frame_nb_samples; ///< number of samples per frame
|
||||
} FliteContext;
|
||||
|
||||
#define OFFSET(x) offsetof(FliteContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
static const AVOption flite_options[] = {
|
||||
{ "list_voices", "list voices and exit", OFFSET(list_voices), AV_OPT_TYPE_INT, {.i64=0}, 0, 1, FLAGS },
|
||||
{ "nb_samples", "set number of samples per frame", OFFSET(frame_nb_samples), AV_OPT_TYPE_INT, {.i64=512}, 0, INT_MAX, FLAGS },
|
||||
{ "n", "set number of samples per frame", OFFSET(frame_nb_samples), AV_OPT_TYPE_INT, {.i64=512}, 0, INT_MAX, FLAGS },
|
||||
{ "text", "set text to speak", OFFSET(text), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ "textfile", "set filename of the text to speak", OFFSET(textfile), AV_OPT_TYPE_STRING, {.str=NULL}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ "v", "set voice", OFFSET(voice_str), AV_OPT_TYPE_STRING, {.str="kal"}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ "voice", "set voice", OFFSET(voice_str), AV_OPT_TYPE_STRING, {.str="kal"}, CHAR_MIN, CHAR_MAX, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(flite);
|
||||
|
||||
static volatile int flite_inited = 0;
|
||||
|
||||
/* declare functions for all the supported voices */
|
||||
#define DECLARE_REGISTER_VOICE_FN(name) \
|
||||
cst_voice *register_cmu_us_## name(const char *); \
|
||||
void unregister_cmu_us_## name(cst_voice *);
|
||||
DECLARE_REGISTER_VOICE_FN(awb);
|
||||
DECLARE_REGISTER_VOICE_FN(kal);
|
||||
DECLARE_REGISTER_VOICE_FN(kal16);
|
||||
DECLARE_REGISTER_VOICE_FN(rms);
|
||||
DECLARE_REGISTER_VOICE_FN(slt);
|
||||
|
||||
struct voice_entry {
|
||||
const char *name;
|
||||
cst_voice * (*register_fn)(const char *);
|
||||
void (*unregister_fn)(cst_voice *);
|
||||
cst_voice *voice;
|
||||
unsigned usage_count;
|
||||
} voice_entry;
|
||||
|
||||
#define MAKE_VOICE_STRUCTURE(voice_name) { \
|
||||
.name = #voice_name, \
|
||||
.register_fn = register_cmu_us_ ## voice_name, \
|
||||
.unregister_fn = unregister_cmu_us_ ## voice_name, \
|
||||
}
|
||||
static struct voice_entry voice_entries[] = {
|
||||
MAKE_VOICE_STRUCTURE(awb),
|
||||
MAKE_VOICE_STRUCTURE(kal),
|
||||
MAKE_VOICE_STRUCTURE(kal16),
|
||||
MAKE_VOICE_STRUCTURE(rms),
|
||||
MAKE_VOICE_STRUCTURE(slt),
|
||||
};
|
||||
|
||||
static void list_voices(void *log_ctx, const char *sep)
|
||||
{
|
||||
int i, n = FF_ARRAY_ELEMS(voice_entries);
|
||||
for (i = 0; i < n; i++)
|
||||
av_log(log_ctx, AV_LOG_INFO, "%s%s",
|
||||
voice_entries[i].name, i < (n-1) ? sep : "\n");
|
||||
}
|
||||
|
||||
static int select_voice(struct voice_entry **entry_ret, const char *voice_name, void *log_ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(voice_entries); i++) {
|
||||
struct voice_entry *entry = &voice_entries[i];
|
||||
if (!strcmp(entry->name, voice_name)) {
|
||||
if (!entry->voice)
|
||||
entry->voice = entry->register_fn(NULL);
|
||||
if (!entry->voice) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Could not register voice '%s'\n", voice_name);
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
entry->usage_count++;
|
||||
*entry_ret = entry;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Could not find voice '%s'\n", voice_name);
|
||||
av_log(log_ctx, AV_LOG_INFO, "Choose between the voices: ");
|
||||
list_voices(log_ctx, ", ");
|
||||
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
FliteContext *flite = ctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
if (flite->list_voices) {
|
||||
list_voices(ctx, "\n");
|
||||
return AVERROR_EXIT;
|
||||
}
|
||||
|
||||
if (!flite_inited) {
|
||||
if (flite_init() < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "flite initialization failed\n");
|
||||
return AVERROR_UNKNOWN;
|
||||
}
|
||||
flite_inited++;
|
||||
}
|
||||
|
||||
if ((ret = select_voice(&flite->voice_entry, flite->voice_str, ctx)) < 0)
|
||||
return ret;
|
||||
flite->voice = flite->voice_entry->voice;
|
||||
|
||||
if (flite->textfile && flite->text) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Both text and textfile options set: only one must be specified\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (flite->textfile) {
|
||||
uint8_t *textbuf;
|
||||
size_t textbuf_size;
|
||||
|
||||
if ((ret = av_file_map(flite->textfile, &textbuf, &textbuf_size, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"The text file '%s' could not be read: %s\n",
|
||||
flite->textfile, av_err2str(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!(flite->text = av_malloc(textbuf_size+1)))
|
||||
return AVERROR(ENOMEM);
|
||||
memcpy(flite->text, textbuf, textbuf_size);
|
||||
flite->text[textbuf_size] = 0;
|
||||
av_file_unmap(textbuf, textbuf_size);
|
||||
}
|
||||
|
||||
if (!flite->text) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"No speech text specified, specify the 'text' or 'textfile' option\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
/* synth all the file data in block */
|
||||
flite->wave = flite_text_to_wave(flite->text, flite->voice);
|
||||
flite->wave_samples = flite->wave->samples;
|
||||
flite->wave_nb_samples = flite->wave->num_samples;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
FliteContext *flite = ctx->priv;
|
||||
|
||||
if (!--flite->voice_entry->usage_count)
|
||||
flite->voice_entry->unregister_fn(flite->voice);
|
||||
flite->voice = NULL;
|
||||
flite->voice_entry = NULL;
|
||||
delete_wave(flite->wave);
|
||||
flite->wave = NULL;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
FliteContext *flite = ctx->priv;
|
||||
|
||||
AVFilterChannelLayouts *chlayouts = NULL;
|
||||
int64_t chlayout = av_get_default_channel_layout(flite->wave->num_channels);
|
||||
AVFilterFormats *sample_formats = NULL;
|
||||
AVFilterFormats *sample_rates = NULL;
|
||||
|
||||
ff_add_channel_layout(&chlayouts, chlayout);
|
||||
ff_set_common_channel_layouts(ctx, chlayouts);
|
||||
ff_add_format(&sample_formats, AV_SAMPLE_FMT_S16);
|
||||
ff_set_common_formats(ctx, sample_formats);
|
||||
ff_add_format(&sample_rates, flite->wave->sample_rate);
|
||||
ff_set_common_samplerates (ctx, sample_rates);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
FliteContext *flite = ctx->priv;
|
||||
|
||||
outlink->sample_rate = flite->wave->sample_rate;
|
||||
outlink->time_base = (AVRational){1, flite->wave->sample_rate};
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "voice:%s fmt:%s sample_rate:%d\n",
|
||||
flite->voice_str,
|
||||
av_get_sample_fmt_name(outlink->format), outlink->sample_rate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFrame *samplesref;
|
||||
FliteContext *flite = outlink->src->priv;
|
||||
int nb_samples = FFMIN(flite->wave_nb_samples, flite->frame_nb_samples);
|
||||
|
||||
if (!nb_samples)
|
||||
return AVERROR_EOF;
|
||||
|
||||
samplesref = ff_get_audio_buffer(outlink, nb_samples);
|
||||
if (!samplesref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
memcpy(samplesref->data[0], flite->wave_samples,
|
||||
nb_samples * flite->wave->num_channels * 2);
|
||||
samplesref->pts = flite->pts;
|
||||
av_frame_set_pkt_pos(samplesref, -1);
|
||||
av_frame_set_sample_rate(samplesref, flite->wave->sample_rate);
|
||||
flite->pts += nb_samples;
|
||||
flite->wave_samples += nb_samples * flite->wave->num_channels;
|
||||
flite->wave_nb_samples -= nb_samples;
|
||||
|
||||
return ff_filter_frame(outlink, samplesref);
|
||||
}
|
||||
|
||||
static const AVFilterPad flite_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_props,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asrc_flite = {
|
||||
.name = "flite",
|
||||
.description = NULL_IF_CONFIG_SMALL("Synthesize voice from text using libflite."),
|
||||
.query_formats = query_formats,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(FliteContext),
|
||||
.inputs = NULL,
|
||||
.outputs = flite_outputs,
|
||||
.priv_class = &flite_class,
|
||||
};
|
||||
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
double frequency;
|
||||
double beep_factor;
|
||||
int samples_per_frame;
|
||||
int sample_rate;
|
||||
int64_t duration;
|
||||
int16_t *sin;
|
||||
int64_t pts;
|
||||
uint32_t phi; ///< current phase of the sine (2pi = 1<<32)
|
||||
uint32_t dphi; ///< phase increment between two samples
|
||||
unsigned beep_period;
|
||||
unsigned beep_index;
|
||||
unsigned beep_length;
|
||||
uint32_t phi_beep; ///< current phase of the beep
|
||||
uint32_t dphi_beep; ///< phase increment of the beep
|
||||
} SineContext;
|
||||
|
||||
#define CONTEXT SineContext
|
||||
#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
|
||||
|
||||
#define OPT_GENERIC(name, field, def, min, max, descr, type, deffield, ...) \
|
||||
{ name, descr, offsetof(CONTEXT, field), AV_OPT_TYPE_ ## type, \
|
||||
{ .deffield = def }, min, max, FLAGS, __VA_ARGS__ }
|
||||
|
||||
#define OPT_INT(name, field, def, min, max, descr, ...) \
|
||||
OPT_GENERIC(name, field, def, min, max, descr, INT, i64, __VA_ARGS__)
|
||||
|
||||
#define OPT_DBL(name, field, def, min, max, descr, ...) \
|
||||
OPT_GENERIC(name, field, def, min, max, descr, DOUBLE, dbl, __VA_ARGS__)
|
||||
|
||||
#define OPT_DUR(name, field, def, min, max, descr, ...) \
|
||||
OPT_GENERIC(name, field, def, min, max, descr, DURATION, str, __VA_ARGS__)
|
||||
|
||||
static const AVOption sine_options[] = {
|
||||
OPT_DBL("frequency", frequency, 440, 0, DBL_MAX, "set the sine frequency"),
|
||||
OPT_DBL("f", frequency, 440, 0, DBL_MAX, "set the sine frequency"),
|
||||
OPT_DBL("beep_factor", beep_factor, 0, 0, DBL_MAX, "set the beep fequency factor"),
|
||||
OPT_DBL("b", beep_factor, 0, 0, DBL_MAX, "set the beep fequency factor"),
|
||||
OPT_INT("sample_rate", sample_rate, 44100, 1, INT_MAX, "set the sample rate"),
|
||||
OPT_INT("r", sample_rate, 44100, 1, INT_MAX, "set the sample rate"),
|
||||
OPT_DUR("duration", duration, 0, 0, INT64_MAX, "set the audio duration"),
|
||||
OPT_DUR("d", duration, 0, 0, INT64_MAX, "set the audio duration"),
|
||||
OPT_INT("samples_per_frame", samples_per_frame, 1024, 0, INT_MAX, "set the number of samples per frame"),
|
||||
{NULL}
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(sine);
|
||||
|
||||
#define LOG_PERIOD 15
|
||||
#define AMPLITUDE 4095
|
||||
#define AMPLITUDE_SHIFT 3
|
||||
|
||||
static void make_sin_table(int16_t *sin)
|
||||
{
|
||||
unsigned half_pi = 1 << (LOG_PERIOD - 2);
|
||||
unsigned ampls = AMPLITUDE << AMPLITUDE_SHIFT;
|
||||
uint64_t unit2 = (uint64_t)(ampls * ampls) << 32;
|
||||
unsigned step, i, c, s, k, new_k, n2;
|
||||
|
||||
/* Principle: if u = exp(i*a1) and v = exp(i*a2), then
|
||||
exp(i*(a1+a2)/2) = (u+v) / length(u+v) */
|
||||
sin[0] = 0;
|
||||
sin[half_pi] = ampls;
|
||||
for (step = half_pi; step > 1; step /= 2) {
|
||||
/* k = (1 << 16) * amplitude / length(u+v)
|
||||
In exact values, k is constant at a given step */
|
||||
k = 0x10000;
|
||||
for (i = 0; i < half_pi / 2; i += step) {
|
||||
s = sin[i] + sin[i + step];
|
||||
c = sin[half_pi - i] + sin[half_pi - i - step];
|
||||
n2 = s * s + c * c;
|
||||
/* Newton's method to solve n² * k² = unit² */
|
||||
while (1) {
|
||||
new_k = (k + unit2 / ((uint64_t)k * n2) + 1) >> 1;
|
||||
if (k == new_k)
|
||||
break;
|
||||
k = new_k;
|
||||
}
|
||||
sin[i + step / 2] = (k * s + 0x7FFF) >> 16;
|
||||
sin[half_pi - i - step / 2] = (k * c + 0x8000) >> 16;
|
||||
}
|
||||
}
|
||||
/* Unshift amplitude */
|
||||
for (i = 0; i <= half_pi; i++)
|
||||
sin[i] = (sin[i] + (1 << (AMPLITUDE_SHIFT - 1))) >> AMPLITUDE_SHIFT;
|
||||
/* Use symmetries to fill the other three quarters */
|
||||
for (i = 0; i < half_pi; i++)
|
||||
sin[half_pi * 2 - i] = sin[i];
|
||||
for (i = 0; i < 2 * half_pi; i++)
|
||||
sin[i + 2 * half_pi] = -sin[i];
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
SineContext *sine = ctx->priv;
|
||||
|
||||
if (!(sine->sin = av_malloc(sizeof(*sine->sin) << LOG_PERIOD)))
|
||||
return AVERROR(ENOMEM);
|
||||
sine->dphi = ldexp(sine->frequency, 32) / sine->sample_rate + 0.5;
|
||||
make_sin_table(sine->sin);
|
||||
|
||||
if (sine->beep_factor) {
|
||||
sine->beep_period = sine->sample_rate;
|
||||
sine->beep_length = sine->beep_period / 25;
|
||||
sine->dphi_beep = ldexp(sine->beep_factor * sine->frequency, 32) /
|
||||
sine->sample_rate + 0.5;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
SineContext *sine = ctx->priv;
|
||||
|
||||
av_freep(&sine->sin);
|
||||
}
|
||||
|
||||
static av_cold int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
SineContext *sine = ctx->priv;
|
||||
static const int64_t chlayouts[] = { AV_CH_LAYOUT_MONO, -1 };
|
||||
int sample_rates[] = { sine->sample_rate, -1 };
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16,
|
||||
AV_SAMPLE_FMT_NONE };
|
||||
|
||||
ff_set_common_formats (ctx, ff_make_format_list(sample_fmts));
|
||||
ff_set_common_channel_layouts(ctx, avfilter_make_format64_list(chlayouts));
|
||||
ff_set_common_samplerates(ctx, ff_make_format_list(sample_rates));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int config_props(AVFilterLink *outlink)
|
||||
{
|
||||
SineContext *sine = outlink->src->priv;
|
||||
sine->duration = av_rescale(sine->duration, sine->sample_rate, AV_TIME_BASE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
SineContext *sine = outlink->src->priv;
|
||||
AVFrame *frame;
|
||||
int i, nb_samples = sine->samples_per_frame;
|
||||
int16_t *samples;
|
||||
|
||||
if (sine->duration) {
|
||||
nb_samples = FFMIN(nb_samples, sine->duration - sine->pts);
|
||||
av_assert1(nb_samples >= 0);
|
||||
if (!nb_samples)
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
if (!(frame = ff_get_audio_buffer(outlink, nb_samples)))
|
||||
return AVERROR(ENOMEM);
|
||||
samples = (int16_t *)frame->data[0];
|
||||
|
||||
for (i = 0; i < nb_samples; i++) {
|
||||
samples[i] = sine->sin[sine->phi >> (32 - LOG_PERIOD)];
|
||||
sine->phi += sine->dphi;
|
||||
if (sine->beep_index < sine->beep_length) {
|
||||
samples[i] += sine->sin[sine->phi_beep >> (32 - LOG_PERIOD)] << 1;
|
||||
sine->phi_beep += sine->dphi_beep;
|
||||
}
|
||||
if (++sine->beep_index == sine->beep_period)
|
||||
sine->beep_index = 0;
|
||||
}
|
||||
|
||||
frame->pts = sine->pts;
|
||||
sine->pts += nb_samples;
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
|
||||
static const AVFilterPad sine_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.request_frame = request_frame,
|
||||
.config_props = config_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asrc_sine = {
|
||||
.name = "sine",
|
||||
.description = NULL_IF_CONFIG_SMALL("Generate sine wave audio signal."),
|
||||
.query_formats = query_formats,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(SineContext),
|
||||
.inputs = NULL,
|
||||
.outputs = sine_outputs,
|
||||
.priv_class = &sine_class,
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) Stefano Sabatini | stefasab at gmail.com
|
||||
* Copyright (c) S.N. Hemanth Meenakshisundaram | smeenaks at ucsd.edu
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
int avfilter_ref_get_channels(AVFilterBufferRef *ref)
|
||||
{
|
||||
return ref->audio ? ref->audio->channels : 0;
|
||||
}
|
||||
|
||||
AVFrame *ff_null_get_audio_buffer(AVFilterLink *link, int nb_samples)
|
||||
{
|
||||
return ff_get_audio_buffer(link->dst->outputs[0], nb_samples);
|
||||
}
|
||||
|
||||
AVFrame *ff_default_get_audio_buffer(AVFilterLink *link, int nb_samples)
|
||||
{
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
int channels = link->channels;
|
||||
int ret;
|
||||
|
||||
av_assert0(channels == av_get_channel_layout_nb_channels(link->channel_layout) || !av_get_channel_layout_nb_channels(link->channel_layout));
|
||||
|
||||
if (!frame)
|
||||
return NULL;
|
||||
|
||||
frame->nb_samples = nb_samples;
|
||||
frame->format = link->format;
|
||||
av_frame_set_channels(frame, link->channels);
|
||||
frame->channel_layout = link->channel_layout;
|
||||
frame->sample_rate = link->sample_rate;
|
||||
ret = av_frame_get_buffer(frame, 0);
|
||||
if (ret < 0) {
|
||||
av_frame_free(&frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
av_samples_set_silence(frame->extended_data, 0, nb_samples, channels,
|
||||
link->format);
|
||||
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
AVFrame *ff_get_audio_buffer(AVFilterLink *link, int nb_samples)
|
||||
{
|
||||
AVFrame *ret = NULL;
|
||||
|
||||
if (link->dstpad->get_audio_buffer)
|
||||
ret = link->dstpad->get_audio_buffer(link, nb_samples);
|
||||
|
||||
if (!ret)
|
||||
ret = ff_default_get_audio_buffer(link, nb_samples);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
AVFilterBufferRef* avfilter_get_audio_buffer_ref_from_arrays_channels(uint8_t **data,
|
||||
int linesize,int perms,
|
||||
int nb_samples,
|
||||
enum AVSampleFormat sample_fmt,
|
||||
int channels,
|
||||
uint64_t channel_layout)
|
||||
{
|
||||
int planes;
|
||||
AVFilterBuffer *samples = av_mallocz(sizeof(*samples));
|
||||
AVFilterBufferRef *samplesref = av_mallocz(sizeof(*samplesref));
|
||||
|
||||
if (!samples || !samplesref)
|
||||
goto fail;
|
||||
|
||||
av_assert0(channels);
|
||||
av_assert0(channel_layout == 0 ||
|
||||
channels == av_get_channel_layout_nb_channels(channel_layout));
|
||||
|
||||
samplesref->buf = samples;
|
||||
samplesref->buf->free = ff_avfilter_default_free_buffer;
|
||||
if (!(samplesref->audio = av_mallocz(sizeof(*samplesref->audio))))
|
||||
goto fail;
|
||||
|
||||
samplesref->audio->nb_samples = nb_samples;
|
||||
samplesref->audio->channel_layout = channel_layout;
|
||||
samplesref->audio->channels = channels;
|
||||
|
||||
planes = av_sample_fmt_is_planar(sample_fmt) ? channels : 1;
|
||||
|
||||
/* make sure the buffer gets read permission or it's useless for output */
|
||||
samplesref->perms = perms | AV_PERM_READ;
|
||||
|
||||
samples->refcount = 1;
|
||||
samplesref->type = AVMEDIA_TYPE_AUDIO;
|
||||
samplesref->format = sample_fmt;
|
||||
|
||||
memcpy(samples->data, data,
|
||||
FFMIN(FF_ARRAY_ELEMS(samples->data), planes)*sizeof(samples->data[0]));
|
||||
memcpy(samplesref->data, samples->data, sizeof(samples->data));
|
||||
|
||||
samples->linesize[0] = samplesref->linesize[0] = linesize;
|
||||
|
||||
if (planes > FF_ARRAY_ELEMS(samples->data)) {
|
||||
samples-> extended_data = av_mallocz(sizeof(*samples->extended_data) *
|
||||
planes);
|
||||
samplesref->extended_data = av_mallocz(sizeof(*samplesref->extended_data) *
|
||||
planes);
|
||||
|
||||
if (!samples->extended_data || !samplesref->extended_data)
|
||||
goto fail;
|
||||
|
||||
memcpy(samples-> extended_data, data, sizeof(*data)*planes);
|
||||
memcpy(samplesref->extended_data, data, sizeof(*data)*planes);
|
||||
} else {
|
||||
samples->extended_data = samples->data;
|
||||
samplesref->extended_data = samplesref->data;
|
||||
}
|
||||
|
||||
samplesref->pts = AV_NOPTS_VALUE;
|
||||
|
||||
return samplesref;
|
||||
|
||||
fail:
|
||||
if (samples && samples->extended_data != samples->data)
|
||||
av_freep(&samples->extended_data);
|
||||
if (samplesref) {
|
||||
av_freep(&samplesref->audio);
|
||||
if (samplesref->extended_data != samplesref->data)
|
||||
av_freep(&samplesref->extended_data);
|
||||
}
|
||||
av_freep(&samplesref);
|
||||
av_freep(&samples);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVFilterBufferRef* avfilter_get_audio_buffer_ref_from_arrays(uint8_t **data,
|
||||
int linesize,int perms,
|
||||
int nb_samples,
|
||||
enum AVSampleFormat sample_fmt,
|
||||
uint64_t channel_layout)
|
||||
{
|
||||
int channels = av_get_channel_layout_nb_channels(channel_layout);
|
||||
return avfilter_get_audio_buffer_ref_from_arrays_channels(data, linesize, perms,
|
||||
nb_samples, sample_fmt,
|
||||
channels, channel_layout);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) Stefano Sabatini | stefasab at gmail.com
|
||||
* Copyright (c) S.N. Hemanth Meenakshisundaram | smeenaks at ucsd.edu
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_AUDIO_H
|
||||
#define AVFILTER_AUDIO_H
|
||||
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
static const enum AVSampleFormat ff_packed_sample_fmts_array[] = {
|
||||
AV_SAMPLE_FMT_U8,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
AV_SAMPLE_FMT_S32,
|
||||
AV_SAMPLE_FMT_FLT,
|
||||
AV_SAMPLE_FMT_DBL,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
static const enum AVSampleFormat ff_planar_sample_fmts_array[] = {
|
||||
AV_SAMPLE_FMT_U8P,
|
||||
AV_SAMPLE_FMT_S16P,
|
||||
AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLTP,
|
||||
AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
|
||||
/** default handler for get_audio_buffer() for audio inputs */
|
||||
AVFrame *ff_default_get_audio_buffer(AVFilterLink *link, int nb_samples);
|
||||
|
||||
/** get_audio_buffer() handler for filters which simply pass audio along */
|
||||
AVFrame *ff_null_get_audio_buffer(AVFilterLink *link, int nb_samples);
|
||||
|
||||
/**
|
||||
* Request an audio samples buffer with a specific set of permissions.
|
||||
*
|
||||
* @param link the output link to the filter from which the buffer will
|
||||
* be requested
|
||||
* @param nb_samples the number of samples per channel
|
||||
* @return A reference to the samples. This must be unreferenced with
|
||||
* avfilter_unref_buffer when you are finished with it.
|
||||
*/
|
||||
AVFrame *ff_get_audio_buffer(AVFilterLink *link, int nb_samples);
|
||||
|
||||
/**
|
||||
* Send a buffer of audio samples to the next filter.
|
||||
*
|
||||
* @param link the output link over which the audio samples are being sent
|
||||
* @param samplesref a reference to the buffer of audio samples being sent. The
|
||||
* receiving filter will free this reference when it no longer
|
||||
* needs it or pass it on to the next filter.
|
||||
*
|
||||
* @return >= 0 on success, a negative AVERROR on error. The receiving filter
|
||||
* is responsible for unreferencing samplesref in case of error.
|
||||
*/
|
||||
int ff_filter_samples(AVFilterLink *link, AVFilterBufferRef *samplesref);
|
||||
|
||||
/**
|
||||
* Send a buffer of audio samples to the next link, without checking
|
||||
* min_samples.
|
||||
*/
|
||||
int ff_filter_samples_framed(AVFilterLink *link,
|
||||
AVFilterBufferRef *samplesref);
|
||||
|
||||
#endif /* AVFILTER_AUDIO_H */
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2011 Stefano Sabatini | stefasab at gmail.com
|
||||
*
|
||||
* 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
|
||||
* libavcodec/libavfilter gluing utilities
|
||||
*/
|
||||
|
||||
#include "avcodec.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
AVFilterBufferRef *avfilter_get_video_buffer_ref_from_frame(const AVFrame *frame,
|
||||
int perms)
|
||||
{
|
||||
AVFilterBufferRef *picref =
|
||||
avfilter_get_video_buffer_ref_from_arrays(frame->data, frame->linesize, perms,
|
||||
frame->width, frame->height,
|
||||
frame->format);
|
||||
if (!picref)
|
||||
return NULL;
|
||||
if (avfilter_copy_frame_props(picref, frame) < 0) {
|
||||
picref->buf->data[0] = NULL;
|
||||
avfilter_unref_bufferp(&picref);
|
||||
}
|
||||
return picref;
|
||||
}
|
||||
|
||||
AVFilterBufferRef *avfilter_get_audio_buffer_ref_from_frame(const AVFrame *frame,
|
||||
int perms)
|
||||
{
|
||||
AVFilterBufferRef *samplesref;
|
||||
int channels = av_frame_get_channels(frame);
|
||||
int64_t layout = av_frame_get_channel_layout(frame);
|
||||
|
||||
if (layout && av_get_channel_layout_nb_channels(layout) != av_frame_get_channels(frame)) {
|
||||
av_log(0, AV_LOG_ERROR, "Layout indicates a different number of channels than actually present\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
samplesref = avfilter_get_audio_buffer_ref_from_arrays_channels(
|
||||
(uint8_t **)frame->extended_data, frame->linesize[0], perms,
|
||||
frame->nb_samples, frame->format, channels, layout);
|
||||
if (!samplesref)
|
||||
return NULL;
|
||||
if (avfilter_copy_frame_props(samplesref, frame) < 0) {
|
||||
samplesref->buf->data[0] = NULL;
|
||||
avfilter_unref_bufferp(&samplesref);
|
||||
}
|
||||
return samplesref;
|
||||
}
|
||||
|
||||
AVFilterBufferRef *avfilter_get_buffer_ref_from_frame(enum AVMediaType type,
|
||||
const AVFrame *frame,
|
||||
int perms)
|
||||
{
|
||||
switch (type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
return avfilter_get_video_buffer_ref_from_frame(frame, perms);
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
return avfilter_get_audio_buffer_ref_from_frame(frame, perms);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int avfilter_copy_buf_props(AVFrame *dst, const AVFilterBufferRef *src)
|
||||
{
|
||||
int planes, nb_channels;
|
||||
|
||||
if (!dst)
|
||||
return AVERROR(EINVAL);
|
||||
/* abort in case the src is NULL and dst is not, avoid inconsistent state in dst */
|
||||
av_assert0(src);
|
||||
|
||||
memcpy(dst->data, src->data, sizeof(dst->data));
|
||||
memcpy(dst->linesize, src->linesize, sizeof(dst->linesize));
|
||||
|
||||
dst->pts = src->pts;
|
||||
dst->format = src->format;
|
||||
av_frame_set_pkt_pos(dst, src->pos);
|
||||
|
||||
switch (src->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
av_assert0(src->video);
|
||||
dst->width = src->video->w;
|
||||
dst->height = src->video->h;
|
||||
dst->sample_aspect_ratio = src->video->sample_aspect_ratio;
|
||||
dst->interlaced_frame = src->video->interlaced;
|
||||
dst->top_field_first = src->video->top_field_first;
|
||||
dst->key_frame = src->video->key_frame;
|
||||
dst->pict_type = src->video->pict_type;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
av_assert0(src->audio);
|
||||
nb_channels = av_get_channel_layout_nb_channels(src->audio->channel_layout);
|
||||
planes = av_sample_fmt_is_planar(src->format) ? nb_channels : 1;
|
||||
|
||||
if (planes > FF_ARRAY_ELEMS(dst->data)) {
|
||||
dst->extended_data = av_mallocz(planes * sizeof(*dst->extended_data));
|
||||
if (!dst->extended_data)
|
||||
return AVERROR(ENOMEM);
|
||||
memcpy(dst->extended_data, src->extended_data,
|
||||
planes * sizeof(*dst->extended_data));
|
||||
} else
|
||||
dst->extended_data = dst->data;
|
||||
dst->nb_samples = src->audio->nb_samples;
|
||||
av_frame_set_sample_rate (dst, src->audio->sample_rate);
|
||||
av_frame_set_channel_layout(dst, src->audio->channel_layout);
|
||||
av_frame_set_channels (dst, src->audio->channels);
|
||||
break;
|
||||
default:
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FF_API_FILL_FRAME
|
||||
int avfilter_fill_frame_from_audio_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *samplesref)
|
||||
{
|
||||
return avfilter_copy_buf_props(frame, samplesref);
|
||||
}
|
||||
|
||||
int avfilter_fill_frame_from_video_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *picref)
|
||||
{
|
||||
return avfilter_copy_buf_props(frame, picref);
|
||||
}
|
||||
|
||||
int avfilter_fill_frame_from_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *ref)
|
||||
{
|
||||
return avfilter_copy_buf_props(frame, ref);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_AVCODEC_H
|
||||
#define AVFILTER_AVCODEC_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* libavcodec/libavfilter gluing utilities
|
||||
*
|
||||
* This should be included in an application ONLY if the installed
|
||||
* libavfilter has been compiled with libavcodec support, otherwise
|
||||
* symbols defined below will not be available.
|
||||
*/
|
||||
|
||||
#include "avfilter.h"
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
/**
|
||||
* Create and return a picref reference from the data and properties
|
||||
* contained in frame.
|
||||
*
|
||||
* @param perms permissions to assign to the new buffer reference
|
||||
* @deprecated avfilter APIs work natively with AVFrame instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
AVFilterBufferRef *avfilter_get_video_buffer_ref_from_frame(const AVFrame *frame, int perms);
|
||||
|
||||
|
||||
/**
|
||||
* Create and return a picref reference from the data and properties
|
||||
* contained in frame.
|
||||
*
|
||||
* @param perms permissions to assign to the new buffer reference
|
||||
* @deprecated avfilter APIs work natively with AVFrame instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
AVFilterBufferRef *avfilter_get_audio_buffer_ref_from_frame(const AVFrame *frame,
|
||||
int perms);
|
||||
|
||||
/**
|
||||
* Create and return a buffer reference from the data and properties
|
||||
* contained in frame.
|
||||
*
|
||||
* @param perms permissions to assign to the new buffer reference
|
||||
* @deprecated avfilter APIs work natively with AVFrame instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
AVFilterBufferRef *avfilter_get_buffer_ref_from_frame(enum AVMediaType type,
|
||||
const AVFrame *frame,
|
||||
int perms);
|
||||
#endif
|
||||
|
||||
#if FF_API_FILL_FRAME
|
||||
/**
|
||||
* Fill an AVFrame with the information stored in samplesref.
|
||||
*
|
||||
* @param frame an already allocated AVFrame
|
||||
* @param samplesref an audio buffer reference
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure
|
||||
* @deprecated Use avfilter_copy_buf_props() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int avfilter_fill_frame_from_audio_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *samplesref);
|
||||
|
||||
/**
|
||||
* Fill an AVFrame with the information stored in picref.
|
||||
*
|
||||
* @param frame an already allocated AVFrame
|
||||
* @param picref a video buffer reference
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure
|
||||
* @deprecated Use avfilter_copy_buf_props() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int avfilter_fill_frame_from_video_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *picref);
|
||||
|
||||
/**
|
||||
* Fill an AVFrame with information stored in ref.
|
||||
*
|
||||
* @param frame an already allocated AVFrame
|
||||
* @param ref a video or audio buffer reference
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure
|
||||
* @deprecated Use avfilter_copy_buf_props() instead.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int avfilter_fill_frame_from_buffer_ref(AVFrame *frame,
|
||||
const AVFilterBufferRef *ref);
|
||||
#endif
|
||||
|
||||
#endif /* AVFILTER_AVCODEC_H */
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Paul B Mahol
|
||||
*
|
||||
* 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
|
||||
* audio to video multimedia vectorscope filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
#include "internal.h"
|
||||
|
||||
enum VectorScopeMode {
|
||||
LISSAJOUS,
|
||||
LISSAJOUS_XY,
|
||||
MODE_NB,
|
||||
};
|
||||
|
||||
typedef struct AudioVectorScopeContext {
|
||||
const AVClass *class;
|
||||
AVFrame *outpicref;
|
||||
int w, h;
|
||||
int hw, hh;
|
||||
enum VectorScopeMode mode;
|
||||
int contrast[3];
|
||||
int fade[3];
|
||||
double zoom;
|
||||
AVRational frame_rate;
|
||||
} AudioVectorScopeContext;
|
||||
|
||||
#define OFFSET(x) offsetof(AudioVectorScopeContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption avectorscope_options[] = {
|
||||
{ "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" },
|
||||
{ "m", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" },
|
||||
{ "lissajous", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS}, 0, 0, FLAGS, "mode" },
|
||||
{ "lissajous_xy", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS_XY}, 0, 0, FLAGS, "mode" },
|
||||
{ "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 },
|
||||
{ "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS },
|
||||
{ "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS },
|
||||
{ "rc", "set red contrast", OFFSET(contrast[0]), AV_OPT_TYPE_INT, {.i64=40}, 0, 255, FLAGS },
|
||||
{ "gc", "set green contrast", OFFSET(contrast[1]), AV_OPT_TYPE_INT, {.i64=160}, 0, 255, FLAGS },
|
||||
{ "bc", "set blue contrast", OFFSET(contrast[2]), AV_OPT_TYPE_INT, {.i64=80}, 0, 255, FLAGS },
|
||||
{ "rf", "set red fade", OFFSET(fade[0]), AV_OPT_TYPE_INT, {.i64=15}, 0, 255, FLAGS },
|
||||
{ "gf", "set green fade", OFFSET(fade[1]), AV_OPT_TYPE_INT, {.i64=10}, 0, 255, FLAGS },
|
||||
{ "bf", "set blue fade", OFFSET(fade[2]), AV_OPT_TYPE_INT, {.i64=5}, 0, 255, FLAGS },
|
||||
{ "zoom", "set zoom factor", OFFSET(zoom), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 10, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(avectorscope);
|
||||
|
||||
static void draw_dot(AudioVectorScopeContext *p, unsigned x, unsigned y)
|
||||
{
|
||||
const int linesize = p->outpicref->linesize[0];
|
||||
uint8_t *dst;
|
||||
|
||||
if (p->zoom > 1) {
|
||||
if (y >= p->h || x >= p->w)
|
||||
return;
|
||||
} else {
|
||||
y = FFMIN(y, p->h - 1);
|
||||
x = FFMIN(x, p->w - 1);
|
||||
}
|
||||
|
||||
dst = &p->outpicref->data[0][y * linesize + x * 4];
|
||||
dst[0] = FFMIN(dst[0] + p->contrast[0], 255);
|
||||
dst[1] = FFMIN(dst[1] + p->contrast[1], 255);
|
||||
dst[2] = FFMIN(dst[2] + p->contrast[2], 255);
|
||||
}
|
||||
|
||||
static void fade(AudioVectorScopeContext *p)
|
||||
{
|
||||
const int linesize = p->outpicref->linesize[0];
|
||||
int i, j;
|
||||
|
||||
if (p->fade[0] || p->fade[1] || p->fade[2]) {
|
||||
uint8_t *d = p->outpicref->data[0];
|
||||
for (i = 0; i < p->h; i++) {
|
||||
for (j = 0; j < p->w*4; j+=4) {
|
||||
d[j+0] = FFMAX(d[j+0] - p->fade[0], 0);
|
||||
d[j+1] = FFMAX(d[j+1] - p->fade[1], 0);
|
||||
d[j+2] = FFMAX(d[j+2] - p->fade[2], 0);
|
||||
}
|
||||
d += linesize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layout = NULL;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE };
|
||||
static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE };
|
||||
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_formats);
|
||||
|
||||
ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO);
|
||||
ff_channel_layouts_ref(layout, &inlink->out_channel_layouts);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_samplerates);
|
||||
|
||||
formats = ff_make_format_list(pix_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AudioVectorScopeContext *p = ctx->priv;
|
||||
int nb_samples;
|
||||
|
||||
nb_samples = FFMAX(1024, ((double)inlink->sample_rate / av_q2d(p->frame_rate)) + 0.5);
|
||||
inlink->partial_buf_size =
|
||||
inlink->min_samples =
|
||||
inlink->max_samples = nb_samples;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AudioVectorScopeContext *p = outlink->src->priv;
|
||||
|
||||
outlink->w = p->w;
|
||||
outlink->h = p->h;
|
||||
outlink->sample_aspect_ratio = (AVRational){1,1};
|
||||
outlink->frame_rate = p->frame_rate;
|
||||
|
||||
p->hw = p->w / 2;
|
||||
p->hh = p->h / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
AudioVectorScopeContext *p = ctx->priv;
|
||||
const int hw = p->hw;
|
||||
const int hh = p->hh;
|
||||
unsigned x, y;
|
||||
const double zoom = p->zoom;
|
||||
int i;
|
||||
|
||||
if (!p->outpicref || p->outpicref->width != outlink->w ||
|
||||
p->outpicref->height != outlink->h) {
|
||||
av_frame_free(&p->outpicref);
|
||||
p->outpicref = ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
||||
if (!p->outpicref)
|
||||
av_frame_free(&insamples);
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < outlink->h; i++)
|
||||
memset(p->outpicref->data[0] + i * p->outpicref->linesize[0], 0, outlink->w * 4);
|
||||
}
|
||||
p->outpicref->pts = insamples->pts;
|
||||
|
||||
fade(p);
|
||||
|
||||
switch (insamples->format) {
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
for (i = 0; i < insamples->nb_samples; i++) {
|
||||
int16_t *src = (int16_t *)insamples->data[0] + i * 2;
|
||||
|
||||
if (p->mode == LISSAJOUS) {
|
||||
x = ((src[1] - src[0]) * zoom / (float)(UINT16_MAX) + 1) * hw;
|
||||
y = (1.0 - (src[0] + src[1]) * zoom / (float)UINT16_MAX) * hh;
|
||||
} else {
|
||||
x = (src[1] * zoom / (float)INT16_MAX + 1) * hw;
|
||||
y = (src[0] * zoom / (float)INT16_MAX + 1) * hh;
|
||||
}
|
||||
|
||||
draw_dot(p, x, y);
|
||||
}
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
for (i = 0; i < insamples->nb_samples; i++) {
|
||||
float *src = (float *)insamples->data[0] + i * 2;
|
||||
|
||||
if (p->mode == LISSAJOUS) {
|
||||
x = ((src[1] - src[0]) * zoom / 2 + 1) * hw;
|
||||
y = (1.0 - (src[0] + src[1]) * zoom / 2) * hh;
|
||||
} else {
|
||||
x = (src[1] * zoom + 1) * hw;
|
||||
y = (src[0] * zoom + 1) * hh;
|
||||
}
|
||||
|
||||
draw_dot(p, x, y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
av_frame_free(&insamples);
|
||||
|
||||
return ff_filter_frame(outlink, av_frame_clone(p->outpicref));
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
AudioVectorScopeContext *p = ctx->priv;
|
||||
|
||||
av_frame_free(&p->outpicref);
|
||||
}
|
||||
|
||||
static const AVFilterPad audiovectorscope_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_input,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad audiovectorscope_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_output,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_avf_avectorscope = {
|
||||
.name = "avectorscope",
|
||||
.description = NULL_IF_CONFIG_SMALL("Convert input audio to vectorscope video output."),
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(AudioVectorScopeContext),
|
||||
.inputs = audiovectorscope_inputs,
|
||||
.outputs = audiovectorscope_outputs,
|
||||
.priv_class = &avectorscope_class,
|
||||
};
|
||||
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Nicolas George
|
||||
*
|
||||
* 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
|
||||
* concat audio-video filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#define FF_BUFQUEUE_SIZE 256
|
||||
#include "bufferqueue.h"
|
||||
#include "internal.h"
|
||||
#include "video.h"
|
||||
#include "audio.h"
|
||||
|
||||
#define TYPE_ALL 2
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
unsigned nb_streams[TYPE_ALL]; /**< number of out streams of each type */
|
||||
unsigned nb_segments;
|
||||
unsigned cur_idx; /**< index of the first input of current segment */
|
||||
int64_t delta_ts; /**< timestamp to add to produce output timestamps */
|
||||
unsigned nb_in_active; /**< number of active inputs in current segment */
|
||||
unsigned unsafe;
|
||||
struct concat_in {
|
||||
int64_t pts;
|
||||
int64_t nb_frames;
|
||||
unsigned eof;
|
||||
struct FFBufQueue queue;
|
||||
} *in;
|
||||
} ConcatContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ConcatContext, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
#define V AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption concat_options[] = {
|
||||
{ "n", "specify the number of segments", OFFSET(nb_segments),
|
||||
AV_OPT_TYPE_INT, { .i64 = 2 }, 2, INT_MAX, V|A|F},
|
||||
{ "v", "specify the number of video streams",
|
||||
OFFSET(nb_streams[AVMEDIA_TYPE_VIDEO]),
|
||||
AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, V|F },
|
||||
{ "a", "specify the number of audio streams",
|
||||
OFFSET(nb_streams[AVMEDIA_TYPE_AUDIO]),
|
||||
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A|F},
|
||||
{ "unsafe", "enable unsafe mode",
|
||||
OFFSET(unsafe),
|
||||
AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V|A|F},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(concat);
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned type, nb_str, idx0 = 0, idx, str, seg;
|
||||
AVFilterFormats *formats, *rates = NULL;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
|
||||
for (type = 0; type < TYPE_ALL; type++) {
|
||||
nb_str = cat->nb_streams[type];
|
||||
for (str = 0; str < nb_str; str++) {
|
||||
idx = idx0;
|
||||
|
||||
/* Set the output formats */
|
||||
formats = ff_all_formats(type);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &ctx->outputs[idx]->in_formats);
|
||||
if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
rates = ff_all_samplerates();
|
||||
if (!rates)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(rates, &ctx->outputs[idx]->in_samplerates);
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_channel_layouts_ref(layouts, &ctx->outputs[idx]->in_channel_layouts);
|
||||
}
|
||||
|
||||
/* Set the same formats for each corresponding input */
|
||||
for (seg = 0; seg < cat->nb_segments; seg++) {
|
||||
ff_formats_ref(formats, &ctx->inputs[idx]->out_formats);
|
||||
if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
ff_formats_ref(rates, &ctx->inputs[idx]->out_samplerates);
|
||||
ff_channel_layouts_ref(layouts, &ctx->inputs[idx]->out_channel_layouts);
|
||||
}
|
||||
idx += ctx->nb_outputs;
|
||||
}
|
||||
|
||||
idx0++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned out_no = FF_OUTLINK_IDX(outlink);
|
||||
unsigned in_no = out_no, seg;
|
||||
AVFilterLink *inlink = ctx->inputs[in_no];
|
||||
|
||||
/* enhancement: find a common one */
|
||||
outlink->time_base = AV_TIME_BASE_Q;
|
||||
outlink->w = inlink->w;
|
||||
outlink->h = inlink->h;
|
||||
outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
|
||||
outlink->format = inlink->format;
|
||||
for (seg = 1; seg < cat->nb_segments; seg++) {
|
||||
inlink = ctx->inputs[in_no += ctx->nb_outputs];
|
||||
if (!outlink->sample_aspect_ratio.num)
|
||||
outlink->sample_aspect_ratio = inlink->sample_aspect_ratio;
|
||||
/* possible enhancement: unsafe mode, do not check */
|
||||
if (outlink->w != inlink->w ||
|
||||
outlink->h != inlink->h ||
|
||||
outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num &&
|
||||
inlink->sample_aspect_ratio.num ||
|
||||
outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Input link %s parameters "
|
||||
"(size %dx%d, SAR %d:%d) do not match the corresponding "
|
||||
"output link %s parameters (%dx%d, SAR %d:%d)\n",
|
||||
ctx->input_pads[in_no].name, inlink->w, inlink->h,
|
||||
inlink->sample_aspect_ratio.num,
|
||||
inlink->sample_aspect_ratio.den,
|
||||
ctx->input_pads[out_no].name, outlink->w, outlink->h,
|
||||
outlink->sample_aspect_ratio.num,
|
||||
outlink->sample_aspect_ratio.den);
|
||||
if (!cat->unsafe)
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int push_frame(AVFilterContext *ctx, unsigned in_no, AVFrame *buf)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned out_no = in_no % ctx->nb_outputs;
|
||||
AVFilterLink * inlink = ctx-> inputs[ in_no];
|
||||
AVFilterLink *outlink = ctx->outputs[out_no];
|
||||
struct concat_in *in = &cat->in[in_no];
|
||||
|
||||
buf->pts = av_rescale_q(buf->pts, inlink->time_base, outlink->time_base);
|
||||
in->pts = buf->pts;
|
||||
in->nb_frames++;
|
||||
/* add duration to input PTS */
|
||||
if (inlink->sample_rate)
|
||||
/* use number of audio samples */
|
||||
in->pts += av_rescale_q(buf->nb_samples,
|
||||
(AVRational){ 1, inlink->sample_rate },
|
||||
outlink->time_base);
|
||||
else if (in->nb_frames >= 2)
|
||||
/* use mean duration */
|
||||
in->pts = av_rescale(in->pts, in->nb_frames, in->nb_frames - 1);
|
||||
|
||||
buf->pts += cat->delta_ts;
|
||||
return ff_filter_frame(outlink, buf);
|
||||
}
|
||||
|
||||
static int process_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned in_no = FF_INLINK_IDX(inlink);
|
||||
|
||||
if (in_no < cat->cur_idx) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Frame after EOF on input %s\n",
|
||||
ctx->input_pads[in_no].name);
|
||||
av_frame_free(&buf);
|
||||
} else if (in_no >= cat->cur_idx + ctx->nb_outputs) {
|
||||
ff_bufqueue_add(ctx, &cat->in[in_no].queue, buf);
|
||||
} else {
|
||||
return push_frame(ctx, in_no, buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
unsigned in_no = FF_INLINK_IDX(inlink);
|
||||
AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs];
|
||||
|
||||
return ff_get_video_buffer(outlink, w, h);
|
||||
}
|
||||
|
||||
static AVFrame *get_audio_buffer(AVFilterLink *inlink, int nb_samples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
unsigned in_no = FF_INLINK_IDX(inlink);
|
||||
AVFilterLink *outlink = ctx->outputs[in_no % ctx->nb_outputs];
|
||||
|
||||
return ff_get_audio_buffer(outlink, nb_samples);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *buf)
|
||||
{
|
||||
return process_frame(inlink, buf);
|
||||
}
|
||||
|
||||
static void close_input(AVFilterContext *ctx, unsigned in_no)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
|
||||
cat->in[in_no].eof = 1;
|
||||
cat->nb_in_active--;
|
||||
av_log(ctx, AV_LOG_VERBOSE, "EOF on %s, %d streams left in segment.\n",
|
||||
ctx->input_pads[in_no].name, cat->nb_in_active);
|
||||
}
|
||||
|
||||
static void find_next_delta_ts(AVFilterContext *ctx, int64_t *seg_delta)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned i = cat->cur_idx;
|
||||
unsigned imax = i + ctx->nb_outputs;
|
||||
int64_t pts;
|
||||
|
||||
pts = cat->in[i++].pts;
|
||||
for (; i < imax; i++)
|
||||
pts = FFMAX(pts, cat->in[i].pts);
|
||||
cat->delta_ts += pts;
|
||||
*seg_delta = pts;
|
||||
}
|
||||
|
||||
static int send_silence(AVFilterContext *ctx, unsigned in_no, unsigned out_no,
|
||||
int64_t seg_delta)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
AVFilterLink *outlink = ctx->outputs[out_no];
|
||||
int64_t base_pts = cat->in[in_no].pts + cat->delta_ts - seg_delta;
|
||||
int64_t nb_samples, sent = 0;
|
||||
int frame_nb_samples, ret;
|
||||
AVRational rate_tb = { 1, ctx->inputs[in_no]->sample_rate };
|
||||
AVFrame *buf;
|
||||
int nb_channels = av_get_channel_layout_nb_channels(outlink->channel_layout);
|
||||
|
||||
if (!rate_tb.den)
|
||||
return AVERROR_BUG;
|
||||
nb_samples = av_rescale_q(seg_delta - cat->in[in_no].pts,
|
||||
outlink->time_base, rate_tb);
|
||||
frame_nb_samples = FFMAX(9600, rate_tb.den / 5); /* arbitrary */
|
||||
while (nb_samples) {
|
||||
frame_nb_samples = FFMIN(frame_nb_samples, nb_samples);
|
||||
buf = ff_get_audio_buffer(outlink, frame_nb_samples);
|
||||
if (!buf)
|
||||
return AVERROR(ENOMEM);
|
||||
av_samples_set_silence(buf->extended_data, 0, frame_nb_samples,
|
||||
nb_channels, outlink->format);
|
||||
buf->pts = base_pts + av_rescale_q(sent, rate_tb, outlink->time_base);
|
||||
ret = ff_filter_frame(outlink, buf);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
sent += frame_nb_samples;
|
||||
nb_samples -= frame_nb_samples;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flush_segment(AVFilterContext *ctx)
|
||||
{
|
||||
int ret;
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned str, str_max;
|
||||
int64_t seg_delta;
|
||||
|
||||
find_next_delta_ts(ctx, &seg_delta);
|
||||
cat->cur_idx += ctx->nb_outputs;
|
||||
cat->nb_in_active = ctx->nb_outputs;
|
||||
av_log(ctx, AV_LOG_VERBOSE, "Segment finished at pts=%"PRId64"\n",
|
||||
cat->delta_ts);
|
||||
|
||||
if (cat->cur_idx < ctx->nb_inputs) {
|
||||
/* pad audio streams with silence */
|
||||
str = cat->nb_streams[AVMEDIA_TYPE_VIDEO];
|
||||
str_max = str + cat->nb_streams[AVMEDIA_TYPE_AUDIO];
|
||||
for (; str < str_max; str++) {
|
||||
ret = send_silence(ctx, cat->cur_idx - ctx->nb_outputs + str, str,
|
||||
seg_delta);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
/* flush queued buffers */
|
||||
/* possible enhancement: flush in PTS order */
|
||||
str_max = cat->cur_idx + ctx->nb_outputs;
|
||||
for (str = cat->cur_idx; str < str_max; str++) {
|
||||
while (cat->in[str].queue.available) {
|
||||
ret = push_frame(ctx, str, ff_bufqueue_get(&cat->in[str].queue));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned out_no = FF_OUTLINK_IDX(outlink);
|
||||
unsigned in_no = out_no + cat->cur_idx;
|
||||
unsigned str, str_max;
|
||||
int ret;
|
||||
|
||||
while (1) {
|
||||
if (in_no >= ctx->nb_inputs)
|
||||
return AVERROR_EOF;
|
||||
if (!cat->in[in_no].eof) {
|
||||
ret = ff_request_frame(ctx->inputs[in_no]);
|
||||
if (ret != AVERROR_EOF)
|
||||
return ret;
|
||||
close_input(ctx, in_no);
|
||||
}
|
||||
/* cycle on all inputs to finish the segment */
|
||||
/* possible enhancement: request in PTS order */
|
||||
str_max = cat->cur_idx + ctx->nb_outputs - 1;
|
||||
for (str = cat->cur_idx; cat->nb_in_active;
|
||||
str = str == str_max ? cat->cur_idx : str + 1) {
|
||||
if (cat->in[str].eof)
|
||||
continue;
|
||||
ret = ff_request_frame(ctx->inputs[str]);
|
||||
if (ret == AVERROR_EOF)
|
||||
close_input(ctx, str);
|
||||
else if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
ret = flush_segment(ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
in_no += ctx->nb_outputs;
|
||||
}
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned seg, type, str;
|
||||
|
||||
/* create input pads */
|
||||
for (seg = 0; seg < cat->nb_segments; seg++) {
|
||||
for (type = 0; type < TYPE_ALL; type++) {
|
||||
for (str = 0; str < cat->nb_streams[type]; str++) {
|
||||
AVFilterPad pad = {
|
||||
.type = type,
|
||||
.get_video_buffer = get_video_buffer,
|
||||
.get_audio_buffer = get_audio_buffer,
|
||||
.filter_frame = filter_frame,
|
||||
};
|
||||
pad.name = av_asprintf("in%d:%c%d", seg, "va"[type], str);
|
||||
ff_insert_inpad(ctx, ctx->nb_inputs, &pad);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* create output pads */
|
||||
for (type = 0; type < TYPE_ALL; type++) {
|
||||
for (str = 0; str < cat->nb_streams[type]; str++) {
|
||||
AVFilterPad pad = {
|
||||
.type = type,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
};
|
||||
pad.name = av_asprintf("out:%c%d", "va"[type], str);
|
||||
ff_insert_outpad(ctx, ctx->nb_outputs, &pad);
|
||||
}
|
||||
}
|
||||
|
||||
cat->in = av_calloc(ctx->nb_inputs, sizeof(*cat->in));
|
||||
if (!cat->in)
|
||||
return AVERROR(ENOMEM);
|
||||
cat->nb_in_active = ctx->nb_outputs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ConcatContext *cat = ctx->priv;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
av_freep(&ctx->input_pads[i].name);
|
||||
ff_bufqueue_discard_all(&cat->in[i].queue);
|
||||
}
|
||||
for (i = 0; i < ctx->nb_outputs; i++)
|
||||
av_freep(&ctx->output_pads[i].name);
|
||||
av_free(cat->in);
|
||||
}
|
||||
|
||||
AVFilter avfilter_avf_concat = {
|
||||
.name = "concat",
|
||||
.description = NULL_IF_CONFIG_SMALL("Concatenate audio and video streams."),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(ConcatContext),
|
||||
.inputs = NULL,
|
||||
.outputs = NULL,
|
||||
.priv_class = &concat_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS,
|
||||
};
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Clément Bœsch
|
||||
* Copyright (c) 2013 Rudolf Polzer <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* audio to spectrum (video) transmedia filter, based on ffplay rdft showmode
|
||||
* (by Michael Niedermayer) and lavfi/avf_showwaves (by Stefano Sabatini).
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "libavcodec/avfft.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
|
||||
enum DisplayMode { COMBINED, SEPARATE, NB_MODES };
|
||||
enum DisplayScale { LINEAR, SQRT, CBRT, LOG, NB_SCALES };
|
||||
enum ColorMode { CHANNEL, INTENSITY, NB_CLMODES };
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int w, h;
|
||||
AVFrame *outpicref;
|
||||
int req_fullfilled;
|
||||
int nb_display_channels;
|
||||
int channel_height;
|
||||
int sliding; ///< 1 if sliding mode, 0 otherwise
|
||||
enum DisplayMode mode; ///< channel display mode
|
||||
enum ColorMode color_mode; ///< display color scheme
|
||||
enum DisplayScale scale;
|
||||
float saturation; ///< color saturation multiplier
|
||||
int xpos; ///< x position (current column)
|
||||
RDFTContext *rdft; ///< Real Discrete Fourier Transform context
|
||||
int rdft_bits; ///< number of bits (RDFT window size = 1<<rdft_bits)
|
||||
FFTSample **rdft_data; ///< bins holder for each (displayed) channels
|
||||
int filled; ///< number of samples (per channel) filled in current rdft_buffer
|
||||
int consumed; ///< number of samples (per channel) consumed from the input frame
|
||||
float *window_func_lut; ///< Window function LUT
|
||||
float *combine_buffer; ///< color combining buffer (3 * h items)
|
||||
} ShowSpectrumContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ShowSpectrumContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption showspectrum_options[] = {
|
||||
{ "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x512"}, 0, 0, FLAGS },
|
||||
{ "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x512"}, 0, 0, FLAGS },
|
||||
{ "slide", "set sliding mode", OFFSET(sliding), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS },
|
||||
{ "mode", "set channel display mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=COMBINED}, COMBINED, NB_MODES-1, FLAGS, "mode" },
|
||||
{ "combined", "combined mode", 0, AV_OPT_TYPE_CONST, {.i64=COMBINED}, 0, 0, FLAGS, "mode" },
|
||||
{ "separate", "separate mode", 0, AV_OPT_TYPE_CONST, {.i64=SEPARATE}, 0, 0, FLAGS, "mode" },
|
||||
{ "color", "set channel coloring", OFFSET(color_mode), AV_OPT_TYPE_INT, {.i64=CHANNEL}, CHANNEL, NB_CLMODES-1, FLAGS, "color" },
|
||||
{ "channel", "separate color for each channel", 0, AV_OPT_TYPE_CONST, {.i64=CHANNEL}, 0, 0, FLAGS, "color" },
|
||||
{ "intensity", "intensity based coloring", 0, AV_OPT_TYPE_CONST, {.i64=INTENSITY}, 0, 0, FLAGS, "color" },
|
||||
{ "scale", "set display scale", OFFSET(scale), AV_OPT_TYPE_INT, {.i64=SQRT}, LINEAR, NB_SCALES-1, FLAGS, "scale" },
|
||||
{ "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=SQRT}, 0, 0, FLAGS, "scale" },
|
||||
{ "cbrt", "cubic root", 0, AV_OPT_TYPE_CONST, {.i64=CBRT}, 0, 0, FLAGS, "scale" },
|
||||
{ "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=LOG}, 0, 0, FLAGS, "scale" },
|
||||
{ "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "scale" },
|
||||
{ "saturation", "color saturation multiplier", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1}, -10, 10, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(showspectrum);
|
||||
|
||||
static const struct {
|
||||
float a, y, u, v;
|
||||
} intensity_color_table[] = {
|
||||
{ 0, 0, 0, 0 },
|
||||
{ 0.13, .03587126228984074, .1573300977624594, -.02548747583751842 },
|
||||
{ 0.30, .18572281794568020, .1772436246393981, .17475554840414750 },
|
||||
{ 0.60, .28184980583656130, -.1593064119945782, .47132074554608920 },
|
||||
{ 0.73, .65830621175547810, -.3716070802232764, .24352759331252930 },
|
||||
{ 0.78, .76318535758242900, -.4307467689263783, .16866496622310430 },
|
||||
{ 0.91, .95336363636363640, -.2045454545454546, .03313636363636363 },
|
||||
{ 1, 1, 0, 0 }
|
||||
};
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ShowSpectrumContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
av_freep(&s->combine_buffer);
|
||||
av_rdft_end(s->rdft);
|
||||
for (i = 0; i < s->nb_display_channels; i++)
|
||||
av_freep(&s->rdft_data[i]);
|
||||
av_freep(&s->rdft_data);
|
||||
av_freep(&s->window_func_lut);
|
||||
av_frame_free(&s->outpicref);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_NONE };
|
||||
static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_NONE };
|
||||
|
||||
/* set input audio formats */
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_formats);
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_samplerates);
|
||||
|
||||
/* set output video format */
|
||||
formats = ff_make_format_list(pix_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
ShowSpectrumContext *s = ctx->priv;
|
||||
int i, rdft_bits, win_size, h;
|
||||
|
||||
outlink->w = s->w;
|
||||
outlink->h = s->h;
|
||||
|
||||
h = (s->mode == COMBINED) ? outlink->h : outlink->h / inlink->channels;
|
||||
s->channel_height = h;
|
||||
|
||||
/* RDFT window size (precision) according to the requested output frame height */
|
||||
for (rdft_bits = 1; 1 << rdft_bits < 2 * h; rdft_bits++);
|
||||
win_size = 1 << rdft_bits;
|
||||
|
||||
/* (re-)configuration if the video output changed (or first init) */
|
||||
if (rdft_bits != s->rdft_bits) {
|
||||
size_t rdft_size, rdft_listsize;
|
||||
AVFrame *outpicref;
|
||||
|
||||
av_rdft_end(s->rdft);
|
||||
s->rdft = av_rdft_init(rdft_bits, DFT_R2C);
|
||||
s->rdft_bits = rdft_bits;
|
||||
|
||||
/* RDFT buffers: x2 for each (display) channel buffer.
|
||||
* Note: we use free and malloc instead of a realloc-like function to
|
||||
* make sure the buffer is aligned in memory for the FFT functions. */
|
||||
for (i = 0; i < s->nb_display_channels; i++)
|
||||
av_freep(&s->rdft_data[i]);
|
||||
av_freep(&s->rdft_data);
|
||||
s->nb_display_channels = inlink->channels;
|
||||
|
||||
if (av_size_mult(sizeof(*s->rdft_data),
|
||||
s->nb_display_channels, &rdft_listsize) < 0)
|
||||
return AVERROR(EINVAL);
|
||||
if (av_size_mult(sizeof(**s->rdft_data),
|
||||
win_size, &rdft_size) < 0)
|
||||
return AVERROR(EINVAL);
|
||||
s->rdft_data = av_malloc(rdft_listsize);
|
||||
if (!s->rdft_data)
|
||||
return AVERROR(ENOMEM);
|
||||
for (i = 0; i < s->nb_display_channels; i++) {
|
||||
s->rdft_data[i] = av_malloc(rdft_size);
|
||||
if (!s->rdft_data[i])
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
s->filled = 0;
|
||||
|
||||
/* pre-calc windowing function (hann here) */
|
||||
s->window_func_lut =
|
||||
av_realloc_f(s->window_func_lut, win_size,
|
||||
sizeof(*s->window_func_lut));
|
||||
if (!s->window_func_lut)
|
||||
return AVERROR(ENOMEM);
|
||||
for (i = 0; i < win_size; i++)
|
||||
s->window_func_lut[i] = .5f * (1 - cos(2*M_PI*i / (win_size-1)));
|
||||
|
||||
/* prepare the initial picref buffer (black frame) */
|
||||
av_frame_free(&s->outpicref);
|
||||
s->outpicref = outpicref =
|
||||
ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
||||
if (!outpicref)
|
||||
return AVERROR(ENOMEM);
|
||||
outlink->sample_aspect_ratio = (AVRational){1,1};
|
||||
for (i = 0; i < outlink->h; i++) {
|
||||
memset(outpicref->data[0] + i * outpicref->linesize[0], 0, outlink->w);
|
||||
memset(outpicref->data[1] + i * outpicref->linesize[1], 128, outlink->w);
|
||||
memset(outpicref->data[2] + i * outpicref->linesize[2], 128, outlink->w);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->xpos >= outlink->w)
|
||||
s->xpos = 0;
|
||||
|
||||
s->combine_buffer =
|
||||
av_realloc_f(s->combine_buffer, outlink->h * 3,
|
||||
sizeof(*s->combine_buffer));
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "s:%dx%d RDFT window size:%d\n",
|
||||
s->w, s->h, win_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static int push_frame(AVFilterLink *outlink)
|
||||
{
|
||||
ShowSpectrumContext *s = outlink->src->priv;
|
||||
|
||||
s->xpos++;
|
||||
if (s->xpos >= outlink->w)
|
||||
s->xpos = 0;
|
||||
s->filled = 0;
|
||||
s->req_fullfilled = 1;
|
||||
|
||||
return ff_filter_frame(outlink, av_frame_clone(s->outpicref));
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
ShowSpectrumContext *s = outlink->src->priv;
|
||||
AVFilterLink *inlink = outlink->src->inputs[0];
|
||||
int ret;
|
||||
|
||||
s->req_fullfilled = 0;
|
||||
do {
|
||||
ret = ff_request_frame(inlink);
|
||||
} while (!s->req_fullfilled && ret >= 0);
|
||||
|
||||
if (ret == AVERROR_EOF && s->outpicref)
|
||||
push_frame(outlink);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int plot_spectrum_column(AVFilterLink *inlink, AVFrame *insamples, int nb_samples)
|
||||
{
|
||||
int ret;
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
ShowSpectrumContext *s = ctx->priv;
|
||||
AVFrame *outpicref = s->outpicref;
|
||||
|
||||
/* nb_freq contains the power of two superior or equal to the output image
|
||||
* height (or half the RDFT window size) */
|
||||
const int nb_freq = 1 << (s->rdft_bits - 1);
|
||||
const int win_size = nb_freq << 1;
|
||||
const double w = 1. / (sqrt(nb_freq) * 32768.);
|
||||
|
||||
int ch, plane, n, y;
|
||||
const int start = s->filled;
|
||||
const int add_samples = FFMIN(win_size - start, nb_samples);
|
||||
|
||||
/* fill RDFT input with the number of samples available */
|
||||
for (ch = 0; ch < s->nb_display_channels; ch++) {
|
||||
const int16_t *p = (int16_t *)insamples->extended_data[ch];
|
||||
|
||||
p += s->consumed;
|
||||
for (n = 0; n < add_samples; n++)
|
||||
s->rdft_data[ch][start + n] = p[n] * s->window_func_lut[start + n];
|
||||
}
|
||||
s->filled += add_samples;
|
||||
|
||||
/* complete RDFT window size? */
|
||||
if (s->filled == win_size) {
|
||||
|
||||
/* channel height */
|
||||
int h = s->channel_height;
|
||||
|
||||
/* run RDFT on each samples set */
|
||||
for (ch = 0; ch < s->nb_display_channels; ch++)
|
||||
av_rdft_calc(s->rdft, s->rdft_data[ch]);
|
||||
|
||||
/* fill a new spectrum column */
|
||||
#define RE(y, ch) s->rdft_data[ch][2 * y + 0]
|
||||
#define IM(y, ch) s->rdft_data[ch][2 * y + 1]
|
||||
#define MAGNITUDE(y, ch) hypot(RE(y, ch), IM(y, ch))
|
||||
|
||||
/* initialize buffer for combining to black */
|
||||
for (y = 0; y < outlink->h; y++) {
|
||||
s->combine_buffer[3 * y ] = 0;
|
||||
s->combine_buffer[3 * y + 1] = 127.5;
|
||||
s->combine_buffer[3 * y + 2] = 127.5;
|
||||
}
|
||||
|
||||
for (ch = 0; ch < s->nb_display_channels; ch++) {
|
||||
float yf, uf, vf;
|
||||
|
||||
/* decide color range */
|
||||
switch (s->mode) {
|
||||
case COMBINED:
|
||||
// reduce range by channel count
|
||||
yf = 256.0f / s->nb_display_channels;
|
||||
switch (s->color_mode) {
|
||||
case INTENSITY:
|
||||
uf = yf;
|
||||
vf = yf;
|
||||
break;
|
||||
case CHANNEL:
|
||||
/* adjust saturation for mixed UV coloring */
|
||||
/* this factor is correct for infinite channels, an approximation otherwise */
|
||||
uf = yf * M_PI;
|
||||
vf = yf * M_PI;
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
break;
|
||||
case SEPARATE:
|
||||
// full range
|
||||
yf = 256.0f;
|
||||
uf = 256.0f;
|
||||
vf = 256.0f;
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
|
||||
if (s->color_mode == CHANNEL) {
|
||||
if (s->nb_display_channels > 1) {
|
||||
uf *= 0.5 * sin((2 * M_PI * ch) / s->nb_display_channels);
|
||||
vf *= 0.5 * cos((2 * M_PI * ch) / s->nb_display_channels);
|
||||
} else {
|
||||
uf = 0.0f;
|
||||
vf = 0.0f;
|
||||
}
|
||||
}
|
||||
uf *= s->saturation;
|
||||
vf *= s->saturation;
|
||||
|
||||
/* draw the channel */
|
||||
for (y = 0; y < h; y++) {
|
||||
int row = (s->mode == COMBINED) ? y : ch * h + y;
|
||||
float *out = &s->combine_buffer[3 * row];
|
||||
|
||||
/* get magnitude */
|
||||
float a = w * MAGNITUDE(y, ch);
|
||||
|
||||
/* apply scale */
|
||||
switch (s->scale) {
|
||||
case LINEAR:
|
||||
break;
|
||||
case SQRT:
|
||||
a = sqrt(a);
|
||||
break;
|
||||
case CBRT:
|
||||
a = cbrt(a);
|
||||
break;
|
||||
case LOG:
|
||||
a = 1 - log(FFMAX(FFMIN(1, a), 1e-6)) / log(1e-6); // zero = -120dBFS
|
||||
break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
|
||||
if (s->color_mode == INTENSITY) {
|
||||
float y, u, v;
|
||||
int i;
|
||||
|
||||
for (i = 1; i < sizeof(intensity_color_table) / sizeof(*intensity_color_table) - 1; i++)
|
||||
if (intensity_color_table[i].a >= a)
|
||||
break;
|
||||
// i now is the first item >= the color
|
||||
// now we know to interpolate between item i - 1 and i
|
||||
if (a <= intensity_color_table[i - 1].a) {
|
||||
y = intensity_color_table[i - 1].y;
|
||||
u = intensity_color_table[i - 1].u;
|
||||
v = intensity_color_table[i - 1].v;
|
||||
} else if (a >= intensity_color_table[i].a) {
|
||||
y = intensity_color_table[i].y;
|
||||
u = intensity_color_table[i].u;
|
||||
v = intensity_color_table[i].v;
|
||||
} else {
|
||||
float start = intensity_color_table[i - 1].a;
|
||||
float end = intensity_color_table[i].a;
|
||||
float lerpfrac = (a - start) / (end - start);
|
||||
y = intensity_color_table[i - 1].y * (1.0f - lerpfrac)
|
||||
+ intensity_color_table[i].y * lerpfrac;
|
||||
u = intensity_color_table[i - 1].u * (1.0f - lerpfrac)
|
||||
+ intensity_color_table[i].u * lerpfrac;
|
||||
v = intensity_color_table[i - 1].v * (1.0f - lerpfrac)
|
||||
+ intensity_color_table[i].v * lerpfrac;
|
||||
}
|
||||
|
||||
out[0] += y * yf;
|
||||
out[1] += u * uf;
|
||||
out[2] += v * vf;
|
||||
} else {
|
||||
out[0] += a * yf;
|
||||
out[1] += a * uf;
|
||||
out[2] += a * vf;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy to output */
|
||||
if (s->sliding) {
|
||||
for (plane = 0; plane < 3; plane++) {
|
||||
for (y = 0; y < outlink->h; y++) {
|
||||
uint8_t *p = outpicref->data[plane] +
|
||||
y * outpicref->linesize[plane];
|
||||
memmove(p, p + 1, outlink->w - 1);
|
||||
}
|
||||
}
|
||||
s->xpos = outlink->w - 1;
|
||||
}
|
||||
for (plane = 0; plane < 3; plane++) {
|
||||
uint8_t *p = outpicref->data[plane] +
|
||||
(outlink->h - 1) * outpicref->linesize[plane] +
|
||||
s->xpos;
|
||||
for (y = 0; y < outlink->h; y++) {
|
||||
*p = rint(FFMAX(0, FFMIN(s->combine_buffer[3 * y + plane], 255)));
|
||||
p -= outpicref->linesize[plane];
|
||||
}
|
||||
}
|
||||
|
||||
outpicref->pts = insamples->pts +
|
||||
av_rescale_q(s->consumed,
|
||||
(AVRational){ 1, inlink->sample_rate },
|
||||
outlink->time_base);
|
||||
ret = push_frame(outlink);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return add_samples;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ShowSpectrumContext *s = ctx->priv;
|
||||
int ret = 0, left_samples = insamples->nb_samples;
|
||||
|
||||
s->consumed = 0;
|
||||
while (left_samples) {
|
||||
int ret = plot_spectrum_column(inlink, insamples, left_samples);
|
||||
if (ret < 0)
|
||||
break;
|
||||
s->consumed += ret;
|
||||
left_samples -= ret;
|
||||
}
|
||||
|
||||
av_frame_free(&insamples);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad showspectrum_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad showspectrum_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_avf_showspectrum = {
|
||||
.name = "showspectrum",
|
||||
.description = NULL_IF_CONFIG_SMALL("Convert input audio to a spectrum video output."),
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(ShowSpectrumContext),
|
||||
.inputs = showspectrum_inputs,
|
||||
.outputs = showspectrum_outputs,
|
||||
.priv_class = &showspectrum_class,
|
||||
};
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* audio to video multimedia filter
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
#include "internal.h"
|
||||
|
||||
enum ShowWavesMode {
|
||||
MODE_POINT,
|
||||
MODE_LINE,
|
||||
MODE_NB,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int w, h;
|
||||
AVRational rate;
|
||||
int buf_idx;
|
||||
AVFrame *outpicref;
|
||||
int req_fullfilled;
|
||||
int n;
|
||||
int sample_count_mod;
|
||||
enum ShowWavesMode mode;
|
||||
} ShowWavesContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ShowWavesContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption showwaves_options[] = {
|
||||
{ "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "600x240"}, 0, 0, FLAGS },
|
||||
{ "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "600x240"}, 0, 0, FLAGS },
|
||||
{ "mode", "select display mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=MODE_POINT}, 0, MODE_NB-1, FLAGS, "mode"},
|
||||
{ "point", "draw a point for each sample", 0, AV_OPT_TYPE_CONST, {.i64=MODE_POINT}, .flags=FLAGS, .unit="mode"},
|
||||
{ "line", "draw a line for each sample", 0, AV_OPT_TYPE_CONST, {.i64=MODE_LINE}, .flags=FLAGS, .unit="mode"},
|
||||
{ "n", "set how many samples to show in the same point", OFFSET(n), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, FLAGS },
|
||||
{ "rate", "set video rate", OFFSET(rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, FLAGS },
|
||||
{ "r", "set video rate", OFFSET(rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(showwaves);
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ShowWavesContext *showwaves = ctx->priv;
|
||||
|
||||
av_frame_free(&showwaves->outpicref);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_NONE };
|
||||
static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_GRAY8, AV_PIX_FMT_NONE };
|
||||
|
||||
/* set input audio formats */
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_formats);
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
|
||||
|
||||
formats = ff_all_samplerates();
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_samplerates);
|
||||
|
||||
/* set output video format */
|
||||
formats = ff_make_format_list(pix_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
ShowWavesContext *showwaves = ctx->priv;
|
||||
|
||||
if (!showwaves->n)
|
||||
showwaves->n = FFMAX(1, ((double)inlink->sample_rate / (showwaves->w * av_q2d(showwaves->rate))) + 0.5);
|
||||
|
||||
showwaves->buf_idx = 0;
|
||||
outlink->w = showwaves->w;
|
||||
outlink->h = showwaves->h;
|
||||
outlink->sample_aspect_ratio = (AVRational){1,1};
|
||||
|
||||
outlink->frame_rate = av_div_q((AVRational){inlink->sample_rate,showwaves->n},
|
||||
(AVRational){showwaves->w,1});
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "s:%dx%d r:%f n:%d\n",
|
||||
showwaves->w, showwaves->h, av_q2d(outlink->frame_rate), showwaves->n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static int push_frame(AVFilterLink *outlink)
|
||||
{
|
||||
ShowWavesContext *showwaves = outlink->src->priv;
|
||||
int ret;
|
||||
|
||||
if ((ret = ff_filter_frame(outlink, showwaves->outpicref)) >= 0)
|
||||
showwaves->req_fullfilled = 1;
|
||||
showwaves->outpicref = NULL;
|
||||
showwaves->buf_idx = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
ShowWavesContext *showwaves = outlink->src->priv;
|
||||
AVFilterLink *inlink = outlink->src->inputs[0];
|
||||
int ret;
|
||||
|
||||
showwaves->req_fullfilled = 0;
|
||||
do {
|
||||
ret = ff_request_frame(inlink);
|
||||
} while (!showwaves->req_fullfilled && ret >= 0);
|
||||
|
||||
if (ret == AVERROR_EOF && showwaves->outpicref)
|
||||
push_frame(outlink);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define MAX_INT16 ((1<<15) -1)
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
ShowWavesContext *showwaves = ctx->priv;
|
||||
const int nb_samples = insamples->nb_samples;
|
||||
AVFrame *outpicref = showwaves->outpicref;
|
||||
int linesize = outpicref ? outpicref->linesize[0] : 0;
|
||||
int16_t *p = (int16_t *)insamples->data[0];
|
||||
int nb_channels = inlink->channels;
|
||||
int i, j, k, h, ret = 0;
|
||||
const int n = showwaves->n;
|
||||
const int x = 255 / (nb_channels * n); /* multiplication factor, pre-computed to avoid in-loop divisions */
|
||||
|
||||
/* draw data in the buffer */
|
||||
for (i = 0; i < nb_samples; i++) {
|
||||
if (!showwaves->outpicref) {
|
||||
showwaves->outpicref = outpicref =
|
||||
ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
||||
if (!outpicref)
|
||||
return AVERROR(ENOMEM);
|
||||
outpicref->width = outlink->w;
|
||||
outpicref->height = outlink->h;
|
||||
outpicref->pts = insamples->pts +
|
||||
av_rescale_q((p - (int16_t *)insamples->data[0]) / nb_channels,
|
||||
(AVRational){ 1, inlink->sample_rate },
|
||||
outlink->time_base);
|
||||
linesize = outpicref->linesize[0];
|
||||
for (j = 0; j < outlink->h; j++)
|
||||
memset(outpicref->data[0] + j * linesize, 0, outlink->w);
|
||||
}
|
||||
for (j = 0; j < nb_channels; j++) {
|
||||
h = showwaves->h/2 - av_rescale(*p++, showwaves->h/2, MAX_INT16);
|
||||
switch (showwaves->mode) {
|
||||
case MODE_POINT:
|
||||
if (h >= 0 && h < outlink->h)
|
||||
*(outpicref->data[0] + showwaves->buf_idx + h * linesize) += x;
|
||||
break;
|
||||
|
||||
case MODE_LINE:
|
||||
{
|
||||
int start = showwaves->h/2, end = av_clip(h, 0, outlink->h-1);
|
||||
if (start > end) FFSWAP(int16_t, start, end);
|
||||
for (k = start; k < end; k++)
|
||||
*(outpicref->data[0] + showwaves->buf_idx + k * linesize) += x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showwaves->sample_count_mod++;
|
||||
if (showwaves->sample_count_mod == n) {
|
||||
showwaves->sample_count_mod = 0;
|
||||
showwaves->buf_idx++;
|
||||
}
|
||||
if (showwaves->buf_idx == showwaves->w)
|
||||
if ((ret = push_frame(outlink)) < 0)
|
||||
break;
|
||||
outpicref = showwaves->outpicref;
|
||||
}
|
||||
|
||||
av_frame_free(&insamples);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad showwaves_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad showwaves_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_avf_showwaves = {
|
||||
.name = "showwaves",
|
||||
.description = NULL_IF_CONFIG_SMALL("Convert input audio to a video output."),
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(ShowWavesContext),
|
||||
.inputs = showwaves_inputs,
|
||||
.outputs = showwaves_outputs,
|
||||
.priv_class = &showwaves_class,
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Filter graphs
|
||||
* copyright (c) 2007 Bobby Bingham
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_AVFILTERGRAPH_H
|
||||
#define AVFILTER_AVFILTERGRAPH_H
|
||||
|
||||
#include "avfilter.h"
|
||||
#include "libavutil/log.h"
|
||||
|
||||
#endif /* AVFILTER_AVFILTERGRAPH_H */
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2005 Robert Edele <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "bbox.h"
|
||||
|
||||
int ff_calculate_bounding_box(FFBoundingBox *bbox,
|
||||
const uint8_t *data, int linesize, int w, int h,
|
||||
int min_val)
|
||||
{
|
||||
int x, y;
|
||||
int start_x;
|
||||
int start_y;
|
||||
int end_x;
|
||||
int end_y;
|
||||
const uint8_t *line;
|
||||
|
||||
/* left bound */
|
||||
for (start_x = 0; start_x < w; start_x++)
|
||||
for (y = 0; y < h; y++)
|
||||
if ((data[y * linesize + start_x] > min_val))
|
||||
goto outl;
|
||||
outl:
|
||||
if (start_x == w) /* no points found */
|
||||
return 0;
|
||||
|
||||
/* right bound */
|
||||
for (end_x = w - 1; end_x >= start_x; end_x--)
|
||||
for (y = 0; y < h; y++)
|
||||
if ((data[y * linesize + end_x] > min_val))
|
||||
goto outr;
|
||||
outr:
|
||||
|
||||
/* top bound */
|
||||
line = data;
|
||||
for (start_y = 0; start_y < h; start_y++) {
|
||||
for (x = 0; x < w; x++)
|
||||
if (line[x] > min_val)
|
||||
goto outt;
|
||||
line += linesize;
|
||||
}
|
||||
outt:
|
||||
|
||||
/* bottom bound */
|
||||
line = data + (h-1)*linesize;
|
||||
for (end_y = h - 1; end_y >= start_y; end_y--) {
|
||||
for (x = 0; x < w; x++)
|
||||
if (line[x] > min_val)
|
||||
goto outb;
|
||||
line -= linesize;
|
||||
}
|
||||
outb:
|
||||
|
||||
bbox->x1 = start_x;
|
||||
bbox->y1 = start_y;
|
||||
bbox->x2 = end_x;
|
||||
bbox->y2 = end_y;
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2005 Robert Edele <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_BBOX_H
|
||||
#define AVFILTER_BBOX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
int x1, x2, y1, y2;
|
||||
} FFBoundingBox;
|
||||
|
||||
/**
|
||||
* Calculate the smallest rectangle that will encompass the
|
||||
* region with values > min_val.
|
||||
*
|
||||
* @param bbox bounding box structure which is updated with the found values.
|
||||
* If no pixels could be found with value > min_val, the
|
||||
* structure is not modified.
|
||||
* @return 1 in case at least one pixel with value > min_val was found,
|
||||
* 0 otherwise
|
||||
*/
|
||||
int ff_calculate_bounding_box(FFBoundingBox *bbox,
|
||||
const uint8_t *data, int linesize,
|
||||
int w, int h, int min_val);
|
||||
|
||||
#endif /* AVFILTER_BBOX_H */
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright Stefano Sabatini <stefasab gmail com>
|
||||
* Copyright Anton Khirnov <anton khirnov net>
|
||||
* Copyright Michael Niedermayer <michaelni gmx at>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "audio.h"
|
||||
#include "avcodec.h"
|
||||
#include "version.h"
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
void ff_avfilter_default_free_buffer(AVFilterBuffer *ptr)
|
||||
{
|
||||
if (ptr->extended_data != ptr->data)
|
||||
av_freep(&ptr->extended_data);
|
||||
av_free(ptr->data[0]);
|
||||
av_free(ptr);
|
||||
}
|
||||
|
||||
static void copy_video_props(AVFilterBufferRefVideoProps *dst, AVFilterBufferRefVideoProps *src) {
|
||||
*dst = *src;
|
||||
if (src->qp_table) {
|
||||
int qsize = src->qp_table_size;
|
||||
dst->qp_table = av_malloc(qsize);
|
||||
memcpy(dst->qp_table, src->qp_table, qsize);
|
||||
}
|
||||
}
|
||||
|
||||
AVFilterBufferRef *avfilter_ref_buffer(AVFilterBufferRef *ref, int pmask)
|
||||
{
|
||||
AVFilterBufferRef *ret = av_malloc(sizeof(AVFilterBufferRef));
|
||||
if (!ret)
|
||||
return NULL;
|
||||
*ret = *ref;
|
||||
|
||||
ret->metadata = NULL;
|
||||
av_dict_copy(&ret->metadata, ref->metadata, 0);
|
||||
|
||||
if (ref->type == AVMEDIA_TYPE_VIDEO) {
|
||||
ret->video = av_malloc(sizeof(AVFilterBufferRefVideoProps));
|
||||
if (!ret->video) {
|
||||
av_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
copy_video_props(ret->video, ref->video);
|
||||
ret->extended_data = ret->data;
|
||||
} else if (ref->type == AVMEDIA_TYPE_AUDIO) {
|
||||
ret->audio = av_malloc(sizeof(AVFilterBufferRefAudioProps));
|
||||
if (!ret->audio) {
|
||||
av_free(ret);
|
||||
return NULL;
|
||||
}
|
||||
*ret->audio = *ref->audio;
|
||||
|
||||
if (ref->extended_data && ref->extended_data != ref->data) {
|
||||
int nb_channels = av_get_channel_layout_nb_channels(ref->audio->channel_layout);
|
||||
if (!(ret->extended_data = av_malloc(sizeof(*ret->extended_data) *
|
||||
nb_channels))) {
|
||||
av_freep(&ret->audio);
|
||||
av_freep(&ret);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(ret->extended_data, ref->extended_data,
|
||||
sizeof(*ret->extended_data) * nb_channels);
|
||||
} else
|
||||
ret->extended_data = ret->data;
|
||||
}
|
||||
ret->perms &= pmask;
|
||||
ret->buf->refcount ++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void avfilter_unref_buffer(AVFilterBufferRef *ref)
|
||||
{
|
||||
if (!ref)
|
||||
return;
|
||||
av_assert0(ref->buf->refcount > 0);
|
||||
if (!(--ref->buf->refcount))
|
||||
ref->buf->free(ref->buf);
|
||||
if (ref->extended_data != ref->data)
|
||||
av_freep(&ref->extended_data);
|
||||
if (ref->video)
|
||||
av_freep(&ref->video->qp_table);
|
||||
av_freep(&ref->video);
|
||||
av_freep(&ref->audio);
|
||||
av_dict_free(&ref->metadata);
|
||||
av_free(ref);
|
||||
}
|
||||
|
||||
void avfilter_unref_bufferp(AVFilterBufferRef **ref)
|
||||
{
|
||||
avfilter_unref_buffer(*ref);
|
||||
*ref = NULL;
|
||||
}
|
||||
|
||||
int avfilter_copy_frame_props(AVFilterBufferRef *dst, const AVFrame *src)
|
||||
{
|
||||
dst->pts = src->pts;
|
||||
dst->pos = av_frame_get_pkt_pos(src);
|
||||
dst->format = src->format;
|
||||
|
||||
av_dict_free(&dst->metadata);
|
||||
av_dict_copy(&dst->metadata, av_frame_get_metadata(src), 0);
|
||||
|
||||
switch (dst->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
dst->video->w = src->width;
|
||||
dst->video->h = src->height;
|
||||
dst->video->sample_aspect_ratio = src->sample_aspect_ratio;
|
||||
dst->video->interlaced = src->interlaced_frame;
|
||||
dst->video->top_field_first = src->top_field_first;
|
||||
dst->video->key_frame = src->key_frame;
|
||||
dst->video->pict_type = src->pict_type;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
dst->audio->sample_rate = src->sample_rate;
|
||||
dst->audio->channel_layout = src->channel_layout;
|
||||
break;
|
||||
default:
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void avfilter_copy_buffer_ref_props(AVFilterBufferRef *dst, AVFilterBufferRef *src)
|
||||
{
|
||||
// copy common properties
|
||||
dst->pts = src->pts;
|
||||
dst->pos = src->pos;
|
||||
|
||||
switch (src->type) {
|
||||
case AVMEDIA_TYPE_VIDEO: {
|
||||
if (dst->video->qp_table)
|
||||
av_freep(&dst->video->qp_table);
|
||||
copy_video_props(dst->video, src->video);
|
||||
break;
|
||||
}
|
||||
case AVMEDIA_TYPE_AUDIO: *dst->audio = *src->audio; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
av_dict_free(&dst->metadata);
|
||||
av_dict_copy(&dst->metadata, src->metadata, 0);
|
||||
}
|
||||
#endif /* FF_API_AVFILTERBUFFER */
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Generic buffer queue
|
||||
* Copyright (c) 2012 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_BUFFERQUEUE_H
|
||||
#define AVFILTER_BUFFERQUEUE_H
|
||||
|
||||
/**
|
||||
* FFBufQueue: simple AVFrame queue API
|
||||
*
|
||||
* Note: this API is not thread-safe. Concurrent access to the same queue
|
||||
* must be protected by a mutex or any synchronization mechanism.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum size of the queue.
|
||||
*
|
||||
* This value can be overridden by definying it before including this
|
||||
* header.
|
||||
* Powers of 2 are recommended.
|
||||
*/
|
||||
#ifndef FF_BUFQUEUE_SIZE
|
||||
#define FF_BUFQUEUE_SIZE 32
|
||||
#endif
|
||||
|
||||
#include "avfilter.h"
|
||||
#include "libavutil/avassert.h"
|
||||
|
||||
/**
|
||||
* Structure holding the queue
|
||||
*/
|
||||
struct FFBufQueue {
|
||||
AVFrame *queue[FF_BUFQUEUE_SIZE];
|
||||
unsigned short head;
|
||||
unsigned short available; /**< number of available buffers */
|
||||
};
|
||||
|
||||
#define BUCKET(i) queue->queue[(queue->head + (i)) % FF_BUFQUEUE_SIZE]
|
||||
|
||||
/**
|
||||
* Test if a buffer queue is full.
|
||||
*/
|
||||
static inline int ff_bufqueue_is_full(struct FFBufQueue *queue)
|
||||
{
|
||||
return queue->available == FF_BUFQUEUE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a buffer to the queue.
|
||||
*
|
||||
* If the queue is already full, then the current last buffer is dropped
|
||||
* (and unrefed) with a warning before adding the new buffer.
|
||||
*/
|
||||
static inline void ff_bufqueue_add(void *log, struct FFBufQueue *queue,
|
||||
AVFrame *buf)
|
||||
{
|
||||
if (ff_bufqueue_is_full(queue)) {
|
||||
av_log(log, AV_LOG_WARNING, "Buffer queue overflow, dropping.\n");
|
||||
av_frame_free(&BUCKET(--queue->available));
|
||||
}
|
||||
BUCKET(queue->available++) = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a buffer from the queue without altering it.
|
||||
*
|
||||
* Buffer with index 0 is the first buffer in the queue.
|
||||
* Return NULL if the queue has not enough buffers.
|
||||
*/
|
||||
static inline AVFrame *ff_bufqueue_peek(struct FFBufQueue *queue,
|
||||
unsigned index)
|
||||
{
|
||||
return index < queue->available ? BUCKET(index) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first buffer from the queue and remove it.
|
||||
*
|
||||
* Do not use on an empty queue.
|
||||
*/
|
||||
static inline AVFrame *ff_bufqueue_get(struct FFBufQueue *queue)
|
||||
{
|
||||
AVFrame *ret = queue->queue[queue->head];
|
||||
av_assert0(queue->available);
|
||||
queue->available--;
|
||||
queue->queue[queue->head] = NULL;
|
||||
queue->head = (queue->head + 1) % FF_BUFQUEUE_SIZE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unref and remove all buffers from the queue.
|
||||
*/
|
||||
static inline void ff_bufqueue_discard_all(struct FFBufQueue *queue)
|
||||
{
|
||||
while (queue->available) {
|
||||
AVFrame *buf = ff_bufqueue_get(queue);
|
||||
av_frame_free(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
#undef BUCKET
|
||||
|
||||
#endif /* AVFILTER_BUFFERQUEUE_H */
|
||||
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* buffer sink
|
||||
*/
|
||||
|
||||
#include "libavutil/audio_fifo.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "buffersink.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
AVFifoBuffer *fifo; ///< FIFO buffer of video frame references
|
||||
unsigned warning_limit;
|
||||
|
||||
/* only used for video */
|
||||
enum AVPixelFormat *pixel_fmts; ///< list of accepted pixel formats, must be terminated with -1
|
||||
int pixel_fmts_size;
|
||||
|
||||
/* only used for audio */
|
||||
enum AVSampleFormat *sample_fmts; ///< list of accepted sample formats, terminated by AV_SAMPLE_FMT_NONE
|
||||
int sample_fmts_size;
|
||||
int64_t *channel_layouts; ///< list of accepted channel layouts, terminated by -1
|
||||
int channel_layouts_size;
|
||||
int *channel_counts; ///< list of accepted channel counts, terminated by -1
|
||||
int channel_counts_size;
|
||||
int all_channel_counts;
|
||||
int *sample_rates; ///< list of accepted sample rates, terminated by -1
|
||||
int sample_rates_size;
|
||||
|
||||
/* only used for compat API */
|
||||
AVAudioFifo *audio_fifo; ///< FIFO for audio samples
|
||||
int64_t next_pts; ///< interpolating audio pts
|
||||
} BufferSinkContext;
|
||||
|
||||
#define NB_ITEMS(list) (list ## _size / sizeof(*list))
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSinkContext *sink = ctx->priv;
|
||||
AVFrame *frame;
|
||||
|
||||
if (sink->audio_fifo)
|
||||
av_audio_fifo_free(sink->audio_fifo);
|
||||
|
||||
if (sink->fifo) {
|
||||
while (av_fifo_size(sink->fifo) >= sizeof(AVFilterBufferRef *)) {
|
||||
av_fifo_generic_read(sink->fifo, &frame, sizeof(frame), NULL);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
av_fifo_free(sink->fifo);
|
||||
sink->fifo = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int add_buffer_ref(AVFilterContext *ctx, AVFrame *ref)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
|
||||
if (av_fifo_space(buf->fifo) < sizeof(AVFilterBufferRef *)) {
|
||||
/* realloc fifo size */
|
||||
if (av_fifo_realloc2(buf->fifo, av_fifo_size(buf->fifo) * 2) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Cannot buffer more frames. Consume some available frames "
|
||||
"before adding new ones.\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
/* cache frame */
|
||||
av_fifo_generic_write(buf->fifo, &ref, sizeof(AVFilterBufferRef *), NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *link, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = link->dst;
|
||||
BufferSinkContext *buf = link->dst->priv;
|
||||
int ret;
|
||||
|
||||
if ((ret = add_buffer_ref(ctx, frame)) < 0)
|
||||
return ret;
|
||||
if (buf->warning_limit &&
|
||||
av_fifo_size(buf->fifo) / sizeof(AVFilterBufferRef *) >= buf->warning_limit) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"%d buffers queued in %s, something may be wrong.\n",
|
||||
buf->warning_limit,
|
||||
(char *)av_x_if_null(ctx->name, ctx->filter->name));
|
||||
buf->warning_limit *= 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
return av_buffersink_get_frame_flags(ctx, frame, 0);
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
int ret;
|
||||
AVFrame *cur_frame;
|
||||
|
||||
/* no picref available, fetch it from the filterchain */
|
||||
if (!av_fifo_size(buf->fifo)) {
|
||||
if (flags & AV_BUFFERSINK_FLAG_NO_REQUEST)
|
||||
return AVERROR(EAGAIN);
|
||||
if ((ret = ff_request_frame(inlink)) < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!av_fifo_size(buf->fifo))
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if (flags & AV_BUFFERSINK_FLAG_PEEK) {
|
||||
cur_frame = *((AVFrame **)av_fifo_peek2(buf->fifo, 0));
|
||||
if ((ret = av_frame_ref(frame, cur_frame)) < 0)
|
||||
return ret;
|
||||
} else {
|
||||
av_fifo_generic_read(buf->fifo, &cur_frame, sizeof(cur_frame), NULL);
|
||||
av_frame_move_ref(frame, cur_frame);
|
||||
av_frame_free(&cur_frame);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_from_fifo(AVFilterContext *ctx, AVFrame *frame,
|
||||
int nb_samples)
|
||||
{
|
||||
BufferSinkContext *s = ctx->priv;
|
||||
AVFilterLink *link = ctx->inputs[0];
|
||||
AVFrame *tmp;
|
||||
|
||||
if (!(tmp = ff_get_audio_buffer(link, nb_samples)))
|
||||
return AVERROR(ENOMEM);
|
||||
av_audio_fifo_read(s->audio_fifo, (void**)tmp->extended_data, nb_samples);
|
||||
|
||||
tmp->pts = s->next_pts;
|
||||
if (s->next_pts != AV_NOPTS_VALUE)
|
||||
s->next_pts += av_rescale_q(nb_samples, (AVRational){1, link->sample_rate},
|
||||
link->time_base);
|
||||
|
||||
av_frame_move_ref(frame, tmp);
|
||||
av_frame_free(&tmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_get_samples(AVFilterContext *ctx,
|
||||
AVFrame *frame, int nb_samples)
|
||||
{
|
||||
BufferSinkContext *s = ctx->priv;
|
||||
AVFilterLink *link = ctx->inputs[0];
|
||||
AVFrame *cur_frame;
|
||||
int ret = 0;
|
||||
|
||||
if (!s->audio_fifo) {
|
||||
int nb_channels = link->channels;
|
||||
if (!(s->audio_fifo = av_audio_fifo_alloc(link->format, nb_channels, nb_samples)))
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
while (ret >= 0) {
|
||||
if (av_audio_fifo_size(s->audio_fifo) >= nb_samples)
|
||||
return read_from_fifo(ctx, frame, nb_samples);
|
||||
|
||||
if (!(cur_frame = av_frame_alloc()))
|
||||
return AVERROR(ENOMEM);
|
||||
ret = av_buffersink_get_frame_flags(ctx, cur_frame, 0);
|
||||
if (ret == AVERROR_EOF && av_audio_fifo_size(s->audio_fifo)) {
|
||||
av_frame_free(&cur_frame);
|
||||
return read_from_fifo(ctx, frame, av_audio_fifo_size(s->audio_fifo));
|
||||
} else if (ret < 0) {
|
||||
av_frame_free(&cur_frame);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cur_frame->pts != AV_NOPTS_VALUE) {
|
||||
s->next_pts = cur_frame->pts -
|
||||
av_rescale_q(av_audio_fifo_size(s->audio_fifo),
|
||||
(AVRational){ 1, link->sample_rate },
|
||||
link->time_base);
|
||||
}
|
||||
|
||||
ret = av_audio_fifo_write(s->audio_fifo, (void**)cur_frame->extended_data,
|
||||
cur_frame->nb_samples);
|
||||
av_frame_free(&cur_frame);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVBufferSinkParams *av_buffersink_params_alloc(void)
|
||||
{
|
||||
static const int pixel_fmts[] = { AV_PIX_FMT_NONE };
|
||||
AVBufferSinkParams *params = av_malloc(sizeof(AVBufferSinkParams));
|
||||
if (!params)
|
||||
return NULL;
|
||||
|
||||
params->pixel_fmts = pixel_fmts;
|
||||
return params;
|
||||
}
|
||||
|
||||
AVABufferSinkParams *av_abuffersink_params_alloc(void)
|
||||
{
|
||||
AVABufferSinkParams *params = av_mallocz(sizeof(AVABufferSinkParams));
|
||||
|
||||
if (!params)
|
||||
return NULL;
|
||||
return params;
|
||||
}
|
||||
|
||||
#define FIFO_INIT_SIZE 8
|
||||
|
||||
static av_cold int common_init(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
|
||||
buf->fifo = av_fifo_alloc(FIFO_INIT_SIZE*sizeof(AVFilterBufferRef *));
|
||||
if (!buf->fifo) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Failed to allocate fifo\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
buf->warning_limit = 100;
|
||||
buf->next_pts = AV_NOPTS_VALUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void av_buffersink_set_frame_size(AVFilterContext *ctx, unsigned frame_size)
|
||||
{
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
|
||||
inlink->min_samples = inlink->max_samples =
|
||||
inlink->partial_buf_size = frame_size;
|
||||
}
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
FF_DISABLE_DEPRECATION_WARNINGS
|
||||
static void compat_free_buffer(AVFilterBuffer *buf)
|
||||
{
|
||||
AVFrame *frame = buf->priv;
|
||||
av_frame_free(&frame);
|
||||
av_free(buf);
|
||||
}
|
||||
|
||||
static int compat_read(AVFilterContext *ctx,
|
||||
AVFilterBufferRef **pbuf, int nb_samples, int flags)
|
||||
{
|
||||
AVFilterBufferRef *buf;
|
||||
AVFrame *frame;
|
||||
int ret;
|
||||
|
||||
if (!pbuf)
|
||||
return ff_poll_frame(ctx->inputs[0]);
|
||||
|
||||
frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if (!nb_samples)
|
||||
ret = av_buffersink_get_frame_flags(ctx, frame, flags);
|
||||
else
|
||||
ret = av_buffersink_get_samples(ctx, frame, nb_samples);
|
||||
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
AV_NOWARN_DEPRECATED(
|
||||
if (ctx->inputs[0]->type == AVMEDIA_TYPE_VIDEO) {
|
||||
buf = avfilter_get_video_buffer_ref_from_arrays(frame->data, frame->linesize,
|
||||
AV_PERM_READ,
|
||||
frame->width, frame->height,
|
||||
frame->format);
|
||||
} else {
|
||||
buf = avfilter_get_audio_buffer_ref_from_arrays(frame->extended_data,
|
||||
frame->linesize[0], AV_PERM_READ,
|
||||
frame->nb_samples,
|
||||
frame->format,
|
||||
frame->channel_layout);
|
||||
}
|
||||
if (!buf) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
avfilter_copy_frame_props(buf, frame);
|
||||
)
|
||||
|
||||
buf->buf->priv = frame;
|
||||
buf->buf->free = compat_free_buffer;
|
||||
|
||||
*pbuf = buf;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_read(AVFilterContext *ctx, AVFilterBufferRef **buf)
|
||||
{
|
||||
return compat_read(ctx, buf, 0, 0);
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_read_samples(AVFilterContext *ctx, AVFilterBufferRef **buf,
|
||||
int nb_samples)
|
||||
{
|
||||
return compat_read(ctx, buf, nb_samples, 0);
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_get_buffer_ref(AVFilterContext *ctx,
|
||||
AVFilterBufferRef **bufref, int flags)
|
||||
{
|
||||
*bufref = NULL;
|
||||
|
||||
av_assert0( !strcmp(ctx->filter->name, "buffersink")
|
||||
|| !strcmp(ctx->filter->name, "abuffersink")
|
||||
|| !strcmp(ctx->filter->name, "ffbuffersink")
|
||||
|| !strcmp(ctx->filter->name, "ffabuffersink"));
|
||||
|
||||
return compat_read(ctx, bufref, 0, flags);
|
||||
}
|
||||
FF_ENABLE_DEPRECATION_WARNINGS
|
||||
#endif
|
||||
|
||||
AVRational av_buffersink_get_frame_rate(AVFilterContext *ctx)
|
||||
{
|
||||
av_assert0( !strcmp(ctx->filter->name, "buffersink")
|
||||
|| !strcmp(ctx->filter->name, "ffbuffersink"));
|
||||
|
||||
return ctx->inputs[0]->frame_rate;
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersink_poll_frame(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
|
||||
av_assert0( !strcmp(ctx->filter->name, "buffersink")
|
||||
|| !strcmp(ctx->filter->name, "abuffersink")
|
||||
|| !strcmp(ctx->filter->name, "ffbuffersink")
|
||||
|| !strcmp(ctx->filter->name, "ffabuffersink"));
|
||||
|
||||
return av_fifo_size(buf->fifo)/sizeof(AVFilterBufferRef *) + ff_poll_frame(inlink);
|
||||
}
|
||||
|
||||
static av_cold int vsink_init(AVFilterContext *ctx, void *opaque)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVBufferSinkParams *params = opaque;
|
||||
int ret;
|
||||
|
||||
if (params) {
|
||||
if ((ret = av_opt_set_int_list(buf, "pix_fmts", params->pixel_fmts, AV_PIX_FMT_NONE, 0)) < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return common_init(ctx);
|
||||
}
|
||||
|
||||
#define CHECK_LIST_SIZE(field) \
|
||||
if (buf->field ## _size % sizeof(*buf->field)) { \
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid size for " #field ": %d, " \
|
||||
"should be multiple of %d\n", \
|
||||
buf->field ## _size, (int)sizeof(*buf->field)); \
|
||||
return AVERROR(EINVAL); \
|
||||
}
|
||||
static int vsink_query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVFilterFormats *formats = NULL;
|
||||
unsigned i;
|
||||
int ret;
|
||||
|
||||
CHECK_LIST_SIZE(pixel_fmts)
|
||||
if (buf->pixel_fmts_size) {
|
||||
for (i = 0; i < NB_ITEMS(buf->pixel_fmts); i++)
|
||||
if ((ret = ff_add_format(&formats, buf->pixel_fmts[i])) < 0) {
|
||||
ff_formats_unref(&formats);
|
||||
return ret;
|
||||
}
|
||||
ff_set_common_formats(ctx, formats);
|
||||
} else {
|
||||
ff_default_query_formats(ctx);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int asink_init(AVFilterContext *ctx, void *opaque)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVABufferSinkParams *params = opaque;
|
||||
int ret;
|
||||
|
||||
if (params) {
|
||||
if ((ret = av_opt_set_int_list(buf, "sample_fmts", params->sample_fmts, AV_SAMPLE_FMT_NONE, 0)) < 0 ||
|
||||
(ret = av_opt_set_int_list(buf, "sample_rates", params->sample_rates, -1, 0)) < 0 ||
|
||||
(ret = av_opt_set_int_list(buf, "channel_layouts", params->channel_layouts, -1, 0)) < 0 ||
|
||||
(ret = av_opt_set_int_list(buf, "channel_counts", params->channel_counts, -1, 0)) < 0 ||
|
||||
(ret = av_opt_set_int(buf, "all_channel_counts", params->all_channel_counts, 0)) < 0)
|
||||
return ret;
|
||||
}
|
||||
return common_init(ctx);
|
||||
}
|
||||
|
||||
static int asink_query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSinkContext *buf = ctx->priv;
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterChannelLayouts *layouts = NULL;
|
||||
unsigned i;
|
||||
int ret;
|
||||
|
||||
CHECK_LIST_SIZE(sample_fmts)
|
||||
CHECK_LIST_SIZE(sample_rates)
|
||||
CHECK_LIST_SIZE(channel_layouts)
|
||||
CHECK_LIST_SIZE(channel_counts)
|
||||
|
||||
if (buf->sample_fmts_size) {
|
||||
for (i = 0; i < NB_ITEMS(buf->sample_fmts); i++)
|
||||
if ((ret = ff_add_format(&formats, buf->sample_fmts[i])) < 0) {
|
||||
ff_formats_unref(&formats);
|
||||
return ret;
|
||||
}
|
||||
ff_set_common_formats(ctx, formats);
|
||||
}
|
||||
|
||||
if (buf->channel_layouts_size || buf->channel_counts_size ||
|
||||
buf->all_channel_counts) {
|
||||
for (i = 0; i < NB_ITEMS(buf->channel_layouts); i++)
|
||||
if ((ret = ff_add_channel_layout(&layouts, buf->channel_layouts[i])) < 0) {
|
||||
ff_channel_layouts_unref(&layouts);
|
||||
return ret;
|
||||
}
|
||||
for (i = 0; i < NB_ITEMS(buf->channel_counts); i++)
|
||||
if ((ret = ff_add_channel_layout(&layouts, FF_COUNT2LAYOUT(buf->channel_counts[i]))) < 0) {
|
||||
ff_channel_layouts_unref(&layouts);
|
||||
return ret;
|
||||
}
|
||||
if (buf->all_channel_counts) {
|
||||
if (layouts)
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Conflicting all_channel_counts and list in options\n");
|
||||
else if (!(layouts = ff_all_channel_counts()))
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
ff_set_common_channel_layouts(ctx, layouts);
|
||||
}
|
||||
|
||||
if (buf->sample_rates_size) {
|
||||
formats = NULL;
|
||||
for (i = 0; i < NB_ITEMS(buf->sample_rates); i++)
|
||||
if ((ret = ff_add_format(&formats, buf->sample_rates[i])) < 0) {
|
||||
ff_formats_unref(&formats);
|
||||
return ret;
|
||||
}
|
||||
ff_set_common_samplerates(ctx, formats);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(BufferSinkContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
static const AVOption buffersink_options[] = {
|
||||
{ "pix_fmts", "set the supported pixel formats", OFFSET(pixel_fmts), AV_OPT_TYPE_BINARY, .flags = FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
#undef FLAGS
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
static const AVOption abuffersink_options[] = {
|
||||
{ "sample_fmts", "set the supported sample formats", OFFSET(sample_fmts), AV_OPT_TYPE_BINARY, .flags = FLAGS },
|
||||
{ "sample_rates", "set the supported sample rates", OFFSET(sample_rates), AV_OPT_TYPE_BINARY, .flags = FLAGS },
|
||||
{ "channel_layouts", "set the supported channel layouts", OFFSET(channel_layouts), AV_OPT_TYPE_BINARY, .flags = FLAGS },
|
||||
{ "channel_counts", "set the supported channel counts", OFFSET(channel_counts), AV_OPT_TYPE_BINARY, .flags = FLAGS },
|
||||
{ "all_channel_counts", "accept all channel counts", OFFSET(all_channel_counts), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS },
|
||||
{ NULL },
|
||||
};
|
||||
#undef FLAGS
|
||||
|
||||
AVFILTER_DEFINE_CLASS(buffersink);
|
||||
AVFILTER_DEFINE_CLASS(abuffersink);
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
|
||||
#define ffbuffersink_options buffersink_options
|
||||
#define ffabuffersink_options abuffersink_options
|
||||
AVFILTER_DEFINE_CLASS(ffbuffersink);
|
||||
AVFILTER_DEFINE_CLASS(ffabuffersink);
|
||||
|
||||
static const AVFilterPad ffbuffersink_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFilter avfilter_vsink_ffbuffersink = {
|
||||
.name = "ffbuffersink",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer video frames, and make them available to the end of the filter graph."),
|
||||
.priv_size = sizeof(BufferSinkContext),
|
||||
.priv_class = &ffbuffersink_class,
|
||||
.init_opaque = vsink_init,
|
||||
.uninit = uninit,
|
||||
|
||||
.query_formats = vsink_query_formats,
|
||||
.inputs = ffbuffersink_inputs,
|
||||
.outputs = NULL,
|
||||
};
|
||||
|
||||
static const AVFilterPad ffabuffersink_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFilter avfilter_asink_ffabuffersink = {
|
||||
.name = "ffabuffersink",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer audio frames, and make them available to the end of the filter graph."),
|
||||
.init_opaque = asink_init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(BufferSinkContext),
|
||||
.priv_class = &ffabuffersink_class,
|
||||
.query_formats = asink_query_formats,
|
||||
.inputs = ffabuffersink_inputs,
|
||||
.outputs = NULL,
|
||||
};
|
||||
#endif /* FF_API_AVFILTERBUFFER */
|
||||
|
||||
static const AVFilterPad avfilter_vsink_buffer_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vsink_buffer = {
|
||||
.name = "buffersink",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer video frames, and make them available to the end of the filter graph."),
|
||||
.priv_size = sizeof(BufferSinkContext),
|
||||
.priv_class = &buffersink_class,
|
||||
.init_opaque = vsink_init,
|
||||
.uninit = uninit,
|
||||
|
||||
.query_formats = vsink_query_formats,
|
||||
.inputs = avfilter_vsink_buffer_inputs,
|
||||
.outputs = NULL,
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_asink_abuffer_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asink_abuffer = {
|
||||
.name = "abuffersink",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer audio frames, and make them available to the end of the filter graph."),
|
||||
.priv_class = &abuffersink_class,
|
||||
.priv_size = sizeof(BufferSinkContext),
|
||||
.init_opaque = asink_init,
|
||||
.uninit = uninit,
|
||||
|
||||
.query_formats = asink_query_formats,
|
||||
.inputs = avfilter_asink_abuffer_inputs,
|
||||
.outputs = NULL,
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_BUFFERSINK_H
|
||||
#define AVFILTER_BUFFERSINK_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* memory buffer sink API for audio and video
|
||||
*/
|
||||
|
||||
#include "avfilter.h"
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
/**
|
||||
* Get an audio/video buffer data from buffer_sink and put it in bufref.
|
||||
*
|
||||
* This function works with both audio and video buffer sinks.
|
||||
*
|
||||
* @param buffer_sink pointer to a buffersink or abuffersink context
|
||||
* @param flags a combination of AV_BUFFERSINK_FLAG_* flags
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_buffersink_get_buffer_ref(AVFilterContext *buffer_sink,
|
||||
AVFilterBufferRef **bufref, int flags);
|
||||
|
||||
/**
|
||||
* Get the number of immediately available frames.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_buffersink_poll_frame(AVFilterContext *ctx);
|
||||
|
||||
/**
|
||||
* Get a buffer with filtered data from sink and put it in buf.
|
||||
*
|
||||
* @param ctx pointer to a context of a buffersink or abuffersink AVFilter.
|
||||
* @param buf pointer to the buffer will be written here if buf is non-NULL. buf
|
||||
* must be freed by the caller using avfilter_unref_buffer().
|
||||
* Buf may also be NULL to query whether a buffer is ready to be
|
||||
* output.
|
||||
*
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_buffersink_read(AVFilterContext *ctx, AVFilterBufferRef **buf);
|
||||
|
||||
/**
|
||||
* Same as av_buffersink_read, but with the ability to specify the number of
|
||||
* samples read. This function is less efficient than av_buffersink_read(),
|
||||
* because it copies the data around.
|
||||
*
|
||||
* @param ctx pointer to a context of the abuffersink AVFilter.
|
||||
* @param buf pointer to the buffer will be written here if buf is non-NULL. buf
|
||||
* must be freed by the caller using avfilter_unref_buffer(). buf
|
||||
* will contain exactly nb_samples audio samples, except at the end
|
||||
* of stream, when it can contain less than nb_samples.
|
||||
* Buf may also be NULL to query whether a buffer is ready to be
|
||||
* output.
|
||||
*
|
||||
* @warning do not mix this function with av_buffersink_read(). Use only one or
|
||||
* the other with a single sink, not both.
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_buffersink_read_samples(AVFilterContext *ctx, AVFilterBufferRef **buf,
|
||||
int nb_samples);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Get a frame with filtered data from sink and put it in frame.
|
||||
*
|
||||
* @param ctx pointer to a buffersink or abuffersink filter context.
|
||||
* @param frame pointer to an allocated frame that will be filled with data.
|
||||
* The data must be freed using av_frame_unref() / av_frame_free()
|
||||
* @param flags a combination of AV_BUFFERSINK_FLAG_* flags
|
||||
*
|
||||
* @return >= 0 in for success, a negative AVERROR code for failure.
|
||||
*/
|
||||
int av_buffersink_get_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags);
|
||||
|
||||
/**
|
||||
* Tell av_buffersink_get_buffer_ref() to read video/samples buffer
|
||||
* reference, but not remove it from the buffer. This is useful if you
|
||||
* need only to read a video/samples buffer, without to fetch it.
|
||||
*/
|
||||
#define AV_BUFFERSINK_FLAG_PEEK 1
|
||||
|
||||
/**
|
||||
* Tell av_buffersink_get_buffer_ref() not to request a frame from its input.
|
||||
* If a frame is already buffered, it is read (and removed from the buffer),
|
||||
* but if no frame is present, return AVERROR(EAGAIN).
|
||||
*/
|
||||
#define AV_BUFFERSINK_FLAG_NO_REQUEST 2
|
||||
|
||||
/**
|
||||
* Struct to use for initializing a buffersink context.
|
||||
*/
|
||||
typedef struct {
|
||||
const enum AVPixelFormat *pixel_fmts; ///< list of allowed pixel formats, terminated by AV_PIX_FMT_NONE
|
||||
} AVBufferSinkParams;
|
||||
|
||||
/**
|
||||
* Create an AVBufferSinkParams structure.
|
||||
*
|
||||
* Must be freed with av_free().
|
||||
*/
|
||||
AVBufferSinkParams *av_buffersink_params_alloc(void);
|
||||
|
||||
/**
|
||||
* Struct to use for initializing an abuffersink context.
|
||||
*/
|
||||
typedef struct {
|
||||
const enum AVSampleFormat *sample_fmts; ///< list of allowed sample formats, terminated by AV_SAMPLE_FMT_NONE
|
||||
const int64_t *channel_layouts; ///< list of allowed channel layouts, terminated by -1
|
||||
const int *channel_counts; ///< list of allowed channel counts, terminated by -1
|
||||
int all_channel_counts; ///< if not 0, accept any channel count or layout
|
||||
int *sample_rates; ///< list of allowed sample rates, terminated by -1
|
||||
} AVABufferSinkParams;
|
||||
|
||||
/**
|
||||
* Create an AVABufferSinkParams structure.
|
||||
*
|
||||
* Must be freed with av_free().
|
||||
*/
|
||||
AVABufferSinkParams *av_abuffersink_params_alloc(void);
|
||||
|
||||
/**
|
||||
* Set the frame size for an audio buffer sink.
|
||||
*
|
||||
* All calls to av_buffersink_get_buffer_ref will return a buffer with
|
||||
* exactly the specified number of samples, or AVERROR(EAGAIN) if there is
|
||||
* not enough. The last buffer at EOF will be padded with 0.
|
||||
*/
|
||||
void av_buffersink_set_frame_size(AVFilterContext *ctx, unsigned frame_size);
|
||||
|
||||
/**
|
||||
* Get the frame rate of the input.
|
||||
*/
|
||||
AVRational av_buffersink_get_frame_rate(AVFilterContext *ctx);
|
||||
|
||||
/**
|
||||
* Get a frame with filtered data from sink and put it in frame.
|
||||
*
|
||||
* @param ctx pointer to a context of a buffersink or abuffersink AVFilter.
|
||||
* @param frame pointer to an allocated frame that will be filled with data.
|
||||
* The data must be freed using av_frame_unref() / av_frame_free()
|
||||
*
|
||||
* @return >= 0 in case of success, a negative AVERROR code in case of
|
||||
* failure.
|
||||
*/
|
||||
int av_buffersink_get_frame(AVFilterContext *ctx, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Same as av_buffersink_get_frame(), but with the ability to specify the number
|
||||
* of samples read. This function is less efficient than
|
||||
* av_buffersink_get_frame(), because it copies the data around.
|
||||
*
|
||||
* @param ctx pointer to a context of the abuffersink AVFilter.
|
||||
* @param frame pointer to an allocated frame that will be filled with data.
|
||||
* The data must be freed using av_frame_unref() / av_frame_free()
|
||||
* frame will contain exactly nb_samples audio samples, except at
|
||||
* the end of stream, when it can contain less than nb_samples.
|
||||
*
|
||||
* @warning do not mix this function with av_buffersink_get_frame(). Use only one or
|
||||
* the other with a single sink, not both.
|
||||
*/
|
||||
int av_buffersink_get_samples(AVFilterContext *ctx, AVFrame *frame, int nb_samples);
|
||||
|
||||
#endif /* AVFILTER_BUFFERSINK_H */
|
||||
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
* Copyright (c) 2008 Vitor Sessak
|
||||
*
|
||||
* 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
|
||||
* memory buffer source filter
|
||||
*/
|
||||
|
||||
#include <float.h>
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/frame.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "buffersrc.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
#include "video.h"
|
||||
#include "avcodec.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
AVFifoBuffer *fifo;
|
||||
AVRational time_base; ///< time_base to set in the output link
|
||||
AVRational frame_rate; ///< frame_rate to set in the output link
|
||||
unsigned nb_failed_requests;
|
||||
unsigned warning_limit;
|
||||
|
||||
/* video only */
|
||||
int w, h;
|
||||
enum AVPixelFormat pix_fmt;
|
||||
AVRational pixel_aspect;
|
||||
char *sws_param;
|
||||
|
||||
/* audio only */
|
||||
int sample_rate;
|
||||
enum AVSampleFormat sample_fmt;
|
||||
char *sample_fmt_str;
|
||||
int channels;
|
||||
uint64_t channel_layout;
|
||||
char *channel_layout_str;
|
||||
|
||||
int eof;
|
||||
} BufferSourceContext;
|
||||
|
||||
#define CHECK_VIDEO_PARAM_CHANGE(s, c, width, height, format)\
|
||||
if (c->w != width || c->h != height || c->pix_fmt != format) {\
|
||||
av_log(s, AV_LOG_INFO, "Changing frame properties on the fly is not supported by all filters.\n");\
|
||||
}
|
||||
|
||||
#define CHECK_AUDIO_PARAM_CHANGE(s, c, srate, ch_layout, ch_count, format)\
|
||||
if (c->sample_fmt != format || c->sample_rate != srate ||\
|
||||
c->channel_layout != ch_layout || c->channels != ch_count) {\
|
||||
av_log(s, AV_LOG_ERROR, "Changing frame properties on the fly is not supported.\n");\
|
||||
return AVERROR(EINVAL);\
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersrc_write_frame(AVFilterContext *ctx, const AVFrame *frame)
|
||||
{
|
||||
return av_buffersrc_add_frame_flags(ctx, (AVFrame *)frame,
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF);
|
||||
}
|
||||
|
||||
int attribute_align_arg av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
return av_buffersrc_add_frame_flags(ctx, frame, 0);
|
||||
}
|
||||
|
||||
static int av_buffersrc_add_frame_internal(AVFilterContext *ctx,
|
||||
AVFrame *frame, int flags);
|
||||
|
||||
int attribute_align_arg av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFrame *frame, int flags)
|
||||
{
|
||||
AVFrame *copy = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if (frame && frame->channel_layout &&
|
||||
av_get_channel_layout_nb_channels(frame->channel_layout) != av_frame_get_channels(frame)) {
|
||||
av_log(0, AV_LOG_ERROR, "Layout indicates a different number of channels than actually present\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (!(flags & AV_BUFFERSRC_FLAG_KEEP_REF) || !frame)
|
||||
return av_buffersrc_add_frame_internal(ctx, frame, flags);
|
||||
|
||||
if (!(copy = av_frame_alloc()))
|
||||
return AVERROR(ENOMEM);
|
||||
ret = av_frame_ref(copy, frame);
|
||||
if (ret >= 0)
|
||||
ret = av_buffersrc_add_frame_internal(ctx, copy, flags);
|
||||
|
||||
av_frame_free(©);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int av_buffersrc_add_frame_internal(AVFilterContext *ctx,
|
||||
AVFrame *frame, int flags)
|
||||
{
|
||||
BufferSourceContext *s = ctx->priv;
|
||||
AVFrame *copy;
|
||||
int ret;
|
||||
|
||||
s->nb_failed_requests = 0;
|
||||
|
||||
if (!frame) {
|
||||
s->eof = 1;
|
||||
return 0;
|
||||
} else if (s->eof)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if (!(flags & AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) {
|
||||
|
||||
switch (ctx->outputs[0]->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
CHECK_VIDEO_PARAM_CHANGE(ctx, s, frame->width, frame->height,
|
||||
frame->format);
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
/* For layouts unknown on input but known on link after negotiation. */
|
||||
if (!frame->channel_layout)
|
||||
frame->channel_layout = s->channel_layout;
|
||||
CHECK_AUDIO_PARAM_CHANGE(ctx, s, frame->sample_rate, frame->channel_layout,
|
||||
av_frame_get_channels(frame), frame->format);
|
||||
break;
|
||||
default:
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!av_fifo_space(s->fifo) &&
|
||||
(ret = av_fifo_realloc2(s->fifo, av_fifo_size(s->fifo) +
|
||||
sizeof(copy))) < 0)
|
||||
return ret;
|
||||
|
||||
if (!(copy = av_frame_alloc()))
|
||||
return AVERROR(ENOMEM);
|
||||
av_frame_move_ref(copy, frame);
|
||||
|
||||
if ((ret = av_fifo_generic_write(s->fifo, ©, sizeof(copy), NULL)) < 0) {
|
||||
av_frame_move_ref(frame, copy);
|
||||
av_frame_free(©);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((flags & AV_BUFFERSRC_FLAG_PUSH))
|
||||
if ((ret = ctx->output_pads[0].request_frame(ctx->outputs[0])) < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
FF_DISABLE_DEPRECATION_WARNINGS
|
||||
static void compat_free_buffer(void *opaque, uint8_t *data)
|
||||
{
|
||||
AVFilterBufferRef *buf = opaque;
|
||||
AV_NOWARN_DEPRECATED(
|
||||
avfilter_unref_buffer(buf);
|
||||
)
|
||||
}
|
||||
|
||||
static void compat_unref_buffer(void *opaque, uint8_t *data)
|
||||
{
|
||||
AVBufferRef *buf = opaque;
|
||||
AV_NOWARN_DEPRECATED(
|
||||
av_buffer_unref(&buf);
|
||||
)
|
||||
}
|
||||
|
||||
int av_buffersrc_add_ref(AVFilterContext *ctx, AVFilterBufferRef *buf,
|
||||
int flags)
|
||||
{
|
||||
BufferSourceContext *s = ctx->priv;
|
||||
AVFrame *frame = NULL;
|
||||
AVBufferRef *dummy_buf = NULL;
|
||||
int ret = 0, planes, i;
|
||||
|
||||
if (!buf) {
|
||||
s->eof = 1;
|
||||
return 0;
|
||||
} else if (s->eof)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
dummy_buf = av_buffer_create(NULL, 0, compat_free_buffer, buf,
|
||||
(buf->perms & AV_PERM_WRITE) ? 0 : AV_BUFFER_FLAG_READONLY);
|
||||
if (!dummy_buf) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
AV_NOWARN_DEPRECATED(
|
||||
if ((ret = avfilter_copy_buf_props(frame, buf)) < 0)
|
||||
goto fail;
|
||||
)
|
||||
|
||||
#define WRAP_PLANE(ref_out, data, data_size) \
|
||||
do { \
|
||||
AVBufferRef *dummy_ref = av_buffer_ref(dummy_buf); \
|
||||
if (!dummy_ref) { \
|
||||
ret = AVERROR(ENOMEM); \
|
||||
goto fail; \
|
||||
} \
|
||||
ref_out = av_buffer_create(data, data_size, compat_unref_buffer, \
|
||||
dummy_ref, (buf->perms & AV_PERM_WRITE) ? 0 : AV_BUFFER_FLAG_READONLY); \
|
||||
if (!ref_out) { \
|
||||
av_frame_unref(frame); \
|
||||
ret = AVERROR(ENOMEM); \
|
||||
goto fail; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
if (ctx->outputs[0]->type == AVMEDIA_TYPE_VIDEO) {
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
|
||||
|
||||
planes = av_pix_fmt_count_planes(frame->format);
|
||||
if (!desc || planes <= 0) {
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < planes; i++) {
|
||||
int v_shift = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
|
||||
int plane_size = (frame->height >> v_shift) * frame->linesize[i];
|
||||
|
||||
WRAP_PLANE(frame->buf[i], frame->data[i], plane_size);
|
||||
}
|
||||
} else {
|
||||
int planar = av_sample_fmt_is_planar(frame->format);
|
||||
int channels = av_get_channel_layout_nb_channels(frame->channel_layout);
|
||||
|
||||
planes = planar ? channels : 1;
|
||||
|
||||
if (planes > FF_ARRAY_ELEMS(frame->buf)) {
|
||||
frame->nb_extended_buf = planes - FF_ARRAY_ELEMS(frame->buf);
|
||||
frame->extended_buf = av_mallocz(sizeof(*frame->extended_buf) *
|
||||
frame->nb_extended_buf);
|
||||
if (!frame->extended_buf) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < FFMIN(planes, FF_ARRAY_ELEMS(frame->buf)); i++)
|
||||
WRAP_PLANE(frame->buf[i], frame->extended_data[i], frame->linesize[0]);
|
||||
|
||||
for (i = 0; i < planes - FF_ARRAY_ELEMS(frame->buf); i++)
|
||||
WRAP_PLANE(frame->extended_buf[i],
|
||||
frame->extended_data[i + FF_ARRAY_ELEMS(frame->buf)],
|
||||
frame->linesize[0]);
|
||||
}
|
||||
|
||||
ret = av_buffersrc_add_frame_flags(ctx, frame, flags);
|
||||
|
||||
fail:
|
||||
av_buffer_unref(&dummy_buf);
|
||||
av_frame_free(&frame);
|
||||
|
||||
return ret;
|
||||
}
|
||||
FF_ENABLE_DEPRECATION_WARNINGS
|
||||
|
||||
int av_buffersrc_buffer(AVFilterContext *ctx, AVFilterBufferRef *buf)
|
||||
{
|
||||
return av_buffersrc_add_ref(ctx, buf, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static av_cold int init_video(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSourceContext *c = ctx->priv;
|
||||
|
||||
if (c->pix_fmt == AV_PIX_FMT_NONE || !c->w || !c->h || av_q2d(c->time_base) <= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid parameters provided.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (!(c->fifo = av_fifo_alloc(sizeof(AVFrame*))))
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "w:%d h:%d pixfmt:%s tb:%d/%d fr:%d/%d sar:%d/%d sws_param:%s\n",
|
||||
c->w, c->h, av_get_pix_fmt_name(c->pix_fmt),
|
||||
c->time_base.num, c->time_base.den, c->frame_rate.num, c->frame_rate.den,
|
||||
c->pixel_aspect.num, c->pixel_aspect.den, (char *)av_x_if_null(c->sws_param, ""));
|
||||
c->warning_limit = 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned av_buffersrc_get_nb_failed_requests(AVFilterContext *buffer_src)
|
||||
{
|
||||
return ((BufferSourceContext *)buffer_src->priv)->nb_failed_requests;
|
||||
}
|
||||
|
||||
#define OFFSET(x) offsetof(BufferSourceContext, x)
|
||||
#define A AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define V AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption buffer_options[] = {
|
||||
{ "width", NULL, OFFSET(w), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
{ "video_size", NULL, OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, .flags = V },
|
||||
{ "height", NULL, OFFSET(h), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
{ "pix_fmt", NULL, OFFSET(pix_fmt), AV_OPT_TYPE_PIXEL_FMT, .flags = V },
|
||||
#if FF_API_OLD_FILTER_OPTS
|
||||
/* those 4 are for compatibility with the old option passing system where each filter
|
||||
* did its own parsing */
|
||||
{ "time_base_num", "deprecated, do not use", OFFSET(time_base.num), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
{ "time_base_den", "deprecated, do not use", OFFSET(time_base.den), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
{ "sar_num", "deprecated, do not use", OFFSET(pixel_aspect.num), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
{ "sar_den", "deprecated, do not use", OFFSET(pixel_aspect.den), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, V },
|
||||
#endif
|
||||
{ "sar", "sample aspect ratio", OFFSET(pixel_aspect), AV_OPT_TYPE_RATIONAL, { .dbl = 1 }, 0, DBL_MAX, V },
|
||||
{ "pixel_aspect", "sample aspect ratio", OFFSET(pixel_aspect), AV_OPT_TYPE_RATIONAL, { .dbl = 1 }, 0, DBL_MAX, V },
|
||||
{ "time_base", NULL, OFFSET(time_base), AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
|
||||
{ "frame_rate", NULL, OFFSET(frame_rate), AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
|
||||
{ "sws_param", NULL, OFFSET(sws_param), AV_OPT_TYPE_STRING, .flags = V },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(buffer);
|
||||
|
||||
static const AVOption abuffer_options[] = {
|
||||
{ "time_base", NULL, OFFSET(time_base), AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, INT_MAX, A },
|
||||
{ "sample_rate", NULL, OFFSET(sample_rate), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A },
|
||||
{ "sample_fmt", NULL, OFFSET(sample_fmt_str), AV_OPT_TYPE_STRING, .flags = A },
|
||||
{ "channel_layout", NULL, OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, .flags = A },
|
||||
{ "channels", NULL, OFFSET(channels), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, A },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(abuffer);
|
||||
|
||||
static av_cold int init_audio(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSourceContext *s = ctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
s->sample_fmt = av_get_sample_fmt(s->sample_fmt_str);
|
||||
if (s->sample_fmt == AV_SAMPLE_FMT_NONE) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid sample format %s\n",
|
||||
s->sample_fmt_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (s->channel_layout_str) {
|
||||
int n;
|
||||
/* TODO reindent */
|
||||
s->channel_layout = av_get_channel_layout(s->channel_layout_str);
|
||||
if (!s->channel_layout) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid channel layout %s.\n",
|
||||
s->channel_layout_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
n = av_get_channel_layout_nb_channels(s->channel_layout);
|
||||
if (s->channels) {
|
||||
if (n != s->channels) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Mismatching channel count %d and layout '%s' "
|
||||
"(%d channels)\n",
|
||||
s->channels, s->channel_layout_str, n);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
s->channels = n;
|
||||
} else if (!s->channels) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Neither number of channels nor "
|
||||
"channel layout specified\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (!(s->fifo = av_fifo_alloc(sizeof(AVFrame*))))
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if (!s->time_base.num)
|
||||
s->time_base = (AVRational){1, s->sample_rate};
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"tb:%d/%d samplefmt:%s samplerate:%d chlayout:%s\n",
|
||||
s->time_base.num, s->time_base.den, s->sample_fmt_str,
|
||||
s->sample_rate, s->channel_layout_str);
|
||||
s->warning_limit = 100;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSourceContext *s = ctx->priv;
|
||||
while (s->fifo && av_fifo_size(s->fifo)) {
|
||||
AVFrame *frame;
|
||||
av_fifo_generic_read(s->fifo, &frame, sizeof(frame), NULL);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
av_fifo_free(s->fifo);
|
||||
s->fifo = NULL;
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
BufferSourceContext *c = ctx->priv;
|
||||
AVFilterChannelLayouts *channel_layouts = NULL;
|
||||
AVFilterFormats *formats = NULL;
|
||||
AVFilterFormats *samplerates = NULL;
|
||||
|
||||
switch (ctx->outputs[0]->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ff_add_format(&formats, c->pix_fmt);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ff_add_format(&formats, c->sample_fmt);
|
||||
ff_set_common_formats(ctx, formats);
|
||||
|
||||
ff_add_format(&samplerates, c->sample_rate);
|
||||
ff_set_common_samplerates(ctx, samplerates);
|
||||
|
||||
ff_add_channel_layout(&channel_layouts,
|
||||
c->channel_layout ? c->channel_layout :
|
||||
FF_COUNT2LAYOUT(c->channels));
|
||||
ff_set_common_channel_layouts(ctx, channel_layouts);
|
||||
break;
|
||||
default:
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_props(AVFilterLink *link)
|
||||
{
|
||||
BufferSourceContext *c = link->src->priv;
|
||||
|
||||
switch (link->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
link->w = c->w;
|
||||
link->h = c->h;
|
||||
link->sample_aspect_ratio = c->pixel_aspect;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (!c->channel_layout)
|
||||
c->channel_layout = link->channel_layout;
|
||||
break;
|
||||
default:
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
link->time_base = c->time_base;
|
||||
link->frame_rate = c->frame_rate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *link)
|
||||
{
|
||||
BufferSourceContext *c = link->src->priv;
|
||||
AVFrame *frame;
|
||||
|
||||
if (!av_fifo_size(c->fifo)) {
|
||||
if (c->eof)
|
||||
return AVERROR_EOF;
|
||||
c->nb_failed_requests++;
|
||||
return AVERROR(EAGAIN);
|
||||
}
|
||||
av_fifo_generic_read(c->fifo, &frame, sizeof(frame), NULL);
|
||||
|
||||
return ff_filter_frame(link, frame);
|
||||
}
|
||||
|
||||
static int poll_frame(AVFilterLink *link)
|
||||
{
|
||||
BufferSourceContext *c = link->src->priv;
|
||||
int size = av_fifo_size(c->fifo);
|
||||
if (!size && c->eof)
|
||||
return AVERROR_EOF;
|
||||
return size/sizeof(AVFrame*);
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_vsrc_buffer_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.request_frame = request_frame,
|
||||
.poll_frame = poll_frame,
|
||||
.config_props = config_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vsrc_buffer = {
|
||||
.name = "buffer",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer video frames, and make them accessible to the filterchain."),
|
||||
.priv_size = sizeof(BufferSourceContext),
|
||||
.query_formats = query_formats,
|
||||
|
||||
.init = init_video,
|
||||
.uninit = uninit,
|
||||
|
||||
.inputs = NULL,
|
||||
.outputs = avfilter_vsrc_buffer_outputs,
|
||||
.priv_class = &buffer_class,
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_asrc_abuffer_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.request_frame = request_frame,
|
||||
.poll_frame = poll_frame,
|
||||
.config_props = config_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_asrc_abuffer = {
|
||||
.name = "abuffer",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer audio frames, and make them accessible to the filterchain."),
|
||||
.priv_size = sizeof(BufferSourceContext),
|
||||
.query_formats = query_formats,
|
||||
|
||||
.init = init_audio,
|
||||
.uninit = uninit,
|
||||
|
||||
.inputs = NULL,
|
||||
.outputs = avfilter_asrc_abuffer_outputs,
|
||||
.priv_class = &abuffer_class,
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_BUFFERSRC_H
|
||||
#define AVFILTER_BUFFERSRC_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Memory buffer source API.
|
||||
*/
|
||||
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "avfilter.h"
|
||||
|
||||
enum {
|
||||
|
||||
/**
|
||||
* Do not check for format changes.
|
||||
*/
|
||||
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT = 1,
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
/**
|
||||
* Ignored
|
||||
*/
|
||||
AV_BUFFERSRC_FLAG_NO_COPY = 2,
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Immediately push the frame to the output.
|
||||
*/
|
||||
AV_BUFFERSRC_FLAG_PUSH = 4,
|
||||
|
||||
/**
|
||||
* Keep a reference to the frame.
|
||||
* If the frame if reference-counted, create a new reference; otherwise
|
||||
* copy the frame data.
|
||||
*/
|
||||
AV_BUFFERSRC_FLAG_KEEP_REF = 8,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Add buffer data in picref to buffer_src.
|
||||
*
|
||||
* @param buffer_src pointer to a buffer source context
|
||||
* @param picref a buffer reference, or NULL to mark EOF
|
||||
* @param flags a combination of AV_BUFFERSRC_FLAG_*
|
||||
* @return >= 0 in case of success, a negative AVERROR code
|
||||
* in case of failure
|
||||
*/
|
||||
int av_buffersrc_add_ref(AVFilterContext *buffer_src,
|
||||
AVFilterBufferRef *picref, int flags);
|
||||
|
||||
/**
|
||||
* Get the number of failed requests.
|
||||
*
|
||||
* A failed request is when the request_frame method is called while no
|
||||
* frame is present in the buffer.
|
||||
* The number is reset when a frame is added.
|
||||
*/
|
||||
unsigned av_buffersrc_get_nb_failed_requests(AVFilterContext *buffer_src);
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
/**
|
||||
* Add a buffer to the filtergraph s.
|
||||
*
|
||||
* @param buf buffer containing frame data to be passed down the filtergraph.
|
||||
* This function will take ownership of buf, the user must not free it.
|
||||
* A NULL buf signals EOF -- i.e. no more frames will be sent to this filter.
|
||||
*
|
||||
* @deprecated use av_buffersrc_write_frame() or av_buffersrc_add_frame()
|
||||
*/
|
||||
attribute_deprecated
|
||||
int av_buffersrc_buffer(AVFilterContext *s, AVFilterBufferRef *buf);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Add a frame to the buffer source.
|
||||
*
|
||||
* @param s an instance of the buffersrc filter.
|
||||
* @param frame frame to be added. If the frame is reference counted, this
|
||||
* function will make a new reference to it. Otherwise the frame data will be
|
||||
* copied.
|
||||
*
|
||||
* @return 0 on success, a negative AVERROR on error
|
||||
*
|
||||
* This function is equivalent to av_buffersrc_add_frame_flags() with the
|
||||
* AV_BUFFERSRC_FLAG_KEEP_REF flag.
|
||||
*/
|
||||
int av_buffersrc_write_frame(AVFilterContext *s, const AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Add a frame to the buffer source.
|
||||
*
|
||||
* @param s an instance of the buffersrc filter.
|
||||
* @param frame frame to be added. If the frame is reference counted, this
|
||||
* function will take ownership of the reference(s) and reset the frame.
|
||||
* Otherwise the frame data will be copied. If this function returns an error,
|
||||
* the input frame is not touched.
|
||||
*
|
||||
* @return 0 on success, a negative AVERROR on error.
|
||||
*
|
||||
* @note the difference between this function and av_buffersrc_write_frame() is
|
||||
* that av_buffersrc_write_frame() creates a new reference to the input frame,
|
||||
* while this function takes ownership of the reference passed to it.
|
||||
*
|
||||
* This function is equivalent to av_buffersrc_add_frame_flags() without the
|
||||
* AV_BUFFERSRC_FLAG_KEEP_REF flag.
|
||||
*/
|
||||
int av_buffersrc_add_frame(AVFilterContext *ctx, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Add a frame to the buffer source.
|
||||
*
|
||||
* By default, if the frame is reference-counted, this function will take
|
||||
* ownership of the reference(s) and reset the frame. This can be controled
|
||||
* using the flags.
|
||||
*
|
||||
* If this function returns an error, the input frame is not touched.
|
||||
*
|
||||
* @param buffer_src pointer to a buffer source context
|
||||
* @param frame a frame, or NULL to mark EOF
|
||||
* @param flags a combination of AV_BUFFERSRC_FLAG_*
|
||||
* @return >= 0 in case of success, a negative AVERROR code
|
||||
* in case of failure
|
||||
*/
|
||||
int av_buffersrc_add_frame_flags(AVFilterContext *buffer_src,
|
||||
AVFrame *frame, int flags);
|
||||
|
||||
|
||||
#endif /* AVFILTER_BUFFERSRC_H */
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Wei Gao <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_DESHAKE_H
|
||||
#define AVFILTER_DESHAKE_H
|
||||
|
||||
#include "config.h"
|
||||
#include "avfilter.h"
|
||||
#include "libavcodec/dsputil.h"
|
||||
#include "transform.h"
|
||||
#if CONFIG_OPENCL
|
||||
#include "libavutil/opencl.h"
|
||||
#endif
|
||||
|
||||
|
||||
enum SearchMethod {
|
||||
EXHAUSTIVE, ///< Search all possible positions
|
||||
SMART_EXHAUSTIVE, ///< Search most possible positions (faster)
|
||||
SEARCH_COUNT
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int x; ///< Horizontal shift
|
||||
int y; ///< Vertical shift
|
||||
} IntMotionVector;
|
||||
|
||||
typedef struct {
|
||||
double x; ///< Horizontal shift
|
||||
double y; ///< Vertical shift
|
||||
} MotionVector;
|
||||
|
||||
typedef struct {
|
||||
MotionVector vector; ///< Motion vector
|
||||
double angle; ///< Angle of rotation
|
||||
double zoom; ///< Zoom percentage
|
||||
} Transform;
|
||||
|
||||
#if CONFIG_OPENCL
|
||||
|
||||
typedef struct {
|
||||
size_t matrix_size;
|
||||
float matrix_y[9];
|
||||
float matrix_uv[9];
|
||||
cl_mem cl_matrix_y;
|
||||
cl_mem cl_matrix_uv;
|
||||
int in_plane_size[8];
|
||||
int out_plane_size[8];
|
||||
int plane_num;
|
||||
cl_mem cl_inbuf;
|
||||
size_t cl_inbuf_size;
|
||||
cl_mem cl_outbuf;
|
||||
size_t cl_outbuf_size;
|
||||
AVOpenCLKernelEnv kernel_env;
|
||||
} DeshakeOpenclContext;
|
||||
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
AVFrame *ref; ///< Previous frame
|
||||
int rx; ///< Maximum horizontal shift
|
||||
int ry; ///< Maximum vertical shift
|
||||
int edge; ///< Edge fill method
|
||||
int blocksize; ///< Size of blocks to compare
|
||||
int contrast; ///< Contrast threshold
|
||||
int search; ///< Motion search method
|
||||
AVCodecContext *avctx;
|
||||
DSPContext c; ///< Context providing optimized SAD methods
|
||||
Transform last; ///< Transform from last frame
|
||||
int refcount; ///< Number of reference frames (defines averaging window)
|
||||
FILE *fp;
|
||||
Transform avg;
|
||||
int cw; ///< Crop motion search to this box
|
||||
int ch;
|
||||
int cx;
|
||||
int cy;
|
||||
char *filename; ///< Motion search detailed log filename
|
||||
int opencl;
|
||||
#if CONFIG_OPENCL
|
||||
DeshakeOpenclContext opencl_ctx;
|
||||
#endif
|
||||
int (* transform)(AVFilterContext *ctx, int width, int height, int cw, int ch,
|
||||
const float *matrix_y, const float *matrix_uv, enum InterpolateMethod interpolate,
|
||||
enum FillMethod fill, AVFrame *in, AVFrame *out);
|
||||
} DeshakeContext;
|
||||
|
||||
#endif /* AVFILTER_DESHAKE_H */
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Wei Gao <[email protected]>
|
||||
*
|
||||
* 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
|
||||
* transform input video
|
||||
*/
|
||||
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "deshake_opencl.h"
|
||||
#include "libavutil/opencl_internal.h"
|
||||
|
||||
#define MATRIX_SIZE 6
|
||||
#define PLANE_NUM 3
|
||||
|
||||
int ff_opencl_transform(AVFilterContext *ctx,
|
||||
int width, int height, int cw, int ch,
|
||||
const float *matrix_y, const float *matrix_uv,
|
||||
enum InterpolateMethod interpolate,
|
||||
enum FillMethod fill, AVFrame *in, AVFrame *out)
|
||||
{
|
||||
int ret = 0;
|
||||
const size_t global_work_size = width * height + 2 * ch * cw;
|
||||
cl_int status;
|
||||
DeshakeContext *deshake = ctx->priv;
|
||||
FFOpenclParam opencl_param = {0};
|
||||
|
||||
opencl_param.ctx = ctx;
|
||||
opencl_param.kernel = deshake->opencl_ctx.kernel_env.kernel;
|
||||
ret = av_opencl_buffer_write(deshake->opencl_ctx.cl_matrix_y, (uint8_t *)matrix_y, deshake->opencl_ctx.matrix_size * sizeof(cl_float));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = av_opencl_buffer_write(deshake->opencl_ctx.cl_matrix_uv, (uint8_t *)matrix_uv, deshake->opencl_ctx.matrix_size * sizeof(cl_float));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if ((unsigned int)interpolate > INTERPOLATE_BIQUADRATIC) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Selected interpolate method is invalid\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
ret = ff_opencl_set_parameter(&opencl_param,
|
||||
FF_OPENCL_PARAM_INFO(deshake->opencl_ctx.cl_inbuf),
|
||||
FF_OPENCL_PARAM_INFO(deshake->opencl_ctx.cl_outbuf),
|
||||
FF_OPENCL_PARAM_INFO(deshake->opencl_ctx.cl_matrix_y),
|
||||
FF_OPENCL_PARAM_INFO(deshake->opencl_ctx.cl_matrix_uv),
|
||||
FF_OPENCL_PARAM_INFO(interpolate),
|
||||
FF_OPENCL_PARAM_INFO(fill),
|
||||
FF_OPENCL_PARAM_INFO(in->linesize[0]),
|
||||
FF_OPENCL_PARAM_INFO(out->linesize[0]),
|
||||
FF_OPENCL_PARAM_INFO(in->linesize[1]),
|
||||
FF_OPENCL_PARAM_INFO(out->linesize[1]),
|
||||
FF_OPENCL_PARAM_INFO(height),
|
||||
FF_OPENCL_PARAM_INFO(width),
|
||||
FF_OPENCL_PARAM_INFO(ch),
|
||||
FF_OPENCL_PARAM_INFO(cw),
|
||||
NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
status = clEnqueueNDRangeKernel(deshake->opencl_ctx.kernel_env.command_queue,
|
||||
deshake->opencl_ctx.kernel_env.kernel, 1, NULL,
|
||||
&global_work_size, NULL, 0, NULL, NULL);
|
||||
if (status != CL_SUCCESS) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OpenCL run kernel error occurred: %s\n", av_opencl_errstr(status));
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
clFinish(deshake->opencl_ctx.kernel_env.command_queue);
|
||||
ret = av_opencl_buffer_read_image(out->data, deshake->opencl_ctx.out_plane_size,
|
||||
deshake->opencl_ctx.plane_num, deshake->opencl_ctx.cl_outbuf,
|
||||
deshake->opencl_ctx.cl_outbuf_size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ff_opencl_deshake_init(AVFilterContext *ctx)
|
||||
{
|
||||
int ret = 0;
|
||||
DeshakeContext *deshake = ctx->priv;
|
||||
ret = av_opencl_init(NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
deshake->opencl_ctx.matrix_size = MATRIX_SIZE;
|
||||
deshake->opencl_ctx.plane_num = PLANE_NUM;
|
||||
ret = av_opencl_buffer_create(&deshake->opencl_ctx.cl_matrix_y,
|
||||
deshake->opencl_ctx.matrix_size*sizeof(cl_float), CL_MEM_READ_ONLY, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = av_opencl_buffer_create(&deshake->opencl_ctx.cl_matrix_uv,
|
||||
deshake->opencl_ctx.matrix_size*sizeof(cl_float), CL_MEM_READ_ONLY, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!deshake->opencl_ctx.kernel_env.kernel) {
|
||||
ret = av_opencl_create_kernel(&deshake->opencl_ctx.kernel_env, "avfilter_transform");
|
||||
if (ret < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "OpenCL failed to create kernel for name 'avfilter_transform'\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ff_opencl_deshake_uninit(AVFilterContext *ctx)
|
||||
{
|
||||
DeshakeContext *deshake = ctx->priv;
|
||||
av_opencl_buffer_release(&deshake->opencl_ctx.cl_inbuf);
|
||||
av_opencl_buffer_release(&deshake->opencl_ctx.cl_outbuf);
|
||||
av_opencl_buffer_release(&deshake->opencl_ctx.cl_matrix_y);
|
||||
av_opencl_buffer_release(&deshake->opencl_ctx.cl_matrix_uv);
|
||||
av_opencl_release_kernel(&deshake->opencl_ctx.kernel_env);
|
||||
av_opencl_uninit();
|
||||
}
|
||||
|
||||
|
||||
int ff_opencl_deshake_process_inout_buf(AVFilterContext *ctx, AVFrame *in, AVFrame *out)
|
||||
{
|
||||
int ret = 0;
|
||||
AVFilterLink *link = ctx->inputs[0];
|
||||
DeshakeContext *deshake = ctx->priv;
|
||||
const int hshift = av_pix_fmt_desc_get(link->format)->log2_chroma_h;
|
||||
int chroma_height = FF_CEIL_RSHIFT(link->h, hshift);
|
||||
|
||||
if ((!deshake->opencl_ctx.cl_inbuf) || (!deshake->opencl_ctx.cl_outbuf)) {
|
||||
deshake->opencl_ctx.in_plane_size[0] = (in->linesize[0] * in->height);
|
||||
deshake->opencl_ctx.in_plane_size[1] = (in->linesize[1] * chroma_height);
|
||||
deshake->opencl_ctx.in_plane_size[2] = (in->linesize[2] * chroma_height);
|
||||
deshake->opencl_ctx.out_plane_size[0] = (out->linesize[0] * out->height);
|
||||
deshake->opencl_ctx.out_plane_size[1] = (out->linesize[1] * chroma_height);
|
||||
deshake->opencl_ctx.out_plane_size[2] = (out->linesize[2] * chroma_height);
|
||||
deshake->opencl_ctx.cl_inbuf_size = deshake->opencl_ctx.in_plane_size[0] +
|
||||
deshake->opencl_ctx.in_plane_size[1] +
|
||||
deshake->opencl_ctx.in_plane_size[2];
|
||||
deshake->opencl_ctx.cl_outbuf_size = deshake->opencl_ctx.out_plane_size[0] +
|
||||
deshake->opencl_ctx.out_plane_size[1] +
|
||||
deshake->opencl_ctx.out_plane_size[2];
|
||||
if (!deshake->opencl_ctx.cl_inbuf) {
|
||||
ret = av_opencl_buffer_create(&deshake->opencl_ctx.cl_inbuf,
|
||||
deshake->opencl_ctx.cl_inbuf_size,
|
||||
CL_MEM_READ_ONLY, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
if (!deshake->opencl_ctx.cl_outbuf) {
|
||||
ret = av_opencl_buffer_create(&deshake->opencl_ctx.cl_outbuf,
|
||||
deshake->opencl_ctx.cl_outbuf_size,
|
||||
CL_MEM_READ_WRITE, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = av_opencl_buffer_write_image(deshake->opencl_ctx.cl_inbuf,
|
||||
deshake->opencl_ctx.cl_inbuf_size,
|
||||
0, in->data,deshake->opencl_ctx.in_plane_size,
|
||||
deshake->opencl_ctx.plane_num);
|
||||
if(ret < 0)
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Wei Gao <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_DESHAKE_OPENCL_H
|
||||
#define AVFILTER_DESHAKE_OPENCL_H
|
||||
|
||||
#include "deshake.h"
|
||||
|
||||
int ff_opencl_deshake_init(AVFilterContext *ctx);
|
||||
|
||||
void ff_opencl_deshake_uninit(AVFilterContext *ctx);
|
||||
|
||||
int ff_opencl_deshake_process_inout_buf(AVFilterContext *ctx, AVFrame *in, AVFrame *out);
|
||||
|
||||
int ff_opencl_transform(AVFilterContext *ctx,
|
||||
int width, int height, int cw, int ch,
|
||||
const float *matrix_y, const float *matrix_uv,
|
||||
enum InterpolateMethod interpolate,
|
||||
enum FillMethod fill, AVFrame *in, AVFrame *out);
|
||||
|
||||
#endif /* AVFILTER_DESHAKE_OPENCL_H */
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* Copyright (C) 2013 Wei Gao <[email protected]>
|
||||
*
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_DESHAKE_OPENCL_KERNEL_H
|
||||
#define AVFILTER_DESHAKE_OPENCL_KERNEL_H
|
||||
|
||||
#include "libavutil/opencl.h"
|
||||
|
||||
const char *ff_kernel_deshake_opencl = AV_OPENCL_KERNEL(
|
||||
|
||||
inline unsigned char pixel(global const unsigned char *src, float x, float y,
|
||||
int w, int h,int stride, unsigned char def)
|
||||
{
|
||||
return (x < 0 || y < 0 || x >= w || y >= h) ? def : src[(int)x + (int)y * stride];
|
||||
}
|
||||
unsigned char interpolate_nearest(float x, float y, global const unsigned char *src,
|
||||
int width, int height, int stride, unsigned char def)
|
||||
{
|
||||
return pixel(src, (int)(x + 0.5), (int)(y + 0.5), width, height, stride, def);
|
||||
}
|
||||
|
||||
unsigned char interpolate_bilinear(float x, float y, global const unsigned char *src,
|
||||
int width, int height, int stride, unsigned char def)
|
||||
{
|
||||
int x_c, x_f, y_c, y_f;
|
||||
int v1, v2, v3, v4;
|
||||
|
||||
if (x < -1 || x > width || y < -1 || y > height) {
|
||||
return def;
|
||||
} else {
|
||||
x_f = (int)x;
|
||||
x_c = x_f + 1;
|
||||
|
||||
y_f = (int)y;
|
||||
y_c = y_f + 1;
|
||||
|
||||
v1 = pixel(src, x_c, y_c, width, height, stride, def);
|
||||
v2 = pixel(src, x_c, y_f, width, height, stride, def);
|
||||
v3 = pixel(src, x_f, y_c, width, height, stride, def);
|
||||
v4 = pixel(src, x_f, y_f, width, height, stride, def);
|
||||
|
||||
return (v1*(x - x_f)*(y - y_f) + v2*((x - x_f)*(y_c - y)) +
|
||||
v3*(x_c - x)*(y - y_f) + v4*((x_c - x)*(y_c - y)));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char interpolate_biquadratic(float x, float y, global const unsigned char *src,
|
||||
int width, int height, int stride, unsigned char def)
|
||||
{
|
||||
int x_c, x_f, y_c, y_f;
|
||||
unsigned char v1, v2, v3, v4;
|
||||
float f1, f2, f3, f4;
|
||||
|
||||
if (x < - 1 || x > width || y < -1 || y > height)
|
||||
return def;
|
||||
else {
|
||||
x_f = (int)x;
|
||||
x_c = x_f + 1;
|
||||
y_f = (int)y;
|
||||
y_c = y_f + 1;
|
||||
|
||||
v1 = pixel(src, x_c, y_c, width, height, stride, def);
|
||||
v2 = pixel(src, x_c, y_f, width, height, stride, def);
|
||||
v3 = pixel(src, x_f, y_c, width, height, stride, def);
|
||||
v4 = pixel(src, x_f, y_f, width, height, stride, def);
|
||||
|
||||
f1 = 1 - sqrt((x_c - x) * (y_c - y));
|
||||
f2 = 1 - sqrt((x_c - x) * (y - y_f));
|
||||
f3 = 1 - sqrt((x - x_f) * (y_c - y));
|
||||
f4 = 1 - sqrt((x - x_f) * (y - y_f));
|
||||
return (v1 * f1 + v2 * f2 + v3 * f3 + v4 * f4) / (f1 + f2 + f3 + f4);
|
||||
}
|
||||
}
|
||||
|
||||
inline const float clipf(float a, float amin, float amax)
|
||||
{
|
||||
if (a < amin) return amin;
|
||||
else if (a > amax) return amax;
|
||||
else return a;
|
||||
}
|
||||
|
||||
inline int mirror(int v, int m)
|
||||
{
|
||||
while ((unsigned)v > (unsigned)m) {
|
||||
v = -v;
|
||||
if (v < 0)
|
||||
v += 2 * m;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
kernel void avfilter_transform(global unsigned char *src,
|
||||
global unsigned char *dst,
|
||||
global float *matrix,
|
||||
global float *matrix2,
|
||||
int interpolate,
|
||||
int fillmethod,
|
||||
int src_stride_lu,
|
||||
int dst_stride_lu,
|
||||
int src_stride_ch,
|
||||
int dst_stride_ch,
|
||||
int height,
|
||||
int width,
|
||||
int ch,
|
||||
int cw)
|
||||
{
|
||||
int global_id = get_global_id(0);
|
||||
|
||||
global unsigned char *dst_y = dst;
|
||||
global unsigned char *dst_u = dst_y + height * dst_stride_lu;
|
||||
global unsigned char *dst_v = dst_u + ch * dst_stride_ch;
|
||||
|
||||
global unsigned char *src_y = src;
|
||||
global unsigned char *src_u = src_y + height * src_stride_lu;
|
||||
global unsigned char *src_v = src_u + ch * src_stride_ch;
|
||||
|
||||
global unsigned char *tempdst;
|
||||
global unsigned char *tempsrc;
|
||||
|
||||
int x;
|
||||
int y;
|
||||
float x_s;
|
||||
float y_s;
|
||||
int tempsrc_stride;
|
||||
int tempdst_stride;
|
||||
int temp_height;
|
||||
int temp_width;
|
||||
int curpos;
|
||||
unsigned char def = 0;
|
||||
if (global_id < width*height) {
|
||||
y = global_id/width;
|
||||
x = global_id%width;
|
||||
x_s = x * matrix[0] + y * matrix[1] + matrix[2];
|
||||
y_s = x * matrix[3] + y * matrix[4] + matrix[5];
|
||||
tempdst = dst_y;
|
||||
tempsrc = src_y;
|
||||
tempsrc_stride = src_stride_lu;
|
||||
tempdst_stride = dst_stride_lu;
|
||||
temp_height = height;
|
||||
temp_width = width;
|
||||
} else if ((global_id >= width*height)&&(global_id < width*height + ch*cw)) {
|
||||
y = (global_id - width*height)/cw;
|
||||
x = (global_id - width*height)%cw;
|
||||
x_s = x * matrix2[0] + y * matrix2[1] + matrix2[2];
|
||||
y_s = x * matrix2[3] + y * matrix2[4] + matrix2[5];
|
||||
tempdst = dst_u;
|
||||
tempsrc = src_u;
|
||||
tempsrc_stride = src_stride_ch;
|
||||
tempdst_stride = dst_stride_ch;
|
||||
temp_height = ch;
|
||||
temp_width = cw;
|
||||
} else {
|
||||
y = (global_id - width*height - ch*cw)/cw;
|
||||
x = (global_id - width*height - ch*cw)%cw;
|
||||
x_s = x * matrix2[0] + y * matrix2[1] + matrix2[2];
|
||||
y_s = x * matrix2[3] + y * matrix2[4] + matrix2[5];
|
||||
tempdst = dst_v;
|
||||
tempsrc = src_v;
|
||||
tempsrc_stride = src_stride_ch;
|
||||
tempdst_stride = dst_stride_ch;
|
||||
temp_height = ch;
|
||||
temp_width = cw;
|
||||
}
|
||||
curpos = y * tempdst_stride + x;
|
||||
switch (fillmethod) {
|
||||
case 0: //FILL_BLANK
|
||||
def = 0;
|
||||
break;
|
||||
case 1: //FILL_ORIGINAL
|
||||
def = tempsrc[y*tempsrc_stride+x];
|
||||
break;
|
||||
case 2: //FILL_CLAMP
|
||||
y_s = clipf(y_s, 0, temp_height - 1);
|
||||
x_s = clipf(x_s, 0, temp_width - 1);
|
||||
def = tempsrc[(int)y_s * tempsrc_stride + (int)x_s];
|
||||
break;
|
||||
case 3: //FILL_MIRROR
|
||||
y_s = mirror(y_s,temp_height - 1);
|
||||
x_s = mirror(x_s,temp_width - 1);
|
||||
def = tempsrc[(int)y_s * tempsrc_stride + (int)x_s];
|
||||
break;
|
||||
}
|
||||
switch (interpolate) {
|
||||
case 0: //INTERPOLATE_NEAREST
|
||||
tempdst[curpos] = interpolate_nearest(x_s, y_s, tempsrc, temp_width, temp_height, tempsrc_stride, def);
|
||||
break;
|
||||
case 1: //INTERPOLATE_BILINEAR
|
||||
tempdst[curpos] = interpolate_bilinear(x_s, y_s, tempsrc, temp_width, temp_height, tempsrc_stride, def);
|
||||
break;
|
||||
case 2: //INTERPOLATE_BIQUADRATIC
|
||||
tempdst[curpos] = interpolate_biquadratic(x_s, y_s, tempsrc, temp_width, temp_height, tempsrc_stride, def);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
#endif /* AVFILTER_DESHAKE_OPENCL_KERNEL_H */
|
||||
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
* Copyright 2011 Stefano Sabatini <stefano.sabatini-lala poste it>
|
||||
* Copyright 2012 Nicolas George <nicolas.george normalesup org>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/colorspace.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "drawutils.h"
|
||||
#include "formats.h"
|
||||
|
||||
enum { RED = 0, GREEN, BLUE, ALPHA };
|
||||
|
||||
int ff_fill_rgba_map(uint8_t *rgba_map, enum AVPixelFormat pix_fmt)
|
||||
{
|
||||
switch (pix_fmt) {
|
||||
case AV_PIX_FMT_0RGB:
|
||||
case AV_PIX_FMT_ARGB: rgba_map[ALPHA] = 0; rgba_map[RED ] = 1; rgba_map[GREEN] = 2; rgba_map[BLUE ] = 3; break;
|
||||
case AV_PIX_FMT_0BGR:
|
||||
case AV_PIX_FMT_ABGR: rgba_map[ALPHA] = 0; rgba_map[BLUE ] = 1; rgba_map[GREEN] = 2; rgba_map[RED ] = 3; break;
|
||||
case AV_PIX_FMT_RGB48LE:
|
||||
case AV_PIX_FMT_RGB48BE:
|
||||
case AV_PIX_FMT_RGBA64BE:
|
||||
case AV_PIX_FMT_RGBA64LE:
|
||||
case AV_PIX_FMT_RGB0:
|
||||
case AV_PIX_FMT_RGBA:
|
||||
case AV_PIX_FMT_RGB24: rgba_map[RED ] = 0; rgba_map[GREEN] = 1; rgba_map[BLUE ] = 2; rgba_map[ALPHA] = 3; break;
|
||||
case AV_PIX_FMT_BGR48LE:
|
||||
case AV_PIX_FMT_BGR48BE:
|
||||
case AV_PIX_FMT_BGRA64BE:
|
||||
case AV_PIX_FMT_BGRA64LE:
|
||||
case AV_PIX_FMT_BGRA:
|
||||
case AV_PIX_FMT_BGR0:
|
||||
case AV_PIX_FMT_BGR24: rgba_map[BLUE ] = 0; rgba_map[GREEN] = 1; rgba_map[RED ] = 2; rgba_map[ALPHA] = 3; break;
|
||||
case AV_PIX_FMT_GBRAP:
|
||||
case AV_PIX_FMT_GBRP: rgba_map[GREEN] = 0; rgba_map[BLUE ] = 1; rgba_map[RED ] = 2; rgba_map[ALPHA] = 3; break;
|
||||
default: /* unsupported */
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_fill_line_with_color(uint8_t *line[4], int pixel_step[4], int w, uint8_t dst_color[4],
|
||||
enum AVPixelFormat pix_fmt, uint8_t rgba_color[4],
|
||||
int *is_packed_rgba, uint8_t rgba_map_ptr[4])
|
||||
{
|
||||
uint8_t rgba_map[4] = {0};
|
||||
int i;
|
||||
const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(pix_fmt);
|
||||
int hsub = pix_desc->log2_chroma_w;
|
||||
|
||||
*is_packed_rgba = ff_fill_rgba_map(rgba_map, pix_fmt) >= 0;
|
||||
|
||||
if (*is_packed_rgba) {
|
||||
pixel_step[0] = (av_get_bits_per_pixel(pix_desc))>>3;
|
||||
for (i = 0; i < 4; i++)
|
||||
dst_color[rgba_map[i]] = rgba_color[i];
|
||||
|
||||
line[0] = av_malloc(w * pixel_step[0]);
|
||||
for (i = 0; i < w; i++)
|
||||
memcpy(line[0] + i * pixel_step[0], dst_color, pixel_step[0]);
|
||||
if (rgba_map_ptr)
|
||||
memcpy(rgba_map_ptr, rgba_map, sizeof(rgba_map[0]) * 4);
|
||||
} else {
|
||||
int plane;
|
||||
|
||||
dst_color[0] = RGB_TO_Y_CCIR(rgba_color[0], rgba_color[1], rgba_color[2]);
|
||||
dst_color[1] = RGB_TO_U_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0);
|
||||
dst_color[2] = RGB_TO_V_CCIR(rgba_color[0], rgba_color[1], rgba_color[2], 0);
|
||||
dst_color[3] = rgba_color[3];
|
||||
|
||||
for (plane = 0; plane < 4; plane++) {
|
||||
int line_size;
|
||||
int hsub1 = (plane == 1 || plane == 2) ? hsub : 0;
|
||||
|
||||
pixel_step[plane] = 1;
|
||||
line_size = FF_CEIL_RSHIFT(w, hsub1) * pixel_step[plane];
|
||||
line[plane] = av_malloc(line_size);
|
||||
memset(line[plane], dst_color[plane], line_size);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ff_draw_rectangle(uint8_t *dst[4], int dst_linesize[4],
|
||||
uint8_t *src[4], int pixelstep[4],
|
||||
int hsub, int vsub, int x, int y, int w, int h)
|
||||
{
|
||||
int i, plane;
|
||||
uint8_t *p;
|
||||
|
||||
for (plane = 0; plane < 4 && dst[plane]; plane++) {
|
||||
int hsub1 = plane == 1 || plane == 2 ? hsub : 0;
|
||||
int vsub1 = plane == 1 || plane == 2 ? vsub : 0;
|
||||
int width = FF_CEIL_RSHIFT(w, hsub1);
|
||||
int height = FF_CEIL_RSHIFT(h, vsub1);
|
||||
|
||||
p = dst[plane] + (y >> vsub1) * dst_linesize[plane];
|
||||
for (i = 0; i < height; i++) {
|
||||
memcpy(p + (x >> hsub1) * pixelstep[plane],
|
||||
src[plane], width * pixelstep[plane]);
|
||||
p += dst_linesize[plane];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ff_copy_rectangle(uint8_t *dst[4], int dst_linesize[4],
|
||||
uint8_t *src[4], int src_linesize[4], int pixelstep[4],
|
||||
int hsub, int vsub, int x, int y, int y2, int w, int h)
|
||||
{
|
||||
int i, plane;
|
||||
uint8_t *p;
|
||||
|
||||
for (plane = 0; plane < 4 && dst[plane]; plane++) {
|
||||
int hsub1 = plane == 1 || plane == 2 ? hsub : 0;
|
||||
int vsub1 = plane == 1 || plane == 2 ? vsub : 0;
|
||||
int width = FF_CEIL_RSHIFT(w, hsub1);
|
||||
int height = FF_CEIL_RSHIFT(h, vsub1);
|
||||
|
||||
p = dst[plane] + (y >> vsub1) * dst_linesize[plane];
|
||||
for (i = 0; i < height; i++) {
|
||||
memcpy(p + (x >> hsub1) * pixelstep[plane],
|
||||
src[plane] + src_linesize[plane]*(i+(y2>>vsub1)), width * pixelstep[plane]);
|
||||
p += dst_linesize[plane];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ff_draw_init(FFDrawContext *draw, enum AVPixelFormat format, unsigned flags)
|
||||
{
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(format);
|
||||
const AVComponentDescriptor *c;
|
||||
unsigned i, nb_planes = 0;
|
||||
int pixelstep[MAX_PLANES] = { 0 };
|
||||
|
||||
if (!desc->name)
|
||||
return AVERROR(EINVAL);
|
||||
if (desc->flags & ~(AV_PIX_FMT_FLAG_PLANAR | AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PSEUDOPAL | AV_PIX_FMT_FLAG_ALPHA))
|
||||
return AVERROR(ENOSYS);
|
||||
for (i = 0; i < desc->nb_components; i++) {
|
||||
c = &desc->comp[i];
|
||||
/* for now, only 8-bits formats */
|
||||
if (c->depth_minus1 != 8 - 1)
|
||||
return AVERROR(ENOSYS);
|
||||
if (c->plane >= MAX_PLANES)
|
||||
return AVERROR(ENOSYS);
|
||||
/* strange interleaving */
|
||||
if (pixelstep[c->plane] != 0 &&
|
||||
pixelstep[c->plane] != c->step_minus1 + 1)
|
||||
return AVERROR(ENOSYS);
|
||||
pixelstep[c->plane] = c->step_minus1 + 1;
|
||||
if (pixelstep[c->plane] >= 8)
|
||||
return AVERROR(ENOSYS);
|
||||
nb_planes = FFMAX(nb_planes, c->plane + 1);
|
||||
}
|
||||
if ((desc->log2_chroma_w || desc->log2_chroma_h) && nb_planes < 3)
|
||||
return AVERROR(ENOSYS); /* exclude NV12 and NV21 */
|
||||
memset(draw, 0, sizeof(*draw));
|
||||
draw->desc = desc;
|
||||
draw->format = format;
|
||||
draw->nb_planes = nb_planes;
|
||||
memcpy(draw->pixelstep, pixelstep, sizeof(draw->pixelstep));
|
||||
draw->hsub[1] = draw->hsub[2] = draw->hsub_max = desc->log2_chroma_w;
|
||||
draw->vsub[1] = draw->vsub[2] = draw->vsub_max = desc->log2_chroma_h;
|
||||
for (i = 0; i < ((desc->nb_components - 1) | 1); i++)
|
||||
draw->comp_mask[desc->comp[i].plane] |=
|
||||
1 << (desc->comp[i].offset_plus1 - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ff_draw_color(FFDrawContext *draw, FFDrawColor *color, const uint8_t rgba[4])
|
||||
{
|
||||
unsigned i;
|
||||
uint8_t rgba_map[4];
|
||||
|
||||
if (rgba != color->rgba)
|
||||
memcpy(color->rgba, rgba, sizeof(color->rgba));
|
||||
if ((draw->desc->flags & AV_PIX_FMT_FLAG_RGB) &&
|
||||
ff_fill_rgba_map(rgba_map, draw->format) >= 0) {
|
||||
if (draw->nb_planes == 1) {
|
||||
for (i = 0; i < 4; i++)
|
||||
color->comp[0].u8[rgba_map[i]] = rgba[i];
|
||||
} else {
|
||||
for (i = 0; i < 4; i++)
|
||||
color->comp[rgba_map[i]].u8[0] = rgba[i];
|
||||
}
|
||||
} else if (draw->nb_planes == 3 || draw->nb_planes == 4) {
|
||||
/* assume YUV */
|
||||
color->comp[0].u8[0] = RGB_TO_Y_CCIR(rgba[0], rgba[1], rgba[2]);
|
||||
color->comp[1].u8[0] = RGB_TO_U_CCIR(rgba[0], rgba[1], rgba[2], 0);
|
||||
color->comp[2].u8[0] = RGB_TO_V_CCIR(rgba[0], rgba[1], rgba[2], 0);
|
||||
color->comp[3].u8[0] = rgba[3];
|
||||
} else if (draw->format == AV_PIX_FMT_GRAY8 || draw->format == AV_PIX_FMT_GRAY8A) {
|
||||
color->comp[0].u8[0] = RGB_TO_Y_CCIR(rgba[0], rgba[1], rgba[2]);
|
||||
color->comp[1].u8[0] = rgba[3];
|
||||
} else {
|
||||
av_log(NULL, AV_LOG_WARNING,
|
||||
"Color conversion not implemented for %s\n", draw->desc->name);
|
||||
memset(color, 128, sizeof(*color));
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t *pointer_at(FFDrawContext *draw, uint8_t *data[], int linesize[],
|
||||
int plane, int x, int y)
|
||||
{
|
||||
return data[plane] +
|
||||
(y >> draw->vsub[plane]) * linesize[plane] +
|
||||
(x >> draw->hsub[plane]) * draw->pixelstep[plane];
|
||||
}
|
||||
|
||||
void ff_copy_rectangle2(FFDrawContext *draw,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
uint8_t *src[], int src_linesize[],
|
||||
int dst_x, int dst_y, int src_x, int src_y,
|
||||
int w, int h)
|
||||
{
|
||||
int plane, y, wp, hp;
|
||||
uint8_t *p, *q;
|
||||
|
||||
for (plane = 0; plane < draw->nb_planes; plane++) {
|
||||
p = pointer_at(draw, src, src_linesize, plane, src_x, src_y);
|
||||
q = pointer_at(draw, dst, dst_linesize, plane, dst_x, dst_y);
|
||||
wp = FF_CEIL_RSHIFT(w, draw->hsub[plane]) * draw->pixelstep[plane];
|
||||
hp = FF_CEIL_RSHIFT(h, draw->vsub[plane]);
|
||||
for (y = 0; y < hp; y++) {
|
||||
memcpy(q, p, wp);
|
||||
p += src_linesize[plane];
|
||||
q += dst_linesize[plane];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ff_fill_rectangle(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
int dst_x, int dst_y, int w, int h)
|
||||
{
|
||||
int plane, x, y, wp, hp;
|
||||
uint8_t *p0, *p;
|
||||
|
||||
for (plane = 0; plane < draw->nb_planes; plane++) {
|
||||
p0 = pointer_at(draw, dst, dst_linesize, plane, dst_x, dst_y);
|
||||
wp = FF_CEIL_RSHIFT(w, draw->hsub[plane]);
|
||||
hp = FF_CEIL_RSHIFT(h, draw->vsub[plane]);
|
||||
if (!hp)
|
||||
return;
|
||||
p = p0;
|
||||
/* copy first line from color */
|
||||
for (x = 0; x < wp; x++) {
|
||||
memcpy(p, color->comp[plane].u8, draw->pixelstep[plane]);
|
||||
p += draw->pixelstep[plane];
|
||||
}
|
||||
wp *= draw->pixelstep[plane];
|
||||
/* copy next lines from first line */
|
||||
p = p0 + dst_linesize[plane];
|
||||
for (y = 1; y < hp; y++) {
|
||||
memcpy(p, p0, wp);
|
||||
p += dst_linesize[plane];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clip interval [x; x+w[ within [0; wmax[.
|
||||
* The resulting w may be negative if the final interval is empty.
|
||||
* dx, if not null, return the difference between in and out value of x.
|
||||
*/
|
||||
static void clip_interval(int wmax, int *x, int *w, int *dx)
|
||||
{
|
||||
if (dx)
|
||||
*dx = 0;
|
||||
if (*x < 0) {
|
||||
if (dx)
|
||||
*dx = -*x;
|
||||
*w += *x;
|
||||
*x = 0;
|
||||
}
|
||||
if (*x + *w > wmax)
|
||||
*w = wmax - *x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompose w pixels starting at x
|
||||
* into start + (w starting at x) + end
|
||||
* with x and w aligned on multiples of 1<<sub.
|
||||
*/
|
||||
static void subsampling_bounds(int sub, int *x, int *w, int *start, int *end)
|
||||
{
|
||||
int mask = (1 << sub) - 1;
|
||||
|
||||
*start = (-*x) & mask;
|
||||
*x += *start;
|
||||
*start = FFMIN(*start, *w);
|
||||
*w -= *start;
|
||||
*end = *w & mask;
|
||||
*w >>= sub;
|
||||
}
|
||||
|
||||
static int component_used(FFDrawContext *draw, int plane, int comp)
|
||||
{
|
||||
return (draw->comp_mask[plane] >> comp) & 1;
|
||||
}
|
||||
|
||||
/* If alpha is in the [ 0 ; 0x1010101 ] range,
|
||||
then alpha * value is in the [ 0 ; 0xFFFFFFFF ] range,
|
||||
and >> 24 gives a correct rounding. */
|
||||
static void blend_line(uint8_t *dst, unsigned src, unsigned alpha,
|
||||
int dx, int w, unsigned hsub, int left, int right)
|
||||
{
|
||||
unsigned asrc = alpha * src;
|
||||
unsigned tau = 0x1010101 - alpha;
|
||||
int x;
|
||||
|
||||
if (left) {
|
||||
unsigned suba = (left * alpha) >> hsub;
|
||||
*dst = (*dst * (0x1010101 - suba) + src * suba) >> 24;
|
||||
dst += dx;
|
||||
}
|
||||
for (x = 0; x < w; x++) {
|
||||
*dst = (*dst * tau + asrc) >> 24;
|
||||
dst += dx;
|
||||
}
|
||||
if (right) {
|
||||
unsigned suba = (right * alpha) >> hsub;
|
||||
*dst = (*dst * (0x1010101 - suba) + src * suba) >> 24;
|
||||
}
|
||||
}
|
||||
|
||||
void ff_blend_rectangle(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
int dst_w, int dst_h,
|
||||
int x0, int y0, int w, int h)
|
||||
{
|
||||
unsigned alpha, nb_planes, nb_comp, plane, comp;
|
||||
int w_sub, h_sub, x_sub, y_sub, left, right, top, bottom, y;
|
||||
uint8_t *p0, *p;
|
||||
|
||||
/* TODO optimize if alpha = 0xFF */
|
||||
clip_interval(dst_w, &x0, &w, NULL);
|
||||
clip_interval(dst_h, &y0, &h, NULL);
|
||||
if (w <= 0 || h <= 0 || !color->rgba[3])
|
||||
return;
|
||||
/* 0x10203 * alpha + 2 is in the [ 2 ; 0x1010101 - 2 ] range */
|
||||
alpha = 0x10203 * color->rgba[3] + 0x2;
|
||||
nb_planes = (draw->nb_planes - 1) | 1; /* eliminate alpha */
|
||||
for (plane = 0; plane < nb_planes; plane++) {
|
||||
nb_comp = draw->pixelstep[plane];
|
||||
p0 = pointer_at(draw, dst, dst_linesize, plane, x0, y0);
|
||||
w_sub = w;
|
||||
h_sub = h;
|
||||
x_sub = x0;
|
||||
y_sub = y0;
|
||||
subsampling_bounds(draw->hsub[plane], &x_sub, &w_sub, &left, &right);
|
||||
subsampling_bounds(draw->vsub[plane], &y_sub, &h_sub, &top, &bottom);
|
||||
for (comp = 0; comp < nb_comp; comp++) {
|
||||
if (!component_used(draw, plane, comp))
|
||||
continue;
|
||||
p = p0 + comp;
|
||||
if (top) {
|
||||
blend_line(p, color->comp[plane].u8[comp], alpha >> 1,
|
||||
draw->pixelstep[plane], w_sub,
|
||||
draw->hsub[plane], left, right);
|
||||
p += dst_linesize[plane];
|
||||
}
|
||||
for (y = 0; y < h_sub; y++) {
|
||||
blend_line(p, color->comp[plane].u8[comp], alpha,
|
||||
draw->pixelstep[plane], w_sub,
|
||||
draw->hsub[plane], left, right);
|
||||
p += dst_linesize[plane];
|
||||
}
|
||||
if (bottom)
|
||||
blend_line(p, color->comp[plane].u8[comp], alpha >> 1,
|
||||
draw->pixelstep[plane], w_sub,
|
||||
draw->hsub[plane], left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void blend_pixel(uint8_t *dst, unsigned src, unsigned alpha,
|
||||
uint8_t *mask, int mask_linesize, int l2depth,
|
||||
unsigned w, unsigned h, unsigned shift, unsigned xm0)
|
||||
{
|
||||
unsigned xm, x, y, t = 0;
|
||||
unsigned xmshf = 3 - l2depth;
|
||||
unsigned xmmod = 7 >> l2depth;
|
||||
unsigned mbits = (1 << (1 << l2depth)) - 1;
|
||||
unsigned mmult = 255 / mbits;
|
||||
|
||||
for (y = 0; y < h; y++) {
|
||||
xm = xm0;
|
||||
for (x = 0; x < w; x++) {
|
||||
t += ((mask[xm >> xmshf] >> ((~xm & xmmod) << l2depth)) & mbits)
|
||||
* mmult;
|
||||
xm++;
|
||||
}
|
||||
mask += mask_linesize;
|
||||
}
|
||||
alpha = (t >> shift) * alpha;
|
||||
*dst = ((0x1010101 - alpha) * *dst + alpha * src) >> 24;
|
||||
}
|
||||
|
||||
static void blend_line_hv(uint8_t *dst, int dst_delta,
|
||||
unsigned src, unsigned alpha,
|
||||
uint8_t *mask, int mask_linesize, int l2depth, int w,
|
||||
unsigned hsub, unsigned vsub,
|
||||
int xm, int left, int right, int hband)
|
||||
{
|
||||
int x;
|
||||
|
||||
if (left) {
|
||||
blend_pixel(dst, src, alpha, mask, mask_linesize, l2depth,
|
||||
left, hband, hsub + vsub, xm);
|
||||
dst += dst_delta;
|
||||
xm += left;
|
||||
}
|
||||
for (x = 0; x < w; x++) {
|
||||
blend_pixel(dst, src, alpha, mask, mask_linesize, l2depth,
|
||||
1 << hsub, hband, hsub + vsub, xm);
|
||||
dst += dst_delta;
|
||||
xm += 1 << hsub;
|
||||
}
|
||||
if (right)
|
||||
blend_pixel(dst, src, alpha, mask, mask_linesize, l2depth,
|
||||
right, hband, hsub + vsub, xm);
|
||||
}
|
||||
|
||||
void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[], int dst_w, int dst_h,
|
||||
uint8_t *mask, int mask_linesize, int mask_w, int mask_h,
|
||||
int l2depth, unsigned endianness, int x0, int y0)
|
||||
{
|
||||
unsigned alpha, nb_planes, nb_comp, plane, comp;
|
||||
int xm0, ym0, w_sub, h_sub, x_sub, y_sub, left, right, top, bottom, y;
|
||||
uint8_t *p0, *p, *m;
|
||||
|
||||
clip_interval(dst_w, &x0, &mask_w, &xm0);
|
||||
clip_interval(dst_h, &y0, &mask_h, &ym0);
|
||||
mask += ym0 * mask_linesize;
|
||||
if (mask_w <= 0 || mask_h <= 0 || !color->rgba[3])
|
||||
return;
|
||||
/* alpha is in the [ 0 ; 0x10203 ] range,
|
||||
alpha * mask is in the [ 0 ; 0x1010101 - 4 ] range */
|
||||
alpha = (0x10307 * color->rgba[3] + 0x3) >> 8;
|
||||
nb_planes = (draw->nb_planes - 1) | 1; /* eliminate alpha */
|
||||
for (plane = 0; plane < nb_planes; plane++) {
|
||||
nb_comp = draw->pixelstep[plane];
|
||||
p0 = pointer_at(draw, dst, dst_linesize, plane, x0, y0);
|
||||
w_sub = mask_w;
|
||||
h_sub = mask_h;
|
||||
x_sub = x0;
|
||||
y_sub = y0;
|
||||
subsampling_bounds(draw->hsub[plane], &x_sub, &w_sub, &left, &right);
|
||||
subsampling_bounds(draw->vsub[plane], &y_sub, &h_sub, &top, &bottom);
|
||||
for (comp = 0; comp < nb_comp; comp++) {
|
||||
if (!component_used(draw, plane, comp))
|
||||
continue;
|
||||
p = p0 + comp;
|
||||
m = mask;
|
||||
if (top) {
|
||||
blend_line_hv(p, draw->pixelstep[plane],
|
||||
color->comp[plane].u8[comp], alpha,
|
||||
m, mask_linesize, l2depth, w_sub,
|
||||
draw->hsub[plane], draw->vsub[plane],
|
||||
xm0, left, right, top);
|
||||
p += dst_linesize[plane];
|
||||
m += top * mask_linesize;
|
||||
}
|
||||
for (y = 0; y < h_sub; y++) {
|
||||
blend_line_hv(p, draw->pixelstep[plane],
|
||||
color->comp[plane].u8[comp], alpha,
|
||||
m, mask_linesize, l2depth, w_sub,
|
||||
draw->hsub[plane], draw->vsub[plane],
|
||||
xm0, left, right, 1 << draw->vsub[plane]);
|
||||
p += dst_linesize[plane];
|
||||
m += mask_linesize << draw->vsub[plane];
|
||||
}
|
||||
if (bottom)
|
||||
blend_line_hv(p, draw->pixelstep[plane],
|
||||
color->comp[plane].u8[comp], alpha,
|
||||
m, mask_linesize, l2depth, w_sub,
|
||||
draw->hsub[plane], draw->vsub[plane],
|
||||
xm0, left, right, bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ff_draw_round_to_sub(FFDrawContext *draw, int sub_dir, int round_dir,
|
||||
int value)
|
||||
{
|
||||
unsigned shift = sub_dir ? draw->vsub_max : draw->hsub_max;
|
||||
|
||||
if (!shift)
|
||||
return value;
|
||||
if (round_dir >= 0)
|
||||
value += round_dir ? (1 << shift) - 1 : 1 << (shift - 1);
|
||||
return (value >> shift) << shift;
|
||||
}
|
||||
|
||||
AVFilterFormats *ff_draw_supported_pixel_formats(unsigned flags)
|
||||
{
|
||||
enum AVPixelFormat i, pix_fmts[AV_PIX_FMT_NB + 1];
|
||||
unsigned n = 0;
|
||||
FFDrawContext draw;
|
||||
|
||||
for (i = 0; i < AV_PIX_FMT_NB; i++)
|
||||
if (ff_draw_init(&draw, i, flags) >= 0)
|
||||
pix_fmts[n++] = i;
|
||||
pix_fmts[n++] = AV_PIX_FMT_NONE;
|
||||
return ff_make_format_list(pix_fmts);
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
|
||||
#undef printf
|
||||
|
||||
int main(void)
|
||||
{
|
||||
enum AVPixelFormat f;
|
||||
const AVPixFmtDescriptor *desc;
|
||||
FFDrawContext draw;
|
||||
FFDrawColor color;
|
||||
int r, i;
|
||||
|
||||
for (f = 0; f < AV_PIX_FMT_NB; f++) {
|
||||
desc = av_pix_fmt_desc_get(f);
|
||||
if (!desc->name)
|
||||
continue;
|
||||
printf("Testing %s...%*s", desc->name,
|
||||
(int)(16 - strlen(desc->name)), "");
|
||||
r = ff_draw_init(&draw, f, 0);
|
||||
if (r < 0) {
|
||||
char buf[128];
|
||||
av_strerror(r, buf, sizeof(buf));
|
||||
printf("no: %s\n", buf);
|
||||
continue;
|
||||
}
|
||||
ff_draw_color(&draw, &color, (uint8_t[]) { 1, 0, 0, 1 });
|
||||
for (i = 0; i < sizeof(color); i++)
|
||||
if (((uint8_t *)&color)[i] != 128)
|
||||
break;
|
||||
if (i == sizeof(color)) {
|
||||
printf("fallback color\n");
|
||||
continue;
|
||||
}
|
||||
printf("ok\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_DRAWUTILS_H
|
||||
#define AVFILTER_DRAWUTILS_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* misc drawing utilities
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "avfilter.h"
|
||||
#include "libavutil/pixfmt.h"
|
||||
|
||||
int ff_fill_rgba_map(uint8_t *rgba_map, enum AVPixelFormat pix_fmt);
|
||||
|
||||
int ff_fill_line_with_color(uint8_t *line[4], int pixel_step[4], int w,
|
||||
uint8_t dst_color[4],
|
||||
enum AVPixelFormat pix_fmt, uint8_t rgba_color[4],
|
||||
int *is_packed_rgba, uint8_t rgba_map[4]);
|
||||
|
||||
void ff_draw_rectangle(uint8_t *dst[4], int dst_linesize[4],
|
||||
uint8_t *src[4], int pixelstep[4],
|
||||
int hsub, int vsub, int x, int y, int w, int h);
|
||||
|
||||
void ff_copy_rectangle(uint8_t *dst[4], int dst_linesize[4],
|
||||
uint8_t *src[4], int src_linesize[4], int pixelstep[4],
|
||||
int hsub, int vsub, int x, int y, int y2, int w, int h);
|
||||
|
||||
#define MAX_PLANES 4
|
||||
|
||||
typedef struct FFDrawContext {
|
||||
const struct AVPixFmtDescriptor *desc;
|
||||
enum AVPixelFormat format;
|
||||
unsigned nb_planes;
|
||||
int pixelstep[MAX_PLANES]; /*< offset between pixels */
|
||||
uint8_t comp_mask[MAX_PLANES]; /*< bitmask of used non-alpha components */
|
||||
uint8_t hsub[MAX_PLANES]; /*< horizontal subsampling */
|
||||
uint8_t vsub[MAX_PLANES]; /*< vertical subsampling */
|
||||
uint8_t hsub_max;
|
||||
uint8_t vsub_max;
|
||||
} FFDrawContext;
|
||||
|
||||
typedef struct FFDrawColor {
|
||||
uint8_t rgba[4];
|
||||
union {
|
||||
uint32_t u32;
|
||||
uint16_t u16;
|
||||
uint8_t u8[4];
|
||||
} comp[MAX_PLANES];
|
||||
} FFDrawColor;
|
||||
|
||||
/**
|
||||
* Init a draw context.
|
||||
*
|
||||
* Only a limited number of pixel formats are supported, if format is not
|
||||
* supported the function will return an error.
|
||||
* No flags currently defined.
|
||||
* @return 0 for success, < 0 for error
|
||||
*/
|
||||
int ff_draw_init(FFDrawContext *draw, enum AVPixelFormat format, unsigned flags);
|
||||
|
||||
/**
|
||||
* Prepare a color.
|
||||
*/
|
||||
void ff_draw_color(FFDrawContext *draw, FFDrawColor *color, const uint8_t rgba[4]);
|
||||
|
||||
/**
|
||||
* Copy a rectangle from an image to another.
|
||||
*
|
||||
* The coordinates must be as even as the subsampling requires.
|
||||
*/
|
||||
void ff_copy_rectangle2(FFDrawContext *draw,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
uint8_t *src[], int src_linesize[],
|
||||
int dst_x, int dst_y, int src_x, int src_y,
|
||||
int w, int h);
|
||||
|
||||
/**
|
||||
* Fill a rectangle with an uniform color.
|
||||
*
|
||||
* The coordinates must be as even as the subsampling requires.
|
||||
* The color needs to be inited with ff_draw_color.
|
||||
*/
|
||||
void ff_fill_rectangle(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
int dst_x, int dst_y, int w, int h);
|
||||
|
||||
/**
|
||||
* Blend a rectangle with an uniform color.
|
||||
*/
|
||||
void ff_blend_rectangle(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[],
|
||||
int dst_w, int dst_h,
|
||||
int x0, int y0, int w, int h);
|
||||
|
||||
/**
|
||||
* Blend an alpha mask with an uniform color.
|
||||
*
|
||||
* @param draw draw context
|
||||
* @param color color for the overlay;
|
||||
* @param dst destination image
|
||||
* @param dst_linesize line stride of the destination
|
||||
* @param dst_w width of the destination image
|
||||
* @param dst_h height of the destination image
|
||||
* @param mask mask
|
||||
* @param mask_linesize line stride of the mask
|
||||
* @param mask_w width of the mask
|
||||
* @param mask_h height of the mask
|
||||
* @param l2depth log2 of depth of the mask (0 for 1bpp, 3 for 8bpp)
|
||||
* @param endianness bit order of the mask (0: MSB to the left)
|
||||
* @param x0 horizontal position of the overlay
|
||||
* @param y0 vertical position of the overlay
|
||||
*/
|
||||
void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
|
||||
uint8_t *dst[], int dst_linesize[], int dst_w, int dst_h,
|
||||
uint8_t *mask, int mask_linesize, int mask_w, int mask_h,
|
||||
int l2depth, unsigned endianness, int x0, int y0);
|
||||
|
||||
/**
|
||||
* Round a dimension according to subsampling.
|
||||
*
|
||||
* @param draw draw context
|
||||
* @param sub_dir 0 for horizontal, 1 for vertical
|
||||
* @param round_dir 0 nearest, -1 round down, +1 round up
|
||||
* @param value value to round
|
||||
* @return the rounded value
|
||||
*/
|
||||
int ff_draw_round_to_sub(FFDrawContext *draw, int sub_dir, int round_dir,
|
||||
int value);
|
||||
|
||||
/**
|
||||
* Return the list of pixel formats supported by the draw functions.
|
||||
*
|
||||
* The flags are the same as ff_draw_init, i.e., none currently.
|
||||
*/
|
||||
AVFilterFormats *ff_draw_supported_pixel_formats(unsigned flags);
|
||||
|
||||
#endif /* AVFILTER_DRAWUTILS_H */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "dualinput.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
|
||||
static int process_frame(FFFrameSync *fs)
|
||||
{
|
||||
AVFilterContext *ctx = fs->parent;
|
||||
FFDualInputContext *s = fs->opaque;
|
||||
AVFrame *mainpic = NULL, *secondpic = NULL;
|
||||
int ret = 0;
|
||||
|
||||
if ((ret = ff_framesync_get_frame(&s->fs, 0, &mainpic, 1)) < 0 ||
|
||||
(ret = ff_framesync_get_frame(&s->fs, 1, &secondpic, 0)) < 0) {
|
||||
av_frame_free(&mainpic);
|
||||
return ret;
|
||||
}
|
||||
av_assert0(mainpic);
|
||||
mainpic->pts = av_rescale_q(mainpic->pts, s->fs.time_base, ctx->outputs[0]->time_base);
|
||||
if (secondpic && !ctx->is_disabled)
|
||||
mainpic = s->process(ctx, mainpic, secondpic);
|
||||
ret = ff_filter_frame(ctx->outputs[0], mainpic);
|
||||
av_assert1(ret != AVERROR(EAGAIN));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ff_dualinput_init(AVFilterContext *ctx, FFDualInputContext *s)
|
||||
{
|
||||
FFFrameSyncIn *in = s->fs.in;
|
||||
|
||||
ff_framesync_init(&s->fs, ctx, 2);
|
||||
s->fs.opaque = s;
|
||||
s->fs.on_event = process_frame;
|
||||
in[0].time_base = ctx->inputs[0]->time_base;
|
||||
in[1].time_base = ctx->inputs[1]->time_base;
|
||||
in[0].sync = 2;
|
||||
in[0].before = EXT_STOP;
|
||||
in[0].after = EXT_INFINITY;
|
||||
in[1].sync = 1;
|
||||
in[1].before = EXT_NULL;
|
||||
in[1].after = EXT_INFINITY;
|
||||
|
||||
if (s->shortest)
|
||||
in[1].after = EXT_STOP;
|
||||
if (!s->repeatlast) {
|
||||
in[0].after = EXT_STOP;
|
||||
in[1].sync = 0;
|
||||
}
|
||||
|
||||
return ff_framesync_configure(&s->fs);
|
||||
}
|
||||
|
||||
int ff_dualinput_filter_frame(FFDualInputContext *s,
|
||||
AVFilterLink *inlink, AVFrame *in)
|
||||
{
|
||||
return ff_framesync_filter_frame(&s->fs, inlink, in);
|
||||
}
|
||||
|
||||
int ff_dualinput_request_frame(FFDualInputContext *s, AVFilterLink *outlink)
|
||||
{
|
||||
return ff_framesync_request_frame(&s->fs, outlink);
|
||||
}
|
||||
|
||||
void ff_dualinput_uninit(FFDualInputContext *s)
|
||||
{
|
||||
ff_framesync_uninit(&s->fs);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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
|
||||
* Double input streams helper for filters
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_DUALINPUT_H
|
||||
#define AVFILTER_DUALINPUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "bufferqueue.h"
|
||||
#include "framesync.h"
|
||||
#include "internal.h"
|
||||
|
||||
typedef struct {
|
||||
FFFrameSync fs;
|
||||
FFFrameSyncIn second_input; /* must be immediately after fs */
|
||||
|
||||
AVFrame *(*process)(AVFilterContext *ctx, AVFrame *main, const AVFrame *second);
|
||||
int shortest; ///< terminate stream when the second input terminates
|
||||
int repeatlast; ///< repeat last second frame
|
||||
} FFDualInputContext;
|
||||
|
||||
int ff_dualinput_init(AVFilterContext *ctx, FFDualInputContext *s);
|
||||
int ff_dualinput_filter_frame(FFDualInputContext *s, AVFilterLink *inlink, AVFrame *in);
|
||||
int ff_dualinput_request_frame(FFDualInputContext *s, AVFilterLink *outlink);
|
||||
void ff_dualinput_uninit(FFDualInputContext *s);
|
||||
|
||||
#endif /* AVFILTER_DUALINPUT_H */
|
||||
@@ -0,0 +1,799 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Clément Bœsch
|
||||
*
|
||||
* This file is part of FFmpeg.
|
||||
*
|
||||
* FFmpeg is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU 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
|
||||
* EBU R.128 implementation
|
||||
* @see http://tech.ebu.ch/loudness
|
||||
* @see https://www.youtube.com/watch?v=iuEtQqC-Sqo "EBU R128 Introduction - Florian Camerer"
|
||||
* @todo True Peak
|
||||
* @todo implement start/stop/reset through filter command injection
|
||||
* @todo support other frequencies to avoid resampling
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/xga_font_data.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/timestamp.h"
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define MAX_CHANNELS 63
|
||||
|
||||
/* pre-filter coefficients */
|
||||
#define PRE_B0 1.53512485958697
|
||||
#define PRE_B1 -2.69169618940638
|
||||
#define PRE_B2 1.19839281085285
|
||||
#define PRE_A1 -1.69065929318241
|
||||
#define PRE_A2 0.73248077421585
|
||||
|
||||
/* RLB-filter coefficients */
|
||||
#define RLB_B0 1.0
|
||||
#define RLB_B1 -2.0
|
||||
#define RLB_B2 1.0
|
||||
#define RLB_A1 -1.99004745483398
|
||||
#define RLB_A2 0.99007225036621
|
||||
|
||||
#define ABS_THRES -70 ///< silence gate: we discard anything below this absolute (LUFS) threshold
|
||||
#define ABS_UP_THRES 10 ///< upper loud limit to consider (ABS_THRES being the minimum)
|
||||
#define HIST_GRAIN 100 ///< defines histogram precision
|
||||
#define HIST_SIZE ((ABS_UP_THRES - ABS_THRES) * HIST_GRAIN + 1)
|
||||
|
||||
/**
|
||||
* A histogram is an array of HIST_SIZE hist_entry storing all the energies
|
||||
* recorded (with an accuracy of 1/HIST_GRAIN) of the loudnesses from ABS_THRES
|
||||
* (at 0) to ABS_UP_THRES (at HIST_SIZE-1).
|
||||
* This fixed-size system avoids the need of a list of energies growing
|
||||
* infinitely over the time and is thus more scalable.
|
||||
*/
|
||||
struct hist_entry {
|
||||
int count; ///< how many times the corresponding value occurred
|
||||
double energy; ///< E = 10^((L + 0.691) / 10)
|
||||
double loudness; ///< L = -0.691 + 10 * log10(E)
|
||||
};
|
||||
|
||||
struct integrator {
|
||||
double *cache[MAX_CHANNELS]; ///< window of filtered samples (N ms)
|
||||
int cache_pos; ///< focus on the last added bin in the cache array
|
||||
double sum[MAX_CHANNELS]; ///< sum of the last N ms filtered samples (cache content)
|
||||
int filled; ///< 1 if the cache is completely filled, 0 otherwise
|
||||
double rel_threshold; ///< relative threshold
|
||||
double sum_kept_powers; ///< sum of the powers (weighted sums) above absolute threshold
|
||||
int nb_kept_powers; ///< number of sum above absolute threshold
|
||||
struct hist_entry *histogram; ///< histogram of the powers, used to compute LRA and I
|
||||
};
|
||||
|
||||
struct rect { int x, y, w, h; };
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class; ///< AVClass context for log and options purpose
|
||||
|
||||
/* video */
|
||||
int do_video; ///< 1 if video output enabled, 0 otherwise
|
||||
int w, h; ///< size of the video output
|
||||
struct rect text; ///< rectangle for the LU legend on the left
|
||||
struct rect graph; ///< rectangle for the main graph in the center
|
||||
struct rect gauge; ///< rectangle for the gauge on the right
|
||||
AVFrame *outpicref; ///< output picture reference, updated regularly
|
||||
int meter; ///< select a EBU mode between +9 and +18
|
||||
int scale_range; ///< the range of LU values according to the meter
|
||||
int y_zero_lu; ///< the y value (pixel position) for 0 LU
|
||||
int *y_line_ref; ///< y reference values for drawing the LU lines in the graph and the gauge
|
||||
|
||||
/* audio */
|
||||
int nb_channels; ///< number of channels in the input
|
||||
double *ch_weighting; ///< channel weighting mapping
|
||||
int sample_count; ///< sample count used for refresh frequency, reset at refresh
|
||||
|
||||
/* Filter caches.
|
||||
* The mult by 3 in the following is for X[i], X[i-1] and X[i-2] */
|
||||
double x[MAX_CHANNELS * 3]; ///< 3 input samples cache for each channel
|
||||
double y[MAX_CHANNELS * 3]; ///< 3 pre-filter samples cache for each channel
|
||||
double z[MAX_CHANNELS * 3]; ///< 3 RLB-filter samples cache for each channel
|
||||
|
||||
#define I400_BINS (48000 * 4 / 10)
|
||||
#define I3000_BINS (48000 * 3)
|
||||
struct integrator i400; ///< 400ms integrator, used for Momentary loudness (M), and Integrated loudness (I)
|
||||
struct integrator i3000; ///< 3s integrator, used for Short term loudness (S), and Loudness Range (LRA)
|
||||
|
||||
/* I and LRA specific */
|
||||
double integrated_loudness; ///< integrated loudness in LUFS (I)
|
||||
double loudness_range; ///< loudness range in LU (LRA)
|
||||
double lra_low, lra_high; ///< low and high LRA values
|
||||
|
||||
/* misc */
|
||||
int loglevel; ///< log level for frame logging
|
||||
int metadata; ///< whether or not to inject loudness results in frames
|
||||
} EBUR128Context;
|
||||
|
||||
#define OFFSET(x) offsetof(EBUR128Context, x)
|
||||
#define A AV_OPT_FLAG_AUDIO_PARAM
|
||||
#define V AV_OPT_FLAG_VIDEO_PARAM
|
||||
#define F AV_OPT_FLAG_FILTERING_PARAM
|
||||
static const AVOption ebur128_options[] = {
|
||||
{ "video", "set video output", OFFSET(do_video), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, V|F },
|
||||
{ "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x480"}, 0, 0, V|F },
|
||||
{ "meter", "set scale meter (+9 to +18)", OFFSET(meter), AV_OPT_TYPE_INT, {.i64 = 9}, 9, 18, V|F },
|
||||
{ "framelog", "force frame logging level", OFFSET(loglevel), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, A|V|F, "level" },
|
||||
{ "info", "information logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_INFO}, INT_MIN, INT_MAX, A|V|F, "level" },
|
||||
{ "verbose", "verbose logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_VERBOSE}, INT_MIN, INT_MAX, A|V|F, "level" },
|
||||
{ "metadata", "inject metadata in the filtergraph", OFFSET(metadata), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, A|V|F },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFILTER_DEFINE_CLASS(ebur128);
|
||||
|
||||
static const uint8_t graph_colors[] = {
|
||||
0xdd, 0x66, 0x66, // value above 0LU non reached
|
||||
0x66, 0x66, 0xdd, // value below 0LU non reached
|
||||
0x96, 0x33, 0x33, // value above 0LU reached
|
||||
0x33, 0x33, 0x96, // value below 0LU reached
|
||||
0xdd, 0x96, 0x96, // value above 0LU line non reached
|
||||
0x96, 0x96, 0xdd, // value below 0LU line non reached
|
||||
0xdd, 0x33, 0x33, // value above 0LU line reached
|
||||
0x33, 0x33, 0xdd, // value below 0LU line reached
|
||||
};
|
||||
|
||||
static const uint8_t *get_graph_color(const EBUR128Context *ebur128, int v, int y)
|
||||
{
|
||||
const int below0 = y > ebur128->y_zero_lu;
|
||||
const int reached = y >= v;
|
||||
const int line = ebur128->y_line_ref[y] || y == ebur128->y_zero_lu;
|
||||
const int colorid = 4*line + 2*reached + below0;
|
||||
return graph_colors + 3*colorid;
|
||||
}
|
||||
|
||||
static inline int lu_to_y(const EBUR128Context *ebur128, double v)
|
||||
{
|
||||
v += 2 * ebur128->meter; // make it in range [0;...]
|
||||
v = av_clipf(v, 0, ebur128->scale_range); // make sure it's in the graph scale
|
||||
v = ebur128->scale_range - v; // invert value (y=0 is on top)
|
||||
return v * ebur128->graph.h / ebur128->scale_range; // rescale from scale range to px height
|
||||
}
|
||||
|
||||
#define FONT8 0
|
||||
#define FONT16 1
|
||||
|
||||
static const uint8_t font_colors[] = {
|
||||
0xdd, 0xdd, 0x00,
|
||||
0x00, 0x96, 0x96,
|
||||
};
|
||||
|
||||
static void drawtext(AVFrame *pic, int x, int y, int ftid, const uint8_t *color, const char *fmt, ...)
|
||||
{
|
||||
int i;
|
||||
char buf[128] = {0};
|
||||
const uint8_t *font;
|
||||
int font_height;
|
||||
va_list vl;
|
||||
|
||||
if (ftid == FONT16) font = avpriv_vga16_font, font_height = 16;
|
||||
else if (ftid == FONT8) font = avpriv_cga_font, font_height = 8;
|
||||
else return;
|
||||
|
||||
va_start(vl, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, vl);
|
||||
va_end(vl);
|
||||
|
||||
for (i = 0; buf[i]; i++) {
|
||||
int char_y, mask;
|
||||
uint8_t *p = pic->data[0] + y*pic->linesize[0] + (x + i*8)*3;
|
||||
|
||||
for (char_y = 0; char_y < font_height; char_y++) {
|
||||
for (mask = 0x80; mask; mask >>= 1) {
|
||||
if (font[buf[i] * font_height + char_y] & mask)
|
||||
memcpy(p, color, 3);
|
||||
else
|
||||
memcpy(p, "\x00\x00\x00", 3);
|
||||
p += 3;
|
||||
}
|
||||
p += pic->linesize[0] - 8*3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawline(AVFrame *pic, int x, int y, int len, int step)
|
||||
{
|
||||
int i;
|
||||
uint8_t *p = pic->data[0] + y*pic->linesize[0] + x*3;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
memcpy(p, "\x00\xff\x00", 3);
|
||||
p += step;
|
||||
}
|
||||
}
|
||||
|
||||
static int config_video_output(AVFilterLink *outlink)
|
||||
{
|
||||
int i, x, y;
|
||||
uint8_t *p;
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
AVFrame *outpicref;
|
||||
|
||||
/* check if there is enough space to represent everything decently */
|
||||
if (ebur128->w < 640 || ebur128->h < 480) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Video size %dx%d is too small, "
|
||||
"minimum size is 640x480\n", ebur128->w, ebur128->h);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
outlink->w = ebur128->w;
|
||||
outlink->h = ebur128->h;
|
||||
|
||||
#define PAD 8
|
||||
|
||||
/* configure text area position and size */
|
||||
ebur128->text.x = PAD;
|
||||
ebur128->text.y = 40;
|
||||
ebur128->text.w = 3 * 8; // 3 characters
|
||||
ebur128->text.h = ebur128->h - PAD - ebur128->text.y;
|
||||
|
||||
/* configure gauge position and size */
|
||||
ebur128->gauge.w = 20;
|
||||
ebur128->gauge.h = ebur128->text.h;
|
||||
ebur128->gauge.x = ebur128->w - PAD - ebur128->gauge.w;
|
||||
ebur128->gauge.y = ebur128->text.y;
|
||||
|
||||
/* configure graph position and size */
|
||||
ebur128->graph.x = ebur128->text.x + ebur128->text.w + PAD;
|
||||
ebur128->graph.y = ebur128->gauge.y;
|
||||
ebur128->graph.w = ebur128->gauge.x - ebur128->graph.x - PAD;
|
||||
ebur128->graph.h = ebur128->gauge.h;
|
||||
|
||||
/* graph and gauge share the LU-to-pixel code */
|
||||
av_assert0(ebur128->graph.h == ebur128->gauge.h);
|
||||
|
||||
/* prepare the initial picref buffer */
|
||||
av_frame_free(&ebur128->outpicref);
|
||||
ebur128->outpicref = outpicref =
|
||||
ff_get_video_buffer(outlink, outlink->w, outlink->h);
|
||||
if (!outpicref)
|
||||
return AVERROR(ENOMEM);
|
||||
outlink->sample_aspect_ratio = (AVRational){1,1};
|
||||
|
||||
/* init y references values (to draw LU lines) */
|
||||
ebur128->y_line_ref = av_calloc(ebur128->graph.h + 1, sizeof(*ebur128->y_line_ref));
|
||||
if (!ebur128->y_line_ref)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
/* black background */
|
||||
memset(outpicref->data[0], 0, ebur128->h * outpicref->linesize[0]);
|
||||
|
||||
/* draw LU legends */
|
||||
drawtext(outpicref, PAD, PAD+16, FONT8, font_colors+3, " LU");
|
||||
for (i = ebur128->meter; i >= -ebur128->meter * 2; i--) {
|
||||
y = lu_to_y(ebur128, i);
|
||||
x = PAD + (i < 10 && i > -10) * 8;
|
||||
ebur128->y_line_ref[y] = i;
|
||||
y -= 4; // -4 to center vertically
|
||||
drawtext(outpicref, x, y + ebur128->graph.y, FONT8, font_colors+3,
|
||||
"%c%d", i < 0 ? '-' : i > 0 ? '+' : ' ', FFABS(i));
|
||||
}
|
||||
|
||||
/* draw graph */
|
||||
ebur128->y_zero_lu = lu_to_y(ebur128, 0);
|
||||
p = outpicref->data[0] + ebur128->graph.y * outpicref->linesize[0]
|
||||
+ ebur128->graph.x * 3;
|
||||
for (y = 0; y < ebur128->graph.h; y++) {
|
||||
const uint8_t *c = get_graph_color(ebur128, INT_MAX, y);
|
||||
|
||||
for (x = 0; x < ebur128->graph.w; x++)
|
||||
memcpy(p + x*3, c, 3);
|
||||
p += outpicref->linesize[0];
|
||||
}
|
||||
|
||||
/* draw fancy rectangles around the graph and the gauge */
|
||||
#define DRAW_RECT(r) do { \
|
||||
drawline(outpicref, r.x, r.y - 1, r.w, 3); \
|
||||
drawline(outpicref, r.x, r.y + r.h, r.w, 3); \
|
||||
drawline(outpicref, r.x - 1, r.y, r.h, outpicref->linesize[0]); \
|
||||
drawline(outpicref, r.x + r.w, r.y, r.h, outpicref->linesize[0]); \
|
||||
} while (0)
|
||||
DRAW_RECT(ebur128->graph);
|
||||
DRAW_RECT(ebur128->gauge);
|
||||
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_audio_input(AVFilterLink *inlink)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
|
||||
/* force 100ms framing in case of metadata injection: the frames must have
|
||||
* a granularity of the window overlap to be accurately exploited */
|
||||
if (ebur128->metadata)
|
||||
inlink->min_samples =
|
||||
inlink->max_samples =
|
||||
inlink->partial_buf_size = inlink->sample_rate / 10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int config_audio_output(AVFilterLink *outlink)
|
||||
{
|
||||
int i;
|
||||
int idx_bitposn = 0;
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
const int nb_channels = av_get_channel_layout_nb_channels(outlink->channel_layout);
|
||||
|
||||
#define BACK_MASK (AV_CH_BACK_LEFT |AV_CH_BACK_CENTER |AV_CH_BACK_RIGHT| \
|
||||
AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_BACK_RIGHT| \
|
||||
AV_CH_SIDE_LEFT |AV_CH_SIDE_RIGHT| \
|
||||
AV_CH_SURROUND_DIRECT_LEFT |AV_CH_SURROUND_DIRECT_RIGHT)
|
||||
|
||||
ebur128->nb_channels = nb_channels;
|
||||
ebur128->ch_weighting = av_calloc(nb_channels, sizeof(*ebur128->ch_weighting));
|
||||
if (!ebur128->ch_weighting)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < nb_channels; i++) {
|
||||
|
||||
/* find the next bit that is set starting from the right */
|
||||
while ((outlink->channel_layout & 1ULL<<idx_bitposn) == 0 && idx_bitposn < 63)
|
||||
idx_bitposn++;
|
||||
|
||||
/* channel weighting */
|
||||
if ((1ULL<<idx_bitposn & AV_CH_LOW_FREQUENCY) ||
|
||||
(1ULL<<idx_bitposn & AV_CH_LOW_FREQUENCY_2)) {
|
||||
ebur128->ch_weighting[i] = 0;
|
||||
} else if (1ULL<<idx_bitposn & BACK_MASK) {
|
||||
ebur128->ch_weighting[i] = 1.41;
|
||||
} else {
|
||||
ebur128->ch_weighting[i] = 1.0;
|
||||
}
|
||||
|
||||
idx_bitposn++;
|
||||
|
||||
if (!ebur128->ch_weighting[i])
|
||||
continue;
|
||||
|
||||
/* bins buffer for the two integration window (400ms and 3s) */
|
||||
ebur128->i400.cache[i] = av_calloc(I400_BINS, sizeof(*ebur128->i400.cache[0]));
|
||||
ebur128->i3000.cache[i] = av_calloc(I3000_BINS, sizeof(*ebur128->i3000.cache[0]));
|
||||
if (!ebur128->i400.cache[i] || !ebur128->i3000.cache[i])
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ENERGY(loudness) (pow(10, ((loudness) + 0.691) / 10.))
|
||||
#define LOUDNESS(energy) (-0.691 + 10 * log10(energy))
|
||||
|
||||
static struct hist_entry *get_histogram(void)
|
||||
{
|
||||
int i;
|
||||
struct hist_entry *h = av_calloc(HIST_SIZE, sizeof(*h));
|
||||
|
||||
if (!h)
|
||||
return NULL;
|
||||
for (i = 0; i < HIST_SIZE; i++) {
|
||||
h[i].loudness = i / (double)HIST_GRAIN + ABS_THRES;
|
||||
h[i].energy = ENERGY(h[i].loudness);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
AVFilterPad pad;
|
||||
|
||||
if (ebur128->loglevel != AV_LOG_INFO &&
|
||||
ebur128->loglevel != AV_LOG_VERBOSE) {
|
||||
if (ebur128->do_video || ebur128->metadata)
|
||||
ebur128->loglevel = AV_LOG_VERBOSE;
|
||||
else
|
||||
ebur128->loglevel = AV_LOG_INFO;
|
||||
}
|
||||
|
||||
// if meter is +9 scale, scale range is from -18 LU to +9 LU (or 3*9)
|
||||
// if meter is +18 scale, scale range is from -36 LU to +18 LU (or 3*18)
|
||||
ebur128->scale_range = 3 * ebur128->meter;
|
||||
|
||||
ebur128->i400.histogram = get_histogram();
|
||||
ebur128->i3000.histogram = get_histogram();
|
||||
if (!ebur128->i400.histogram || !ebur128->i3000.histogram)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
ebur128->integrated_loudness = ABS_THRES;
|
||||
ebur128->loudness_range = 0;
|
||||
|
||||
/* insert output pads */
|
||||
if (ebur128->do_video) {
|
||||
pad = (AVFilterPad){
|
||||
.name = av_strdup("out0"),
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_video_output,
|
||||
};
|
||||
if (!pad.name)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_insert_outpad(ctx, 0, &pad);
|
||||
}
|
||||
pad = (AVFilterPad){
|
||||
.name = av_asprintf("out%d", ebur128->do_video),
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_audio_output,
|
||||
};
|
||||
if (!pad.name)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_insert_outpad(ctx, ebur128->do_video, &pad);
|
||||
|
||||
/* summary */
|
||||
av_log(ctx, AV_LOG_VERBOSE, "EBU +%d scale\n", ebur128->meter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define HIST_POS(power) (int)(((power) - ABS_THRES) * HIST_GRAIN)
|
||||
|
||||
/* loudness and power should be set such as loudness = -0.691 +
|
||||
* 10*log10(power), we just avoid doing that calculus two times */
|
||||
static int gate_update(struct integrator *integ, double power,
|
||||
double loudness, int gate_thres)
|
||||
{
|
||||
int ipower;
|
||||
double relative_threshold;
|
||||
int gate_hist_pos;
|
||||
|
||||
/* update powers histograms by incrementing current power count */
|
||||
ipower = av_clip(HIST_POS(loudness), 0, HIST_SIZE - 1);
|
||||
integ->histogram[ipower].count++;
|
||||
|
||||
/* compute relative threshold and get its position in the histogram */
|
||||
integ->sum_kept_powers += power;
|
||||
integ->nb_kept_powers++;
|
||||
relative_threshold = integ->sum_kept_powers / integ->nb_kept_powers;
|
||||
if (!relative_threshold)
|
||||
relative_threshold = 1e-12;
|
||||
integ->rel_threshold = LOUDNESS(relative_threshold) + gate_thres;
|
||||
gate_hist_pos = av_clip(HIST_POS(integ->rel_threshold), 0, HIST_SIZE - 1);
|
||||
|
||||
return gate_hist_pos;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *insamples)
|
||||
{
|
||||
int i, ch, idx_insample;
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
const int nb_channels = ebur128->nb_channels;
|
||||
const int nb_samples = insamples->nb_samples;
|
||||
const double *samples = (double *)insamples->data[0];
|
||||
AVFrame *pic = ebur128->outpicref;
|
||||
|
||||
for (idx_insample = 0; idx_insample < nb_samples; idx_insample++) {
|
||||
const int bin_id_400 = ebur128->i400.cache_pos;
|
||||
const int bin_id_3000 = ebur128->i3000.cache_pos;
|
||||
|
||||
#define MOVE_TO_NEXT_CACHED_ENTRY(time) do { \
|
||||
ebur128->i##time.cache_pos++; \
|
||||
if (ebur128->i##time.cache_pos == I##time##_BINS) { \
|
||||
ebur128->i##time.filled = 1; \
|
||||
ebur128->i##time.cache_pos = 0; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
MOVE_TO_NEXT_CACHED_ENTRY(400);
|
||||
MOVE_TO_NEXT_CACHED_ENTRY(3000);
|
||||
|
||||
for (ch = 0; ch < nb_channels; ch++) {
|
||||
double bin;
|
||||
|
||||
ebur128->x[ch * 3] = *samples++; // set X[i]
|
||||
|
||||
if (!ebur128->ch_weighting[ch])
|
||||
continue;
|
||||
|
||||
/* Y[i] = X[i]*b0 + X[i-1]*b1 + X[i-2]*b2 - Y[i-1]*a1 - Y[i-2]*a2 */
|
||||
#define FILTER(Y, X, name) do { \
|
||||
double *dst = ebur128->Y + ch*3; \
|
||||
double *src = ebur128->X + ch*3; \
|
||||
dst[2] = dst[1]; \
|
||||
dst[1] = dst[0]; \
|
||||
dst[0] = src[0]*name##_B0 + src[1]*name##_B1 + src[2]*name##_B2 \
|
||||
- dst[1]*name##_A1 - dst[2]*name##_A2; \
|
||||
} while (0)
|
||||
|
||||
// TODO: merge both filters in one?
|
||||
FILTER(y, x, PRE); // apply pre-filter
|
||||
ebur128->x[ch * 3 + 2] = ebur128->x[ch * 3 + 1];
|
||||
ebur128->x[ch * 3 + 1] = ebur128->x[ch * 3 ];
|
||||
FILTER(z, y, RLB); // apply RLB-filter
|
||||
|
||||
bin = ebur128->z[ch * 3] * ebur128->z[ch * 3];
|
||||
|
||||
/* add the new value, and limit the sum to the cache size (400ms or 3s)
|
||||
* by removing the oldest one */
|
||||
ebur128->i400.sum [ch] = ebur128->i400.sum [ch] + bin - ebur128->i400.cache [ch][bin_id_400];
|
||||
ebur128->i3000.sum[ch] = ebur128->i3000.sum[ch] + bin - ebur128->i3000.cache[ch][bin_id_3000];
|
||||
|
||||
/* override old cache entry with the new value */
|
||||
ebur128->i400.cache [ch][bin_id_400 ] = bin;
|
||||
ebur128->i3000.cache[ch][bin_id_3000] = bin;
|
||||
}
|
||||
|
||||
/* For integrated loudness, gating blocks are 400ms long with 75%
|
||||
* overlap (see BS.1770-2 p5), so a re-computation is needed each 100ms
|
||||
* (4800 samples at 48kHz). */
|
||||
if (++ebur128->sample_count == 4800) {
|
||||
double loudness_400, loudness_3000;
|
||||
double power_400 = 1e-12, power_3000 = 1e-12;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
const int64_t pts = insamples->pts +
|
||||
av_rescale_q(idx_insample, (AVRational){ 1, inlink->sample_rate },
|
||||
outlink->time_base);
|
||||
|
||||
ebur128->sample_count = 0;
|
||||
|
||||
#define COMPUTE_LOUDNESS(m, time) do { \
|
||||
if (ebur128->i##time.filled) { \
|
||||
/* weighting sum of the last <time> ms */ \
|
||||
for (ch = 0; ch < nb_channels; ch++) \
|
||||
power_##time += ebur128->ch_weighting[ch] * ebur128->i##time.sum[ch]; \
|
||||
power_##time /= I##time##_BINS; \
|
||||
} \
|
||||
loudness_##time = LOUDNESS(power_##time); \
|
||||
} while (0)
|
||||
|
||||
COMPUTE_LOUDNESS(M, 400);
|
||||
COMPUTE_LOUDNESS(S, 3000);
|
||||
|
||||
/* Integrated loudness */
|
||||
#define I_GATE_THRES -10 // initially defined to -8 LU in the first EBU standard
|
||||
|
||||
if (loudness_400 >= ABS_THRES) {
|
||||
double integrated_sum = 0;
|
||||
int nb_integrated = 0;
|
||||
int gate_hist_pos = gate_update(&ebur128->i400, power_400,
|
||||
loudness_400, I_GATE_THRES);
|
||||
|
||||
/* compute integrated loudness by summing the histogram values
|
||||
* above the relative threshold */
|
||||
for (i = gate_hist_pos; i < HIST_SIZE; i++) {
|
||||
const int nb_v = ebur128->i400.histogram[i].count;
|
||||
nb_integrated += nb_v;
|
||||
integrated_sum += nb_v * ebur128->i400.histogram[i].energy;
|
||||
}
|
||||
if (nb_integrated)
|
||||
ebur128->integrated_loudness = LOUDNESS(integrated_sum / nb_integrated);
|
||||
}
|
||||
|
||||
/* LRA */
|
||||
#define LRA_GATE_THRES -20
|
||||
#define LRA_LOWER_PRC 10
|
||||
#define LRA_HIGHER_PRC 95
|
||||
|
||||
/* XXX: example code in EBU 3342 is ">=" but formula in BS.1770
|
||||
* specs is ">" */
|
||||
if (loudness_3000 >= ABS_THRES) {
|
||||
int nb_powers = 0;
|
||||
int gate_hist_pos = gate_update(&ebur128->i3000, power_3000,
|
||||
loudness_3000, LRA_GATE_THRES);
|
||||
|
||||
for (i = gate_hist_pos; i < HIST_SIZE; i++)
|
||||
nb_powers += ebur128->i3000.histogram[i].count;
|
||||
if (nb_powers) {
|
||||
int n, nb_pow;
|
||||
|
||||
/* get lower loudness to consider */
|
||||
n = 0;
|
||||
nb_pow = LRA_LOWER_PRC * nb_powers / 100. + 0.5;
|
||||
for (i = gate_hist_pos; i < HIST_SIZE; i++) {
|
||||
n += ebur128->i3000.histogram[i].count;
|
||||
if (n >= nb_pow) {
|
||||
ebur128->lra_low = ebur128->i3000.histogram[i].loudness;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* get higher loudness to consider */
|
||||
n = nb_powers;
|
||||
nb_pow = LRA_HIGHER_PRC * nb_powers / 100. + 0.5;
|
||||
for (i = HIST_SIZE - 1; i >= 0; i--) {
|
||||
n -= ebur128->i3000.histogram[i].count;
|
||||
if (n < nb_pow) {
|
||||
ebur128->lra_high = ebur128->i3000.histogram[i].loudness;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: show low & high on the graph?
|
||||
ebur128->loudness_range = ebur128->lra_high - ebur128->lra_low;
|
||||
}
|
||||
}
|
||||
|
||||
#define LOG_FMT "M:%6.1f S:%6.1f I:%6.1f LUFS LRA:%6.1f LU"
|
||||
|
||||
/* push one video frame */
|
||||
if (ebur128->do_video) {
|
||||
int x, y, ret;
|
||||
uint8_t *p;
|
||||
|
||||
const int y_loudness_lu_graph = lu_to_y(ebur128, loudness_3000 + 23);
|
||||
const int y_loudness_lu_gauge = lu_to_y(ebur128, loudness_400 + 23);
|
||||
|
||||
/* draw the graph using the short-term loudness */
|
||||
p = pic->data[0] + ebur128->graph.y*pic->linesize[0] + ebur128->graph.x*3;
|
||||
for (y = 0; y < ebur128->graph.h; y++) {
|
||||
const uint8_t *c = get_graph_color(ebur128, y_loudness_lu_graph, y);
|
||||
|
||||
memmove(p, p + 3, (ebur128->graph.w - 1) * 3);
|
||||
memcpy(p + (ebur128->graph.w - 1) * 3, c, 3);
|
||||
p += pic->linesize[0];
|
||||
}
|
||||
|
||||
/* draw the gauge using the momentary loudness */
|
||||
p = pic->data[0] + ebur128->gauge.y*pic->linesize[0] + ebur128->gauge.x*3;
|
||||
for (y = 0; y < ebur128->gauge.h; y++) {
|
||||
const uint8_t *c = get_graph_color(ebur128, y_loudness_lu_gauge, y);
|
||||
|
||||
for (x = 0; x < ebur128->gauge.w; x++)
|
||||
memcpy(p + x*3, c, 3);
|
||||
p += pic->linesize[0];
|
||||
}
|
||||
|
||||
/* draw textual info */
|
||||
drawtext(pic, PAD, PAD - PAD/2, FONT16, font_colors,
|
||||
LOG_FMT " ", // padding to erase trailing characters
|
||||
loudness_400, loudness_3000,
|
||||
ebur128->integrated_loudness, ebur128->loudness_range);
|
||||
|
||||
/* set pts and push frame */
|
||||
pic->pts = pts;
|
||||
ret = ff_filter_frame(outlink, av_frame_clone(pic));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ebur128->metadata) { /* happens only once per filter_frame call */
|
||||
char metabuf[128];
|
||||
#define SET_META(name, var) do { \
|
||||
snprintf(metabuf, sizeof(metabuf), "%.3f", var); \
|
||||
av_dict_set(&insamples->metadata, "lavfi.r128." name, metabuf, 0); \
|
||||
} while (0)
|
||||
SET_META("M", loudness_400);
|
||||
SET_META("S", loudness_3000);
|
||||
SET_META("I", ebur128->integrated_loudness);
|
||||
SET_META("LRA", ebur128->loudness_range);
|
||||
SET_META("LRA.low", ebur128->lra_low);
|
||||
SET_META("LRA.high", ebur128->lra_high);
|
||||
}
|
||||
|
||||
av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT "\n",
|
||||
av_ts2timestr(pts, &outlink->time_base),
|
||||
loudness_400, loudness_3000,
|
||||
ebur128->integrated_loudness, ebur128->loudness_range);
|
||||
}
|
||||
}
|
||||
|
||||
return ff_filter_frame(ctx->outputs[ebur128->do_video], insamples);
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
AVFilterFormats *formats;
|
||||
AVFilterChannelLayouts *layouts;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_NONE };
|
||||
static const int input_srate[] = {48000, -1}; // ITU-R BS.1770 provides coeff only for 48kHz
|
||||
static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB24, AV_PIX_FMT_NONE };
|
||||
|
||||
/* set optional output video format */
|
||||
if (ebur128->do_video) {
|
||||
formats = ff_make_format_list(pix_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
outlink = ctx->outputs[1];
|
||||
}
|
||||
|
||||
/* set input and output audio formats
|
||||
* Note: ff_set_common_* functions are not used because they affect all the
|
||||
* links, and thus break the video format negotiation */
|
||||
formats = ff_make_format_list(sample_fmts);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_formats);
|
||||
ff_formats_ref(formats, &outlink->in_formats);
|
||||
|
||||
layouts = ff_all_channel_layouts();
|
||||
if (!layouts)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts);
|
||||
ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts);
|
||||
|
||||
formats = ff_make_format_list(input_srate);
|
||||
if (!formats)
|
||||
return AVERROR(ENOMEM);
|
||||
ff_formats_ref(formats, &inlink->out_samplerates);
|
||||
ff_formats_ref(formats, &outlink->in_samplerates);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
int i;
|
||||
EBUR128Context *ebur128 = ctx->priv;
|
||||
|
||||
av_log(ctx, AV_LOG_INFO, "Summary:\n\n"
|
||||
" Integrated loudness:\n"
|
||||
" I: %5.1f LUFS\n"
|
||||
" Threshold: %5.1f LUFS\n\n"
|
||||
" Loudness range:\n"
|
||||
" LRA: %5.1f LU\n"
|
||||
" Threshold: %5.1f LUFS\n"
|
||||
" LRA low: %5.1f LUFS\n"
|
||||
" LRA high: %5.1f LUFS\n",
|
||||
ebur128->integrated_loudness, ebur128->i400.rel_threshold,
|
||||
ebur128->loudness_range, ebur128->i3000.rel_threshold,
|
||||
ebur128->lra_low, ebur128->lra_high);
|
||||
|
||||
av_freep(&ebur128->y_line_ref);
|
||||
av_freep(&ebur128->ch_weighting);
|
||||
av_freep(&ebur128->i400.histogram);
|
||||
av_freep(&ebur128->i3000.histogram);
|
||||
for (i = 0; i < ebur128->nb_channels; i++) {
|
||||
av_freep(&ebur128->i400.cache[i]);
|
||||
av_freep(&ebur128->i3000.cache[i]);
|
||||
}
|
||||
for (i = 0; i < ctx->nb_outputs; i++)
|
||||
av_freep(&ctx->output_pads[i].name);
|
||||
av_frame_free(&ebur128->outpicref);
|
||||
}
|
||||
|
||||
static const AVFilterPad ebur128_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
.config_props = config_audio_input,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_ebur128 = {
|
||||
.name = "ebur128",
|
||||
.description = NULL_IF_CONFIG_SMALL("EBU R128 scanner."),
|
||||
.priv_size = sizeof(EBUR128Context),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.inputs = ebur128_inputs,
|
||||
.outputs = NULL,
|
||||
.priv_class = &ebur128_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* audio and video interleaver
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "bufferqueue.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
int nb_inputs;
|
||||
struct FFBufQueue *queues;
|
||||
} InterleaveContext;
|
||||
|
||||
#define OFFSET(x) offsetof(InterleaveContext, x)
|
||||
|
||||
#define DEFINE_OPTIONS(filt_name, flags_) \
|
||||
static const AVOption filt_name##_options[] = { \
|
||||
{ "nb_inputs", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64 = 2}, 1, INT_MAX, .flags = flags_ }, \
|
||||
{ "n", "set number of inputs", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64 = 2}, 1, INT_MAX, .flags = flags_ }, \
|
||||
{ NULL } \
|
||||
}
|
||||
|
||||
inline static int push_frame(AVFilterContext *ctx)
|
||||
{
|
||||
InterleaveContext *s = ctx->priv;
|
||||
AVFrame *frame;
|
||||
int i, queue_idx = -1;
|
||||
int64_t pts_min = INT64_MAX;
|
||||
|
||||
/* look for oldest frame */
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
struct FFBufQueue *q = &s->queues[i];
|
||||
|
||||
if (!q->available && !ctx->inputs[i]->closed)
|
||||
return 0;
|
||||
if (q->available) {
|
||||
frame = ff_bufqueue_peek(q, 0);
|
||||
if (frame->pts < pts_min) {
|
||||
pts_min = frame->pts;
|
||||
queue_idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* all inputs are closed */
|
||||
if (queue_idx < 0)
|
||||
return AVERROR_EOF;
|
||||
|
||||
frame = ff_bufqueue_get(&s->queues[queue_idx]);
|
||||
av_log(ctx, AV_LOG_DEBUG, "queue:%d -> frame time:%f\n",
|
||||
queue_idx, frame->pts * av_q2d(AV_TIME_BASE_Q));
|
||||
return ff_filter_frame(ctx->outputs[0], frame);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
InterleaveContext *s = ctx->priv;
|
||||
unsigned in_no = FF_INLINK_IDX(inlink);
|
||||
|
||||
if (frame->pts == AV_NOPTS_VALUE) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"NOPTS value for input frame cannot be accepted, frame discarded\n");
|
||||
av_frame_free(&frame);
|
||||
return AVERROR_INVALIDDATA;
|
||||
}
|
||||
|
||||
/* queue frame */
|
||||
frame->pts = av_rescale_q(frame->pts, inlink->time_base, AV_TIME_BASE_Q);
|
||||
av_log(ctx, AV_LOG_DEBUG, "frame pts:%f -> queue idx:%d available:%d\n",
|
||||
frame->pts * av_q2d(AV_TIME_BASE_Q), in_no, s->queues[in_no].available);
|
||||
ff_bufqueue_add(ctx, &s->queues[in_no], frame);
|
||||
|
||||
return push_frame(ctx);
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
InterleaveContext *s = ctx->priv;
|
||||
const AVFilterPad *outpad = &ctx->filter->outputs[0];
|
||||
int i;
|
||||
|
||||
s->queues = av_calloc(s->nb_inputs, sizeof(s->queues[0]));
|
||||
if (!s->queues)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
for (i = 0; i < s->nb_inputs; i++) {
|
||||
AVFilterPad inpad = { 0 };
|
||||
|
||||
inpad.name = av_asprintf("input%d", i);
|
||||
if (!inpad.name)
|
||||
return AVERROR(ENOMEM);
|
||||
inpad.type = outpad->type;
|
||||
inpad.filter_frame = filter_frame;
|
||||
|
||||
switch (outpad->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
inpad.get_video_buffer = ff_null_get_video_buffer; break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
inpad.get_audio_buffer = ff_null_get_audio_buffer; break;
|
||||
default:
|
||||
av_assert0(0);
|
||||
}
|
||||
ff_insert_inpad(ctx, i, &inpad);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
InterleaveContext *s = ctx->priv;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
ff_bufqueue_discard_all(&s->queues[i]);
|
||||
av_freep(&s->queues[i]);
|
||||
av_freep(&ctx->input_pads[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
static int config_output(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
AVFilterLink *inlink0 = ctx->inputs[0];
|
||||
int i;
|
||||
|
||||
if (outlink->type == AVMEDIA_TYPE_VIDEO) {
|
||||
outlink->time_base = AV_TIME_BASE_Q;
|
||||
outlink->w = inlink0->w;
|
||||
outlink->h = inlink0->h;
|
||||
outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio;
|
||||
outlink->format = inlink0->format;
|
||||
outlink->frame_rate = (AVRational) {1, 0};
|
||||
for (i = 1; i < ctx->nb_inputs; i++) {
|
||||
AVFilterLink *inlink = ctx->inputs[i];
|
||||
|
||||
if (outlink->w != inlink->w ||
|
||||
outlink->h != inlink->h ||
|
||||
outlink->sample_aspect_ratio.num != inlink->sample_aspect_ratio.num ||
|
||||
outlink->sample_aspect_ratio.den != inlink->sample_aspect_ratio.den) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Parameters for input link %s "
|
||||
"(size %dx%d, SAR %d:%d) do not match the corresponding "
|
||||
"output link parameters (%dx%d, SAR %d:%d)\n",
|
||||
ctx->input_pads[i].name, inlink->w, inlink->h,
|
||||
inlink->sample_aspect_ratio.num,
|
||||
inlink->sample_aspect_ratio.den,
|
||||
outlink->w, outlink->h,
|
||||
outlink->sample_aspect_ratio.num,
|
||||
outlink->sample_aspect_ratio.den);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
InterleaveContext *s = ctx->priv;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ctx->nb_inputs; i++) {
|
||||
if (!s->queues[i].available && !ctx->inputs[i]->closed) {
|
||||
ret = ff_request_frame(ctx->inputs[i]);
|
||||
if (ret != AVERROR_EOF)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return push_frame(ctx);
|
||||
}
|
||||
|
||||
#if CONFIG_INTERLEAVE_FILTER
|
||||
|
||||
DEFINE_OPTIONS(interleave, AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
|
||||
AVFILTER_DEFINE_CLASS(interleave);
|
||||
|
||||
static const AVFilterPad interleave_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_interleave = {
|
||||
.name = "interleave",
|
||||
.description = NULL_IF_CONFIG_SMALL("Temporally interleave video inputs."),
|
||||
.priv_size = sizeof(InterleaveContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.outputs = interleave_outputs,
|
||||
.priv_class = &interleave_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_AINTERLEAVE_FILTER
|
||||
|
||||
DEFINE_OPTIONS(ainterleave, AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
|
||||
AVFILTER_DEFINE_CLASS(ainterleave);
|
||||
|
||||
static const AVFilterPad ainterleave_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_ainterleave = {
|
||||
.name = "ainterleave",
|
||||
.description = NULL_IF_CONFIG_SMALL("Temporally interleave audio inputs."),
|
||||
.priv_size = sizeof(InterleaveContext),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.outputs = ainterleave_outputs,
|
||||
.priv_class = &ainterleave_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_INPUTS,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Clément Bœsch
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/lfg.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/random_seed.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
|
||||
enum mode {
|
||||
MODE_NONE,
|
||||
MODE_RO,
|
||||
MODE_RW,
|
||||
MODE_TOGGLE,
|
||||
MODE_RANDOM,
|
||||
NB_MODES
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
AVLFG lfg;
|
||||
int64_t random_seed;
|
||||
enum mode mode;
|
||||
} PermsContext;
|
||||
|
||||
#define OFFSET(x) offsetof(PermsContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
|
||||
|
||||
static const AVOption options[] = {
|
||||
{ "mode", "select permissions mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = MODE_NONE}, MODE_NONE, NB_MODES-1, FLAGS, "mode" },
|
||||
{ "none", "do nothing", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_NONE}, INT_MIN, INT_MAX, FLAGS, "mode" },
|
||||
{ "ro", "set all output frames read-only", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_RO}, INT_MIN, INT_MAX, FLAGS, "mode" },
|
||||
{ "rw", "set all output frames writable", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_RW}, INT_MIN, INT_MAX, FLAGS, "mode" },
|
||||
{ "toggle", "switch permissions", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_TOGGLE}, INT_MIN, INT_MAX, FLAGS, "mode" },
|
||||
{ "random", "set permissions randomly", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_RANDOM}, INT_MIN, INT_MAX, FLAGS, "mode" },
|
||||
{ "seed", "set the seed for the random mode", OFFSET(random_seed), AV_OPT_TYPE_INT64, {.i64 = -1}, -1, UINT32_MAX, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
PermsContext *perms = ctx->priv;
|
||||
|
||||
if (perms->mode == MODE_RANDOM) {
|
||||
uint32_t seed;
|
||||
|
||||
if (perms->random_seed == -1)
|
||||
perms->random_seed = av_get_random_seed();
|
||||
seed = perms->random_seed;
|
||||
av_log(ctx, AV_LOG_INFO, "random seed: 0x%08x\n", seed);
|
||||
av_lfg_init(&perms->lfg, seed);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum perm { RO, RW };
|
||||
static const char *perm_str[2] = { "RO", "RW" };
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
int ret;
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
PermsContext *perms = ctx->priv;
|
||||
AVFrame *out = frame;
|
||||
enum perm in_perm = av_frame_is_writable(frame) ? RW : RO;
|
||||
enum perm out_perm;
|
||||
|
||||
switch (perms->mode) {
|
||||
case MODE_TOGGLE: out_perm = in_perm == RO ? RW : RO; break;
|
||||
case MODE_RANDOM: out_perm = av_lfg_get(&perms->lfg) & 1 ? RW : RO; break;
|
||||
case MODE_RO: out_perm = RO; break;
|
||||
case MODE_RW: out_perm = RW; break;
|
||||
default: out_perm = in_perm; break;
|
||||
}
|
||||
|
||||
av_log(ctx, AV_LOG_VERBOSE, "%s -> %s%s\n",
|
||||
perm_str[in_perm], perm_str[out_perm],
|
||||
in_perm == out_perm ? " (no-op)" : "");
|
||||
|
||||
if (in_perm == RO && out_perm == RW) {
|
||||
if ((ret = av_frame_make_writable(frame)) < 0)
|
||||
return ret;
|
||||
} else if (in_perm == RW && out_perm == RO) {
|
||||
out = av_frame_clone(frame);
|
||||
if (!out)
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
ret = ff_filter_frame(ctx->outputs[0], out);
|
||||
|
||||
if (in_perm == RW && out_perm == RO)
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if CONFIG_APERMS_FILTER
|
||||
|
||||
#define aperms_options options
|
||||
AVFILTER_DEFINE_CLASS(aperms);
|
||||
|
||||
static const AVFilterPad aperms_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad aperms_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aperms = {
|
||||
.name = "aperms",
|
||||
.description = NULL_IF_CONFIG_SMALL("Set permissions for the output audio frame."),
|
||||
.init = init,
|
||||
.priv_size = sizeof(PermsContext),
|
||||
.inputs = aperms_inputs,
|
||||
.outputs = aperms_outputs,
|
||||
.priv_class = &aperms_class,
|
||||
};
|
||||
#endif /* CONFIG_APERMS_FILTER */
|
||||
|
||||
#if CONFIG_PERMS_FILTER
|
||||
|
||||
#define perms_options options
|
||||
AVFILTER_DEFINE_CLASS(perms);
|
||||
|
||||
static const AVFilterPad perms_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad perms_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_perms = {
|
||||
.name = "perms",
|
||||
.description = NULL_IF_CONFIG_SMALL("Set permissions for the output video frame."),
|
||||
.init = init,
|
||||
.priv_size = sizeof(PermsContext),
|
||||
.inputs = perms_inputs,
|
||||
.outputs = perms_outputs,
|
||||
.priv_class = &perms_class,
|
||||
};
|
||||
#endif /* CONFIG_PERMS_FILTER */
|
||||
@@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright (c) 2011 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* filter for selecting which frame passes in the filterchain
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/fifo.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "audio.h"
|
||||
#include "formats.h"
|
||||
#include "internal.h"
|
||||
#include "video.h"
|
||||
|
||||
#if CONFIG_AVCODEC
|
||||
#include "libavcodec/dsputil.h"
|
||||
#endif
|
||||
|
||||
static const char *const var_names[] = {
|
||||
"TB", ///< timebase
|
||||
|
||||
"pts", ///< original pts in the file of the frame
|
||||
"start_pts", ///< first PTS in the stream, expressed in TB units
|
||||
"prev_pts", ///< previous frame PTS
|
||||
"prev_selected_pts", ///< previous selected frame PTS
|
||||
|
||||
"t", ///< first PTS in seconds
|
||||
"start_t", ///< first PTS in the stream, expressed in seconds
|
||||
"prev_t", ///< previous frame time
|
||||
"prev_selected_t", ///< previously selected time
|
||||
|
||||
"pict_type", ///< the type of picture in the movie
|
||||
"I",
|
||||
"P",
|
||||
"B",
|
||||
"S",
|
||||
"SI",
|
||||
"SP",
|
||||
"BI",
|
||||
"PICT_TYPE_I",
|
||||
"PICT_TYPE_P",
|
||||
"PICT_TYPE_B",
|
||||
"PICT_TYPE_S",
|
||||
"PICT_TYPE_SI",
|
||||
"PICT_TYPE_SP",
|
||||
"PICT_TYPE_BI",
|
||||
|
||||
"interlace_type", ///< the frame interlace type
|
||||
"PROGRESSIVE",
|
||||
"TOPFIRST",
|
||||
"BOTTOMFIRST",
|
||||
|
||||
"consumed_samples_n",///< number of samples consumed by the filter (only audio)
|
||||
"samples_n", ///< number of samples in the current frame (only audio)
|
||||
"sample_rate", ///< sample rate (only audio)
|
||||
|
||||
"n", ///< frame number (starting from zero)
|
||||
"selected_n", ///< selected frame number (starting from zero)
|
||||
"prev_selected_n", ///< number of the last selected frame
|
||||
|
||||
"key", ///< tell if the frame is a key frame
|
||||
"pos", ///< original position in the file of the frame
|
||||
|
||||
"scene",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_TB,
|
||||
|
||||
VAR_PTS,
|
||||
VAR_START_PTS,
|
||||
VAR_PREV_PTS,
|
||||
VAR_PREV_SELECTED_PTS,
|
||||
|
||||
VAR_T,
|
||||
VAR_START_T,
|
||||
VAR_PREV_T,
|
||||
VAR_PREV_SELECTED_T,
|
||||
|
||||
VAR_PICT_TYPE,
|
||||
VAR_I,
|
||||
VAR_P,
|
||||
VAR_B,
|
||||
VAR_S,
|
||||
VAR_SI,
|
||||
VAR_SP,
|
||||
VAR_BI,
|
||||
VAR_PICT_TYPE_I,
|
||||
VAR_PICT_TYPE_P,
|
||||
VAR_PICT_TYPE_B,
|
||||
VAR_PICT_TYPE_S,
|
||||
VAR_PICT_TYPE_SI,
|
||||
VAR_PICT_TYPE_SP,
|
||||
VAR_PICT_TYPE_BI,
|
||||
|
||||
VAR_INTERLACE_TYPE,
|
||||
VAR_INTERLACE_TYPE_P,
|
||||
VAR_INTERLACE_TYPE_T,
|
||||
VAR_INTERLACE_TYPE_B,
|
||||
|
||||
VAR_CONSUMED_SAMPLES_N,
|
||||
VAR_SAMPLES_N,
|
||||
VAR_SAMPLE_RATE,
|
||||
|
||||
VAR_N,
|
||||
VAR_SELECTED_N,
|
||||
VAR_PREV_SELECTED_N,
|
||||
|
||||
VAR_KEY,
|
||||
VAR_POS,
|
||||
|
||||
VAR_SCENE,
|
||||
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *expr_str;
|
||||
AVExpr *expr;
|
||||
double var_values[VAR_VARS_NB];
|
||||
int do_scene_detect; ///< 1 if the expression requires scene detection variables, 0 otherwise
|
||||
#if CONFIG_AVCODEC
|
||||
AVCodecContext *avctx; ///< codec context required for the DSPContext (scene detect only)
|
||||
DSPContext c; ///< context providing optimized SAD methods (scene detect only)
|
||||
double prev_mafd; ///< previous MAFD (scene detect only)
|
||||
#endif
|
||||
AVFrame *prev_picref; ///< previous frame (scene detect only)
|
||||
double select;
|
||||
int select_out; ///< mark the selected output pad index
|
||||
int nb_outputs;
|
||||
} SelectContext;
|
||||
|
||||
#define OFFSET(x) offsetof(SelectContext, x)
|
||||
#define DEFINE_OPTIONS(filt_name, FLAGS) \
|
||||
static const AVOption filt_name##_options[] = { \
|
||||
{ "expr", "set an expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags=FLAGS }, \
|
||||
{ "e", "set an expression to use for selecting frames", OFFSET(expr_str), AV_OPT_TYPE_STRING, { .str = "1" }, .flags=FLAGS }, \
|
||||
{ "outputs", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, .flags=FLAGS }, \
|
||||
{ "n", "set the number of outputs", OFFSET(nb_outputs), AV_OPT_TYPE_INT, {.i64 = 1}, 1, INT_MAX, .flags=FLAGS }, \
|
||||
{ NULL } \
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink);
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
int i, ret;
|
||||
|
||||
if ((ret = av_expr_parse(&select->expr, select->expr_str,
|
||||
var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Error while parsing expression '%s'\n",
|
||||
select->expr_str);
|
||||
return ret;
|
||||
}
|
||||
select->do_scene_detect = !!strstr(select->expr_str, "scene");
|
||||
|
||||
for (i = 0; i < select->nb_outputs; i++) {
|
||||
AVFilterPad pad = { 0 };
|
||||
|
||||
pad.name = av_asprintf("output%d", i);
|
||||
if (!pad.name)
|
||||
return AVERROR(ENOMEM);
|
||||
pad.type = ctx->filter->inputs[0].type;
|
||||
pad.request_frame = request_frame;
|
||||
ff_insert_outpad(ctx, i, &pad);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define INTERLACE_TYPE_P 0
|
||||
#define INTERLACE_TYPE_T 1
|
||||
#define INTERLACE_TYPE_B 2
|
||||
|
||||
static int config_input(AVFilterLink *inlink)
|
||||
{
|
||||
SelectContext *select = inlink->dst->priv;
|
||||
|
||||
select->var_values[VAR_N] = 0.0;
|
||||
select->var_values[VAR_SELECTED_N] = 0.0;
|
||||
|
||||
select->var_values[VAR_TB] = av_q2d(inlink->time_base);
|
||||
|
||||
select->var_values[VAR_PREV_PTS] = NAN;
|
||||
select->var_values[VAR_PREV_SELECTED_PTS] = NAN;
|
||||
select->var_values[VAR_PREV_SELECTED_T] = NAN;
|
||||
select->var_values[VAR_PREV_T] = NAN;
|
||||
select->var_values[VAR_START_PTS] = NAN;
|
||||
select->var_values[VAR_START_T] = NAN;
|
||||
|
||||
select->var_values[VAR_I] = AV_PICTURE_TYPE_I;
|
||||
select->var_values[VAR_P] = AV_PICTURE_TYPE_P;
|
||||
select->var_values[VAR_B] = AV_PICTURE_TYPE_B;
|
||||
select->var_values[VAR_SI] = AV_PICTURE_TYPE_SI;
|
||||
select->var_values[VAR_SP] = AV_PICTURE_TYPE_SP;
|
||||
select->var_values[VAR_BI] = AV_PICTURE_TYPE_BI;
|
||||
select->var_values[VAR_PICT_TYPE_I] = AV_PICTURE_TYPE_I;
|
||||
select->var_values[VAR_PICT_TYPE_P] = AV_PICTURE_TYPE_P;
|
||||
select->var_values[VAR_PICT_TYPE_B] = AV_PICTURE_TYPE_B;
|
||||
select->var_values[VAR_PICT_TYPE_SI] = AV_PICTURE_TYPE_SI;
|
||||
select->var_values[VAR_PICT_TYPE_SP] = AV_PICTURE_TYPE_SP;
|
||||
select->var_values[VAR_PICT_TYPE_BI] = AV_PICTURE_TYPE_BI;
|
||||
|
||||
select->var_values[VAR_INTERLACE_TYPE_P] = INTERLACE_TYPE_P;
|
||||
select->var_values[VAR_INTERLACE_TYPE_T] = INTERLACE_TYPE_T;
|
||||
select->var_values[VAR_INTERLACE_TYPE_B] = INTERLACE_TYPE_B;
|
||||
|
||||
select->var_values[VAR_PICT_TYPE] = NAN;
|
||||
select->var_values[VAR_INTERLACE_TYPE] = NAN;
|
||||
select->var_values[VAR_SCENE] = NAN;
|
||||
select->var_values[VAR_CONSUMED_SAMPLES_N] = NAN;
|
||||
select->var_values[VAR_SAMPLES_N] = NAN;
|
||||
|
||||
select->var_values[VAR_SAMPLE_RATE] =
|
||||
inlink->type == AVMEDIA_TYPE_AUDIO ? inlink->sample_rate : NAN;
|
||||
|
||||
#if CONFIG_AVCODEC
|
||||
if (select->do_scene_detect) {
|
||||
select->avctx = avcodec_alloc_context3(NULL);
|
||||
if (!select->avctx)
|
||||
return AVERROR(ENOMEM);
|
||||
avpriv_dsputil_init(&select->c, select->avctx);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_AVCODEC
|
||||
static double get_scene_score(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
double ret = 0;
|
||||
SelectContext *select = ctx->priv;
|
||||
AVFrame *prev_picref = select->prev_picref;
|
||||
|
||||
if (prev_picref &&
|
||||
frame->height == prev_picref->height &&
|
||||
frame->width == prev_picref->width &&
|
||||
frame->linesize[0] == prev_picref->linesize[0]) {
|
||||
int x, y, nb_sad = 0;
|
||||
int64_t sad = 0;
|
||||
double mafd, diff;
|
||||
uint8_t *p1 = frame->data[0];
|
||||
uint8_t *p2 = prev_picref->data[0];
|
||||
const int linesize = frame->linesize[0];
|
||||
|
||||
for (y = 0; y < frame->height - 8; y += 8) {
|
||||
for (x = 0; x < frame->width*3 - 8; x += 8) {
|
||||
sad += select->c.sad[1](select, p1 + x, p2 + x,
|
||||
linesize, 8);
|
||||
nb_sad += 8 * 8;
|
||||
}
|
||||
p1 += 8 * linesize;
|
||||
p2 += 8 * linesize;
|
||||
}
|
||||
emms_c();
|
||||
mafd = nb_sad ? sad / nb_sad : 0;
|
||||
diff = fabs(mafd - select->prev_mafd);
|
||||
ret = av_clipf(FFMIN(mafd, diff) / 100., 0, 1);
|
||||
select->prev_mafd = mafd;
|
||||
av_frame_free(&prev_picref);
|
||||
}
|
||||
select->prev_picref = av_frame_clone(frame);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define D2TS(d) (isnan(d) ? AV_NOPTS_VALUE : (int64_t)(d))
|
||||
#define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
|
||||
|
||||
static void select_frame(AVFilterContext *ctx, AVFrame *frame)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
double res;
|
||||
|
||||
if (isnan(select->var_values[VAR_START_PTS]))
|
||||
select->var_values[VAR_START_PTS] = TS2D(frame->pts);
|
||||
if (isnan(select->var_values[VAR_START_T]))
|
||||
select->var_values[VAR_START_T] = TS2D(frame->pts) * av_q2d(inlink->time_base);
|
||||
|
||||
select->var_values[VAR_N ] = inlink->frame_count;
|
||||
select->var_values[VAR_PTS] = TS2D(frame->pts);
|
||||
select->var_values[VAR_T ] = TS2D(frame->pts) * av_q2d(inlink->time_base);
|
||||
select->var_values[VAR_POS] = av_frame_get_pkt_pos(frame) == -1 ? NAN : av_frame_get_pkt_pos(frame);
|
||||
|
||||
switch (inlink->type) {
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
select->var_values[VAR_SAMPLES_N] = frame->nb_samples;
|
||||
break;
|
||||
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
select->var_values[VAR_INTERLACE_TYPE] =
|
||||
!frame->interlaced_frame ? INTERLACE_TYPE_P :
|
||||
frame->top_field_first ? INTERLACE_TYPE_T : INTERLACE_TYPE_B;
|
||||
select->var_values[VAR_PICT_TYPE] = frame->pict_type;
|
||||
#if CONFIG_AVCODEC
|
||||
if (select->do_scene_detect) {
|
||||
char buf[32];
|
||||
select->var_values[VAR_SCENE] = get_scene_score(ctx, frame);
|
||||
// TODO: document metadata
|
||||
snprintf(buf, sizeof(buf), "%f", select->var_values[VAR_SCENE]);
|
||||
av_dict_set(avpriv_frame_get_metadatap(frame), "lavfi.scene_score", buf, 0);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
select->select = res = av_expr_eval(select->expr, select->var_values, NULL);
|
||||
av_log(inlink->dst, AV_LOG_DEBUG,
|
||||
"n:%f pts:%f t:%f key:%d",
|
||||
select->var_values[VAR_N],
|
||||
select->var_values[VAR_PTS],
|
||||
select->var_values[VAR_T],
|
||||
(int)select->var_values[VAR_KEY]);
|
||||
|
||||
switch (inlink->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
av_log(inlink->dst, AV_LOG_DEBUG, " interlace_type:%c pict_type:%c scene:%f",
|
||||
select->var_values[VAR_INTERLACE_TYPE] == INTERLACE_TYPE_P ? 'P' :
|
||||
select->var_values[VAR_INTERLACE_TYPE] == INTERLACE_TYPE_T ? 'T' :
|
||||
select->var_values[VAR_INTERLACE_TYPE] == INTERLACE_TYPE_B ? 'B' : '?',
|
||||
av_get_picture_type_char(select->var_values[VAR_PICT_TYPE]),
|
||||
select->var_values[VAR_SCENE]);
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
av_log(inlink->dst, AV_LOG_DEBUG, " samples_n:%d consumed_samples_n:%d",
|
||||
(int)select->var_values[VAR_SAMPLES_N],
|
||||
(int)select->var_values[VAR_CONSUMED_SAMPLES_N]);
|
||||
break;
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
select->select_out = -1; /* drop */
|
||||
} else if (isnan(res) || res < 0) {
|
||||
select->select_out = 0; /* first output */
|
||||
} else {
|
||||
select->select_out = FFMIN(ceilf(res)-1, select->nb_outputs-1); /* other outputs */
|
||||
}
|
||||
|
||||
av_log(inlink->dst, AV_LOG_DEBUG, " -> select:%f select_out:%d\n", res, select->select_out);
|
||||
|
||||
if (res) {
|
||||
select->var_values[VAR_PREV_SELECTED_N] = select->var_values[VAR_N];
|
||||
select->var_values[VAR_PREV_SELECTED_PTS] = select->var_values[VAR_PTS];
|
||||
select->var_values[VAR_PREV_SELECTED_T] = select->var_values[VAR_T];
|
||||
select->var_values[VAR_SELECTED_N] += 1.0;
|
||||
if (inlink->type == AVMEDIA_TYPE_AUDIO)
|
||||
select->var_values[VAR_CONSUMED_SAMPLES_N] += frame->nb_samples;
|
||||
}
|
||||
|
||||
select->var_values[VAR_PREV_PTS] = select->var_values[VAR_PTS];
|
||||
select->var_values[VAR_PREV_T] = select->var_values[VAR_T];
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
SelectContext *select = ctx->priv;
|
||||
|
||||
select_frame(ctx, frame);
|
||||
if (select->select)
|
||||
return ff_filter_frame(ctx->outputs[select->select_out], frame);
|
||||
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
SelectContext *select = ctx->priv;
|
||||
AVFilterLink *inlink = outlink->src->inputs[0];
|
||||
int out_no = FF_OUTLINK_IDX(outlink);
|
||||
|
||||
do {
|
||||
int ret = ff_request_frame(inlink);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} while (select->select_out != out_no);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
int i;
|
||||
|
||||
av_expr_free(select->expr);
|
||||
select->expr = NULL;
|
||||
|
||||
for (i = 0; i < ctx->nb_outputs; i++)
|
||||
av_freep(&ctx->output_pads[i].name);
|
||||
|
||||
#if CONFIG_AVCODEC
|
||||
if (select->do_scene_detect) {
|
||||
av_frame_free(&select->prev_picref);
|
||||
if (select->avctx) {
|
||||
avcodec_close(select->avctx);
|
||||
av_freep(&select->avctx);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static int query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
|
||||
if (!select->do_scene_detect) {
|
||||
return ff_default_query_formats(ctx);
|
||||
} else {
|
||||
static const enum AVPixelFormat pix_fmts[] = {
|
||||
AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24,
|
||||
AV_PIX_FMT_NONE
|
||||
};
|
||||
ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if CONFIG_ASELECT_FILTER
|
||||
|
||||
DEFINE_OPTIONS(aselect, AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
|
||||
AVFILTER_DEFINE_CLASS(aselect);
|
||||
|
||||
static av_cold int aselect_init(AVFilterContext *ctx)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
int ret;
|
||||
|
||||
if ((ret = init(ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
if (select->do_scene_detect) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Scene detection is ignored in aselect filter\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_af_aselect_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_input,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_aselect = {
|
||||
.name = "aselect",
|
||||
.description = NULL_IF_CONFIG_SMALL("Select audio frames to pass in output."),
|
||||
.init = aselect_init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(SelectContext),
|
||||
.inputs = avfilter_af_aselect_inputs,
|
||||
.priv_class = &aselect_class,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
|
||||
};
|
||||
#endif /* CONFIG_ASELECT_FILTER */
|
||||
|
||||
#if CONFIG_SELECT_FILTER
|
||||
|
||||
DEFINE_OPTIONS(select, AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM);
|
||||
AVFILTER_DEFINE_CLASS(select);
|
||||
|
||||
static av_cold int select_init(AVFilterContext *ctx)
|
||||
{
|
||||
SelectContext *select = ctx->priv;
|
||||
int ret;
|
||||
|
||||
if ((ret = init(ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
if (select->do_scene_detect && !CONFIG_AVCODEC) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Scene detection is not available without libavcodec.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_vf_select_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_input,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_select = {
|
||||
.name = "select",
|
||||
.description = NULL_IF_CONFIG_SMALL("Select video frames to pass in output."),
|
||||
.init = select_init,
|
||||
.uninit = uninit,
|
||||
.query_formats = query_formats,
|
||||
.priv_size = sizeof(SelectContext),
|
||||
.priv_class = &select_class,
|
||||
.inputs = avfilter_vf_select_inputs,
|
||||
.flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
|
||||
};
|
||||
#endif /* CONFIG_SELECT_FILTER */
|
||||
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* Copyright (c) 2012 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* send commands filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/file.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "avfiltergraph.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
|
||||
#define COMMAND_FLAG_ENTER 1
|
||||
#define COMMAND_FLAG_LEAVE 2
|
||||
|
||||
static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
|
||||
{
|
||||
static const char * const flag_strings[] = { "enter", "leave" };
|
||||
int i, is_first = 1;
|
||||
|
||||
av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
|
||||
for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
|
||||
if (flags & 1<<i) {
|
||||
if (!is_first)
|
||||
av_bprint_chars(pbuf, '+', 1);
|
||||
av_bprintf(pbuf, "%s", flag_strings[i]);
|
||||
is_first = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pbuf->str;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int flags;
|
||||
char *target, *command, *arg;
|
||||
int index;
|
||||
} Command;
|
||||
|
||||
typedef struct {
|
||||
int64_t start_ts; ///< start timestamp expressed as microseconds units
|
||||
int64_t end_ts; ///< end timestamp expressed as microseconds units
|
||||
int index; ///< unique index for these interval commands
|
||||
Command *commands;
|
||||
int nb_commands;
|
||||
int enabled; ///< current time detected inside this interval
|
||||
} Interval;
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
Interval *intervals;
|
||||
int nb_intervals;
|
||||
|
||||
char *commands_filename;
|
||||
char *commands_str;
|
||||
} SendCmdContext;
|
||||
|
||||
#define OFFSET(x) offsetof(SendCmdContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
|
||||
static const AVOption options[] = {
|
||||
{ "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
#define SPACES " \f\t\n\r"
|
||||
|
||||
static void skip_comments(const char **buf)
|
||||
{
|
||||
while (**buf) {
|
||||
/* skip leading spaces */
|
||||
*buf += strspn(*buf, SPACES);
|
||||
if (**buf != '#')
|
||||
break;
|
||||
|
||||
(*buf)++;
|
||||
|
||||
/* skip comment until the end of line */
|
||||
*buf += strcspn(*buf, "\n");
|
||||
if (**buf)
|
||||
(*buf)++;
|
||||
}
|
||||
}
|
||||
|
||||
#define COMMAND_DELIMS " \f\t\n\r,;"
|
||||
|
||||
static int parse_command(Command *cmd, int cmd_count, int interval_count,
|
||||
const char **buf, void *log_ctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
memset(cmd, 0, sizeof(Command));
|
||||
cmd->index = cmd_count;
|
||||
|
||||
/* format: [FLAGS] target command arg */
|
||||
*buf += strspn(*buf, SPACES);
|
||||
|
||||
/* parse flags */
|
||||
if (**buf == '[') {
|
||||
(*buf)++; /* skip "[" */
|
||||
|
||||
while (**buf) {
|
||||
int len = strcspn(*buf, "|+]");
|
||||
|
||||
if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
|
||||
else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
|
||||
else {
|
||||
char flag_buf[64];
|
||||
av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Unknown flag '%s' in interval #%d, command #%d\n",
|
||||
flag_buf, interval_count, cmd_count);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
*buf += len;
|
||||
if (**buf == ']')
|
||||
break;
|
||||
if (!strspn(*buf, "+|")) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid flags char '%c' in interval #%d, command #%d\n",
|
||||
**buf, interval_count, cmd_count);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (**buf)
|
||||
(*buf)++;
|
||||
}
|
||||
|
||||
if (**buf != ']') {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Missing flag terminator or extraneous data found at the end of flags "
|
||||
"in interval #%d, command #%d\n", interval_count, cmd_count);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
(*buf)++; /* skip "]" */
|
||||
} else {
|
||||
cmd->flags = COMMAND_FLAG_ENTER;
|
||||
}
|
||||
|
||||
*buf += strspn(*buf, SPACES);
|
||||
cmd->target = av_get_token(buf, COMMAND_DELIMS);
|
||||
if (!cmd->target || !cmd->target[0]) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No target specified in interval #%d, command #%d\n",
|
||||
interval_count, cmd_count);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
*buf += strspn(*buf, SPACES);
|
||||
cmd->command = av_get_token(buf, COMMAND_DELIMS);
|
||||
if (!cmd->command || !cmd->command[0]) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No command specified in interval #%d, command #%d\n",
|
||||
interval_count, cmd_count);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
*buf += strspn(*buf, SPACES);
|
||||
cmd->arg = av_get_token(buf, COMMAND_DELIMS);
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
av_freep(&cmd->target);
|
||||
av_freep(&cmd->command);
|
||||
av_freep(&cmd->arg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
|
||||
const char **buf, void *log_ctx)
|
||||
{
|
||||
int cmd_count = 0;
|
||||
int ret, n = 0;
|
||||
AVBPrint pbuf;
|
||||
|
||||
*cmds = NULL;
|
||||
*nb_cmds = 0;
|
||||
|
||||
while (**buf) {
|
||||
Command cmd;
|
||||
|
||||
if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
|
||||
return ret;
|
||||
cmd_count++;
|
||||
|
||||
/* (re)allocate commands array if required */
|
||||
if (*nb_cmds == n) {
|
||||
n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
|
||||
*cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
|
||||
if (!*cmds) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Could not (re)allocate command array\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
(*cmds)[(*nb_cmds)++] = cmd;
|
||||
|
||||
*buf += strspn(*buf, SPACES);
|
||||
if (**buf && **buf != ';' && **buf != ',') {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Missing separator or extraneous data found at the end of "
|
||||
"interval #%d, in command #%d\n",
|
||||
interval_count, cmd_count);
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
|
||||
make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
if (**buf == ';')
|
||||
break;
|
||||
if (**buf == ',')
|
||||
(*buf)++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DELIMS " \f\t\n\r,;"
|
||||
|
||||
static int parse_interval(Interval *interval, int interval_count,
|
||||
const char **buf, void *log_ctx)
|
||||
{
|
||||
char *intervalstr;
|
||||
int ret;
|
||||
|
||||
*buf += strspn(*buf, SPACES);
|
||||
if (!**buf)
|
||||
return 0;
|
||||
|
||||
/* reset data */
|
||||
memset(interval, 0, sizeof(Interval));
|
||||
interval->index = interval_count;
|
||||
|
||||
/* format: INTERVAL COMMANDS */
|
||||
|
||||
/* parse interval */
|
||||
intervalstr = av_get_token(buf, DELIMS);
|
||||
if (intervalstr && intervalstr[0]) {
|
||||
char *start, *end;
|
||||
|
||||
start = av_strtok(intervalstr, "-", &end);
|
||||
if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid start time specification '%s' in interval #%d\n",
|
||||
start, interval_count);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (end) {
|
||||
if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid end time specification '%s' in interval #%d\n",
|
||||
end, interval_count);
|
||||
goto end;
|
||||
}
|
||||
} else {
|
||||
interval->end_ts = INT64_MAX;
|
||||
}
|
||||
if (interval->end_ts < interval->start_ts) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid end time '%s' in interval #%d: "
|
||||
"cannot be lesser than start time '%s'\n",
|
||||
end, interval_count, start);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
} else {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No interval specified for interval #%d\n", interval_count);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* parse commands */
|
||||
ret = parse_commands(&interval->commands, &interval->nb_commands,
|
||||
interval_count, buf, log_ctx);
|
||||
|
||||
end:
|
||||
av_free(intervalstr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int parse_intervals(Interval **intervals, int *nb_intervals,
|
||||
const char *buf, void *log_ctx)
|
||||
{
|
||||
int interval_count = 0;
|
||||
int ret, n = 0;
|
||||
|
||||
*intervals = NULL;
|
||||
*nb_intervals = 0;
|
||||
|
||||
while (1) {
|
||||
Interval interval;
|
||||
|
||||
skip_comments(&buf);
|
||||
if (!(*buf))
|
||||
break;
|
||||
|
||||
if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
buf += strspn(buf, SPACES);
|
||||
if (*buf) {
|
||||
if (*buf != ';') {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Missing terminator or extraneous data found at the end of interval #%d\n",
|
||||
interval_count);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
buf++; /* skip ';' */
|
||||
}
|
||||
interval_count++;
|
||||
|
||||
/* (re)allocate commands array if required */
|
||||
if (*nb_intervals == n) {
|
||||
n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
|
||||
*intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
|
||||
if (!*intervals) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Could not (re)allocate intervals array\n");
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
}
|
||||
|
||||
(*intervals)[(*nb_intervals)++] = interval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmp_intervals(const void *a, const void *b)
|
||||
{
|
||||
const Interval *i1 = a;
|
||||
const Interval *i2 = b;
|
||||
int64_t ts_diff = i1->start_ts - i2->start_ts;
|
||||
int ret;
|
||||
|
||||
ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0;
|
||||
return ret == 0 ? i1->index - i2->index : ret;
|
||||
}
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
SendCmdContext *sendcmd = ctx->priv;
|
||||
int ret, i, j;
|
||||
|
||||
if (sendcmd->commands_filename && sendcmd->commands_str) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Only one of the filename or commands options must be specified\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
if (sendcmd->commands_filename) {
|
||||
uint8_t *file_buf, *buf;
|
||||
size_t file_bufsize;
|
||||
ret = av_file_map(sendcmd->commands_filename,
|
||||
&file_buf, &file_bufsize, 0, ctx);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* create a 0-terminated string based on the read file */
|
||||
buf = av_malloc(file_bufsize + 1);
|
||||
if (!buf) {
|
||||
av_file_unmap(file_buf, file_bufsize);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
memcpy(buf, file_buf, file_bufsize);
|
||||
buf[file_bufsize] = 0;
|
||||
av_file_unmap(file_buf, file_bufsize);
|
||||
sendcmd->commands_str = buf;
|
||||
}
|
||||
|
||||
if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
|
||||
sendcmd->commands_str, ctx)) < 0)
|
||||
return ret;
|
||||
|
||||
qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
|
||||
|
||||
av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
|
||||
for (i = 0; i < sendcmd->nb_intervals; i++) {
|
||||
AVBPrint pbuf;
|
||||
Interval *interval = &sendcmd->intervals[i];
|
||||
av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
|
||||
(double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
|
||||
for (j = 0; j < interval->nb_commands; j++) {
|
||||
Command *cmd = &interval->commands[j];
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
" [%s] target:%s command:%s arg:%s index:%d\n",
|
||||
make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
SendCmdContext *sendcmd = ctx->priv;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < sendcmd->nb_intervals; i++) {
|
||||
Interval *interval = &sendcmd->intervals[i];
|
||||
for (j = 0; j < interval->nb_commands; j++) {
|
||||
Command *cmd = &interval->commands[j];
|
||||
av_free(cmd->target);
|
||||
av_free(cmd->command);
|
||||
av_free(cmd->arg);
|
||||
}
|
||||
av_free(interval->commands);
|
||||
}
|
||||
av_freep(&sendcmd->intervals);
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
SendCmdContext *sendcmd = ctx->priv;
|
||||
int64_t ts;
|
||||
int i, j, ret;
|
||||
|
||||
if (ref->pts == AV_NOPTS_VALUE)
|
||||
goto end;
|
||||
|
||||
ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
|
||||
|
||||
#define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
|
||||
|
||||
for (i = 0; i < sendcmd->nb_intervals; i++) {
|
||||
Interval *interval = &sendcmd->intervals[i];
|
||||
int flags = 0;
|
||||
|
||||
if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
|
||||
flags += COMMAND_FLAG_ENTER;
|
||||
interval->enabled = 1;
|
||||
}
|
||||
if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
|
||||
flags += COMMAND_FLAG_LEAVE;
|
||||
interval->enabled = 0;
|
||||
}
|
||||
|
||||
if (flags) {
|
||||
AVBPrint pbuf;
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
|
||||
make_command_flags_str(&pbuf, flags), interval->index,
|
||||
(double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
|
||||
(double)ts/1000000);
|
||||
|
||||
for (j = 0; flags && j < interval->nb_commands; j++) {
|
||||
Command *cmd = &interval->commands[j];
|
||||
char buf[1024];
|
||||
|
||||
if (cmd->flags & flags) {
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"Processing command #%d target:%s command:%s arg:%s\n",
|
||||
cmd->index, cmd->target, cmd->command, cmd->arg);
|
||||
ret = avfilter_graph_send_command(inlink->graph,
|
||||
cmd->target, cmd->command, cmd->arg,
|
||||
buf, sizeof(buf),
|
||||
AVFILTER_CMD_FLAG_ONE);
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"Command reply for command #%d: ret:%s res:%s\n",
|
||||
cmd->index, av_err2str(ret), buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
switch (inlink->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
return ff_filter_frame(inlink->dst->outputs[0], ref);
|
||||
}
|
||||
|
||||
return AVERROR(ENOSYS);
|
||||
}
|
||||
|
||||
#if CONFIG_SENDCMD_FILTER
|
||||
|
||||
#define sendcmd_options options
|
||||
AVFILTER_DEFINE_CLASS(sendcmd);
|
||||
|
||||
static const AVFilterPad sendcmd_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad sendcmd_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_sendcmd = {
|
||||
.name = "sendcmd",
|
||||
.description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(SendCmdContext),
|
||||
.inputs = sendcmd_inputs,
|
||||
.outputs = sendcmd_outputs,
|
||||
.priv_class = &sendcmd_class,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_ASENDCMD_FILTER
|
||||
|
||||
#define asendcmd_options options
|
||||
AVFILTER_DEFINE_CLASS(asendcmd);
|
||||
|
||||
static const AVFilterPad asendcmd_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad asendcmd_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_asendcmd = {
|
||||
.name = "asendcmd",
|
||||
.description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(SendCmdContext),
|
||||
.inputs = asendcmd_inputs,
|
||||
.outputs = asendcmd_outputs,
|
||||
.priv_class = &asendcmd_class,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* Set timebase for the output link.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/internal.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavutil/rational.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
|
||||
static const char *const var_names[] = {
|
||||
"AVTB", /* default timebase 1/AV_TIME_BASE */
|
||||
"intb", /* input timebase */
|
||||
"sr", /* sample rate */
|
||||
NULL
|
||||
};
|
||||
|
||||
enum var_name {
|
||||
VAR_AVTB,
|
||||
VAR_INTB,
|
||||
VAR_SR,
|
||||
VAR_VARS_NB
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
char *tb_expr;
|
||||
double var_values[VAR_VARS_NB];
|
||||
} SetTBContext;
|
||||
|
||||
#define OFFSET(x) offsetof(SetTBContext, x)
|
||||
#define DEFINE_OPTIONS(filt_name, filt_type) \
|
||||
static const AVOption filt_name##_options[] = { \
|
||||
{ "expr", "set expression determining the output timebase", OFFSET(tb_expr), AV_OPT_TYPE_STRING, {.str="intb"}, \
|
||||
.flags=AV_OPT_FLAG_##filt_type##_PARAM|AV_OPT_FLAG_FILTERING_PARAM }, \
|
||||
{ "tb", "set expression determining the output timebase", OFFSET(tb_expr), AV_OPT_TYPE_STRING, {.str="intb"}, \
|
||||
.flags=AV_OPT_FLAG_##filt_type##_PARAM|AV_OPT_FLAG_FILTERING_PARAM }, \
|
||||
{ NULL } \
|
||||
}
|
||||
|
||||
static int config_output_props(AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
SetTBContext *settb = ctx->priv;
|
||||
AVFilterLink *inlink = ctx->inputs[0];
|
||||
AVRational time_base;
|
||||
int ret;
|
||||
double res;
|
||||
|
||||
settb->var_values[VAR_AVTB] = av_q2d(AV_TIME_BASE_Q);
|
||||
settb->var_values[VAR_INTB] = av_q2d(inlink->time_base);
|
||||
settb->var_values[VAR_SR] = inlink->sample_rate;
|
||||
|
||||
outlink->w = inlink->w;
|
||||
outlink->h = inlink->h;
|
||||
|
||||
if ((ret = av_expr_parse_and_eval(&res, settb->tb_expr, var_names, settb->var_values,
|
||||
NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for timebase.\n", settb->tb_expr);
|
||||
return ret;
|
||||
}
|
||||
time_base = av_d2q(res, INT_MAX);
|
||||
if (time_base.num <= 0 || time_base.den <= 0) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Invalid non-positive values for the timebase num:%d or den:%d.\n",
|
||||
time_base.num, time_base.den);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
outlink->time_base = time_base;
|
||||
av_log(outlink->src, AV_LOG_VERBOSE, "tb:%d/%d -> tb:%d/%d\n",
|
||||
inlink ->time_base.num, inlink ->time_base.den,
|
||||
outlink->time_base.num, outlink->time_base.den);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
AVFilterLink *outlink = ctx->outputs[0];
|
||||
|
||||
if (av_cmp_q(inlink->time_base, outlink->time_base)) {
|
||||
int64_t orig_pts = frame->pts;
|
||||
frame->pts = av_rescale_q(frame->pts, inlink->time_base, outlink->time_base);
|
||||
av_log(ctx, AV_LOG_DEBUG, "tb:%d/%d pts:%"PRId64" -> tb:%d/%d pts:%"PRId64"\n",
|
||||
inlink ->time_base.num, inlink ->time_base.den, orig_pts,
|
||||
outlink->time_base.num, outlink->time_base.den, frame->pts);
|
||||
}
|
||||
|
||||
return ff_filter_frame(outlink, frame);
|
||||
}
|
||||
|
||||
#if CONFIG_SETTB_FILTER
|
||||
|
||||
DEFINE_OPTIONS(settb, VIDEO);
|
||||
AVFILTER_DEFINE_CLASS(settb);
|
||||
|
||||
static const AVFilterPad avfilter_vf_settb_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_vf_settb_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.config_props = config_output_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_settb = {
|
||||
.name = "settb",
|
||||
.description = NULL_IF_CONFIG_SMALL("Set timebase for the video output link."),
|
||||
.priv_size = sizeof(SetTBContext),
|
||||
.priv_class = &settb_class,
|
||||
.inputs = avfilter_vf_settb_inputs,
|
||||
.outputs = avfilter_vf_settb_outputs,
|
||||
};
|
||||
#endif
|
||||
|
||||
#if CONFIG_ASETTB_FILTER
|
||||
|
||||
DEFINE_OPTIONS(asettb, AUDIO);
|
||||
AVFILTER_DEFINE_CLASS(asettb);
|
||||
|
||||
static const AVFilterPad avfilter_af_asettb_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_asettb_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.config_props = config_output_props,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_asettb = {
|
||||
.name = "asettb",
|
||||
.description = NULL_IF_CONFIG_SMALL("Set timebase for the audio output link."),
|
||||
.priv_size = sizeof(SetTBContext),
|
||||
.inputs = avfilter_af_asettb_inputs,
|
||||
.outputs = avfilter_af_asettb_outputs,
|
||||
.priv_class = &asettb_class,
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
* receive commands through libzeromq and broker them to filters
|
||||
*/
|
||||
|
||||
#include <zmq.h>
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "avfiltergraph.h"
|
||||
#include "audio.h"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct {
|
||||
const AVClass *class;
|
||||
void *zmq;
|
||||
void *responder;
|
||||
char *bind_address;
|
||||
int command_count;
|
||||
} ZMQContext;
|
||||
|
||||
#define OFFSET(x) offsetof(ZMQContext, x)
|
||||
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
|
||||
static const AVOption options[] = {
|
||||
{ "bind_address", "set bind address", OFFSET(bind_address), AV_OPT_TYPE_STRING, {.str = "tcp://*:5555"}, 0, 0, FLAGS },
|
||||
{ "b", "set bind address", OFFSET(bind_address), AV_OPT_TYPE_STRING, {.str = "tcp://*:5555"}, 0, 0, FLAGS },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
ZMQContext *zmq = ctx->priv;
|
||||
|
||||
zmq->zmq = zmq_ctx_new();
|
||||
if (!zmq->zmq) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Could not create ZMQ context: %s\n", zmq_strerror(errno));
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
zmq->responder = zmq_socket(zmq->zmq, ZMQ_REP);
|
||||
if (!zmq->responder) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Could not create ZMQ socket: %s\n", zmq_strerror(errno));
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
if (zmq_bind(zmq->responder, zmq->bind_address) == -1) {
|
||||
av_log(ctx, AV_LOG_ERROR,
|
||||
"Could not bind ZMQ socket to address '%s': %s\n",
|
||||
zmq->bind_address, zmq_strerror(errno));
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
zmq->command_count = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void av_cold uninit(AVFilterContext *ctx)
|
||||
{
|
||||
ZMQContext *zmq = ctx->priv;
|
||||
|
||||
zmq_close(zmq->responder);
|
||||
zmq_ctx_destroy(zmq->zmq);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *target, *command, *arg;
|
||||
} Command;
|
||||
|
||||
#define SPACES " \f\t\n\r"
|
||||
|
||||
static int parse_command(Command *cmd, const char *command_str, void *log_ctx)
|
||||
{
|
||||
const char **buf = &command_str;
|
||||
|
||||
cmd->target = av_get_token(buf, SPACES);
|
||||
if (!cmd->target || !cmd->target[0]) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No target specified in command '%s'\n", command_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
cmd->command = av_get_token(buf, SPACES);
|
||||
if (!cmd->command || !cmd->command[0]) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No command specified in command '%s'\n", command_str);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
cmd->arg = av_get_token(buf, SPACES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int recv_msg(AVFilterContext *ctx, char **buf, int *buf_size)
|
||||
{
|
||||
ZMQContext *zmq = ctx->priv;
|
||||
zmq_msg_t msg;
|
||||
int ret = 0;
|
||||
|
||||
if (zmq_msg_init(&msg) == -1) {
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Could not initialize receive message: %s\n", zmq_strerror(errno));
|
||||
return AVERROR_EXTERNAL;
|
||||
}
|
||||
|
||||
if (zmq_msg_recv(&msg, zmq->responder, ZMQ_DONTWAIT) == -1) {
|
||||
if (errno != EAGAIN)
|
||||
av_log(ctx, AV_LOG_WARNING,
|
||||
"Could not receive message: %s\n", zmq_strerror(errno));
|
||||
ret = AVERROR_EXTERNAL;
|
||||
goto end;
|
||||
}
|
||||
|
||||
*buf_size = zmq_msg_size(&msg) + 1;
|
||||
*buf = av_malloc(*buf_size);
|
||||
if (!*buf) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
memcpy(*buf, zmq_msg_data(&msg), *buf_size);
|
||||
(*buf)[*buf_size-1] = 0;
|
||||
|
||||
end:
|
||||
zmq_msg_close(&msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
|
||||
{
|
||||
AVFilterContext *ctx = inlink->dst;
|
||||
ZMQContext *zmq = ctx->priv;
|
||||
|
||||
while (1) {
|
||||
char cmd_buf[1024];
|
||||
char *recv_buf, *send_buf;
|
||||
int recv_buf_size;
|
||||
Command cmd = {0};
|
||||
int ret;
|
||||
|
||||
/* receive command */
|
||||
if (recv_msg(ctx, &recv_buf, &recv_buf_size) < 0)
|
||||
break;
|
||||
zmq->command_count++;
|
||||
|
||||
/* parse command */
|
||||
if (parse_command(&cmd, recv_buf, ctx) < 0) {
|
||||
av_log(ctx, AV_LOG_ERROR, "Could not parse command #%d\n", zmq->command_count);
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* process command */
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"Processing command #%d target:%s command:%s arg:%s\n",
|
||||
zmq->command_count, cmd.target, cmd.command, cmd.arg);
|
||||
ret = avfilter_graph_send_command(inlink->graph,
|
||||
cmd.target, cmd.command, cmd.arg,
|
||||
cmd_buf, sizeof(cmd_buf),
|
||||
AVFILTER_CMD_FLAG_ONE);
|
||||
send_buf = av_asprintf("%d %s%s%s",
|
||||
-ret, av_err2str(ret), cmd_buf[0] ? "\n" : "", cmd_buf);
|
||||
if (!send_buf) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
av_log(ctx, AV_LOG_VERBOSE,
|
||||
"Sending command reply for command #%d:\n%s\n",
|
||||
zmq->command_count, send_buf);
|
||||
if (zmq_send(zmq->responder, send_buf, strlen(send_buf), 0) == -1)
|
||||
av_log(ctx, AV_LOG_ERROR, "Failed to send reply for command #%d: %s\n",
|
||||
zmq->command_count, zmq_strerror(ret));
|
||||
|
||||
end:
|
||||
av_freep(&send_buf);
|
||||
av_freep(&recv_buf);
|
||||
recv_buf_size = 0;
|
||||
av_freep(&cmd.target);
|
||||
av_freep(&cmd.command);
|
||||
av_freep(&cmd.arg);
|
||||
}
|
||||
|
||||
return ff_filter_frame(ctx->outputs[0], ref);
|
||||
}
|
||||
|
||||
#if CONFIG_ZMQ_FILTER
|
||||
|
||||
#define zmq_options options
|
||||
AVFILTER_DEFINE_CLASS(zmq);
|
||||
|
||||
static const AVFilterPad zmq_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad zmq_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_zmq = {
|
||||
.name = "zmq",
|
||||
.description = NULL_IF_CONFIG_SMALL("Receive commands through ZMQ and broker them to filters."),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(ZMQContext),
|
||||
.inputs = zmq_inputs,
|
||||
.outputs = zmq_outputs,
|
||||
.priv_class = &zmq_class,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if CONFIG_AZMQ_FILTER
|
||||
|
||||
#define azmq_options options
|
||||
AVFILTER_DEFINE_CLASS(azmq);
|
||||
|
||||
static const AVFilterPad azmq_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = filter_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad azmq_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_azmq = {
|
||||
.name = "azmq",
|
||||
.description = NULL_IF_CONFIG_SMALL("Receive commands through ZMQ and broker them to filters."),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.priv_size = sizeof(ZMQContext),
|
||||
.inputs = azmq_inputs,
|
||||
.outputs = azmq_outputs,
|
||||
.priv_class = &azmq_class,
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* Copyright (c) 2007 Bobby Bingham
|
||||
*
|
||||
* 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
|
||||
* FIFO buffering filter
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
|
||||
#include "audio.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "video.h"
|
||||
|
||||
typedef struct Buf {
|
||||
AVFrame *frame;
|
||||
struct Buf *next;
|
||||
} Buf;
|
||||
|
||||
typedef struct {
|
||||
Buf root;
|
||||
Buf *last; ///< last buffered frame
|
||||
|
||||
/**
|
||||
* When a specific number of output samples is requested, the partial
|
||||
* buffer is stored here
|
||||
*/
|
||||
AVFrame *out;
|
||||
int allocated_samples; ///< number of samples out was allocated for
|
||||
} FifoContext;
|
||||
|
||||
static av_cold int init(AVFilterContext *ctx)
|
||||
{
|
||||
FifoContext *fifo = ctx->priv;
|
||||
fifo->last = &fifo->root;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold void uninit(AVFilterContext *ctx)
|
||||
{
|
||||
FifoContext *fifo = ctx->priv;
|
||||
Buf *buf, *tmp;
|
||||
|
||||
for (buf = fifo->root.next; buf; buf = tmp) {
|
||||
tmp = buf->next;
|
||||
av_frame_free(&buf->frame);
|
||||
av_free(buf);
|
||||
}
|
||||
|
||||
av_frame_free(&fifo->out);
|
||||
}
|
||||
|
||||
static int add_to_queue(AVFilterLink *inlink, AVFrame *frame)
|
||||
{
|
||||
FifoContext *fifo = inlink->dst->priv;
|
||||
|
||||
fifo->last->next = av_mallocz(sizeof(Buf));
|
||||
if (!fifo->last->next) {
|
||||
av_frame_free(&frame);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
fifo->last = fifo->last->next;
|
||||
fifo->last->frame = frame;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void queue_pop(FifoContext *s)
|
||||
{
|
||||
Buf *tmp = s->root.next->next;
|
||||
if (s->last == s->root.next)
|
||||
s->last = &s->root;
|
||||
av_freep(&s->root.next);
|
||||
s->root.next = tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move data pointers and pts offset samples forward.
|
||||
*/
|
||||
static void buffer_offset(AVFilterLink *link, AVFrame *frame,
|
||||
int offset)
|
||||
{
|
||||
int nb_channels = av_get_channel_layout_nb_channels(link->channel_layout);
|
||||
int planar = av_sample_fmt_is_planar(link->format);
|
||||
int planes = planar ? nb_channels : 1;
|
||||
int block_align = av_get_bytes_per_sample(link->format) * (planar ? 1 : nb_channels);
|
||||
int i;
|
||||
|
||||
av_assert0(frame->nb_samples > offset);
|
||||
|
||||
for (i = 0; i < planes; i++)
|
||||
frame->extended_data[i] += block_align * offset;
|
||||
if (frame->data != frame->extended_data)
|
||||
memcpy(frame->data, frame->extended_data,
|
||||
FFMIN(planes, FF_ARRAY_ELEMS(frame->data)) * sizeof(*frame->data));
|
||||
frame->linesize[0] -= block_align*offset;
|
||||
frame->nb_samples -= offset;
|
||||
|
||||
if (frame->pts != AV_NOPTS_VALUE) {
|
||||
frame->pts += av_rescale_q(offset, (AVRational){1, link->sample_rate},
|
||||
link->time_base);
|
||||
}
|
||||
}
|
||||
|
||||
static int calc_ptr_alignment(AVFrame *frame)
|
||||
{
|
||||
int planes = av_sample_fmt_is_planar(frame->format) ?
|
||||
av_get_channel_layout_nb_channels(frame->channel_layout) : 1;
|
||||
int min_align = 128;
|
||||
int p;
|
||||
|
||||
for (p = 0; p < planes; p++) {
|
||||
int cur_align = 128;
|
||||
while ((intptr_t)frame->extended_data[p] % cur_align)
|
||||
cur_align >>= 1;
|
||||
if (cur_align < min_align)
|
||||
min_align = cur_align;
|
||||
}
|
||||
return min_align;
|
||||
}
|
||||
|
||||
static int return_audio_frame(AVFilterContext *ctx)
|
||||
{
|
||||
AVFilterLink *link = ctx->outputs[0];
|
||||
FifoContext *s = ctx->priv;
|
||||
AVFrame *head = s->root.next ? s->root.next->frame : NULL;
|
||||
AVFrame *out;
|
||||
int ret;
|
||||
|
||||
/* if head is NULL then we're flushing the remaining samples in out */
|
||||
if (!head && !s->out)
|
||||
return AVERROR_EOF;
|
||||
|
||||
if (!s->out &&
|
||||
head->nb_samples >= link->request_samples &&
|
||||
calc_ptr_alignment(head) >= 32) {
|
||||
if (head->nb_samples == link->request_samples) {
|
||||
out = head;
|
||||
queue_pop(s);
|
||||
} else {
|
||||
out = av_frame_clone(head);
|
||||
if (!out)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
out->nb_samples = link->request_samples;
|
||||
buffer_offset(link, head, link->request_samples);
|
||||
}
|
||||
} else {
|
||||
int nb_channels = av_get_channel_layout_nb_channels(link->channel_layout);
|
||||
|
||||
if (!s->out) {
|
||||
s->out = ff_get_audio_buffer(link, link->request_samples);
|
||||
if (!s->out)
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
s->out->nb_samples = 0;
|
||||
s->out->pts = head->pts;
|
||||
s->allocated_samples = link->request_samples;
|
||||
} else if (link->request_samples != s->allocated_samples) {
|
||||
av_log(ctx, AV_LOG_ERROR, "request_samples changed before the "
|
||||
"buffer was returned.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
while (s->out->nb_samples < s->allocated_samples) {
|
||||
int len;
|
||||
|
||||
if (!s->root.next) {
|
||||
ret = ff_request_frame(ctx->inputs[0]);
|
||||
if (ret == AVERROR_EOF) {
|
||||
av_samples_set_silence(s->out->extended_data,
|
||||
s->out->nb_samples,
|
||||
s->allocated_samples -
|
||||
s->out->nb_samples,
|
||||
nb_channels, link->format);
|
||||
s->out->nb_samples = s->allocated_samples;
|
||||
break;
|
||||
} else if (ret < 0)
|
||||
return ret;
|
||||
av_assert0(s->root.next); // If ff_request_frame() succeeded then we should have a frame
|
||||
}
|
||||
head = s->root.next->frame;
|
||||
|
||||
len = FFMIN(s->allocated_samples - s->out->nb_samples,
|
||||
head->nb_samples);
|
||||
|
||||
av_samples_copy(s->out->extended_data, head->extended_data,
|
||||
s->out->nb_samples, 0, len, nb_channels,
|
||||
link->format);
|
||||
s->out->nb_samples += len;
|
||||
|
||||
if (len == head->nb_samples) {
|
||||
av_frame_free(&head);
|
||||
queue_pop(s);
|
||||
} else {
|
||||
buffer_offset(link, head, len);
|
||||
}
|
||||
}
|
||||
out = s->out;
|
||||
s->out = NULL;
|
||||
}
|
||||
return ff_filter_frame(link, out);
|
||||
}
|
||||
|
||||
static int request_frame(AVFilterLink *outlink)
|
||||
{
|
||||
FifoContext *fifo = outlink->src->priv;
|
||||
int ret = 0;
|
||||
|
||||
if (!fifo->root.next) {
|
||||
if ((ret = ff_request_frame(outlink->src->inputs[0])) < 0) {
|
||||
if (ret == AVERROR_EOF && outlink->request_samples)
|
||||
return return_audio_frame(outlink->src);
|
||||
return ret;
|
||||
}
|
||||
av_assert0(fifo->root.next);
|
||||
}
|
||||
|
||||
if (outlink->request_samples) {
|
||||
return return_audio_frame(outlink->src);
|
||||
} else {
|
||||
ret = ff_filter_frame(outlink, fifo->root.next->frame);
|
||||
queue_pop(fifo);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const AVFilterPad avfilter_vf_fifo_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.filter_frame = add_to_queue,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_vf_fifo_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_VIDEO,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_vf_fifo = {
|
||||
.name = "fifo",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer input images and send them when they are requested."),
|
||||
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
|
||||
.priv_size = sizeof(FifoContext),
|
||||
|
||||
.inputs = avfilter_vf_fifo_inputs,
|
||||
.outputs = avfilter_vf_fifo_outputs,
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_afifo_inputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.filter_frame = add_to_queue,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static const AVFilterPad avfilter_af_afifo_outputs[] = {
|
||||
{
|
||||
.name = "default",
|
||||
.type = AVMEDIA_TYPE_AUDIO,
|
||||
.request_frame = request_frame,
|
||||
},
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
AVFilter avfilter_af_afifo = {
|
||||
.name = "afifo",
|
||||
.description = NULL_IF_CONFIG_SMALL("Buffer input frames and send them when they are requested."),
|
||||
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
|
||||
.priv_size = sizeof(FifoContext),
|
||||
|
||||
.inputs = avfilter_af_afifo_inputs,
|
||||
.outputs = avfilter_af_afifo_outputs,
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Stefano Sabatini
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavfilter/avfilter.h"
|
||||
#include "libavfilter/formats.h"
|
||||
|
||||
static void print_formats(AVFilterContext *filter_ctx)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
#define PRINT_FMTS(inout, outin, INOUT) \
|
||||
for (i = 0; i < filter_ctx->nb_##inout##puts; i++) { \
|
||||
if (filter_ctx->inout##puts[i]->type == AVMEDIA_TYPE_VIDEO) { \
|
||||
AVFilterFormats *fmts = \
|
||||
filter_ctx->inout##puts[i]->outin##_formats; \
|
||||
for (j = 0; j < fmts->nb_formats; j++) \
|
||||
if(av_get_pix_fmt_name(fmts->formats[j])) \
|
||||
printf(#INOUT "PUT[%d] %s: fmt:%s\n", \
|
||||
i, filter_ctx->filter->inout##puts[i].name, \
|
||||
av_get_pix_fmt_name(fmts->formats[j])); \
|
||||
} else if (filter_ctx->inout##puts[i]->type == AVMEDIA_TYPE_AUDIO) { \
|
||||
AVFilterFormats *fmts; \
|
||||
AVFilterChannelLayouts *layouts; \
|
||||
\
|
||||
fmts = filter_ctx->inout##puts[i]->outin##_formats; \
|
||||
for (j = 0; j < fmts->nb_formats; j++) \
|
||||
printf(#INOUT "PUT[%d] %s: fmt:%s\n", \
|
||||
i, filter_ctx->filter->inout##puts[i].name, \
|
||||
av_get_sample_fmt_name(fmts->formats[j])); \
|
||||
\
|
||||
layouts = filter_ctx->inout##puts[i]->outin##_channel_layouts; \
|
||||
for (j = 0; j < layouts->nb_channel_layouts; j++) { \
|
||||
char buf[256]; \
|
||||
av_get_channel_layout_string(buf, sizeof(buf), -1, \
|
||||
layouts->channel_layouts[j]); \
|
||||
printf(#INOUT "PUT[%d] %s: chlayout:%s\n", \
|
||||
i, filter_ctx->filter->inout##puts[i].name, buf); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
|
||||
PRINT_FMTS(in, out, IN);
|
||||
PRINT_FMTS(out, in, OUT);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
AVFilter *filter;
|
||||
AVFilterContext *filter_ctx;
|
||||
AVFilterGraph *graph_ctx;
|
||||
const char *filter_name;
|
||||
const char *filter_args = NULL;
|
||||
int i;
|
||||
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Missing filter name as argument\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
filter_name = argv[1];
|
||||
if (argc > 2)
|
||||
filter_args = argv[2];
|
||||
|
||||
/* allocate graph */
|
||||
graph_ctx = avfilter_graph_alloc();
|
||||
if (!graph_ctx)
|
||||
return 1;
|
||||
|
||||
avfilter_register_all();
|
||||
|
||||
/* get a corresponding filter and open it */
|
||||
if (!(filter = avfilter_get_by_name(filter_name))) {
|
||||
fprintf(stderr, "Unrecognized filter with name '%s'\n", filter_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* open filter and add it to the graph */
|
||||
if (!(filter_ctx = avfilter_graph_alloc_filter(graph_ctx, filter, filter_name))) {
|
||||
fprintf(stderr, "Impossible to open filter with name '%s'\n",
|
||||
filter_name);
|
||||
return 1;
|
||||
}
|
||||
if (avfilter_init_str(filter_ctx, filter_args) < 0) {
|
||||
fprintf(stderr, "Impossible to init filter '%s' with arguments '%s'\n",
|
||||
filter_name, filter_args);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* create a link for each of the input pads */
|
||||
for (i = 0; i < filter_ctx->nb_inputs; i++) {
|
||||
AVFilterLink *link = av_mallocz(sizeof(AVFilterLink));
|
||||
link->type = filter_ctx->filter->inputs[i].type;
|
||||
filter_ctx->inputs[i] = link;
|
||||
}
|
||||
for (i = 0; i < filter_ctx->nb_outputs; i++) {
|
||||
AVFilterLink *link = av_mallocz(sizeof(AVFilterLink));
|
||||
link->type = filter_ctx->filter->outputs[i].type;
|
||||
filter_ctx->outputs[i] = link;
|
||||
}
|
||||
|
||||
if (filter->query_formats)
|
||||
filter->query_formats(filter_ctx);
|
||||
else
|
||||
ff_default_query_formats(filter_ctx);
|
||||
|
||||
print_formats(filter_ctx);
|
||||
|
||||
avfilter_free(filter_ctx);
|
||||
avfilter_graph_free(&graph_ctx);
|
||||
fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,664 @@
|
||||
/*
|
||||
* Filter layer - format negotiation
|
||||
* Copyright (c) 2007 Bobby Bingham
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/common.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "avfilter.h"
|
||||
#include "internal.h"
|
||||
#include "formats.h"
|
||||
|
||||
#define KNOWN(l) (!FF_LAYOUT2COUNT(l)) /* for readability */
|
||||
|
||||
/**
|
||||
* Add all refs from a to ret and destroy a.
|
||||
*/
|
||||
#define MERGE_REF(ret, a, fmts, type, fail) \
|
||||
do { \
|
||||
type ***tmp; \
|
||||
int i; \
|
||||
\
|
||||
if (!(tmp = av_realloc(ret->refs, \
|
||||
sizeof(*tmp) * (ret->refcount + a->refcount)))) \
|
||||
goto fail; \
|
||||
ret->refs = tmp; \
|
||||
\
|
||||
for (i = 0; i < a->refcount; i ++) { \
|
||||
ret->refs[ret->refcount] = a->refs[i]; \
|
||||
*ret->refs[ret->refcount++] = ret; \
|
||||
} \
|
||||
\
|
||||
av_freep(&a->refs); \
|
||||
av_freep(&a->fmts); \
|
||||
av_freep(&a); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* Add all formats common for a and b to ret, copy the refs and destroy
|
||||
* a and b.
|
||||
*/
|
||||
#define MERGE_FORMATS(ret, a, b, fmts, nb, type, fail) \
|
||||
do { \
|
||||
int i, j, k = 0, count = FFMIN(a->nb, b->nb); \
|
||||
\
|
||||
if (!(ret = av_mallocz(sizeof(*ret)))) \
|
||||
goto fail; \
|
||||
\
|
||||
if (count) { \
|
||||
if (!(ret->fmts = av_malloc(sizeof(*ret->fmts) * count))) \
|
||||
goto fail; \
|
||||
for (i = 0; i < a->nb; i++) \
|
||||
for (j = 0; j < b->nb; j++) \
|
||||
if (a->fmts[i] == b->fmts[j]) { \
|
||||
if(k >= FFMIN(a->nb, b->nb)){ \
|
||||
av_log(NULL, AV_LOG_ERROR, "Duplicate formats in avfilter_merge_formats() detected\n"); \
|
||||
av_free(ret->fmts); \
|
||||
av_free(ret); \
|
||||
return NULL; \
|
||||
} \
|
||||
ret->fmts[k++] = a->fmts[i]; \
|
||||
} \
|
||||
} \
|
||||
ret->nb = k; \
|
||||
/* check that there was at least one common format */ \
|
||||
if (!ret->nb) \
|
||||
goto fail; \
|
||||
\
|
||||
MERGE_REF(ret, a, fmts, type, fail); \
|
||||
MERGE_REF(ret, b, fmts, type, fail); \
|
||||
} while (0)
|
||||
|
||||
AVFilterFormats *ff_merge_formats(AVFilterFormats *a, AVFilterFormats *b,
|
||||
enum AVMediaType type)
|
||||
{
|
||||
AVFilterFormats *ret = NULL;
|
||||
int i, j;
|
||||
int alpha1=0, alpha2=0;
|
||||
int chroma1=0, chroma2=0;
|
||||
|
||||
if (a == b)
|
||||
return a;
|
||||
|
||||
/* Do not lose chroma or alpha in merging.
|
||||
It happens if both lists have formats with chroma (resp. alpha), but
|
||||
the only formats in common do not have it (e.g. YUV+gray vs.
|
||||
RGB+gray): in that case, the merging would select the gray format,
|
||||
possibly causing a lossy conversion elsewhere in the graph.
|
||||
To avoid that, pretend that there are no common formats to force the
|
||||
insertion of a conversion filter. */
|
||||
if (type == AVMEDIA_TYPE_VIDEO)
|
||||
for (i = 0; i < a->nb_formats; i++)
|
||||
for (j = 0; j < b->nb_formats; j++) {
|
||||
const AVPixFmtDescriptor *adesc = av_pix_fmt_desc_get(a->formats[i]);
|
||||
const AVPixFmtDescriptor *bdesc = av_pix_fmt_desc_get(b->formats[j]);
|
||||
alpha2 |= adesc->flags & bdesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
chroma2|= adesc->nb_components > 1 && bdesc->nb_components > 1;
|
||||
if (a->formats[i] == b->formats[j]) {
|
||||
alpha1 |= adesc->flags & AV_PIX_FMT_FLAG_ALPHA;
|
||||
chroma1|= adesc->nb_components > 1;
|
||||
}
|
||||
}
|
||||
|
||||
// If chroma or alpha can be lost through merging then do not merge
|
||||
if (alpha2 > alpha1 || chroma2 > chroma1)
|
||||
return NULL;
|
||||
|
||||
MERGE_FORMATS(ret, a, b, formats, nb_formats, AVFilterFormats, fail);
|
||||
|
||||
return ret;
|
||||
fail:
|
||||
if (ret) {
|
||||
av_freep(&ret->refs);
|
||||
av_freep(&ret->formats);
|
||||
}
|
||||
av_freep(&ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVFilterFormats *ff_merge_samplerates(AVFilterFormats *a,
|
||||
AVFilterFormats *b)
|
||||
{
|
||||
AVFilterFormats *ret = NULL;
|
||||
|
||||
if (a == b) return a;
|
||||
|
||||
if (a->nb_formats && b->nb_formats) {
|
||||
MERGE_FORMATS(ret, a, b, formats, nb_formats, AVFilterFormats, fail);
|
||||
} else if (a->nb_formats) {
|
||||
MERGE_REF(a, b, formats, AVFilterFormats, fail);
|
||||
ret = a;
|
||||
} else {
|
||||
MERGE_REF(b, a, formats, AVFilterFormats, fail);
|
||||
ret = b;
|
||||
}
|
||||
|
||||
return ret;
|
||||
fail:
|
||||
if (ret) {
|
||||
av_freep(&ret->refs);
|
||||
av_freep(&ret->formats);
|
||||
}
|
||||
av_freep(&ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AVFilterChannelLayouts *ff_merge_channel_layouts(AVFilterChannelLayouts *a,
|
||||
AVFilterChannelLayouts *b)
|
||||
{
|
||||
AVFilterChannelLayouts *ret = NULL;
|
||||
unsigned a_all = a->all_layouts + a->all_counts;
|
||||
unsigned b_all = b->all_layouts + b->all_counts;
|
||||
int ret_max, ret_nb = 0, i, j, round;
|
||||
|
||||
if (a == b) return a;
|
||||
|
||||
/* Put the most generic set in a, to avoid doing everything twice */
|
||||
if (a_all < b_all) {
|
||||
FFSWAP(AVFilterChannelLayouts *, a, b);
|
||||
FFSWAP(unsigned, a_all, b_all);
|
||||
}
|
||||
if (a_all) {
|
||||
if (a_all == 1 && !b_all) {
|
||||
/* keep only known layouts in b; works also for b_all = 1 */
|
||||
for (i = j = 0; i < b->nb_channel_layouts; i++)
|
||||
if (KNOWN(b->channel_layouts[i]))
|
||||
b->channel_layouts[j++] = b->channel_layouts[i];
|
||||
/* Not optimal: the unknown layouts of b may become known after
|
||||
another merge. */
|
||||
if (!j)
|
||||
return NULL;
|
||||
b->nb_channel_layouts = j;
|
||||
}
|
||||
MERGE_REF(b, a, channel_layouts, AVFilterChannelLayouts, fail);
|
||||
return b;
|
||||
}
|
||||
|
||||
ret_max = a->nb_channel_layouts + b->nb_channel_layouts;
|
||||
if (!(ret = av_mallocz(sizeof(*ret))) ||
|
||||
!(ret->channel_layouts = av_malloc(sizeof(*ret->channel_layouts) *
|
||||
ret_max)))
|
||||
goto fail;
|
||||
|
||||
/* a[known] intersect b[known] */
|
||||
for (i = 0; i < a->nb_channel_layouts; i++) {
|
||||
if (!KNOWN(a->channel_layouts[i]))
|
||||
continue;
|
||||
for (j = 0; j < b->nb_channel_layouts; j++) {
|
||||
if (a->channel_layouts[i] == b->channel_layouts[j]) {
|
||||
ret->channel_layouts[ret_nb++] = a->channel_layouts[i];
|
||||
a->channel_layouts[i] = b->channel_layouts[j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 1st round: a[known] intersect b[generic]
|
||||
2nd round: a[generic] intersect b[known] */
|
||||
for (round = 0; round < 2; round++) {
|
||||
for (i = 0; i < a->nb_channel_layouts; i++) {
|
||||
uint64_t fmt = a->channel_layouts[i], bfmt;
|
||||
if (!fmt || !KNOWN(fmt))
|
||||
continue;
|
||||
bfmt = FF_COUNT2LAYOUT(av_get_channel_layout_nb_channels(fmt));
|
||||
for (j = 0; j < b->nb_channel_layouts; j++)
|
||||
if (b->channel_layouts[j] == bfmt)
|
||||
ret->channel_layouts[ret_nb++] = a->channel_layouts[i];
|
||||
}
|
||||
/* 1st round: swap to prepare 2nd round; 2nd round: put it back */
|
||||
FFSWAP(AVFilterChannelLayouts *, a, b);
|
||||
}
|
||||
/* a[generic] intersect b[generic] */
|
||||
for (i = 0; i < a->nb_channel_layouts; i++) {
|
||||
if (KNOWN(a->channel_layouts[i]))
|
||||
continue;
|
||||
for (j = 0; j < b->nb_channel_layouts; j++)
|
||||
if (a->channel_layouts[i] == b->channel_layouts[j])
|
||||
ret->channel_layouts[ret_nb++] = a->channel_layouts[i];
|
||||
}
|
||||
|
||||
ret->nb_channel_layouts = ret_nb;
|
||||
if (!ret->nb_channel_layouts)
|
||||
goto fail;
|
||||
MERGE_REF(ret, a, channel_layouts, AVFilterChannelLayouts, fail);
|
||||
MERGE_REF(ret, b, channel_layouts, AVFilterChannelLayouts, fail);
|
||||
return ret;
|
||||
|
||||
fail:
|
||||
if (ret) {
|
||||
av_freep(&ret->refs);
|
||||
av_freep(&ret->channel_layouts);
|
||||
}
|
||||
av_freep(&ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int ff_fmt_is_in(int fmt, const int *fmts)
|
||||
{
|
||||
const int *p;
|
||||
|
||||
for (p = fmts; *p != -1; p++) {
|
||||
if (fmt == *p)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define COPY_INT_LIST(list_copy, list, type) { \
|
||||
int count = 0; \
|
||||
if (list) \
|
||||
for (count = 0; list[count] != -1; count++) \
|
||||
; \
|
||||
list_copy = av_calloc(count+1, sizeof(type)); \
|
||||
if (list_copy) { \
|
||||
memcpy(list_copy, list, sizeof(type) * count); \
|
||||
list_copy[count] = -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define MAKE_FORMAT_LIST(type, field, count_field) \
|
||||
type *formats; \
|
||||
int count = 0; \
|
||||
if (fmts) \
|
||||
for (count = 0; fmts[count] != -1; count++) \
|
||||
; \
|
||||
formats = av_mallocz(sizeof(*formats)); \
|
||||
if (!formats) return NULL; \
|
||||
formats->count_field = count; \
|
||||
if (count) { \
|
||||
formats->field = av_malloc(sizeof(*formats->field)*count); \
|
||||
if (!formats->field) { \
|
||||
av_free(formats); \
|
||||
return NULL; \
|
||||
} \
|
||||
}
|
||||
|
||||
AVFilterFormats *ff_make_format_list(const int *fmts)
|
||||
{
|
||||
MAKE_FORMAT_LIST(AVFilterFormats, formats, nb_formats);
|
||||
while (count--)
|
||||
formats->formats[count] = fmts[count];
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
AVFilterChannelLayouts *avfilter_make_format64_list(const int64_t *fmts)
|
||||
{
|
||||
MAKE_FORMAT_LIST(AVFilterChannelLayouts,
|
||||
channel_layouts, nb_channel_layouts);
|
||||
if (count)
|
||||
memcpy(formats->channel_layouts, fmts,
|
||||
sizeof(*formats->channel_layouts) * count);
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
#define ADD_FORMAT(f, fmt, type, list, nb) \
|
||||
do { \
|
||||
type *fmts; \
|
||||
\
|
||||
if (!(*f) && !(*f = av_mallocz(sizeof(**f)))) \
|
||||
return AVERROR(ENOMEM); \
|
||||
\
|
||||
fmts = av_realloc((*f)->list, \
|
||||
sizeof(*(*f)->list) * ((*f)->nb + 1));\
|
||||
if (!fmts) \
|
||||
return AVERROR(ENOMEM); \
|
||||
\
|
||||
(*f)->list = fmts; \
|
||||
(*f)->list[(*f)->nb++] = fmt; \
|
||||
} while (0)
|
||||
|
||||
int ff_add_format(AVFilterFormats **avff, int64_t fmt)
|
||||
{
|
||||
ADD_FORMAT(avff, fmt, int, formats, nb_formats);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_add_channel_layout(AVFilterChannelLayouts **l, uint64_t channel_layout)
|
||||
{
|
||||
av_assert1(!(*l && (*l)->all_layouts));
|
||||
ADD_FORMAT(l, channel_layout, uint64_t, channel_layouts, nb_channel_layouts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
AVFilterFormats *ff_all_formats(enum AVMediaType type)
|
||||
{
|
||||
AVFilterFormats *ret = NULL;
|
||||
int fmt;
|
||||
int num_formats = type == AVMEDIA_TYPE_VIDEO ? AV_PIX_FMT_NB :
|
||||
type == AVMEDIA_TYPE_AUDIO ? AV_SAMPLE_FMT_NB : 0;
|
||||
|
||||
for (fmt = 0; fmt < num_formats; fmt++) {
|
||||
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
|
||||
if ((type != AVMEDIA_TYPE_VIDEO) ||
|
||||
(type == AVMEDIA_TYPE_VIDEO && !(desc->flags & AV_PIX_FMT_FLAG_HWACCEL)))
|
||||
ff_add_format(&ret, fmt);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
const int64_t avfilter_all_channel_layouts[] = {
|
||||
#include "all_channel_layouts.inc"
|
||||
-1
|
||||
};
|
||||
|
||||
// AVFilterFormats *avfilter_make_all_channel_layouts(void)
|
||||
// {
|
||||
// return avfilter_make_format64_list(avfilter_all_channel_layouts);
|
||||
// }
|
||||
|
||||
AVFilterFormats *ff_planar_sample_fmts(void)
|
||||
{
|
||||
AVFilterFormats *ret = NULL;
|
||||
int fmt;
|
||||
|
||||
for (fmt = 0; fmt < AV_SAMPLE_FMT_NB; fmt++)
|
||||
if (av_sample_fmt_is_planar(fmt))
|
||||
ff_add_format(&ret, fmt);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVFilterFormats *ff_all_samplerates(void)
|
||||
{
|
||||
AVFilterFormats *ret = av_mallocz(sizeof(*ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVFilterChannelLayouts *ff_all_channel_layouts(void)
|
||||
{
|
||||
AVFilterChannelLayouts *ret = av_mallocz(sizeof(*ret));
|
||||
if (!ret)
|
||||
return NULL;
|
||||
ret->all_layouts = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVFilterChannelLayouts *ff_all_channel_counts(void)
|
||||
{
|
||||
AVFilterChannelLayouts *ret = av_mallocz(sizeof(*ret));
|
||||
if (!ret)
|
||||
return NULL;
|
||||
ret->all_layouts = ret->all_counts = 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define FORMATS_REF(f, ref) \
|
||||
do { \
|
||||
*ref = f; \
|
||||
f->refs = av_realloc(f->refs, sizeof(*f->refs) * ++f->refcount); \
|
||||
f->refs[f->refcount-1] = ref; \
|
||||
} while (0)
|
||||
|
||||
void ff_channel_layouts_ref(AVFilterChannelLayouts *f, AVFilterChannelLayouts **ref)
|
||||
{
|
||||
FORMATS_REF(f, ref);
|
||||
}
|
||||
|
||||
void ff_formats_ref(AVFilterFormats *f, AVFilterFormats **ref)
|
||||
{
|
||||
FORMATS_REF(f, ref);
|
||||
}
|
||||
|
||||
#define FIND_REF_INDEX(ref, idx) \
|
||||
do { \
|
||||
int i; \
|
||||
for (i = 0; i < (*ref)->refcount; i ++) \
|
||||
if((*ref)->refs[i] == ref) { \
|
||||
idx = i; \
|
||||
break; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FORMATS_UNREF(ref, list) \
|
||||
do { \
|
||||
int idx = -1; \
|
||||
\
|
||||
if (!*ref) \
|
||||
return; \
|
||||
\
|
||||
FIND_REF_INDEX(ref, idx); \
|
||||
\
|
||||
if (idx >= 0) \
|
||||
memmove((*ref)->refs + idx, (*ref)->refs + idx + 1, \
|
||||
sizeof(*(*ref)->refs) * ((*ref)->refcount - idx - 1)); \
|
||||
\
|
||||
if(!--(*ref)->refcount) { \
|
||||
av_free((*ref)->list); \
|
||||
av_free((*ref)->refs); \
|
||||
av_free(*ref); \
|
||||
} \
|
||||
*ref = NULL; \
|
||||
} while (0)
|
||||
|
||||
void ff_formats_unref(AVFilterFormats **ref)
|
||||
{
|
||||
FORMATS_UNREF(ref, formats);
|
||||
}
|
||||
|
||||
void ff_channel_layouts_unref(AVFilterChannelLayouts **ref)
|
||||
{
|
||||
FORMATS_UNREF(ref, channel_layouts);
|
||||
}
|
||||
|
||||
#define FORMATS_CHANGEREF(oldref, newref) \
|
||||
do { \
|
||||
int idx = -1; \
|
||||
\
|
||||
FIND_REF_INDEX(oldref, idx); \
|
||||
\
|
||||
if (idx >= 0) { \
|
||||
(*oldref)->refs[idx] = newref; \
|
||||
*newref = *oldref; \
|
||||
*oldref = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void ff_channel_layouts_changeref(AVFilterChannelLayouts **oldref,
|
||||
AVFilterChannelLayouts **newref)
|
||||
{
|
||||
FORMATS_CHANGEREF(oldref, newref);
|
||||
}
|
||||
|
||||
void ff_formats_changeref(AVFilterFormats **oldref, AVFilterFormats **newref)
|
||||
{
|
||||
FORMATS_CHANGEREF(oldref, newref);
|
||||
}
|
||||
|
||||
#define SET_COMMON_FORMATS(ctx, fmts, in_fmts, out_fmts, ref, list) \
|
||||
{ \
|
||||
int count = 0, i; \
|
||||
\
|
||||
for (i = 0; i < ctx->nb_inputs; i++) { \
|
||||
if (ctx->inputs[i] && !ctx->inputs[i]->out_fmts) { \
|
||||
ref(fmts, &ctx->inputs[i]->out_fmts); \
|
||||
count++; \
|
||||
} \
|
||||
} \
|
||||
for (i = 0; i < ctx->nb_outputs; i++) { \
|
||||
if (ctx->outputs[i] && !ctx->outputs[i]->in_fmts) { \
|
||||
ref(fmts, &ctx->outputs[i]->in_fmts); \
|
||||
count++; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if (!count) { \
|
||||
av_freep(&fmts->list); \
|
||||
av_freep(&fmts->refs); \
|
||||
av_freep(&fmts); \
|
||||
} \
|
||||
}
|
||||
|
||||
void ff_set_common_channel_layouts(AVFilterContext *ctx,
|
||||
AVFilterChannelLayouts *layouts)
|
||||
{
|
||||
SET_COMMON_FORMATS(ctx, layouts, in_channel_layouts, out_channel_layouts,
|
||||
ff_channel_layouts_ref, channel_layouts);
|
||||
}
|
||||
|
||||
void ff_set_common_samplerates(AVFilterContext *ctx,
|
||||
AVFilterFormats *samplerates)
|
||||
{
|
||||
SET_COMMON_FORMATS(ctx, samplerates, in_samplerates, out_samplerates,
|
||||
ff_formats_ref, formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for query_formats() which sets all links to the same list of
|
||||
* formats. If there are no links hooked to this filter, the list of formats is
|
||||
* freed.
|
||||
*/
|
||||
void ff_set_common_formats(AVFilterContext *ctx, AVFilterFormats *formats)
|
||||
{
|
||||
SET_COMMON_FORMATS(ctx, formats, in_formats, out_formats,
|
||||
ff_formats_ref, formats);
|
||||
}
|
||||
|
||||
static int default_query_formats_common(AVFilterContext *ctx,
|
||||
AVFilterChannelLayouts *(layouts)(void))
|
||||
{
|
||||
enum AVMediaType type = ctx->inputs && ctx->inputs [0] ? ctx->inputs [0]->type :
|
||||
ctx->outputs && ctx->outputs[0] ? ctx->outputs[0]->type :
|
||||
AVMEDIA_TYPE_VIDEO;
|
||||
|
||||
ff_set_common_formats(ctx, ff_all_formats(type));
|
||||
if (type == AVMEDIA_TYPE_AUDIO) {
|
||||
ff_set_common_channel_layouts(ctx, layouts());
|
||||
ff_set_common_samplerates(ctx, ff_all_samplerates());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_default_query_formats(AVFilterContext *ctx)
|
||||
{
|
||||
return default_query_formats_common(ctx, ff_all_channel_layouts);
|
||||
}
|
||||
|
||||
int ff_query_formats_all(AVFilterContext *ctx)
|
||||
{
|
||||
return default_query_formats_common(ctx, ff_all_channel_counts);
|
||||
}
|
||||
|
||||
/* internal functions for parsing audio format arguments */
|
||||
|
||||
int ff_parse_pixel_format(enum AVPixelFormat *ret, const char *arg, void *log_ctx)
|
||||
{
|
||||
char *tail;
|
||||
int pix_fmt = av_get_pix_fmt(arg);
|
||||
if (pix_fmt == AV_PIX_FMT_NONE) {
|
||||
pix_fmt = strtol(arg, &tail, 0);
|
||||
if (*tail || (unsigned)pix_fmt >= AV_PIX_FMT_NB) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Invalid pixel format '%s'\n", arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
*ret = pix_fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_parse_sample_format(int *ret, const char *arg, void *log_ctx)
|
||||
{
|
||||
char *tail;
|
||||
int sfmt = av_get_sample_fmt(arg);
|
||||
if (sfmt == AV_SAMPLE_FMT_NONE) {
|
||||
sfmt = strtol(arg, &tail, 0);
|
||||
if (*tail || (unsigned)sfmt >= AV_SAMPLE_FMT_NB) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Invalid sample format '%s'\n", arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
*ret = sfmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_parse_time_base(AVRational *ret, const char *arg, void *log_ctx)
|
||||
{
|
||||
AVRational r;
|
||||
if(av_parse_ratio(&r, arg, INT_MAX, 0, log_ctx) < 0 ||r.num<=0 ||r.den<=0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Invalid time base '%s'\n", arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
*ret = r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_parse_sample_rate(int *ret, const char *arg, void *log_ctx)
|
||||
{
|
||||
char *tail;
|
||||
double srate = av_strtod(arg, &tail);
|
||||
if (*tail || srate < 1 || (int)srate != srate || srate > INT_MAX) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Invalid sample rate '%s'\n", arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
*ret = srate;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_parse_channel_layout(int64_t *ret, int *nret, const char *arg,
|
||||
void *log_ctx)
|
||||
{
|
||||
char *tail;
|
||||
int64_t chlayout, count;
|
||||
|
||||
if (nret) {
|
||||
count = strtol(arg, &tail, 10);
|
||||
if (*tail == 'c' && !tail[1] && count > 0 && count < 63) {
|
||||
*nret = count;
|
||||
*ret = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
chlayout = av_get_channel_layout(arg);
|
||||
if (chlayout == 0) {
|
||||
chlayout = strtol(arg, &tail, 10);
|
||||
if (*tail || chlayout == 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Invalid channel layout '%s'\n", arg);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
}
|
||||
*ret = chlayout;
|
||||
if (nret)
|
||||
*nret = av_get_channel_layout_nb_channels(chlayout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
|
||||
#undef printf
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const int64_t *cl;
|
||||
char buf[512];
|
||||
|
||||
for (cl = avfilter_all_channel_layouts; *cl != -1; cl++) {
|
||||
av_get_channel_layout_string(buf, sizeof(buf), -1, *cl);
|
||||
printf("%s\n", buf);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_FORMATS_H
|
||||
#define AVFILTER_FORMATS_H
|
||||
|
||||
#include "avfilter.h"
|
||||
|
||||
/**
|
||||
* A list of supported formats for one end of a filter link. This is used
|
||||
* during the format negotiation process to try to pick the best format to
|
||||
* use to minimize the number of necessary conversions. Each filter gives a
|
||||
* list of the formats supported by each input and output pad. The list
|
||||
* given for each pad need not be distinct - they may be references to the
|
||||
* same list of formats, as is often the case when a filter supports multiple
|
||||
* formats, but will always output the same format as it is given in input.
|
||||
*
|
||||
* In this way, a list of possible input formats and a list of possible
|
||||
* output formats are associated with each link. When a set of formats is
|
||||
* negotiated over a link, the input and output lists are merged to form a
|
||||
* new list containing only the common elements of each list. In the case
|
||||
* that there were no common elements, a format conversion is necessary.
|
||||
* Otherwise, the lists are merged, and all other links which reference
|
||||
* either of the format lists involved in the merge are also affected.
|
||||
*
|
||||
* For example, consider the filter chain:
|
||||
* filter (a) --> (b) filter (b) --> (c) filter
|
||||
*
|
||||
* where the letters in parenthesis indicate a list of formats supported on
|
||||
* the input or output of the link. Suppose the lists are as follows:
|
||||
* (a) = {A, B}
|
||||
* (b) = {A, B, C}
|
||||
* (c) = {B, C}
|
||||
*
|
||||
* First, the first link's lists are merged, yielding:
|
||||
* filter (a) --> (a) filter (a) --> (c) filter
|
||||
*
|
||||
* Notice that format list (b) now refers to the same list as filter list (a).
|
||||
* Next, the lists for the second link are merged, yielding:
|
||||
* filter (a) --> (a) filter (a) --> (a) filter
|
||||
*
|
||||
* where (a) = {B}.
|
||||
*
|
||||
* Unfortunately, when the format lists at the two ends of a link are merged,
|
||||
* we must ensure that all links which reference either pre-merge format list
|
||||
* get updated as well. Therefore, we have the format list structure store a
|
||||
* pointer to each of the pointers to itself.
|
||||
*/
|
||||
struct AVFilterFormats {
|
||||
unsigned nb_formats; ///< number of formats
|
||||
int *formats; ///< list of media formats
|
||||
|
||||
unsigned refcount; ///< number of references to this list
|
||||
struct AVFilterFormats ***refs; ///< references to this list
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of supported channel layouts.
|
||||
*
|
||||
* The list works the same as AVFilterFormats, except for the following
|
||||
* differences:
|
||||
* - A list with all_layouts = 1 means all channel layouts with a known
|
||||
* disposition; nb_channel_layouts must then be 0.
|
||||
* - A list with all_counts = 1 means all channel counts, with a known or
|
||||
* unknown disposition; nb_channel_layouts must then be 0 and all_layouts 1.
|
||||
* - The list must not contain a layout with a known disposition and a
|
||||
* channel count with unknown disposition with the same number of channels
|
||||
* (e.g. AV_CH_LAYOUT_STEREO and FF_COUNT2LAYOUT(2).
|
||||
*/
|
||||
typedef struct AVFilterChannelLayouts {
|
||||
uint64_t *channel_layouts; ///< list of channel layouts
|
||||
int nb_channel_layouts; ///< number of channel layouts
|
||||
char all_layouts; ///< accept any known channel layout
|
||||
char all_counts; ///< accept any channel layout or count
|
||||
|
||||
unsigned refcount; ///< number of references to this list
|
||||
struct AVFilterChannelLayouts ***refs; ///< references to this list
|
||||
} AVFilterChannelLayouts;
|
||||
|
||||
/**
|
||||
* Encode a channel count as a channel layout.
|
||||
* FF_COUNT2LAYOUT(c) means any channel layout with c channels, with a known
|
||||
* or unknown disposition.
|
||||
* The result is only valid inside AVFilterChannelLayouts and immediately
|
||||
* related functions.
|
||||
*/
|
||||
#define FF_COUNT2LAYOUT(c) (0x8000000000000000ULL | (c))
|
||||
|
||||
/**
|
||||
* Decode a channel count encoded as a channel layout.
|
||||
* Return 0 if the channel layout was a real one.
|
||||
*/
|
||||
#define FF_LAYOUT2COUNT(l) (((l) & 0x8000000000000000ULL) ? \
|
||||
(int)((l) & 0x7FFFFFFF) : 0)
|
||||
|
||||
/**
|
||||
* Return a channel layouts/samplerates list which contains the intersection of
|
||||
* the layouts/samplerates of a and b. Also, all the references of a, all the
|
||||
* references of b, and a and b themselves will be deallocated.
|
||||
*
|
||||
* If a and b do not share any common elements, neither is modified, and NULL
|
||||
* is returned.
|
||||
*/
|
||||
AVFilterChannelLayouts *ff_merge_channel_layouts(AVFilterChannelLayouts *a,
|
||||
AVFilterChannelLayouts *b);
|
||||
AVFilterFormats *ff_merge_samplerates(AVFilterFormats *a,
|
||||
AVFilterFormats *b);
|
||||
|
||||
/**
|
||||
* Construct an empty AVFilterChannelLayouts/AVFilterFormats struct --
|
||||
* representing any channel layout (with known disposition)/sample rate.
|
||||
*/
|
||||
AVFilterChannelLayouts *ff_all_channel_layouts(void);
|
||||
AVFilterFormats *ff_all_samplerates(void);
|
||||
|
||||
/**
|
||||
* Construct an AVFilterChannelLayouts coding for any channel layout, with
|
||||
* known or unknown disposition.
|
||||
*/
|
||||
AVFilterChannelLayouts *ff_all_channel_counts(void);
|
||||
|
||||
AVFilterChannelLayouts *avfilter_make_format64_list(const int64_t *fmts);
|
||||
|
||||
|
||||
/**
|
||||
* A helper for query_formats() which sets all links to the same list of channel
|
||||
* layouts/sample rates. If there are no links hooked to this filter, the list
|
||||
* is freed.
|
||||
*/
|
||||
void ff_set_common_channel_layouts(AVFilterContext *ctx,
|
||||
AVFilterChannelLayouts *layouts);
|
||||
void ff_set_common_samplerates(AVFilterContext *ctx,
|
||||
AVFilterFormats *samplerates);
|
||||
|
||||
/**
|
||||
* A helper for query_formats() which sets all links to the same list of
|
||||
* formats. If there are no links hooked to this filter, the list of formats is
|
||||
* freed.
|
||||
*/
|
||||
void ff_set_common_formats(AVFilterContext *ctx, AVFilterFormats *formats);
|
||||
|
||||
int ff_add_channel_layout(AVFilterChannelLayouts **l, uint64_t channel_layout);
|
||||
|
||||
/**
|
||||
* Add *ref as a new reference to f.
|
||||
*/
|
||||
void ff_channel_layouts_ref(AVFilterChannelLayouts *f,
|
||||
AVFilterChannelLayouts **ref);
|
||||
|
||||
/**
|
||||
* Remove a reference to a channel layouts list.
|
||||
*/
|
||||
void ff_channel_layouts_unref(AVFilterChannelLayouts **ref);
|
||||
|
||||
void ff_channel_layouts_changeref(AVFilterChannelLayouts **oldref,
|
||||
AVFilterChannelLayouts **newref);
|
||||
|
||||
int ff_default_query_formats(AVFilterContext *ctx);
|
||||
|
||||
/**
|
||||
* Set the formats list to all existing formats.
|
||||
* This function behaves like ff_default_query_formats(), except it also
|
||||
* accepts channel layouts with unknown disposition. It should only be used
|
||||
* with audio filters.
|
||||
*/
|
||||
int ff_query_formats_all(AVFilterContext *ctx);
|
||||
|
||||
|
||||
/**
|
||||
* Create a list of supported formats. This is intended for use in
|
||||
* AVFilter->query_formats().
|
||||
*
|
||||
* @param fmts list of media formats, terminated by -1
|
||||
* @return the format list, with no existing references
|
||||
*/
|
||||
AVFilterFormats *ff_make_format_list(const int *fmts);
|
||||
|
||||
/**
|
||||
* Add fmt to the list of media formats contained in *avff.
|
||||
* If *avff is NULL the function allocates the filter formats struct
|
||||
* and puts its pointer in *avff.
|
||||
*
|
||||
* @return a non negative value in case of success, or a negative
|
||||
* value corresponding to an AVERROR code in case of error
|
||||
*/
|
||||
int ff_add_format(AVFilterFormats **avff, int64_t fmt);
|
||||
|
||||
/**
|
||||
* Return a list of all formats supported by FFmpeg for the given media type.
|
||||
*/
|
||||
AVFilterFormats *ff_all_formats(enum AVMediaType type);
|
||||
|
||||
/**
|
||||
* Construct a formats list containing all planar sample formats.
|
||||
*/
|
||||
AVFilterFormats *ff_planar_sample_fmts(void);
|
||||
|
||||
/**
|
||||
* Return a format list which contains the intersection of the formats of
|
||||
* a and b. Also, all the references of a, all the references of b, and
|
||||
* a and b themselves will be deallocated.
|
||||
*
|
||||
* If a and b do not share any common formats, neither is modified, and NULL
|
||||
* is returned.
|
||||
*/
|
||||
AVFilterFormats *ff_merge_formats(AVFilterFormats *a, AVFilterFormats *b,
|
||||
enum AVMediaType type);
|
||||
|
||||
/**
|
||||
* Add *ref as a new reference to formats.
|
||||
* That is the pointers will point like in the ascii art below:
|
||||
* ________
|
||||
* |formats |<--------.
|
||||
* | ____ | ____|___________________
|
||||
* | |refs| | | __|_
|
||||
* | |* * | | | | | | AVFilterLink
|
||||
* | |* *--------->|*ref|
|
||||
* | |____| | | |____|
|
||||
* |________| |________________________
|
||||
*/
|
||||
void ff_formats_ref(AVFilterFormats *formats, AVFilterFormats **ref);
|
||||
|
||||
/**
|
||||
* If *ref is non-NULL, remove *ref as a reference to the format list
|
||||
* it currently points to, deallocates that list if this was the last
|
||||
* reference, and sets *ref to NULL.
|
||||
*
|
||||
* Before After
|
||||
* ________ ________ NULL
|
||||
* |formats |<--------. |formats | ^
|
||||
* | ____ | ____|________________ | ____ | ____|________________
|
||||
* | |refs| | | __|_ | |refs| | | __|_
|
||||
* | |* * | | | | | | AVFilterLink | |* * | | | | | | AVFilterLink
|
||||
* | |* *--------->|*ref| | |* | | | |*ref|
|
||||
* | |____| | | |____| | |____| | | |____|
|
||||
* |________| |_____________________ |________| |_____________________
|
||||
*/
|
||||
void ff_formats_unref(AVFilterFormats **ref);
|
||||
|
||||
/**
|
||||
*
|
||||
* Before After
|
||||
* ________ ________
|
||||
* |formats |<---------. |formats |<---------.
|
||||
* | ____ | ___|___ | ____ | ___|___
|
||||
* | |refs| | | | | | |refs| | | | | NULL
|
||||
* | |* *--------->|*oldref| | |* *--------->|*newref| ^
|
||||
* | |* * | | |_______| | |* * | | |_______| ___|___
|
||||
* | |____| | | |____| | | | |
|
||||
* |________| |________| |*oldref|
|
||||
* |_______|
|
||||
*/
|
||||
void ff_formats_changeref(AVFilterFormats **oldref, AVFilterFormats **newref);
|
||||
|
||||
#endif /* AVFILTER_FORMATS_H */
|
||||
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/avassert.h"
|
||||
#include "avfilter.h"
|
||||
#include "bufferqueue.h"
|
||||
#include "framesync.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define OFFSET(member) offsetof(FFFrameSync, member)
|
||||
|
||||
static const char *framesync_name(void *ptr)
|
||||
{
|
||||
return "framesync";
|
||||
}
|
||||
|
||||
static const AVClass framesync_class = {
|
||||
.version = LIBAVUTIL_VERSION_INT,
|
||||
.class_name = "framesync",
|
||||
.item_name = framesync_name,
|
||||
.category = AV_CLASS_CATEGORY_FILTER,
|
||||
.option = NULL,
|
||||
.parent_log_context_offset = OFFSET(parent),
|
||||
};
|
||||
|
||||
enum {
|
||||
STATE_BOF,
|
||||
STATE_RUN,
|
||||
STATE_EOF,
|
||||
};
|
||||
|
||||
void ff_framesync_init(FFFrameSync *fs, void *parent, unsigned nb_in)
|
||||
{
|
||||
fs->class = &framesync_class;
|
||||
fs->parent = parent;
|
||||
fs->nb_in = nb_in;
|
||||
}
|
||||
|
||||
static void framesync_sync_level_update(FFFrameSync *fs)
|
||||
{
|
||||
unsigned i, level = 0;
|
||||
|
||||
for (i = 0; i < fs->nb_in; i++)
|
||||
if (fs->in[i].state != STATE_EOF)
|
||||
level = FFMAX(level, fs->in[i].sync);
|
||||
av_assert0(level <= fs->sync_level);
|
||||
if (level < fs->sync_level)
|
||||
av_log(fs, AV_LOG_VERBOSE, "Sync level %u\n", level);
|
||||
if (level)
|
||||
fs->sync_level = level;
|
||||
else
|
||||
fs->eof = 1;
|
||||
}
|
||||
|
||||
int ff_framesync_configure(FFFrameSync *fs)
|
||||
{
|
||||
unsigned i;
|
||||
int64_t gcd, lcm;
|
||||
|
||||
if (!fs->time_base.num) {
|
||||
for (i = 0; i < fs->nb_in; i++) {
|
||||
if (fs->in[i].sync) {
|
||||
if (fs->time_base.num) {
|
||||
gcd = av_gcd(fs->time_base.den, fs->in[i].time_base.den);
|
||||
lcm = (fs->time_base.den / gcd) * fs->in[i].time_base.den;
|
||||
if (lcm < AV_TIME_BASE / 2) {
|
||||
fs->time_base.den = lcm;
|
||||
fs->time_base.num = av_gcd(fs->time_base.num,
|
||||
fs->in[i].time_base.num);
|
||||
} else {
|
||||
fs->time_base.num = 1;
|
||||
fs->time_base.den = AV_TIME_BASE;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
fs->time_base = fs->in[i].time_base;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fs->time_base.num) {
|
||||
av_log(fs, AV_LOG_ERROR, "Impossible to set time base\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
av_log(fs, AV_LOG_VERBOSE, "Selected %d/%d time base\n",
|
||||
fs->time_base.num, fs->time_base.den);
|
||||
}
|
||||
|
||||
for (i = 0; i < fs->nb_in; i++)
|
||||
fs->in[i].pts = fs->in[i].pts_next = AV_NOPTS_VALUE;
|
||||
fs->sync_level = UINT_MAX;
|
||||
framesync_sync_level_update(fs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void framesync_advance(FFFrameSync *fs)
|
||||
{
|
||||
int latest;
|
||||
unsigned i;
|
||||
int64_t pts;
|
||||
|
||||
if (fs->eof)
|
||||
return;
|
||||
while (!fs->frame_ready) {
|
||||
latest = -1;
|
||||
for (i = 0; i < fs->nb_in; i++) {
|
||||
if (!fs->in[i].have_next) {
|
||||
if (latest < 0 || fs->in[i].pts < fs->in[latest].pts)
|
||||
latest = i;
|
||||
}
|
||||
}
|
||||
if (latest >= 0) {
|
||||
fs->in_request = latest;
|
||||
break;
|
||||
}
|
||||
|
||||
pts = fs->in[0].pts_next;
|
||||
for (i = 1; i < fs->nb_in; i++)
|
||||
if (fs->in[i].pts_next < pts)
|
||||
pts = fs->in[i].pts_next;
|
||||
if (pts == INT64_MAX) {
|
||||
fs->eof = 1;
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < fs->nb_in; i++) {
|
||||
if (fs->in[i].pts_next == pts ||
|
||||
(fs->in[i].before == EXT_INFINITY &&
|
||||
fs->in[i].state == STATE_BOF)) {
|
||||
av_frame_free(&fs->in[i].frame);
|
||||
fs->in[i].frame = fs->in[i].frame_next;
|
||||
fs->in[i].pts = fs->in[i].pts_next;
|
||||
fs->in[i].frame_next = NULL;
|
||||
fs->in[i].pts_next = AV_NOPTS_VALUE;
|
||||
fs->in[i].have_next = 0;
|
||||
fs->in[i].state = fs->in[i].frame ? STATE_RUN : STATE_EOF;
|
||||
if (fs->in[i].sync == fs->sync_level && fs->in[i].frame)
|
||||
fs->frame_ready = 1;
|
||||
if (fs->in[i].state == STATE_EOF &&
|
||||
fs->in[i].after == EXT_STOP)
|
||||
fs->eof = 1;
|
||||
}
|
||||
}
|
||||
if (fs->eof)
|
||||
fs->frame_ready = 0;
|
||||
if (fs->frame_ready)
|
||||
for (i = 0; i < fs->nb_in; i++)
|
||||
if ((fs->in[i].state == STATE_BOF &&
|
||||
fs->in[i].before == EXT_STOP))
|
||||
fs->frame_ready = 0;
|
||||
fs->pts = pts;
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t framesync_pts_extrapolate(FFFrameSync *fs, unsigned in,
|
||||
int64_t pts)
|
||||
{
|
||||
/* Possible enhancement: use the link's frame rate */
|
||||
return pts + 1;
|
||||
}
|
||||
|
||||
static void framesync_inject_frame(FFFrameSync *fs, unsigned in, AVFrame *frame)
|
||||
{
|
||||
int64_t pts;
|
||||
|
||||
av_assert0(!fs->in[in].have_next);
|
||||
if (frame) {
|
||||
pts = av_rescale_q(frame->pts, fs->in[in].time_base, fs->time_base);
|
||||
frame->pts = pts;
|
||||
} else {
|
||||
pts = fs->in[in].state != STATE_RUN || fs->in[in].after == EXT_INFINITY
|
||||
? INT64_MAX : framesync_pts_extrapolate(fs, in, fs->in[in].pts);
|
||||
fs->in[in].sync = 0;
|
||||
framesync_sync_level_update(fs);
|
||||
}
|
||||
fs->in[in].frame_next = frame;
|
||||
fs->in[in].pts_next = pts;
|
||||
fs->in[in].have_next = 1;
|
||||
}
|
||||
|
||||
int ff_framesync_add_frame(FFFrameSync *fs, unsigned in, AVFrame *frame)
|
||||
{
|
||||
av_assert1(in < fs->nb_in);
|
||||
if (!fs->in[in].have_next)
|
||||
framesync_inject_frame(fs, in, frame);
|
||||
else
|
||||
ff_bufqueue_add(fs, &fs->in[in].queue, frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ff_framesync_next(FFFrameSync *fs)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
av_assert0(!fs->frame_ready);
|
||||
for (i = 0; i < fs->nb_in; i++)
|
||||
if (!fs->in[i].have_next && fs->in[i].queue.available)
|
||||
framesync_inject_frame(fs, i, ff_bufqueue_get(&fs->in[i].queue));
|
||||
fs->frame_ready = 0;
|
||||
framesync_advance(fs);
|
||||
}
|
||||
|
||||
void ff_framesync_drop(FFFrameSync *fs)
|
||||
{
|
||||
fs->frame_ready = 0;
|
||||
}
|
||||
|
||||
int ff_framesync_get_frame(FFFrameSync *fs, unsigned in, AVFrame **rframe,
|
||||
unsigned get)
|
||||
{
|
||||
AVFrame *frame;
|
||||
unsigned need_copy = 0, i;
|
||||
int64_t pts_next;
|
||||
int ret;
|
||||
|
||||
if (!fs->in[in].frame) {
|
||||
*rframe = NULL;
|
||||
return 0;
|
||||
}
|
||||
frame = fs->in[in].frame;
|
||||
if (get) {
|
||||
/* Find out if we need to copy the frame: is there another sync
|
||||
stream, and do we know if its current frame will outlast this one? */
|
||||
pts_next = fs->in[in].have_next ? fs->in[in].pts_next : INT64_MAX;
|
||||
for (i = 0; i < fs->nb_in && !need_copy; i++)
|
||||
if (i != in && fs->in[i].sync &&
|
||||
(!fs->in[i].have_next || fs->in[i].pts_next < pts_next))
|
||||
need_copy = 1;
|
||||
if (need_copy) {
|
||||
if (!(frame = av_frame_clone(frame)))
|
||||
return AVERROR(ENOMEM);
|
||||
if ((ret = av_frame_make_writable(frame)) < 0) {
|
||||
av_frame_free(&frame);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
fs->in[in].frame = NULL;
|
||||
}
|
||||
fs->frame_ready = 0;
|
||||
}
|
||||
*rframe = frame;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ff_framesync_uninit(FFFrameSync *fs)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < fs->nb_in; i++) {
|
||||
av_frame_free(&fs->in[i].frame);
|
||||
av_frame_free(&fs->in[i].frame_next);
|
||||
ff_bufqueue_discard_all(&fs->in[i].queue);
|
||||
}
|
||||
}
|
||||
|
||||
int ff_framesync_process_frame(FFFrameSync *fs, unsigned all)
|
||||
{
|
||||
int ret, count = 0;
|
||||
|
||||
av_assert0(fs->on_event);
|
||||
while (1) {
|
||||
ff_framesync_next(fs);
|
||||
if (fs->eof || !fs->frame_ready)
|
||||
break;
|
||||
if ((ret = fs->on_event(fs)) < 0)
|
||||
return ret;
|
||||
ff_framesync_drop(fs);
|
||||
count++;
|
||||
if (!all)
|
||||
break;
|
||||
}
|
||||
if (!count && fs->eof)
|
||||
return AVERROR_EOF;
|
||||
return count;
|
||||
}
|
||||
|
||||
int ff_framesync_filter_frame(FFFrameSync *fs, AVFilterLink *inlink,
|
||||
AVFrame *in)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if ((ret = ff_framesync_process_frame(fs, 1)) < 0)
|
||||
return ret;
|
||||
if ((ret = ff_framesync_add_frame(fs, FF_INLINK_IDX(inlink), in)) < 0)
|
||||
return ret;
|
||||
if ((ret = ff_framesync_process_frame(fs, 0)) < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ff_framesync_request_frame(FFFrameSync *fs, AVFilterLink *outlink)
|
||||
{
|
||||
AVFilterContext *ctx = outlink->src;
|
||||
int input, ret;
|
||||
|
||||
if ((ret = ff_framesync_process_frame(fs, 0)) < 0)
|
||||
return ret;
|
||||
if (ret > 0)
|
||||
return 0;
|
||||
if (fs->eof)
|
||||
return AVERROR_EOF;
|
||||
outlink->flags |= FF_LINK_FLAG_REQUEST_LOOP;
|
||||
input = fs->in_request;
|
||||
ret = ff_request_frame(ctx->inputs[input]);
|
||||
if (ret == AVERROR_EOF) {
|
||||
if ((ret = ff_framesync_add_frame(fs, input, NULL)) < 0)
|
||||
return ret;
|
||||
if ((ret = ff_framesync_process_frame(fs, 0)) < 0)
|
||||
return ret;
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_FRAMESYNC_H
|
||||
#define AVFILTER_FRAMESYNC_H
|
||||
|
||||
#include "bufferqueue.h"
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* Callback-based API similar to dualinput.
|
||||
* Export convenient options.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This API is intended as a helper for filters that have several video
|
||||
* input and need to combine them somehow. If the inputs have different or
|
||||
* variable frame rate, getting the input frames to match requires a rather
|
||||
* complex logic and a few user-tunable options.
|
||||
*
|
||||
* In this API, when a set of synchronized input frames is ready to be
|
||||
* procesed is called a frame event. Frame event can be generated in
|
||||
* response to input frames on any or all inputs and the handling of
|
||||
* situations where some stream extend beyond the beginning or the end of
|
||||
* others can be configured.
|
||||
*
|
||||
* The basic working of this API is the following:
|
||||
*
|
||||
* - When a frame is available on any input, add it using
|
||||
* ff_framesync_add_frame().
|
||||
*
|
||||
* - When a frame event is ready to be processed (i.e. after adding a frame
|
||||
* or when requested on input):
|
||||
* - call ff_framesync_next();
|
||||
* - if fs->frame_ready is true, process the frames;
|
||||
* - call ff_framesync_drop().
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stream extrapolation mode
|
||||
*
|
||||
* Describe how the frames of a stream are extrapolated before the first one
|
||||
* and after EOF to keep sync with possibly longer other streams.
|
||||
*/
|
||||
enum FFFrameSyncExtMode {
|
||||
|
||||
/**
|
||||
* Completely stop all streams with this one.
|
||||
*/
|
||||
EXT_STOP,
|
||||
|
||||
/**
|
||||
* Ignore this stream and continue processing the other ones.
|
||||
*/
|
||||
EXT_NULL,
|
||||
|
||||
/**
|
||||
* Extend the frame to infinity.
|
||||
*/
|
||||
EXT_INFINITY,
|
||||
};
|
||||
|
||||
/**
|
||||
* Input stream structure
|
||||
*/
|
||||
typedef struct FFFrameSyncIn {
|
||||
|
||||
/**
|
||||
* Queue of incoming AVFrame, and NULL to mark EOF
|
||||
*/
|
||||
struct FFBufQueue queue;
|
||||
|
||||
/**
|
||||
* Extrapolation mode for timestamps before the first frame
|
||||
*/
|
||||
enum FFFrameSyncExtMode before;
|
||||
|
||||
/**
|
||||
* Extrapolation mode for timestamps after the last frame
|
||||
*/
|
||||
enum FFFrameSyncExtMode after;
|
||||
|
||||
/**
|
||||
* Time base for the incoming frames
|
||||
*/
|
||||
AVRational time_base;
|
||||
|
||||
/**
|
||||
* Current frame, may be NULL before the first one or after EOF
|
||||
*/
|
||||
AVFrame *frame;
|
||||
|
||||
/**
|
||||
* Next frame, for internal use
|
||||
*/
|
||||
AVFrame *frame_next;
|
||||
|
||||
/**
|
||||
* PTS of the current frame
|
||||
*/
|
||||
int64_t pts;
|
||||
|
||||
/**
|
||||
* PTS of the next frame, for internal use
|
||||
*/
|
||||
int64_t pts_next;
|
||||
|
||||
/**
|
||||
* Boolean flagging the next frame, for internal use
|
||||
*/
|
||||
uint8_t have_next;
|
||||
|
||||
/**
|
||||
* State: before first, in stream or after EOF, for internal use
|
||||
*/
|
||||
uint8_t state;
|
||||
|
||||
/**
|
||||
* Synchronization level: frames on input at the highest sync level will
|
||||
* generate output frame events.
|
||||
*
|
||||
* For example, if inputs #0 and #1 have sync level 2 and input #2 has
|
||||
* sync level 1, then a frame on either input #0 or #1 will generate a
|
||||
* frame event, but not a frame on input #2 until both inputs #0 and #1
|
||||
* have reached EOF.
|
||||
*
|
||||
* If sync is 0, no frame event will be generated.
|
||||
*/
|
||||
unsigned sync;
|
||||
|
||||
} FFFrameSyncIn;
|
||||
|
||||
/**
|
||||
* Frame sync structure.
|
||||
*/
|
||||
typedef struct FFFrameSync {
|
||||
const AVClass *class;
|
||||
void *parent;
|
||||
|
||||
/**
|
||||
* Number of input streams
|
||||
*/
|
||||
unsigned nb_in;
|
||||
|
||||
/**
|
||||
* Time base for the output events
|
||||
*/
|
||||
AVRational time_base;
|
||||
|
||||
/**
|
||||
* Timestamp of the current event
|
||||
*/
|
||||
int64_t pts;
|
||||
|
||||
/**
|
||||
* Callback called when a frame event is ready
|
||||
*/
|
||||
int (*on_event)(struct FFFrameSync *fs);
|
||||
|
||||
/**
|
||||
* Opaque pointer, not used by the API
|
||||
*/
|
||||
void *opaque;
|
||||
|
||||
/**
|
||||
* Index of the input that requires a request
|
||||
*/
|
||||
unsigned in_request;
|
||||
|
||||
/**
|
||||
* Synchronization level: only inputs with the same sync level are sync
|
||||
* sources.
|
||||
*/
|
||||
unsigned sync_level;
|
||||
|
||||
/**
|
||||
* Flag indicating that a frame event is ready
|
||||
*/
|
||||
uint8_t frame_ready;
|
||||
|
||||
/**
|
||||
* Flag indicating that output has reached EOF.
|
||||
*/
|
||||
uint8_t eof;
|
||||
|
||||
/**
|
||||
* Array of inputs; all inputs must be in consecutive memory
|
||||
*/
|
||||
FFFrameSyncIn in[1]; /* must be the last field */
|
||||
|
||||
} FFFrameSync;
|
||||
|
||||
/**
|
||||
* Initialize a frame sync structure.
|
||||
*
|
||||
* The entire structure is expected to be already set to 0.
|
||||
*
|
||||
* @param fs frame sync structure to initialize
|
||||
* @param parent parent object, used for logging
|
||||
* @param nb_in number of inputs
|
||||
*/
|
||||
void ff_framesync_init(FFFrameSync *fs, void *parent, unsigned nb_in);
|
||||
|
||||
/**
|
||||
* Configure a frame sync structure.
|
||||
*
|
||||
* Must be called after all options are set but before all use.
|
||||
*
|
||||
* @return >= 0 for success or a negative error code
|
||||
*/
|
||||
int ff_framesync_configure(FFFrameSync *fs);
|
||||
|
||||
/**
|
||||
* Free all memory currently allocated.
|
||||
*/
|
||||
void ff_framesync_uninit(FFFrameSync *fs);
|
||||
|
||||
/**
|
||||
* Add a frame to an input
|
||||
*
|
||||
* Typically called from the filter_frame() method.
|
||||
*
|
||||
* @param fs frame sync structure
|
||||
* @param in index of the input
|
||||
* @param frame input frame, or NULL for EOF
|
||||
*/
|
||||
int ff_framesync_add_frame(FFFrameSync *fs, unsigned in, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Prepare the next frame event.
|
||||
*
|
||||
* The status of the operation can be found in fs->frame_ready and fs->eof.
|
||||
*/
|
||||
void ff_framesync_next(FFFrameSync *fs);
|
||||
|
||||
/**
|
||||
* Drop the current frame event.
|
||||
*/
|
||||
void ff_framesync_drop(FFFrameSync *fs);
|
||||
|
||||
/**
|
||||
* Get the current frame in an input.
|
||||
*
|
||||
* @param fs frame sync structure
|
||||
* @param in index of the input
|
||||
* @param rframe used to return the current frame (or NULL)
|
||||
* @param get if not zero, the calling code needs to get ownership of
|
||||
* the returned frame; the current frame will either be
|
||||
* duplicated or removed from the framesync structure
|
||||
*/
|
||||
int ff_framesync_get_frame(FFFrameSync *fs, unsigned in, AVFrame **rframe,
|
||||
unsigned get);
|
||||
|
||||
/**
|
||||
* Process one or several frame using the on_event callback.
|
||||
*
|
||||
* @return number of frames processed or negative error code
|
||||
*/
|
||||
int ff_framesync_process_frame(FFFrameSync *fs, unsigned all);
|
||||
|
||||
|
||||
/**
|
||||
* Accept a frame on a filter input.
|
||||
*
|
||||
* This function can be the complete implementation of all filter_frame
|
||||
* methods of a filter using framesync.
|
||||
*/
|
||||
int ff_framesync_filter_frame(FFFrameSync *fs, AVFilterLink *inlink,
|
||||
AVFrame *in);
|
||||
|
||||
/**
|
||||
* Request a frame on the filter output.
|
||||
*
|
||||
* This function can be the complete implementation of all filter_frame
|
||||
* methods of a filter using framesync if it has only one output.
|
||||
*/
|
||||
int ff_framesync_request_frame(FFFrameSync *fs, AVFilterLink *outlink);
|
||||
|
||||
#endif /* AVFILTER_FRAMESYNC_H */
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2010 Nolan Lum <[email protected]>
|
||||
* Copyright (c) 2009 Loren Merritt <[email protected]>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_GRADFUN_H
|
||||
#define AVFILTER_GRADFUN_H
|
||||
|
||||
#include "avfilter.h"
|
||||
|
||||
/// Holds instance-specific information for gradfun.
|
||||
typedef struct GradFunContext {
|
||||
const AVClass *class;
|
||||
float strength;
|
||||
int thresh; ///< threshold for gradient algorithm
|
||||
int radius; ///< blur radius
|
||||
int chroma_w; ///< width of the chroma planes
|
||||
int chroma_h; ///< weight of the chroma planes
|
||||
int chroma_r; ///< blur radius for the chroma planes
|
||||
uint16_t *buf; ///< holds image data for blur algorithm passed into filter.
|
||||
/// DSP functions.
|
||||
void (*filter_line) (uint8_t *dst, const uint8_t *src, const uint16_t *dc, int width, int thresh, const uint16_t *dithers);
|
||||
void (*blur_line) (uint16_t *dc, uint16_t *buf, const uint16_t *buf1, const uint8_t *src, int src_linesize, int width);
|
||||
} GradFunContext;
|
||||
|
||||
void ff_gradfun_init_x86(GradFunContext *gf);
|
||||
|
||||
void ff_gradfun_filter_line_c(uint8_t *dst, const uint8_t *src, const uint16_t *dc, int width, int thresh, const uint16_t *dithers);
|
||||
void ff_gradfun_blur_line_c(uint16_t *dc, uint16_t *buf, const uint16_t *buf1, const uint8_t *src, int src_linesize, int width);
|
||||
|
||||
#endif /* AVFILTER_GRADFUN_H */
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Filter graphs to bad ASCII-art
|
||||
* Copyright (c) 2012 Nicolas George
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "libavutil/channel_layout.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "avfilter.h"
|
||||
#include "avfiltergraph.h"
|
||||
|
||||
static int print_link_prop(AVBPrint *buf, AVFilterLink *link)
|
||||
{
|
||||
char *format;
|
||||
char layout[64];
|
||||
|
||||
if (!buf)
|
||||
buf = &(AVBPrint){ 0 }; /* dummy buffer */
|
||||
switch (link->type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
format = av_x_if_null(av_get_pix_fmt_name(link->format), "?");
|
||||
av_bprintf(buf, "[%dx%d %d:%d %s]", link->w, link->h,
|
||||
link->sample_aspect_ratio.num,
|
||||
link->sample_aspect_ratio.den,
|
||||
format);
|
||||
break;
|
||||
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
av_get_channel_layout_string(layout, sizeof(layout),
|
||||
link->channels, link->channel_layout);
|
||||
format = av_x_if_null(av_get_sample_fmt_name(link->format), "?");
|
||||
av_bprintf(buf, "[%dHz %s:%s]",
|
||||
(int)link->sample_rate, format, layout);
|
||||
break;
|
||||
|
||||
default:
|
||||
av_bprintf(buf, "?");
|
||||
break;
|
||||
}
|
||||
return buf->len;
|
||||
}
|
||||
|
||||
static void avfilter_graph_dump_to_buf(AVBPrint *buf, AVFilterGraph *graph)
|
||||
{
|
||||
unsigned i, j, x, e;
|
||||
|
||||
for (i = 0; i < graph->nb_filters; i++) {
|
||||
AVFilterContext *filter = graph->filters[i];
|
||||
unsigned max_src_name = 0, max_dst_name = 0;
|
||||
unsigned max_in_name = 0, max_out_name = 0;
|
||||
unsigned max_in_fmt = 0, max_out_fmt = 0;
|
||||
unsigned width, height, in_indent;
|
||||
unsigned lname = strlen(filter->name);
|
||||
unsigned ltype = strlen(filter->filter->name);
|
||||
|
||||
for (j = 0; j < filter->nb_inputs; j++) {
|
||||
AVFilterLink *l = filter->inputs[j];
|
||||
unsigned ln = strlen(l->src->name) + 1 + strlen(l->srcpad->name);
|
||||
max_src_name = FFMAX(max_src_name, ln);
|
||||
max_in_name = FFMAX(max_in_name, strlen(l->dstpad->name));
|
||||
max_in_fmt = FFMAX(max_in_fmt, print_link_prop(NULL, l));
|
||||
}
|
||||
for (j = 0; j < filter->nb_outputs; j++) {
|
||||
AVFilterLink *l = filter->outputs[j];
|
||||
unsigned ln = strlen(l->dst->name) + 1 + strlen(l->dstpad->name);
|
||||
max_dst_name = FFMAX(max_dst_name, ln);
|
||||
max_out_name = FFMAX(max_out_name, strlen(l->srcpad->name));
|
||||
max_out_fmt = FFMAX(max_out_fmt, print_link_prop(NULL, l));
|
||||
}
|
||||
in_indent = max_src_name + max_in_name + max_in_fmt;
|
||||
in_indent += in_indent ? 4 : 0;
|
||||
width = FFMAX(lname + 2, ltype + 4);
|
||||
height = FFMAX3(2, filter->nb_inputs, filter->nb_outputs);
|
||||
av_bprint_chars(buf, ' ', in_indent);
|
||||
av_bprintf(buf, "+");
|
||||
av_bprint_chars(buf, '-', width);
|
||||
av_bprintf(buf, "+\n");
|
||||
for (j = 0; j < height; j++) {
|
||||
unsigned in_no = j - (height - filter->nb_inputs ) / 2;
|
||||
unsigned out_no = j - (height - filter->nb_outputs) / 2;
|
||||
|
||||
/* Input link */
|
||||
if (in_no < filter->nb_inputs) {
|
||||
AVFilterLink *l = filter->inputs[in_no];
|
||||
e = buf->len + max_src_name + 2;
|
||||
av_bprintf(buf, "%s:%s", l->src->name, l->srcpad->name);
|
||||
av_bprint_chars(buf, '-', e - buf->len);
|
||||
e = buf->len + max_in_fmt + 2 +
|
||||
max_in_name - strlen(l->dstpad->name);
|
||||
print_link_prop(buf, l);
|
||||
av_bprint_chars(buf, '-', e - buf->len);
|
||||
av_bprintf(buf, "%s", l->dstpad->name);
|
||||
} else {
|
||||
av_bprint_chars(buf, ' ', in_indent);
|
||||
}
|
||||
|
||||
/* Filter */
|
||||
av_bprintf(buf, "|");
|
||||
if (j == (height - 2) / 2) {
|
||||
x = (width - lname) / 2;
|
||||
av_bprintf(buf, "%*s%-*s", x, "", width - x, filter->name);
|
||||
} else if (j == (height - 2) / 2 + 1) {
|
||||
x = (width - ltype - 2) / 2;
|
||||
av_bprintf(buf, "%*s(%s)%*s", x, "", filter->filter->name,
|
||||
width - ltype - 2 - x, "");
|
||||
} else {
|
||||
av_bprint_chars(buf, ' ', width);
|
||||
}
|
||||
av_bprintf(buf, "|");
|
||||
|
||||
/* Output link */
|
||||
if (out_no < filter->nb_outputs) {
|
||||
AVFilterLink *l = filter->outputs[out_no];
|
||||
unsigned ln = strlen(l->dst->name) + 1 +
|
||||
strlen(l->dstpad->name);
|
||||
e = buf->len + max_out_name + 2;
|
||||
av_bprintf(buf, "%s", l->srcpad->name);
|
||||
av_bprint_chars(buf, '-', e - buf->len);
|
||||
e = buf->len + max_out_fmt + 2 +
|
||||
max_dst_name - ln;
|
||||
print_link_prop(buf, l);
|
||||
av_bprint_chars(buf, '-', e - buf->len);
|
||||
av_bprintf(buf, "%s:%s", l->dst->name, l->dstpad->name);
|
||||
}
|
||||
av_bprintf(buf, "\n");
|
||||
}
|
||||
av_bprint_chars(buf, ' ', in_indent);
|
||||
av_bprintf(buf, "+");
|
||||
av_bprint_chars(buf, '-', width);
|
||||
av_bprintf(buf, "+\n");
|
||||
av_bprintf(buf, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
char *avfilter_graph_dump(AVFilterGraph *graph, const char *options)
|
||||
{
|
||||
AVBPrint buf;
|
||||
char *dump;
|
||||
|
||||
av_bprint_init(&buf, 0, 0);
|
||||
avfilter_graph_dump_to_buf(&buf, graph);
|
||||
av_bprint_init(&buf, buf.len + 1, buf.len + 1);
|
||||
avfilter_graph_dump_to_buf(&buf, graph);
|
||||
av_bprint_finalize(&buf, &dump);
|
||||
return dump;
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
/*
|
||||
* filter graph parser
|
||||
* Copyright (c) 2008 Vitor Sessak
|
||||
* Copyright (c) 2007 Bobby Bingham
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/mem.h"
|
||||
#include "avfilter.h"
|
||||
|
||||
#define WHITESPACES " \n\t"
|
||||
|
||||
/**
|
||||
* Link two filters together.
|
||||
*
|
||||
* @see avfilter_link()
|
||||
*/
|
||||
static int link_filter(AVFilterContext *src, int srcpad,
|
||||
AVFilterContext *dst, int dstpad,
|
||||
void *log_ctx)
|
||||
{
|
||||
int ret;
|
||||
if ((ret = avfilter_link(src, srcpad, dst, dstpad))) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Cannot create the link %s:%d -> %s:%d\n",
|
||||
src->filter->name, srcpad, dst->filter->name, dstpad);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the name of a link, which has the format "[linkname]".
|
||||
*
|
||||
* @return a pointer (that need to be freed after use) to the name
|
||||
* between parenthesis
|
||||
*/
|
||||
static char *parse_link_name(const char **buf, void *log_ctx)
|
||||
{
|
||||
const char *start = *buf;
|
||||
char *name;
|
||||
(*buf)++;
|
||||
|
||||
name = av_get_token(buf, "]");
|
||||
|
||||
if (!name[0]) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Bad (empty?) label found in the following: \"%s\".\n", start);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (*(*buf)++ != ']') {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Mismatched '[' found in the following: \"%s\".\n", start);
|
||||
fail:
|
||||
av_freep(&name);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of a filter, initialize and insert it in the
|
||||
* filtergraph in *ctx.
|
||||
*
|
||||
* @param filt_ctx put here a filter context in case of successful creation and configuration, NULL otherwise.
|
||||
* @param ctx the filtergraph context
|
||||
* @param index an index which is supposed to be unique for each filter instance added to the filtergraph
|
||||
* @param filt_name the name of the filter to create
|
||||
* @param args the arguments provided to the filter during its initialization
|
||||
* @param log_ctx the log context to use
|
||||
* @return >= 0 in case of success, a negative AVERROR code otherwise
|
||||
*/
|
||||
static int create_filter(AVFilterContext **filt_ctx, AVFilterGraph *ctx, int index,
|
||||
const char *filt_name, const char *args, void *log_ctx)
|
||||
{
|
||||
AVFilter *filt;
|
||||
char inst_name[30];
|
||||
char *tmp_args = NULL;
|
||||
int ret;
|
||||
|
||||
snprintf(inst_name, sizeof(inst_name), "Parsed_%s_%d", filt_name, index);
|
||||
|
||||
filt = avfilter_get_by_name(filt_name);
|
||||
|
||||
if (!filt) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No such filter: '%s'\n", filt_name);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
*filt_ctx = avfilter_graph_alloc_filter(ctx, filt, inst_name);
|
||||
if (!*filt_ctx) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Error creating filter '%s'\n", filt_name);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
|
||||
if (!strcmp(filt_name, "scale") && args && !strstr(args, "flags") &&
|
||||
ctx->scale_sws_opts) {
|
||||
tmp_args = av_asprintf("%s:%s",
|
||||
args, ctx->scale_sws_opts);
|
||||
if (!tmp_args)
|
||||
return AVERROR(ENOMEM);
|
||||
args = tmp_args;
|
||||
}
|
||||
|
||||
ret = avfilter_init_str(*filt_ctx, args);
|
||||
if (ret < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Error initializing filter '%s'", filt_name);
|
||||
if (args)
|
||||
av_log(log_ctx, AV_LOG_ERROR, " with args '%s'", args);
|
||||
av_log(log_ctx, AV_LOG_ERROR, "\n");
|
||||
}
|
||||
|
||||
av_free(tmp_args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string of the form FILTER_NAME[=PARAMS], and create a
|
||||
* corresponding filter instance which is added to graph with
|
||||
* create_filter().
|
||||
*
|
||||
* @param filt_ctx Pointer that is set to the created and configured filter
|
||||
* context on success, set to NULL on failure.
|
||||
* @param filt_ctx put here a pointer to the created filter context on
|
||||
* success, NULL otherwise
|
||||
* @param buf pointer to the buffer to parse, *buf will be updated to
|
||||
* point to the char next after the parsed string
|
||||
* @param index an index which is assigned to the created filter
|
||||
* instance, and which is supposed to be unique for each filter
|
||||
* instance added to the filtergraph
|
||||
* @return >= 0 in case of success, a negative AVERROR code otherwise
|
||||
*/
|
||||
static int parse_filter(AVFilterContext **filt_ctx, const char **buf, AVFilterGraph *graph,
|
||||
int index, void *log_ctx)
|
||||
{
|
||||
char *opts = NULL;
|
||||
char *name = av_get_token(buf, "=,;[\n");
|
||||
int ret;
|
||||
|
||||
if (**buf == '=') {
|
||||
(*buf)++;
|
||||
opts = av_get_token(buf, "[],;\n");
|
||||
}
|
||||
|
||||
ret = create_filter(filt_ctx, graph, index, name, opts, log_ctx);
|
||||
av_free(name);
|
||||
av_free(opts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
AVFilterInOut *avfilter_inout_alloc(void)
|
||||
{
|
||||
return av_mallocz(sizeof(AVFilterInOut));
|
||||
}
|
||||
|
||||
void avfilter_inout_free(AVFilterInOut **inout)
|
||||
{
|
||||
while (*inout) {
|
||||
AVFilterInOut *next = (*inout)->next;
|
||||
av_freep(&(*inout)->name);
|
||||
av_freep(inout);
|
||||
*inout = next;
|
||||
}
|
||||
}
|
||||
|
||||
static AVFilterInOut *extract_inout(const char *label, AVFilterInOut **links)
|
||||
{
|
||||
AVFilterInOut *ret;
|
||||
|
||||
while (*links && (!(*links)->name || strcmp((*links)->name, label)))
|
||||
links = &((*links)->next);
|
||||
|
||||
ret = *links;
|
||||
|
||||
if (ret) {
|
||||
*links = ret->next;
|
||||
ret->next = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void insert_inout(AVFilterInOut **inouts, AVFilterInOut *element)
|
||||
{
|
||||
element->next = *inouts;
|
||||
*inouts = element;
|
||||
}
|
||||
|
||||
static void append_inout(AVFilterInOut **inouts, AVFilterInOut **element)
|
||||
{
|
||||
while (*inouts && (*inouts)->next)
|
||||
inouts = &((*inouts)->next);
|
||||
|
||||
if (!*inouts)
|
||||
*inouts = *element;
|
||||
else
|
||||
(*inouts)->next = *element;
|
||||
*element = NULL;
|
||||
}
|
||||
|
||||
static int link_filter_inouts(AVFilterContext *filt_ctx,
|
||||
AVFilterInOut **curr_inputs,
|
||||
AVFilterInOut **open_inputs, void *log_ctx)
|
||||
{
|
||||
int pad, ret;
|
||||
|
||||
for (pad = 0; pad < filt_ctx->nb_inputs; pad++) {
|
||||
AVFilterInOut *p = *curr_inputs;
|
||||
|
||||
if (p) {
|
||||
*curr_inputs = (*curr_inputs)->next;
|
||||
p->next = NULL;
|
||||
} else if (!(p = av_mallocz(sizeof(*p))))
|
||||
return AVERROR(ENOMEM);
|
||||
|
||||
if (p->filter_ctx) {
|
||||
ret = link_filter(p->filter_ctx, p->pad_idx, filt_ctx, pad, log_ctx);
|
||||
av_free(p->name);
|
||||
av_free(p);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
p->filter_ctx = filt_ctx;
|
||||
p->pad_idx = pad;
|
||||
append_inout(open_inputs, &p);
|
||||
}
|
||||
}
|
||||
|
||||
if (*curr_inputs) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Too many inputs specified for the \"%s\" filter.\n",
|
||||
filt_ctx->filter->name);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
pad = filt_ctx->nb_outputs;
|
||||
while (pad--) {
|
||||
AVFilterInOut *currlinkn = av_mallocz(sizeof(AVFilterInOut));
|
||||
if (!currlinkn)
|
||||
return AVERROR(ENOMEM);
|
||||
currlinkn->filter_ctx = filt_ctx;
|
||||
currlinkn->pad_idx = pad;
|
||||
insert_inout(curr_inputs, currlinkn);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_inputs(const char **buf, AVFilterInOut **curr_inputs,
|
||||
AVFilterInOut **open_outputs, void *log_ctx)
|
||||
{
|
||||
AVFilterInOut *parsed_inputs = NULL;
|
||||
int pad = 0;
|
||||
|
||||
while (**buf == '[') {
|
||||
char *name = parse_link_name(buf, log_ctx);
|
||||
AVFilterInOut *match;
|
||||
|
||||
if (!name)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
/* First check if the label is not in the open_outputs list */
|
||||
match = extract_inout(name, open_outputs);
|
||||
|
||||
if (match) {
|
||||
av_free(name);
|
||||
} else {
|
||||
/* Not in the list, so add it as an input */
|
||||
if (!(match = av_mallocz(sizeof(AVFilterInOut)))) {
|
||||
av_free(name);
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
match->name = name;
|
||||
match->pad_idx = pad;
|
||||
}
|
||||
|
||||
append_inout(&parsed_inputs, &match);
|
||||
|
||||
*buf += strspn(*buf, WHITESPACES);
|
||||
pad++;
|
||||
}
|
||||
|
||||
append_inout(&parsed_inputs, curr_inputs);
|
||||
*curr_inputs = parsed_inputs;
|
||||
|
||||
return pad;
|
||||
}
|
||||
|
||||
static int parse_outputs(const char **buf, AVFilterInOut **curr_inputs,
|
||||
AVFilterInOut **open_inputs,
|
||||
AVFilterInOut **open_outputs, void *log_ctx)
|
||||
{
|
||||
int ret, pad = 0;
|
||||
|
||||
while (**buf == '[') {
|
||||
char *name = parse_link_name(buf, log_ctx);
|
||||
AVFilterInOut *match;
|
||||
|
||||
AVFilterInOut *input = *curr_inputs;
|
||||
|
||||
if (!name)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if (!input) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"No output pad can be associated to link label '%s'.\n", name);
|
||||
av_free(name);
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
*curr_inputs = (*curr_inputs)->next;
|
||||
|
||||
/* First check if the label is not in the open_inputs list */
|
||||
match = extract_inout(name, open_inputs);
|
||||
|
||||
if (match) {
|
||||
if ((ret = link_filter(input->filter_ctx, input->pad_idx,
|
||||
match->filter_ctx, match->pad_idx, log_ctx)) < 0) {
|
||||
av_free(name);
|
||||
return ret;
|
||||
}
|
||||
av_free(match->name);
|
||||
av_free(name);
|
||||
av_free(match);
|
||||
av_free(input);
|
||||
} else {
|
||||
/* Not in the list, so add the first input as a open_output */
|
||||
input->name = name;
|
||||
insert_inout(open_outputs, input);
|
||||
}
|
||||
*buf += strspn(*buf, WHITESPACES);
|
||||
pad++;
|
||||
}
|
||||
|
||||
return pad;
|
||||
}
|
||||
|
||||
static int parse_sws_flags(const char **buf, AVFilterGraph *graph)
|
||||
{
|
||||
char *p = strchr(*buf, ';');
|
||||
|
||||
if (strncmp(*buf, "sws_flags=", 10))
|
||||
return 0;
|
||||
|
||||
if (!p) {
|
||||
av_log(graph, AV_LOG_ERROR, "sws_flags not terminated with ';'.\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
*buf += 4; // keep the 'flags=' part
|
||||
|
||||
av_freep(&graph->scale_sws_opts);
|
||||
if (!(graph->scale_sws_opts = av_mallocz(p - *buf + 1)))
|
||||
return AVERROR(ENOMEM);
|
||||
av_strlcpy(graph->scale_sws_opts, *buf, p - *buf + 1);
|
||||
|
||||
*buf = p + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
|
||||
AVFilterInOut **inputs,
|
||||
AVFilterInOut **outputs)
|
||||
{
|
||||
int index = 0, ret = 0;
|
||||
char chr = 0;
|
||||
|
||||
AVFilterInOut *curr_inputs = NULL, *open_inputs = NULL, *open_outputs = NULL;
|
||||
|
||||
filters += strspn(filters, WHITESPACES);
|
||||
|
||||
if ((ret = parse_sws_flags(&filters, graph)) < 0)
|
||||
goto fail;
|
||||
|
||||
do {
|
||||
AVFilterContext *filter;
|
||||
filters += strspn(filters, WHITESPACES);
|
||||
|
||||
if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, graph)) < 0)
|
||||
goto end;
|
||||
if ((ret = parse_filter(&filter, &filters, graph, index, graph)) < 0)
|
||||
goto end;
|
||||
|
||||
|
||||
if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, graph)) < 0)
|
||||
goto end;
|
||||
|
||||
if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs,
|
||||
graph)) < 0)
|
||||
goto end;
|
||||
|
||||
filters += strspn(filters, WHITESPACES);
|
||||
chr = *filters++;
|
||||
|
||||
if (chr == ';' && curr_inputs)
|
||||
append_inout(&open_outputs, &curr_inputs);
|
||||
index++;
|
||||
} while (chr == ',' || chr == ';');
|
||||
|
||||
if (chr) {
|
||||
av_log(graph, AV_LOG_ERROR,
|
||||
"Unable to parse graph description substring: \"%s\"\n",
|
||||
filters - 1);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
append_inout(&open_outputs, &curr_inputs);
|
||||
|
||||
|
||||
*inputs = open_inputs;
|
||||
*outputs = open_outputs;
|
||||
return 0;
|
||||
|
||||
fail:end:
|
||||
while (graph->nb_filters)
|
||||
avfilter_free(graph->filters[0]);
|
||||
av_freep(&graph->filters);
|
||||
avfilter_inout_free(&open_inputs);
|
||||
avfilter_inout_free(&open_outputs);
|
||||
avfilter_inout_free(&curr_inputs);
|
||||
|
||||
*inputs = NULL;
|
||||
*outputs = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if HAVE_INCOMPATIBLE_LIBAV_ABI || !FF_API_OLD_GRAPH_PARSE
|
||||
int avfilter_graph_parse(AVFilterGraph *graph, const char *filters,
|
||||
AVFilterInOut *open_inputs,
|
||||
AVFilterInOut *open_outputs, void *log_ctx)
|
||||
{
|
||||
int ret;
|
||||
AVFilterInOut *cur, *match, *inputs = NULL, *outputs = NULL;
|
||||
|
||||
if ((ret = avfilter_graph_parse2(graph, filters, &inputs, &outputs)) < 0)
|
||||
goto fail;
|
||||
|
||||
/* First input can be omitted if it is "[in]" */
|
||||
if (inputs && !inputs->name)
|
||||
inputs->name = av_strdup("in");
|
||||
for (cur = inputs; cur; cur = cur->next) {
|
||||
if (!cur->name) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Not enough inputs specified for the \"%s\" filter.\n",
|
||||
cur->filter_ctx->filter->name);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
if (!(match = extract_inout(cur->name, &open_outputs)))
|
||||
continue;
|
||||
ret = avfilter_link(match->filter_ctx, match->pad_idx,
|
||||
cur->filter_ctx, cur->pad_idx);
|
||||
avfilter_inout_free(&match);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Last output can be omitted if it is "[out]" */
|
||||
if (outputs && !outputs->name)
|
||||
outputs->name = av_strdup("out");
|
||||
for (cur = outputs; cur; cur = cur->next) {
|
||||
if (!cur->name) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid filterchain containing an unlabelled output pad: \"%s\"\n",
|
||||
filters);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto fail;
|
||||
}
|
||||
if (!(match = extract_inout(cur->name, &open_inputs)))
|
||||
continue;
|
||||
ret = avfilter_link(cur->filter_ctx, cur->pad_idx,
|
||||
match->filter_ctx, match->pad_idx);
|
||||
avfilter_inout_free(&match);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
fail:
|
||||
if (ret < 0) {
|
||||
while (graph->nb_filters)
|
||||
avfilter_free(graph->filters[0]);
|
||||
av_freep(&graph->filters);
|
||||
}
|
||||
avfilter_inout_free(&inputs);
|
||||
avfilter_inout_free(&outputs);
|
||||
avfilter_inout_free(&open_inputs);
|
||||
avfilter_inout_free(&open_outputs);
|
||||
return ret;
|
||||
#else
|
||||
int avfilter_graph_parse(AVFilterGraph *graph, const char *filters,
|
||||
AVFilterInOut **inputs, AVFilterInOut **outputs,
|
||||
void *log_ctx)
|
||||
{
|
||||
return avfilter_graph_parse_ptr(graph, filters, inputs, outputs, log_ctx);
|
||||
#endif
|
||||
}
|
||||
|
||||
int avfilter_graph_parse_ptr(AVFilterGraph *graph, const char *filters,
|
||||
AVFilterInOut **open_inputs_ptr, AVFilterInOut **open_outputs_ptr,
|
||||
void *log_ctx)
|
||||
{
|
||||
int index = 0, ret = 0;
|
||||
char chr = 0;
|
||||
|
||||
AVFilterInOut *curr_inputs = NULL;
|
||||
AVFilterInOut *open_inputs = open_inputs_ptr ? *open_inputs_ptr : NULL;
|
||||
AVFilterInOut *open_outputs = open_outputs_ptr ? *open_outputs_ptr : NULL;
|
||||
|
||||
if ((ret = parse_sws_flags(&filters, graph)) < 0)
|
||||
goto end;
|
||||
|
||||
do {
|
||||
AVFilterContext *filter;
|
||||
const char *filterchain = filters;
|
||||
filters += strspn(filters, WHITESPACES);
|
||||
|
||||
if ((ret = parse_inputs(&filters, &curr_inputs, &open_outputs, log_ctx)) < 0)
|
||||
goto end;
|
||||
|
||||
if ((ret = parse_filter(&filter, &filters, graph, index, log_ctx)) < 0)
|
||||
goto end;
|
||||
|
||||
if (filter->nb_inputs == 1 && !curr_inputs && !index) {
|
||||
/* First input pad, assume it is "[in]" if not specified */
|
||||
const char *tmp = "[in]";
|
||||
if ((ret = parse_inputs(&tmp, &curr_inputs, &open_outputs, log_ctx)) < 0)
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((ret = link_filter_inouts(filter, &curr_inputs, &open_inputs, log_ctx)) < 0)
|
||||
goto end;
|
||||
|
||||
if ((ret = parse_outputs(&filters, &curr_inputs, &open_inputs, &open_outputs,
|
||||
log_ctx)) < 0)
|
||||
goto end;
|
||||
|
||||
filters += strspn(filters, WHITESPACES);
|
||||
chr = *filters++;
|
||||
|
||||
if (chr == ';' && curr_inputs) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Invalid filterchain containing an unlabelled output pad: \"%s\"\n",
|
||||
filterchain);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
index++;
|
||||
} while (chr == ',' || chr == ';');
|
||||
|
||||
if (chr) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Unable to parse graph description substring: \"%s\"\n",
|
||||
filters - 1);
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (curr_inputs) {
|
||||
/* Last output pad, assume it is "[out]" if not specified */
|
||||
const char *tmp = "[out]";
|
||||
if ((ret = parse_outputs(&tmp, &curr_inputs, &open_inputs, &open_outputs,
|
||||
log_ctx)) < 0)
|
||||
goto end;
|
||||
}
|
||||
|
||||
end:
|
||||
/* clear open_in/outputs only if not passed as parameters */
|
||||
if (open_inputs_ptr) *open_inputs_ptr = open_inputs;
|
||||
else avfilter_inout_free(&open_inputs);
|
||||
if (open_outputs_ptr) *open_outputs_ptr = open_outputs;
|
||||
else avfilter_inout_free(&open_outputs);
|
||||
avfilter_inout_free(&curr_inputs);
|
||||
|
||||
if (ret < 0) {
|
||||
while (graph->nb_filters)
|
||||
avfilter_free(graph->filters[0]);
|
||||
av_freep(&graph->filters);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_INTERNAL_H
|
||||
#define AVFILTER_INTERNAL_H
|
||||
|
||||
/**
|
||||
* @file
|
||||
* internal API functions
|
||||
*/
|
||||
|
||||
#include "libavutil/internal.h"
|
||||
#include "avfilter.h"
|
||||
#include "avfiltergraph.h"
|
||||
#include "formats.h"
|
||||
#include "thread.h"
|
||||
#include "version.h"
|
||||
#include "video.h"
|
||||
|
||||
#define POOL_SIZE 32
|
||||
typedef struct AVFilterPool {
|
||||
AVFilterBufferRef *pic[POOL_SIZE];
|
||||
int count;
|
||||
int refcount;
|
||||
int draining;
|
||||
} AVFilterPool;
|
||||
|
||||
typedef struct AVFilterCommand {
|
||||
double time; ///< time expressed in seconds
|
||||
char *command; ///< command
|
||||
char *arg; ///< optional argument for the command
|
||||
int flags;
|
||||
struct AVFilterCommand *next;
|
||||
} AVFilterCommand;
|
||||
|
||||
/**
|
||||
* Update the position of a link in the age heap.
|
||||
*/
|
||||
void ff_avfilter_graph_update_heap(AVFilterGraph *graph, AVFilterLink *link);
|
||||
|
||||
#if !FF_API_AVFILTERPAD_PUBLIC
|
||||
/**
|
||||
* A filter pad used for either input or output.
|
||||
*/
|
||||
struct AVFilterPad {
|
||||
/**
|
||||
* Pad name. The name is unique among inputs and among outputs, but an
|
||||
* input may have the same name as an output. This may be NULL if this
|
||||
* pad has no need to ever be referenced by name.
|
||||
*/
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* AVFilterPad type.
|
||||
*/
|
||||
enum AVMediaType type;
|
||||
|
||||
/**
|
||||
* Callback function to get a video buffer. If NULL, the filter system will
|
||||
* use ff_default_get_video_buffer().
|
||||
*
|
||||
* Input video pads only.
|
||||
*/
|
||||
AVFrame *(*get_video_buffer)(AVFilterLink *link, int w, int h);
|
||||
|
||||
/**
|
||||
* Callback function to get an audio buffer. If NULL, the filter system will
|
||||
* use ff_default_get_audio_buffer().
|
||||
*
|
||||
* Input audio pads only.
|
||||
*/
|
||||
AVFrame *(*get_audio_buffer)(AVFilterLink *link, int nb_samples);
|
||||
|
||||
/**
|
||||
* Filtering callback. This is where a filter receives a frame with
|
||||
* audio/video data and should do its processing.
|
||||
*
|
||||
* Input pads only.
|
||||
*
|
||||
* @return >= 0 on success, a negative AVERROR on error. This function
|
||||
* must ensure that samplesref is properly unreferenced on error if it
|
||||
* hasn't been passed on to another filter.
|
||||
*/
|
||||
int (*filter_frame)(AVFilterLink *link, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Frame poll callback. This returns the number of immediately available
|
||||
* samples. It should return a positive value if the next request_frame()
|
||||
* is guaranteed to return one frame (with no delay).
|
||||
*
|
||||
* Defaults to just calling the source poll_frame() method.
|
||||
*
|
||||
* Output pads only.
|
||||
*/
|
||||
int (*poll_frame)(AVFilterLink *link);
|
||||
|
||||
/**
|
||||
* Frame request callback. A call to this should result in at least one
|
||||
* frame being output over the given link. This should return zero on
|
||||
* success, and another value on error.
|
||||
*
|
||||
* Output pads only.
|
||||
*/
|
||||
int (*request_frame)(AVFilterLink *link);
|
||||
|
||||
/**
|
||||
* Link configuration callback.
|
||||
*
|
||||
* For output pads, this should set the link properties such as
|
||||
* width/height. This should NOT set the format property - that is
|
||||
* negotiated between filters by the filter system using the
|
||||
* query_formats() callback before this function is called.
|
||||
*
|
||||
* For input pads, this should check the properties of the link, and update
|
||||
* the filter's internal state as necessary.
|
||||
*
|
||||
* For both input and output filters, this should return zero on success,
|
||||
* and another value on error.
|
||||
*/
|
||||
int (*config_props)(AVFilterLink *link);
|
||||
|
||||
/**
|
||||
* The filter expects a fifo to be inserted on its input link,
|
||||
* typically because it has a delay.
|
||||
*
|
||||
* input pads only.
|
||||
*/
|
||||
int needs_fifo;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct AVFilterGraphInternal {
|
||||
void *thread;
|
||||
avfilter_execute_func *thread_execute;
|
||||
};
|
||||
|
||||
struct AVFilterInternal {
|
||||
avfilter_execute_func *execute;
|
||||
};
|
||||
|
||||
#if FF_API_AVFILTERBUFFER
|
||||
/** default handler for freeing audio/video buffer when there are no references left */
|
||||
void ff_avfilter_default_free_buffer(AVFilterBuffer *buf);
|
||||
#endif
|
||||
|
||||
/** Tell is a format is contained in the provided list terminated by -1. */
|
||||
int ff_fmt_is_in(int fmt, const int *fmts);
|
||||
|
||||
/* Functions to parse audio format arguments */
|
||||
|
||||
/**
|
||||
* Parse a pixel format.
|
||||
*
|
||||
* @param ret pixel format pointer to where the value should be written
|
||||
* @param arg string to parse
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_parse_pixel_format(enum AVPixelFormat *ret, const char *arg, void *log_ctx);
|
||||
|
||||
/**
|
||||
* Parse a sample rate.
|
||||
*
|
||||
* @param ret unsigned integer pointer to where the value should be written
|
||||
* @param arg string to parse
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_parse_sample_rate(int *ret, const char *arg, void *log_ctx);
|
||||
|
||||
/**
|
||||
* Parse a time base.
|
||||
*
|
||||
* @param ret unsigned AVRational pointer to where the value should be written
|
||||
* @param arg string to parse
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_parse_time_base(AVRational *ret, const char *arg, void *log_ctx);
|
||||
|
||||
/**
|
||||
* Parse a sample format name or a corresponding integer representation.
|
||||
*
|
||||
* @param ret integer pointer to where the value should be written
|
||||
* @param arg string to parse
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_parse_sample_format(int *ret, const char *arg, void *log_ctx);
|
||||
|
||||
/**
|
||||
* Parse a channel layout or a corresponding integer representation.
|
||||
*
|
||||
* @param ret 64bit integer pointer to where the value should be written.
|
||||
* @param nret integer pointer to the number of channels;
|
||||
* if not NULL, then unknown channel layouts are accepted
|
||||
* @param arg string to parse
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_parse_channel_layout(int64_t *ret, int *nret, const char *arg,
|
||||
void *log_ctx);
|
||||
|
||||
void ff_update_link_current_pts(AVFilterLink *link, int64_t pts);
|
||||
|
||||
void ff_command_queue_pop(AVFilterContext *filter);
|
||||
|
||||
/* misc trace functions */
|
||||
|
||||
/* #define FF_AVFILTER_TRACE */
|
||||
|
||||
#ifdef FF_AVFILTER_TRACE
|
||||
# define ff_tlog(pctx, ...) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__)
|
||||
#else
|
||||
# define ff_tlog(pctx, ...) do { if (0) av_log(pctx, AV_LOG_DEBUG, __VA_ARGS__); } while (0)
|
||||
#endif
|
||||
|
||||
#define FF_TPRINTF_START(ctx, func) ff_tlog(NULL, "%-16s: ", #func)
|
||||
|
||||
char *ff_get_ref_perms_string(char *buf, size_t buf_size, int perms);
|
||||
|
||||
void ff_tlog_ref(void *ctx, AVFrame *ref, int end);
|
||||
|
||||
void ff_tlog_link(void *ctx, AVFilterLink *link, int end);
|
||||
|
||||
/**
|
||||
* Insert a new pad.
|
||||
*
|
||||
* @param idx Insertion point. Pad is inserted at the end if this point
|
||||
* is beyond the end of the list of pads.
|
||||
* @param count Pointer to the number of pads in the list
|
||||
* @param padidx_off Offset within an AVFilterLink structure to the element
|
||||
* to increment when inserting a new pad causes link
|
||||
* numbering to change
|
||||
* @param pads Pointer to the pointer to the beginning of the list of pads
|
||||
* @param links Pointer to the pointer to the beginning of the list of links
|
||||
* @param newpad The new pad to add. A copy is made when adding.
|
||||
* @return >= 0 in case of success, a negative AVERROR code on error
|
||||
*/
|
||||
int ff_insert_pad(unsigned idx, unsigned *count, size_t padidx_off,
|
||||
AVFilterPad **pads, AVFilterLink ***links,
|
||||
AVFilterPad *newpad);
|
||||
|
||||
/** Insert a new input pad for the filter. */
|
||||
static inline int ff_insert_inpad(AVFilterContext *f, unsigned index,
|
||||
AVFilterPad *p)
|
||||
{
|
||||
int ret = ff_insert_pad(index, &f->nb_inputs, offsetof(AVFilterLink, dstpad),
|
||||
&f->input_pads, &f->inputs, p);
|
||||
#if FF_API_FOO_COUNT
|
||||
FF_DISABLE_DEPRECATION_WARNINGS
|
||||
f->input_count = f->nb_inputs;
|
||||
FF_ENABLE_DEPRECATION_WARNINGS
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Insert a new output pad for the filter. */
|
||||
static inline int ff_insert_outpad(AVFilterContext *f, unsigned index,
|
||||
AVFilterPad *p)
|
||||
{
|
||||
int ret = ff_insert_pad(index, &f->nb_outputs, offsetof(AVFilterLink, srcpad),
|
||||
&f->output_pads, &f->outputs, p);
|
||||
#if FF_API_FOO_COUNT
|
||||
FF_DISABLE_DEPRECATION_WARNINGS
|
||||
f->output_count = f->nb_outputs;
|
||||
FF_ENABLE_DEPRECATION_WARNINGS
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll a frame from the filter chain.
|
||||
*
|
||||
* @param link the input link
|
||||
* @return the number of immediately available frames, a negative
|
||||
* number in case of error
|
||||
*/
|
||||
int ff_poll_frame(AVFilterLink *link);
|
||||
|
||||
/**
|
||||
* Request an input frame from the filter at the other end of the link.
|
||||
*
|
||||
* @param link the input link
|
||||
* @return zero on success
|
||||
*/
|
||||
int ff_request_frame(AVFilterLink *link);
|
||||
|
||||
#define AVFILTER_DEFINE_CLASS(fname) \
|
||||
static const AVClass fname##_class = { \
|
||||
.class_name = #fname, \
|
||||
.item_name = av_default_item_name, \
|
||||
.option = fname##_options, \
|
||||
.version = LIBAVUTIL_VERSION_INT, \
|
||||
.category = AV_CLASS_CATEGORY_FILTER, \
|
||||
}
|
||||
|
||||
AVFilterBufferRef *ff_copy_buffer_ref(AVFilterLink *outlink,
|
||||
AVFilterBufferRef *ref);
|
||||
|
||||
/**
|
||||
* Find the index of a link.
|
||||
*
|
||||
* I.e. find i such that link == ctx->(in|out)puts[i]
|
||||
*/
|
||||
#define FF_INLINK_IDX(link) ((int)((link)->dstpad - (link)->dst->input_pads))
|
||||
#define FF_OUTLINK_IDX(link) ((int)((link)->srcpad - (link)->src->output_pads))
|
||||
|
||||
int ff_buffersink_read_compat(AVFilterContext *ctx, AVFilterBufferRef **buf);
|
||||
int ff_buffersink_read_samples_compat(AVFilterContext *ctx, AVFilterBufferRef **pbuf,
|
||||
int nb_samples);
|
||||
/**
|
||||
* Send a frame of data to the next filter.
|
||||
*
|
||||
* @param link the output link over which the data is being sent
|
||||
* @param frame a reference to the buffer of data being sent. The
|
||||
* receiving filter will free this reference when it no longer
|
||||
* needs it or pass it on to the next filter.
|
||||
*
|
||||
* @return >= 0 on success, a negative AVERROR on error. The receiving filter
|
||||
* is responsible for unreferencing frame in case of error.
|
||||
*/
|
||||
int ff_filter_frame(AVFilterLink *link, AVFrame *frame);
|
||||
|
||||
/**
|
||||
* Flags for AVFilterLink.flags.
|
||||
*/
|
||||
enum {
|
||||
|
||||
/**
|
||||
* Frame requests may need to loop in order to be fulfilled.
|
||||
* A filter must set this flags on an output link if it may return 0 in
|
||||
* request_frame() without filtering a frame.
|
||||
*/
|
||||
FF_LINK_FLAG_REQUEST_LOOP = 1,
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocate a new filter context and return it.
|
||||
*
|
||||
* @param filter what filter to create an instance of
|
||||
* @param inst_name name to give to the new filter context
|
||||
*
|
||||
* @return newly created filter context or NULL on failure
|
||||
*/
|
||||
AVFilterContext *ff_filter_alloc(const AVFilter *filter, const char *inst_name);
|
||||
|
||||
/**
|
||||
* Remove a filter from a graph;
|
||||
*/
|
||||
void ff_filter_graph_remove_filter(AVFilterGraph *graph, AVFilterContext *filter);
|
||||
|
||||
#endif /* AVFILTER_INTERNAL_H */
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2012 Stefano Sabatini <stefasab gmail com>
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "lavfutils.h"
|
||||
|
||||
int ff_load_image(uint8_t *data[4], int linesize[4],
|
||||
int *w, int *h, enum AVPixelFormat *pix_fmt,
|
||||
const char *filename, void *log_ctx)
|
||||
{
|
||||
AVInputFormat *iformat = NULL;
|
||||
AVFormatContext *format_ctx = NULL;
|
||||
AVCodec *codec;
|
||||
AVCodecContext *codec_ctx;
|
||||
AVFrame *frame;
|
||||
int frame_decoded, ret = 0;
|
||||
AVPacket pkt;
|
||||
|
||||
av_init_packet(&pkt);
|
||||
|
||||
av_register_all();
|
||||
|
||||
iformat = av_find_input_format("image2");
|
||||
if ((ret = avformat_open_input(&format_ctx, filename, iformat, NULL)) < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR,
|
||||
"Failed to open input file '%s'\n", filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
codec_ctx = format_ctx->streams[0]->codec;
|
||||
codec = avcodec_find_decoder(codec_ctx->codec_id);
|
||||
if (!codec) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Failed to find codec\n");
|
||||
ret = AVERROR(EINVAL);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Failed to open codec\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!(frame = avcodec_alloc_frame()) ) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Failed to alloc frame\n");
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = av_read_frame(format_ctx, &pkt);
|
||||
if (ret < 0) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Failed to read frame from file\n");
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = avcodec_decode_video2(codec_ctx, frame, &frame_decoded, &pkt);
|
||||
if (ret < 0 || !frame_decoded) {
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Failed to decode image from file\n");
|
||||
goto end;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
*w = frame->width;
|
||||
*h = frame->height;
|
||||
*pix_fmt = frame->format;
|
||||
|
||||
if ((ret = av_image_alloc(data, linesize, *w, *h, *pix_fmt, 16)) < 0)
|
||||
goto end;
|
||||
ret = 0;
|
||||
|
||||
av_image_copy(data, linesize, (const uint8_t **)frame->data, frame->linesize, *pix_fmt, *w, *h);
|
||||
|
||||
end:
|
||||
av_free_packet(&pkt);
|
||||
avcodec_close(codec_ctx);
|
||||
avformat_close_input(&format_ctx);
|
||||
av_freep(&frame);
|
||||
|
||||
if (ret < 0)
|
||||
av_log(log_ctx, AV_LOG_ERROR, "Error loading image file '%s'\n", filename);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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
|
||||
* Miscellaneous utilities which make use of the libavformat library
|
||||
*/
|
||||
|
||||
#ifndef AVFILTER_LAVFUTILS_H
|
||||
#define AVFILTER_LAVFUTILS_H
|
||||
|
||||
#include "libavformat/avformat.h"
|
||||
|
||||
/**
|
||||
* Load image from filename and put the resulting image in data.
|
||||
*
|
||||
* @param w pointer to the width of the loaded image
|
||||
* @param h pointer to the height of the loaded image
|
||||
* @param pix_fmt pointer to the pixel format of the loaded image
|
||||
* @param filename the name of the image file to load
|
||||
* @param log_ctx log context
|
||||
* @return >= 0 in case of success, a negative error code otherwise.
|
||||
*/
|
||||
int ff_load_image(uint8_t *data[4], int linesize[4],
|
||||
int *w, int *h, enum AVPixelFormat *pix_fmt,
|
||||
const char *filename, void *log_ctx);
|
||||
|
||||
#endif /* AVFILTER_LAVFUTILS_H */
|
||||
@@ -0,0 +1,14 @@
|
||||
prefix=/usr/local
|
||||
exec_prefix=${prefix}
|
||||
libdir=${prefix}/lib
|
||||
includedir=${prefix}/include
|
||||
|
||||
Name: libavfilter
|
||||
Description: FFmpeg audio/video filtering library
|
||||
Version: 3.90.100
|
||||
Requires: libswresample = 0.17.104, libswscale = 2.5.101, libavformat = 55.19.104, libavcodec = 55.39.101, libavutil = 52.48.101
|
||||
Requires.private:
|
||||
Conflicts:
|
||||
Libs: -L${libdir} -lavfilter -lavicap32 -lpsapi -lole32 -lstrmiids -luuid -lws2_32 -liconv -lm -lz -lpsapi -ladvapi32 -lshell32
|
||||
Libs.private:
|
||||
Cflags: -I${includedir}
|
||||
@@ -0,0 +1,5 @@
|
||||
LIBAVFILTER_$MAJOR {
|
||||
global: avfilter_*; av_*;
|
||||
ff_default_query_formats;
|
||||
local: *;
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Generic libav* helpers
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AV_HELPERS_H
|
||||
#define MPLAYER_AV_HELPERS_H
|
||||
|
||||
void ff_init_avcodec(void);
|
||||
void ff_init_avformat(void);
|
||||
|
||||
#endif /* MPLAYER_AV_HELPERS_H */
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_CPUDETECT_H
|
||||
#define MPLAYER_CPUDETECT_H
|
||||
|
||||
#define CPUTYPE_I386 3
|
||||
#define CPUTYPE_I486 4
|
||||
#define CPUTYPE_I586 5
|
||||
#define CPUTYPE_I686 6
|
||||
|
||||
#include "libavutil/x86_cpu.h"
|
||||
|
||||
typedef struct cpucaps_s {
|
||||
int cpuType;
|
||||
int cpuModel;
|
||||
int cpuStepping;
|
||||
int hasMMX;
|
||||
int hasMMX2;
|
||||
int has3DNow;
|
||||
int has3DNowExt;
|
||||
int hasSSE;
|
||||
int hasSSE2;
|
||||
int hasSSE3;
|
||||
int hasSSSE3;
|
||||
int hasSSE4;
|
||||
int hasSSE42;
|
||||
int hasSSE4a;
|
||||
int hasAVX;
|
||||
int isX86;
|
||||
unsigned cl_size; /* size of cache line */
|
||||
int hasAltiVec;
|
||||
int hasTSC;
|
||||
} CpuCaps;
|
||||
|
||||
extern CpuCaps ff_gCpuCaps;
|
||||
|
||||
void ff_do_cpuid(unsigned int ax, unsigned int *p);
|
||||
|
||||
void ff_GetCpuCaps(CpuCaps *caps);
|
||||
|
||||
/* returned value is malloc()'ed so free() it after use */
|
||||
char *ff_GetCpuFriendlyName(unsigned int regs[], unsigned int regs2[]);
|
||||
|
||||
#endif /* MPLAYER_CPUDETECT_H */
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "img_format.h"
|
||||
#include "stdio.h"
|
||||
#include "libavutil/bswap.h"
|
||||
|
||||
const char *ff_vo_format_name(int format)
|
||||
{
|
||||
static char unknown_format[20];
|
||||
switch(format)
|
||||
{
|
||||
case IMGFMT_RGB1: return "RGB 1-bit";
|
||||
case IMGFMT_RGB4: return "RGB 4-bit";
|
||||
case IMGFMT_RG4B: return "RGB 4-bit per byte";
|
||||
case IMGFMT_RGB8: return "RGB 8-bit";
|
||||
case IMGFMT_RGB12: return "RGB 12-bit";
|
||||
case IMGFMT_RGB15: return "RGB 15-bit";
|
||||
case IMGFMT_RGB16: return "RGB 16-bit";
|
||||
case IMGFMT_RGB24: return "RGB 24-bit";
|
||||
// case IMGFMT_RGB32: return "RGB 32-bit";
|
||||
case IMGFMT_RGB48LE: return "RGB 48-bit LE";
|
||||
case IMGFMT_RGB48BE: return "RGB 48-bit BE";
|
||||
case IMGFMT_RGB64LE: return "RGB 64-bit LE";
|
||||
case IMGFMT_RGB64BE: return "RGB 64-bit BE";
|
||||
case IMGFMT_BGR1: return "BGR 1-bit";
|
||||
case IMGFMT_BGR4: return "BGR 4-bit";
|
||||
case IMGFMT_BG4B: return "BGR 4-bit per byte";
|
||||
case IMGFMT_BGR8: return "BGR 8-bit";
|
||||
case IMGFMT_BGR12: return "BGR 12-bit";
|
||||
case IMGFMT_BGR15: return "BGR 15-bit";
|
||||
case IMGFMT_BGR16: return "BGR 16-bit";
|
||||
case IMGFMT_BGR24: return "BGR 24-bit";
|
||||
// case IMGFMT_BGR32: return "BGR 32-bit";
|
||||
case IMGFMT_ABGR: return "ABGR";
|
||||
case IMGFMT_BGRA: return "BGRA";
|
||||
case IMGFMT_ARGB: return "ARGB";
|
||||
case IMGFMT_RGBA: return "RGBA";
|
||||
case IMGFMT_GBR24P: return "Planar GBR 24-bit";
|
||||
case IMGFMT_GBR12P: return "Planar GBR 36-bit";
|
||||
case IMGFMT_GBR14P: return "Planar GBR 42-bit";
|
||||
case IMGFMT_YVU9: return "Planar YVU9";
|
||||
case IMGFMT_IF09: return "Planar IF09";
|
||||
case IMGFMT_YV12: return "Planar YV12";
|
||||
case IMGFMT_I420: return "Planar I420";
|
||||
case IMGFMT_IYUV: return "Planar IYUV";
|
||||
case IMGFMT_CLPL: return "Planar CLPL";
|
||||
case IMGFMT_Y800: return "Planar Y800";
|
||||
case IMGFMT_Y8: return "Planar Y8";
|
||||
case IMGFMT_Y8A: return "Planar Y8 with alpha";
|
||||
case IMGFMT_Y16_LE: return "Planar Y16 little-endian";
|
||||
case IMGFMT_Y16_BE: return "Planar Y16 big-endian";
|
||||
case IMGFMT_420P16_LE: return "Planar 420P 16-bit little-endian";
|
||||
case IMGFMT_420P16_BE: return "Planar 420P 16-bit big-endian";
|
||||
case IMGFMT_420P14_LE: return "Planar 420P 14-bit little-endian";
|
||||
case IMGFMT_420P14_BE: return "Planar 420P 14-bit big-endian";
|
||||
case IMGFMT_420P12_LE: return "Planar 420P 12-bit little-endian";
|
||||
case IMGFMT_420P12_BE: return "Planar 420P 12-bit big-endian";
|
||||
case IMGFMT_420P10_LE: return "Planar 420P 10-bit little-endian";
|
||||
case IMGFMT_420P10_BE: return "Planar 420P 10-bit big-endian";
|
||||
case IMGFMT_420P9_LE: return "Planar 420P 9-bit little-endian";
|
||||
case IMGFMT_420P9_BE: return "Planar 420P 9-bit big-endian";
|
||||
case IMGFMT_422P16_LE: return "Planar 422P 16-bit little-endian";
|
||||
case IMGFMT_422P16_BE: return "Planar 422P 16-bit big-endian";
|
||||
case IMGFMT_422P14_LE: return "Planar 422P 14-bit little-endian";
|
||||
case IMGFMT_422P14_BE: return "Planar 422P 14-bit big-endian";
|
||||
case IMGFMT_422P12_LE: return "Planar 422P 12-bit little-endian";
|
||||
case IMGFMT_422P12_BE: return "Planar 422P 12-bit big-endian";
|
||||
case IMGFMT_422P10_LE: return "Planar 422P 10-bit little-endian";
|
||||
case IMGFMT_422P10_BE: return "Planar 422P 10-bit big-endian";
|
||||
case IMGFMT_422P9_LE: return "Planar 422P 9-bit little-endian";
|
||||
case IMGFMT_422P9_BE: return "Planar 422P 9-bit big-endian";
|
||||
case IMGFMT_444P16_LE: return "Planar 444P 16-bit little-endian";
|
||||
case IMGFMT_444P16_BE: return "Planar 444P 16-bit big-endian";
|
||||
case IMGFMT_444P14_LE: return "Planar 444P 14-bit little-endian";
|
||||
case IMGFMT_444P14_BE: return "Planar 444P 14-bit big-endian";
|
||||
case IMGFMT_444P12_LE: return "Planar 444P 12-bit little-endian";
|
||||
case IMGFMT_444P12_BE: return "Planar 444P 12-bit big-endian";
|
||||
case IMGFMT_444P10_LE: return "Planar 444P 10-bit little-endian";
|
||||
case IMGFMT_444P10_BE: return "Planar 444P 10-bit big-endian";
|
||||
case IMGFMT_444P9_LE: return "Planar 444P 9-bit little-endian";
|
||||
case IMGFMT_444P9_BE: return "Planar 444P 9-bit big-endian";
|
||||
case IMGFMT_420A: return "Planar 420P with alpha";
|
||||
case IMGFMT_444P: return "Planar 444P";
|
||||
case IMGFMT_444A: return "Planar 444P with alpha";
|
||||
case IMGFMT_422P: return "Planar 422P";
|
||||
case IMGFMT_422A: return "Planar 422P with alpha";
|
||||
case IMGFMT_411P: return "Planar 411P";
|
||||
case IMGFMT_NV12: return "Planar NV12";
|
||||
case IMGFMT_NV21: return "Planar NV21";
|
||||
case IMGFMT_HM12: return "Planar NV12 Macroblock";
|
||||
case IMGFMT_IUYV: return "Packed IUYV";
|
||||
case IMGFMT_IY41: return "Packed IY41";
|
||||
case IMGFMT_IYU1: return "Packed IYU1";
|
||||
case IMGFMT_IYU2: return "Packed IYU2";
|
||||
case IMGFMT_UYVY: return "Packed UYVY";
|
||||
case IMGFMT_UYNV: return "Packed UYNV";
|
||||
case IMGFMT_cyuv: return "Packed CYUV";
|
||||
case IMGFMT_Y422: return "Packed Y422";
|
||||
case IMGFMT_YUY2: return "Packed YUY2";
|
||||
case IMGFMT_YUNV: return "Packed YUNV";
|
||||
case IMGFMT_YVYU: return "Packed YVYU";
|
||||
case IMGFMT_Y41P: return "Packed Y41P";
|
||||
case IMGFMT_Y211: return "Packed Y211";
|
||||
case IMGFMT_Y41T: return "Packed Y41T";
|
||||
case IMGFMT_Y42T: return "Packed Y42T";
|
||||
case IMGFMT_V422: return "Packed V422";
|
||||
case IMGFMT_V655: return "Packed V655";
|
||||
case IMGFMT_CLJR: return "Packed CLJR";
|
||||
case IMGFMT_YUVP: return "Packed YUVP";
|
||||
case IMGFMT_UYVP: return "Packed UYVP";
|
||||
case IMGFMT_MPEGPES: return "Mpeg PES";
|
||||
case IMGFMT_ZRMJPEGNI: return "Zoran MJPEG non-interlaced";
|
||||
case IMGFMT_ZRMJPEGIT: return "Zoran MJPEG top field first";
|
||||
case IMGFMT_ZRMJPEGIB: return "Zoran MJPEG bottom field first";
|
||||
case IMGFMT_XVMC_MOCO_MPEG2: return "MPEG1/2 Motion Compensation";
|
||||
case IMGFMT_XVMC_IDCT_MPEG2: return "MPEG1/2 Motion Compensation and IDCT";
|
||||
case IMGFMT_VDPAU_MPEG1: return "MPEG1 VDPAU acceleration";
|
||||
case IMGFMT_VDPAU_MPEG2: return "MPEG2 VDPAU acceleration";
|
||||
case IMGFMT_VDPAU_H264: return "H.264 VDPAU acceleration";
|
||||
case IMGFMT_VDPAU_MPEG4: return "MPEG-4 Part 2 VDPAU acceleration";
|
||||
case IMGFMT_VDPAU_WMV3: return "WMV3 VDPAU acceleration";
|
||||
case IMGFMT_VDPAU_VC1: return "VC1 VDPAU acceleration";
|
||||
}
|
||||
snprintf(unknown_format,20,"Unknown 0x%04x",format);
|
||||
return unknown_format;
|
||||
}
|
||||
|
||||
int ff_mp_get_chroma_shift(int format, int *x_shift, int *y_shift, int *component_bits)
|
||||
{
|
||||
int xs = 0, ys = 0;
|
||||
int bpp;
|
||||
int err = 0;
|
||||
int bits = 8;
|
||||
if ((format & 0xff0000f0) == 0x34000050)
|
||||
format = av_bswap32(format);
|
||||
if ((format & 0xf00000ff) == 0x50000034) {
|
||||
switch (format >> 24) {
|
||||
case 0x50:
|
||||
break;
|
||||
case 0x51:
|
||||
bits = 16;
|
||||
break;
|
||||
case 0x52:
|
||||
bits = 10;
|
||||
break;
|
||||
case 0x53:
|
||||
bits = 9;
|
||||
break;
|
||||
default:
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
switch (format & 0x00ffffff) {
|
||||
case 0x00343434: // 444
|
||||
xs = 0;
|
||||
ys = 0;
|
||||
break;
|
||||
case 0x00323234: // 422
|
||||
xs = 1;
|
||||
ys = 0;
|
||||
break;
|
||||
case 0x00303234: // 420
|
||||
xs = 1;
|
||||
ys = 1;
|
||||
break;
|
||||
case 0x00313134: // 411
|
||||
xs = 2;
|
||||
ys = 0;
|
||||
break;
|
||||
case 0x00303434: // 440
|
||||
xs = 0;
|
||||
ys = 1;
|
||||
break;
|
||||
default:
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
} else switch (format) {
|
||||
case IMGFMT_444A:
|
||||
xs = 0;
|
||||
ys = 0;
|
||||
break;
|
||||
case IMGFMT_422A:
|
||||
xs = 1;
|
||||
ys = 0;
|
||||
break;
|
||||
case IMGFMT_420A:
|
||||
case IMGFMT_I420:
|
||||
case IMGFMT_IYUV:
|
||||
case IMGFMT_YV12:
|
||||
xs = 1;
|
||||
ys = 1;
|
||||
break;
|
||||
case IMGFMT_IF09:
|
||||
case IMGFMT_YVU9:
|
||||
xs = 2;
|
||||
ys = 2;
|
||||
break;
|
||||
case IMGFMT_Y8:
|
||||
case IMGFMT_Y800:
|
||||
xs = 31;
|
||||
ys = 31;
|
||||
break;
|
||||
default:
|
||||
err = 1;
|
||||
break;
|
||||
}
|
||||
if (x_shift) *x_shift = xs;
|
||||
if (y_shift) *y_shift = ys;
|
||||
if (component_bits) *component_bits = bits;
|
||||
bpp = 8 + ((16 >> xs) >> ys);
|
||||
if (format == IMGFMT_420A || format == IMGFMT_422A || format == IMGFMT_444A)
|
||||
bpp += 8;
|
||||
bpp *= (bits + 7) >> 3;
|
||||
return err ? 0 : bpp;
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_IMG_FORMAT_H
|
||||
#define MPLAYER_IMG_FORMAT_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
/* RGB/BGR Formats */
|
||||
|
||||
#define IMGFMT_RGB_MASK 0xFFFFFF00
|
||||
#define IMGFMT_RGB (('R'<<24)|('G'<<16)|('B'<<8))
|
||||
#define IMGFMT_RGB1 (IMGFMT_RGB|1)
|
||||
#define IMGFMT_RGB4 (IMGFMT_RGB|4)
|
||||
#define IMGFMT_RGB4_CHAR (IMGFMT_RGB|4|128) // RGB4 with 1 pixel per byte
|
||||
#define IMGFMT_RGB8 (IMGFMT_RGB|8)
|
||||
#define IMGFMT_RGB12 (IMGFMT_RGB|12)
|
||||
#define IMGFMT_RGB15 (IMGFMT_RGB|15)
|
||||
#define IMGFMT_RGB16 (IMGFMT_RGB|16)
|
||||
#define IMGFMT_RGB24 (IMGFMT_RGB|24)
|
||||
#define IMGFMT_RGB32 (IMGFMT_RGB|32)
|
||||
#define IMGFMT_RGB48LE (IMGFMT_RGB|48)
|
||||
#define IMGFMT_RGB48BE (IMGFMT_RGB|48|128)
|
||||
#define IMGFMT_RGB64LE (IMGFMT_RGB|64)
|
||||
#define IMGFMT_RGB64BE (IMGFMT_RGB|64|128)
|
||||
|
||||
#define IMGFMT_BGR_MASK 0xFFFFFF00
|
||||
#define IMGFMT_BGR (('B'<<24)|('G'<<16)|('R'<<8))
|
||||
#define IMGFMT_BGR1 (IMGFMT_BGR|1)
|
||||
#define IMGFMT_BGR4 (IMGFMT_BGR|4)
|
||||
#define IMGFMT_BGR4_CHAR (IMGFMT_BGR|4|128) // BGR4 with 1 pixel per byte
|
||||
#define IMGFMT_BGR8 (IMGFMT_BGR|8)
|
||||
#define IMGFMT_BGR12 (IMGFMT_BGR|12)
|
||||
#define IMGFMT_BGR15 (IMGFMT_BGR|15)
|
||||
#define IMGFMT_BGR16 (IMGFMT_BGR|16)
|
||||
#define IMGFMT_BGR24 (IMGFMT_BGR|24)
|
||||
#define IMGFMT_BGR32 (IMGFMT_BGR|32)
|
||||
|
||||
#define IMGFMT_GBR24P (('G'<<24)|('B'<<16)|('R'<<8)|24)
|
||||
#define IMGFMT_GBR12PLE (('G'<<24)|('B'<<16)|('R'<<8)|36)
|
||||
#define IMGFMT_GBR12PBE (('G'<<24)|('B'<<16)|('R'<<8)|36|128)
|
||||
#define IMGFMT_GBR14PLE (('G'<<24)|('B'<<16)|('R'<<8)|42)
|
||||
#define IMGFMT_GBR14PBE (('G'<<24)|('B'<<16)|('R'<<8)|42|128)
|
||||
|
||||
#if HAVE_BIGENDIAN
|
||||
#define IMGFMT_ABGR IMGFMT_RGB32
|
||||
#define IMGFMT_BGRA (IMGFMT_RGB32|128)
|
||||
#define IMGFMT_ARGB IMGFMT_BGR32
|
||||
#define IMGFMT_RGBA (IMGFMT_BGR32|128)
|
||||
#define IMGFMT_RGB64NE IMGFMT_RGB64BE
|
||||
#define IMGFMT_RGB48NE IMGFMT_RGB48BE
|
||||
#define IMGFMT_RGB12BE IMGFMT_RGB12
|
||||
#define IMGFMT_RGB12LE (IMGFMT_RGB12|128)
|
||||
#define IMGFMT_RGB15BE IMGFMT_RGB15
|
||||
#define IMGFMT_RGB15LE (IMGFMT_RGB15|128)
|
||||
#define IMGFMT_RGB16BE IMGFMT_RGB16
|
||||
#define IMGFMT_RGB16LE (IMGFMT_RGB16|128)
|
||||
#define IMGFMT_BGR12BE IMGFMT_BGR12
|
||||
#define IMGFMT_BGR12LE (IMGFMT_BGR12|128)
|
||||
#define IMGFMT_BGR15BE IMGFMT_BGR15
|
||||
#define IMGFMT_BGR15LE (IMGFMT_BGR15|128)
|
||||
#define IMGFMT_BGR16BE IMGFMT_BGR16
|
||||
#define IMGFMT_BGR16LE (IMGFMT_BGR16|128)
|
||||
#define IMGFMT_GBR12P IMGFMT_GBR12PBE
|
||||
#define IMGFMT_GBR14P IMGFMT_GBR14PBE
|
||||
#else
|
||||
#define IMGFMT_ABGR (IMGFMT_BGR32|128)
|
||||
#define IMGFMT_BGRA IMGFMT_BGR32
|
||||
#define IMGFMT_ARGB (IMGFMT_RGB32|128)
|
||||
#define IMGFMT_RGBA IMGFMT_RGB32
|
||||
#define IMGFMT_RGB64NE IMGFMT_RGB64LE
|
||||
#define IMGFMT_RGB48NE IMGFMT_RGB48LE
|
||||
#define IMGFMT_RGB12BE (IMGFMT_RGB12|128)
|
||||
#define IMGFMT_RGB12LE IMGFMT_RGB12
|
||||
#define IMGFMT_RGB15BE (IMGFMT_RGB15|128)
|
||||
#define IMGFMT_RGB15LE IMGFMT_RGB15
|
||||
#define IMGFMT_RGB16BE (IMGFMT_RGB16|128)
|
||||
#define IMGFMT_RGB16LE IMGFMT_RGB16
|
||||
#define IMGFMT_BGR12BE (IMGFMT_BGR12|128)
|
||||
#define IMGFMT_BGR12LE IMGFMT_BGR12
|
||||
#define IMGFMT_BGR15BE (IMGFMT_BGR15|128)
|
||||
#define IMGFMT_BGR15LE IMGFMT_BGR15
|
||||
#define IMGFMT_BGR16BE (IMGFMT_BGR16|128)
|
||||
#define IMGFMT_BGR16LE IMGFMT_BGR16
|
||||
#define IMGFMT_GBR12P IMGFMT_GBR12PLE
|
||||
#define IMGFMT_GBR14P IMGFMT_GBR14PLE
|
||||
#endif
|
||||
|
||||
/* old names for compatibility */
|
||||
#define IMGFMT_RG4B IMGFMT_RGB4_CHAR
|
||||
#define IMGFMT_BG4B IMGFMT_BGR4_CHAR
|
||||
|
||||
#define IMGFMT_IS_RGB(fmt) (((fmt)&IMGFMT_RGB_MASK)==IMGFMT_RGB)
|
||||
#define IMGFMT_IS_BGR(fmt) (((fmt)&IMGFMT_BGR_MASK)==IMGFMT_BGR)
|
||||
|
||||
#define IMGFMT_RGB_DEPTH(fmt) ((fmt)&0x7F)
|
||||
#define IMGFMT_BGR_DEPTH(fmt) ((fmt)&0x7F)
|
||||
|
||||
|
||||
/* Planar YUV Formats */
|
||||
|
||||
#define IMGFMT_YVU9 0x39555659
|
||||
#define IMGFMT_IF09 0x39304649
|
||||
#define IMGFMT_YV12 0x32315659
|
||||
#define IMGFMT_I420 0x30323449
|
||||
#define IMGFMT_IYUV 0x56555949
|
||||
#define IMGFMT_CLPL 0x4C504C43
|
||||
#define IMGFMT_Y800 0x30303859
|
||||
#define IMGFMT_Y8 0x20203859
|
||||
#define IMGFMT_NV12 0x3231564E
|
||||
#define IMGFMT_NV21 0x3132564E
|
||||
#define IMGFMT_Y16_LE 0x20363159
|
||||
|
||||
/* unofficial Planar Formats, FIXME if official 4CC exists */
|
||||
#define IMGFMT_444P 0x50343434
|
||||
#define IMGFMT_422P 0x50323234
|
||||
#define IMGFMT_411P 0x50313134
|
||||
#define IMGFMT_440P 0x50303434
|
||||
#define IMGFMT_HM12 0x32314D48
|
||||
#define IMGFMT_Y16_BE 0x59313620
|
||||
|
||||
// Gray with alpha
|
||||
#define IMGFMT_Y8A 0x59320008
|
||||
// 4:2:0 planar with alpha
|
||||
#define IMGFMT_420A 0x41303234
|
||||
// 4:2:2 planar with alpha
|
||||
#define IMGFMT_422A 0x41323234
|
||||
// 4:4:4 planar with alpha
|
||||
#define IMGFMT_444A 0x41343434
|
||||
|
||||
#define IMGFMT_444P16_LE 0x51343434
|
||||
#define IMGFMT_444P16_BE 0x34343451
|
||||
#define IMGFMT_444P14_LE 0x54343434
|
||||
#define IMGFMT_444P14_BE 0x34343454
|
||||
#define IMGFMT_444P12_LE 0x55343434
|
||||
#define IMGFMT_444P12_BE 0x34343455
|
||||
#define IMGFMT_444P10_LE 0x52343434
|
||||
#define IMGFMT_444P10_BE 0x34343452
|
||||
#define IMGFMT_444P9_LE 0x53343434
|
||||
#define IMGFMT_444P9_BE 0x34343453
|
||||
#define IMGFMT_422P16_LE 0x51323234
|
||||
#define IMGFMT_422P16_BE 0x34323251
|
||||
#define IMGFMT_422P14_LE 0x54323234
|
||||
#define IMGFMT_422P14_BE 0x34323254
|
||||
#define IMGFMT_422P12_LE 0x55323234
|
||||
#define IMGFMT_422P12_BE 0x34323255
|
||||
#define IMGFMT_422P10_LE 0x52323234
|
||||
#define IMGFMT_422P10_BE 0x34323252
|
||||
#define IMGFMT_422P9_LE 0x53323234
|
||||
#define IMGFMT_422P9_BE 0x34323253
|
||||
#define IMGFMT_420P16_LE 0x51303234
|
||||
#define IMGFMT_420P16_BE 0x34323051
|
||||
#define IMGFMT_420P14_LE 0x54303234
|
||||
#define IMGFMT_420P14_BE 0x34323054
|
||||
#define IMGFMT_420P12_LE 0x55303234
|
||||
#define IMGFMT_420P12_BE 0x34323055
|
||||
#define IMGFMT_420P10_LE 0x52303234
|
||||
#define IMGFMT_420P10_BE 0x34323052
|
||||
#define IMGFMT_420P9_LE 0x53303234
|
||||
#define IMGFMT_420P9_BE 0x34323053
|
||||
#if HAVE_BIGENDIAN
|
||||
#define IMGFMT_444P16 IMGFMT_444P16_BE
|
||||
#define IMGFMT_444P14 IMGFMT_444P14_BE
|
||||
#define IMGFMT_444P12 IMGFMT_444P12_BE
|
||||
#define IMGFMT_444P10 IMGFMT_444P10_BE
|
||||
#define IMGFMT_444P9 IMGFMT_444P9_BE
|
||||
#define IMGFMT_422P16 IMGFMT_422P16_BE
|
||||
#define IMGFMT_422P14 IMGFMT_422P14_BE
|
||||
#define IMGFMT_422P12 IMGFMT_422P12_BE
|
||||
#define IMGFMT_422P10 IMGFMT_422P10_BE
|
||||
#define IMGFMT_422P9 IMGFMT_422P9_BE
|
||||
#define IMGFMT_420P16 IMGFMT_420P16_BE
|
||||
#define IMGFMT_420P14 IMGFMT_420P14_BE
|
||||
#define IMGFMT_420P12 IMGFMT_420P12_BE
|
||||
#define IMGFMT_420P10 IMGFMT_420P10_BE
|
||||
#define IMGFMT_420P9 IMGFMT_420P9_BE
|
||||
#define IMGFMT_Y16 IMGFMT_Y16_BE
|
||||
#define IMGFMT_IS_YUVP16_NE(fmt) IMGFMT_IS_YUVP16_BE(fmt)
|
||||
#else
|
||||
#define IMGFMT_444P16 IMGFMT_444P16_LE
|
||||
#define IMGFMT_444P14 IMGFMT_444P14_LE
|
||||
#define IMGFMT_444P12 IMGFMT_444P12_LE
|
||||
#define IMGFMT_444P10 IMGFMT_444P10_LE
|
||||
#define IMGFMT_444P9 IMGFMT_444P9_LE
|
||||
#define IMGFMT_422P16 IMGFMT_422P16_LE
|
||||
#define IMGFMT_422P14 IMGFMT_422P14_LE
|
||||
#define IMGFMT_422P12 IMGFMT_422P12_LE
|
||||
#define IMGFMT_422P10 IMGFMT_422P10_LE
|
||||
#define IMGFMT_422P9 IMGFMT_422P9_LE
|
||||
#define IMGFMT_420P16 IMGFMT_420P16_LE
|
||||
#define IMGFMT_420P14 IMGFMT_420P14_LE
|
||||
#define IMGFMT_420P12 IMGFMT_420P12_LE
|
||||
#define IMGFMT_420P10 IMGFMT_420P10_LE
|
||||
#define IMGFMT_420P9 IMGFMT_420P9_LE
|
||||
#define IMGFMT_Y16 IMGFMT_Y16_LE
|
||||
#define IMGFMT_IS_YUVP16_NE(fmt) IMGFMT_IS_YUVP16_LE(fmt)
|
||||
#endif
|
||||
|
||||
#define IMGFMT_IS_YUVP16_LE(fmt) (((fmt - 0x51000034) & 0xfc0000ff) == 0)
|
||||
#define IMGFMT_IS_YUVP16_BE(fmt) (((fmt - 0x34000051) & 0xff0000fc) == 0)
|
||||
#define IMGFMT_IS_YUVP16(fmt) (IMGFMT_IS_YUVP16_LE(fmt) || IMGFMT_IS_YUVP16_BE(fmt))
|
||||
|
||||
/**
|
||||
* \brief Find the corresponding full 16 bit format, i.e. IMGFMT_420P10_LE -> IMGFMT_420P16_LE
|
||||
* \return normalized format ID or 0 if none exists.
|
||||
*/
|
||||
static inline int normalize_yuvp16(int fmt) {
|
||||
if (IMGFMT_IS_YUVP16_LE(fmt))
|
||||
return (fmt & 0x00ffffff) | 0x51000000;
|
||||
if (IMGFMT_IS_YUVP16_BE(fmt))
|
||||
return (fmt & 0xffffff00) | 0x00000051;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Packed YUV Formats */
|
||||
|
||||
#define IMGFMT_IUYV 0x56595549 // Interlaced UYVY
|
||||
#define IMGFMT_IY41 0x31435949 // Interlaced Y41P
|
||||
#define IMGFMT_IYU1 0x31555949
|
||||
#define IMGFMT_IYU2 0x32555949
|
||||
#define IMGFMT_UYVY 0x59565955
|
||||
#define IMGFMT_UYNV 0x564E5955 // Exactly same as UYVY
|
||||
#define IMGFMT_cyuv 0x76757963 // upside-down UYVY
|
||||
#define IMGFMT_Y422 0x32323459 // Exactly same as UYVY
|
||||
#define IMGFMT_YUY2 0x32595559
|
||||
#define IMGFMT_YUNV 0x564E5559 // Exactly same as YUY2
|
||||
#define IMGFMT_YVYU 0x55595659
|
||||
#define IMGFMT_Y41P 0x50313459
|
||||
#define IMGFMT_Y211 0x31313259
|
||||
#define IMGFMT_Y41T 0x54313459 // Y41P, Y lsb = transparency
|
||||
#define IMGFMT_Y42T 0x54323459 // UYVY, Y lsb = transparency
|
||||
#define IMGFMT_V422 0x32323456 // upside-down UYVY?
|
||||
#define IMGFMT_V655 0x35353656
|
||||
#define IMGFMT_CLJR 0x524A4C43
|
||||
#define IMGFMT_YUVP 0x50565559 // 10-bit YUYV
|
||||
#define IMGFMT_UYVP 0x50565955 // 10-bit UYVY
|
||||
|
||||
/* Compressed Formats */
|
||||
#define IMGFMT_MPEGPES (('M'<<24)|('P'<<16)|('E'<<8)|('S'))
|
||||
#define IMGFMT_MJPEG (('M')|('J'<<8)|('P'<<16)|('G'<<24))
|
||||
/* Formats that are understood by zoran chips, we include
|
||||
* non-interlaced, interlaced top-first, interlaced bottom-first */
|
||||
#define IMGFMT_ZRMJPEGNI (('Z'<<24)|('R'<<16)|('N'<<8)|('I'))
|
||||
#define IMGFMT_ZRMJPEGIT (('Z'<<24)|('R'<<16)|('I'<<8)|('T'))
|
||||
#define IMGFMT_ZRMJPEGIB (('Z'<<24)|('R'<<16)|('I'<<8)|('B'))
|
||||
|
||||
// I think that this code could not be used by any other codec/format
|
||||
#define IMGFMT_XVMC 0x1DC70000
|
||||
#define IMGFMT_XVMC_MASK 0xFFFF0000
|
||||
#define IMGFMT_IS_XVMC(fmt) (((fmt)&IMGFMT_XVMC_MASK)==IMGFMT_XVMC)
|
||||
//these are chroma420
|
||||
#define IMGFMT_XVMC_MOCO_MPEG2 (IMGFMT_XVMC|0x02)
|
||||
#define IMGFMT_XVMC_IDCT_MPEG2 (IMGFMT_XVMC|0x82)
|
||||
|
||||
// VDPAU specific format.
|
||||
#define IMGFMT_VDPAU 0x1DC80000
|
||||
#define IMGFMT_VDPAU_MASK 0xFFFF0000
|
||||
#define IMGFMT_IS_VDPAU(fmt) (((fmt)&IMGFMT_VDPAU_MASK)==IMGFMT_VDPAU)
|
||||
#define IMGFMT_VDPAU_MPEG1 (IMGFMT_VDPAU|0x01)
|
||||
#define IMGFMT_VDPAU_MPEG2 (IMGFMT_VDPAU|0x02)
|
||||
#define IMGFMT_VDPAU_H264 (IMGFMT_VDPAU|0x03)
|
||||
#define IMGFMT_VDPAU_WMV3 (IMGFMT_VDPAU|0x04)
|
||||
#define IMGFMT_VDPAU_VC1 (IMGFMT_VDPAU|0x05)
|
||||
#define IMGFMT_VDPAU_MPEG4 (IMGFMT_VDPAU|0x06)
|
||||
|
||||
#define IMGFMT_IS_HWACCEL(fmt) (IMGFMT_IS_VDPAU(fmt) || IMGFMT_IS_XVMC(fmt))
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
int size;
|
||||
int id; // stream id. usually 0x1E0
|
||||
int timestamp; // pts, 90000 Hz counter based
|
||||
} vo_mpegpes_t;
|
||||
|
||||
const char *ff_vo_format_name(int format);
|
||||
|
||||
/**
|
||||
* Calculates the scale shifts for the chroma planes for planar YUV
|
||||
*
|
||||
* \param component_bits bits per component
|
||||
* \return bits-per-pixel for format if successful (i.e. format is 3 or 4-planes planar YUV), 0 otherwise
|
||||
*/
|
||||
int ff_mp_get_chroma_shift(int format, int *x_shift, int *y_shift, int *component_bits);
|
||||
|
||||
#endif /* MPLAYER_IMG_FORMAT_H */
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer 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.
|
||||
*
|
||||
* MPlayer 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 MPlayer; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_FASTMEMCPY_H
|
||||
#define MPLAYER_FASTMEMCPY_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
void * fast_memcpy(void * to, const void * from, size_t len);
|
||||
void * mem2agpcpy(void * to, const void * from, size_t len);
|
||||
|
||||
#if ! defined(CONFIG_FASTMEMCPY) || ! (HAVE_MMX || HAVE_MMX2 || HAVE_AMD3DNOW /* || HAVE_SSE || HAVE_SSE2 */)
|
||||
#define mem2agpcpy(a,b,c) memcpy(a,b,c)
|
||||
#define fast_memcpy(a,b,c) memcpy(a,b,c)
|
||||
#endif
|
||||
|
||||
static inline void * mem2agpcpy_pic(void * dst, const void * src, int bytesPerLine, int height, int dstStride, int srcStride)
|
||||
{
|
||||
int i;
|
||||
void *retval=dst;
|
||||
|
||||
if(dstStride == srcStride)
|
||||
{
|
||||
if (srcStride < 0) {
|
||||
src = (const uint8_t*)src + (height-1)*srcStride;
|
||||
dst = (uint8_t*)dst + (height-1)*dstStride;
|
||||
srcStride = -srcStride;
|
||||
}
|
||||
|
||||
mem2agpcpy(dst, src, srcStride*height);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(i=0; i<height; i++)
|
||||
{
|
||||
mem2agpcpy(dst, src, bytesPerLine);
|
||||
src = (const uint8_t*)src + srcStride;
|
||||
dst = (uint8_t*)dst + dstStride;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#define memcpy_pic(d, s, b, h, ds, ss) memcpy_pic2(d, s, b, h, ds, ss, 0)
|
||||
#define my_memcpy_pic(d, s, b, h, ds, ss) memcpy_pic2(d, s, b, h, ds, ss, 1)
|
||||
|
||||
/**
|
||||
* \param limit2width always skip data between end of line and start of next
|
||||
* instead of copying the full block when strides are the same
|
||||
*/
|
||||
static inline void * memcpy_pic2(void * dst, const void * src,
|
||||
int bytesPerLine, int height,
|
||||
int dstStride, int srcStride, int limit2width)
|
||||
{
|
||||
int i;
|
||||
void *retval=dst;
|
||||
|
||||
if(!limit2width && dstStride == srcStride)
|
||||
{
|
||||
if (srcStride < 0) {
|
||||
src = (const uint8_t*)src + (height-1)*srcStride;
|
||||
dst = (uint8_t*)dst + (height-1)*dstStride;
|
||||
srcStride = -srcStride;
|
||||
}
|
||||
|
||||
fast_memcpy(dst, src, srcStride*height);
|
||||
}
|
||||
else
|
||||
{
|
||||
for(i=0; i<height; i++)
|
||||
{
|
||||
fast_memcpy(dst, src, bytesPerLine);
|
||||
src = (const uint8_t*)src + srcStride;
|
||||
dst = (uint8_t*)dst + dstStride;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
#endif /* MPLAYER_FASTMEMCPY_H */
|
||||
@@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright (C) Aaron Holtzman - Aug 1999
|
||||
* Strongly modified, most parts rewritten: A'rpi/ESP-team - 2000-2001
|
||||
* (C) MPlayer developers
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_VIDEO_OUT_H
|
||||
#define MPLAYER_VIDEO_OUT_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
//#include "sub/font_load.h"
|
||||
#include "../img_format.h"
|
||||
//#include "vidix/vidix.h"
|
||||
|
||||
#define VO_EVENT_EXPOSE 1
|
||||
#define VO_EVENT_RESIZE 2
|
||||
#define VO_EVENT_KEYPRESS 4
|
||||
#define VO_EVENT_REINIT 8
|
||||
#define VO_EVENT_MOVE 16
|
||||
|
||||
/* Obsolete: VOCTRL_QUERY_VAA 1 */
|
||||
/* does the device support the required format */
|
||||
#define VOCTRL_QUERY_FORMAT 2
|
||||
/* signal a device reset seek */
|
||||
#define VOCTRL_RESET 3
|
||||
/* true if vo driver can use GUI created windows */
|
||||
#define VOCTRL_GUISUPPORT 4
|
||||
#define VOCTRL_GUI_NOWINDOW 19
|
||||
/* used to switch to fullscreen */
|
||||
#define VOCTRL_FULLSCREEN 5
|
||||
/* signal a device pause */
|
||||
#define VOCTRL_PAUSE 7
|
||||
/* start/resume playback */
|
||||
#define VOCTRL_RESUME 8
|
||||
/* libmpcodecs direct rendering: */
|
||||
#define VOCTRL_GET_IMAGE 9
|
||||
#define VOCTRL_DRAW_IMAGE 13
|
||||
#define VOCTRL_SET_SPU_PALETTE 14
|
||||
/* decoding ahead: */
|
||||
#define VOCTRL_GET_NUM_FRAMES 10
|
||||
#define VOCTRL_GET_FRAME_NUM 11
|
||||
#define VOCTRL_SET_FRAME_NUM 12
|
||||
#define VOCTRL_GET_PANSCAN 15
|
||||
#define VOCTRL_SET_PANSCAN 16
|
||||
/* equalizer controls */
|
||||
#define VOCTRL_SET_EQUALIZER 17
|
||||
#define VOCTRL_GET_EQUALIZER 18
|
||||
//#define VOCTRL_GUI_NOWINDOW 19
|
||||
/* Frame duplication */
|
||||
#define VOCTRL_DUPLICATE_FRAME 20
|
||||
// ... 21
|
||||
#define VOCTRL_START_SLICE 21
|
||||
|
||||
#define VOCTRL_ONTOP 25
|
||||
#define VOCTRL_ROOTWIN 26
|
||||
#define VOCTRL_BORDER 27
|
||||
#define VOCTRL_DRAW_EOSD 28
|
||||
#define VOCTRL_GET_EOSD_RES 29
|
||||
|
||||
#define VOCTRL_SET_DEINTERLACE 30
|
||||
#define VOCTRL_GET_DEINTERLACE 31
|
||||
|
||||
#define VOCTRL_UPDATE_SCREENINFO 32
|
||||
|
||||
// Vo can be used by xover
|
||||
#define VOCTRL_XOVERLAY_SUPPORT 22
|
||||
|
||||
#define VOCTRL_XOVERLAY_SET_COLORKEY 24
|
||||
typedef struct {
|
||||
uint32_t x11; // The raw x11 color
|
||||
uint16_t r,g,b;
|
||||
} mp_colorkey_t;
|
||||
|
||||
#define VOCTRL_XOVERLAY_SET_WIN 23
|
||||
typedef struct {
|
||||
int x,y;
|
||||
int w,h;
|
||||
} mp_win_t;
|
||||
|
||||
#define VO_TRUE 1
|
||||
#define VO_FALSE 0
|
||||
#define VO_ERROR -1
|
||||
#define VO_NOTAVAIL -2
|
||||
#define VO_NOTIMPL -3
|
||||
|
||||
#define VOFLAG_FULLSCREEN 0x01
|
||||
#define VOFLAG_MODESWITCHING 0x02
|
||||
#define VOFLAG_SWSCALE 0x04
|
||||
#define VOFLAG_FLIPPING 0x08
|
||||
#define VOFLAG_HIDDEN 0x10 //< Use to create a hidden window
|
||||
#define VOFLAG_STEREO 0x20 //< Use to create a stereo-capable window
|
||||
#define VOFLAG_XOVERLAY_SUB_VO 0x10000
|
||||
|
||||
typedef struct vo_info_s
|
||||
{
|
||||
/* driver name ("Matrox Millennium G200/G400" */
|
||||
const char *name;
|
||||
/* short name (for config strings) ("mga") */
|
||||
const char *short_name;
|
||||
/* author ("Aaron Holtzman <[email protected]>") */
|
||||
const char *author;
|
||||
/* any additional comments */
|
||||
const char *comment;
|
||||
} vo_info_t;
|
||||
|
||||
typedef struct vo_functions_s
|
||||
{
|
||||
const vo_info_t *info;
|
||||
/*
|
||||
* Preinitializes driver (real INITIALIZATION)
|
||||
* arg - currently it's vo_subdevice
|
||||
* returns: zero on successful initialization, non-zero on error.
|
||||
*/
|
||||
int (*preinit)(const char *arg);
|
||||
/*
|
||||
* Initialize (means CONFIGURE) the display driver.
|
||||
* params:
|
||||
* width,height: image source size
|
||||
* d_width,d_height: size of the requested window size, just a hint
|
||||
* fullscreen: flag, 0=windowd 1=fullscreen, just a hint
|
||||
* title: window title, if available
|
||||
* format: fourcc of pixel format
|
||||
* returns : zero on successful initialization, non-zero on error.
|
||||
*/
|
||||
int (*config)(uint32_t width, uint32_t height, uint32_t d_width,
|
||||
uint32_t d_height, uint32_t fullscreen, char *title,
|
||||
uint32_t format);
|
||||
|
||||
/*
|
||||
* Control interface
|
||||
*/
|
||||
int (*control)(uint32_t request, void *data, ...);
|
||||
|
||||
/*
|
||||
* Display a new RGB/BGR frame of the video to the screen.
|
||||
* params:
|
||||
* src[0] - pointer to the image
|
||||
*/
|
||||
int (*draw_frame)(uint8_t *src[]);
|
||||
|
||||
/*
|
||||
* Draw a planar YUV slice to the buffer:
|
||||
* params:
|
||||
* src[3] = source image planes (Y,U,V)
|
||||
* stride[3] = source image planes line widths (in bytes)
|
||||
* w,h = width*height of area to be copied (in Y pixels)
|
||||
* x,y = position at the destination image (in Y pixels)
|
||||
*/
|
||||
int (*draw_slice)(uint8_t *src[], int stride[], int w,int h, int x,int y);
|
||||
|
||||
/*
|
||||
* Draws OSD to the screen buffer
|
||||
*/
|
||||
void (*draw_osd)(void);
|
||||
|
||||
/*
|
||||
* Blit/Flip buffer to the screen. Must be called after each frame!
|
||||
*/
|
||||
void (*flip_page)(void);
|
||||
|
||||
/*
|
||||
* This func is called after every frames to handle keyboard and
|
||||
* other events. It's called in PAUSE mode too!
|
||||
*/
|
||||
void (*check_events)(void);
|
||||
|
||||
/*
|
||||
* Closes driver. Should restore the original state of the system.
|
||||
*/
|
||||
void (*uninit)(void);
|
||||
} vo_functions_t;
|
||||
|
||||
const vo_functions_t* init_best_video_out(char** vo_list);
|
||||
int config_video_out(const vo_functions_t *vo, uint32_t width, uint32_t height,
|
||||
uint32_t d_width, uint32_t d_height, uint32_t flags,
|
||||
char *title, uint32_t format);
|
||||
void list_video_out(void);
|
||||
|
||||
// NULL terminated array of all drivers
|
||||
extern const vo_functions_t* const video_out_drivers[];
|
||||
|
||||
extern int vo_flags;
|
||||
|
||||
extern int vo_config_count;
|
||||
|
||||
extern int xinerama_screen;
|
||||
extern int xinerama_x;
|
||||
extern int xinerama_y;
|
||||
|
||||
// correct resolution/bpp on screen: (should be autodetected by vo_init())
|
||||
extern int vo_depthonscreen;
|
||||
extern int vo_screenwidth;
|
||||
extern int vo_screenheight;
|
||||
|
||||
// requested resolution/bpp: (-x -y -bpp options)
|
||||
extern int vo_dx;
|
||||
extern int vo_dy;
|
||||
extern int vo_dwidth;
|
||||
extern int vo_dheight;
|
||||
extern int vo_dbpp;
|
||||
|
||||
extern int vo_grabpointer;
|
||||
extern int vo_doublebuffering;
|
||||
extern int vo_directrendering;
|
||||
extern int vo_vsync;
|
||||
extern int vo_fsmode;
|
||||
extern float vo_panscan;
|
||||
extern int vo_adapter_num;
|
||||
extern int vo_refresh_rate;
|
||||
extern int vo_keepaspect;
|
||||
extern int vo_rootwin;
|
||||
extern int vo_ontop;
|
||||
extern int vo_border;
|
||||
|
||||
extern int vo_gamma_gamma;
|
||||
extern int vo_gamma_brightness;
|
||||
extern int vo_gamma_saturation;
|
||||
extern int vo_gamma_contrast;
|
||||
extern int vo_gamma_hue;
|
||||
extern int vo_gamma_red_intensity;
|
||||
extern int vo_gamma_green_intensity;
|
||||
extern int vo_gamma_blue_intensity;
|
||||
|
||||
extern int vo_nomouse_input;
|
||||
extern int enable_mouse_movements;
|
||||
|
||||
extern int vo_pts;
|
||||
extern float vo_fps;
|
||||
|
||||
extern char *vo_subdevice;
|
||||
|
||||
extern int vo_colorkey;
|
||||
|
||||
extern char *vo_winname;
|
||||
extern char *vo_wintitle;
|
||||
|
||||
extern int64_t WinID;
|
||||
|
||||
typedef struct {
|
||||
float min;
|
||||
float max;
|
||||
} range_t;
|
||||
|
||||
float range_max(range_t *r);
|
||||
int in_range(range_t *r, float f);
|
||||
range_t *str2range(char *s);
|
||||
extern char *monitor_hfreq_str;
|
||||
extern char *monitor_vfreq_str;
|
||||
extern char *monitor_dotclock_str;
|
||||
|
||||
struct mp_keymap {
|
||||
int from;
|
||||
int to;
|
||||
};
|
||||
int lookup_keymap_table(const struct mp_keymap *map, int key);
|
||||
struct vo_rect {
|
||||
int left, right, top, bottom, width, height;
|
||||
};
|
||||
void calc_src_dst_rects(int src_width, int src_height, struct vo_rect *src, struct vo_rect *dst,
|
||||
struct vo_rect *borders, const struct vo_rect *crop);
|
||||
void vo_mouse_movement(int posx, int posy);
|
||||
|
||||
#endif /* MPLAYER_VIDEO_OUT_H */
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#if HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "img_format.h"
|
||||
#include "mp_image.h"
|
||||
|
||||
#include "libvo/fastmemcpy.h"
|
||||
//#include "libavutil/mem.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
|
||||
void ff_mp_image_alloc_planes(mp_image_t *mpi) {
|
||||
uint32_t temp[256];
|
||||
if (avpriv_set_systematic_pal2(temp, ff_mp2ff_pix_fmt(mpi->imgfmt)) >= 0)
|
||||
mpi->flags |= MP_IMGFLAG_RGB_PALETTE;
|
||||
|
||||
// IF09 - allocate space for 4. plane delta info - unused
|
||||
if (mpi->imgfmt == IMGFMT_IF09) {
|
||||
mpi->planes[0]=av_malloc(mpi->bpp*mpi->width*(mpi->height+2)/8+
|
||||
mpi->chroma_width*mpi->chroma_height);
|
||||
} else
|
||||
mpi->planes[0]=av_malloc(mpi->bpp*mpi->width*(mpi->height+2)/8);
|
||||
if (mpi->flags&MP_IMGFLAG_PLANAR) {
|
||||
int bpp = IMGFMT_IS_YUVP16(mpi->imgfmt)? 2 : 1;
|
||||
// YV12/I420/YVU9/IF09. feel free to add other planar formats here...
|
||||
mpi->stride[0]=mpi->stride[3]=bpp*mpi->width;
|
||||
if(mpi->num_planes > 2){
|
||||
mpi->stride[1]=mpi->stride[2]=bpp*mpi->chroma_width;
|
||||
if(mpi->flags&MP_IMGFLAG_SWAPPED){
|
||||
// I420/IYUV (Y,U,V)
|
||||
mpi->planes[1]=mpi->planes[0]+mpi->stride[0]*mpi->height;
|
||||
mpi->planes[2]=mpi->planes[1]+mpi->stride[1]*mpi->chroma_height;
|
||||
if (mpi->num_planes > 3)
|
||||
mpi->planes[3]=mpi->planes[2]+mpi->stride[2]*mpi->chroma_height;
|
||||
} else {
|
||||
// YV12,YVU9,IF09 (Y,V,U)
|
||||
mpi->planes[2]=mpi->planes[0]+mpi->stride[0]*mpi->height;
|
||||
mpi->planes[1]=mpi->planes[2]+mpi->stride[1]*mpi->chroma_height;
|
||||
if (mpi->num_planes > 3)
|
||||
mpi->planes[3]=mpi->planes[1]+mpi->stride[1]*mpi->chroma_height;
|
||||
}
|
||||
} else {
|
||||
// NV12/NV21
|
||||
mpi->stride[1]=mpi->chroma_width;
|
||||
mpi->planes[1]=mpi->planes[0]+mpi->stride[0]*mpi->height;
|
||||
}
|
||||
} else {
|
||||
mpi->stride[0]=mpi->width*mpi->bpp/8;
|
||||
if (mpi->flags & MP_IMGFLAG_RGB_PALETTE) {
|
||||
mpi->planes[1] = av_malloc(1024);
|
||||
memcpy(mpi->planes[1], temp, 1024);
|
||||
}
|
||||
}
|
||||
mpi->flags|=MP_IMGFLAG_ALLOCATED;
|
||||
}
|
||||
|
||||
mp_image_t* ff_alloc_mpi(int w, int h, unsigned long int fmt) {
|
||||
mp_image_t* mpi = ff_new_mp_image(w,h);
|
||||
|
||||
ff_mp_image_setfmt(mpi,fmt);
|
||||
ff_mp_image_alloc_planes(mpi);
|
||||
|
||||
return mpi;
|
||||
}
|
||||
|
||||
void ff_copy_mpi(mp_image_t *dmpi, mp_image_t *mpi) {
|
||||
if(mpi->flags&MP_IMGFLAG_PLANAR){
|
||||
memcpy_pic(dmpi->planes[0],mpi->planes[0], mpi->w, mpi->h,
|
||||
dmpi->stride[0],mpi->stride[0]);
|
||||
memcpy_pic(dmpi->planes[1],mpi->planes[1], mpi->chroma_width, mpi->chroma_height,
|
||||
dmpi->stride[1],mpi->stride[1]);
|
||||
memcpy_pic(dmpi->planes[2], mpi->planes[2], mpi->chroma_width, mpi->chroma_height,
|
||||
dmpi->stride[2],mpi->stride[2]);
|
||||
} else {
|
||||
memcpy_pic(dmpi->planes[0],mpi->planes[0],
|
||||
mpi->w*(dmpi->bpp/8), mpi->h,
|
||||
dmpi->stride[0],mpi->stride[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void ff_mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt){
|
||||
mpi->flags&=~(MP_IMGFLAG_PLANAR|MP_IMGFLAG_YUV|MP_IMGFLAG_SWAPPED);
|
||||
mpi->imgfmt=out_fmt;
|
||||
// compressed formats
|
||||
if(out_fmt == IMGFMT_MPEGPES ||
|
||||
out_fmt == IMGFMT_ZRMJPEGNI || out_fmt == IMGFMT_ZRMJPEGIT || out_fmt == IMGFMT_ZRMJPEGIB ||
|
||||
IMGFMT_IS_HWACCEL(out_fmt)){
|
||||
mpi->bpp=0;
|
||||
return;
|
||||
}
|
||||
mpi->num_planes=1;
|
||||
if (IMGFMT_IS_RGB(out_fmt)) {
|
||||
if (IMGFMT_RGB_DEPTH(out_fmt) < 8 && !(out_fmt&128))
|
||||
mpi->bpp = IMGFMT_RGB_DEPTH(out_fmt);
|
||||
else
|
||||
mpi->bpp=(IMGFMT_RGB_DEPTH(out_fmt)+7)&(~7);
|
||||
return;
|
||||
}
|
||||
if (IMGFMT_IS_BGR(out_fmt)) {
|
||||
if (IMGFMT_BGR_DEPTH(out_fmt) < 8 && !(out_fmt&128))
|
||||
mpi->bpp = IMGFMT_BGR_DEPTH(out_fmt);
|
||||
else
|
||||
mpi->bpp=(IMGFMT_BGR_DEPTH(out_fmt)+7)&(~7);
|
||||
mpi->flags|=MP_IMGFLAG_SWAPPED;
|
||||
return;
|
||||
}
|
||||
mpi->num_planes=3;
|
||||
if (out_fmt == IMGFMT_GBR24P) {
|
||||
mpi->bpp=24;
|
||||
mpi->flags|=MP_IMGFLAG_PLANAR;
|
||||
return;
|
||||
} else if (out_fmt == IMGFMT_GBR12P) {
|
||||
mpi->bpp=36;
|
||||
mpi->flags|=MP_IMGFLAG_PLANAR;
|
||||
return;
|
||||
} else if (out_fmt == IMGFMT_GBR14P) {
|
||||
mpi->bpp=42;
|
||||
mpi->flags|=MP_IMGFLAG_PLANAR;
|
||||
return;
|
||||
}
|
||||
mpi->flags|=MP_IMGFLAG_YUV;
|
||||
if (ff_mp_get_chroma_shift(out_fmt, NULL, NULL, NULL)) {
|
||||
mpi->flags|=MP_IMGFLAG_PLANAR;
|
||||
mpi->bpp = ff_mp_get_chroma_shift(out_fmt, &mpi->chroma_x_shift, &mpi->chroma_y_shift, NULL);
|
||||
mpi->chroma_width = mpi->width >> mpi->chroma_x_shift;
|
||||
mpi->chroma_height = mpi->height >> mpi->chroma_y_shift;
|
||||
}
|
||||
switch(out_fmt){
|
||||
case IMGFMT_I420:
|
||||
case IMGFMT_IYUV:
|
||||
mpi->flags|=MP_IMGFLAG_SWAPPED;
|
||||
case IMGFMT_YV12:
|
||||
return;
|
||||
case IMGFMT_420A:
|
||||
case IMGFMT_422A:
|
||||
case IMGFMT_444A:
|
||||
case IMGFMT_IF09:
|
||||
mpi->num_planes=4;
|
||||
case IMGFMT_YVU9:
|
||||
case IMGFMT_444P:
|
||||
case IMGFMT_422P:
|
||||
case IMGFMT_411P:
|
||||
case IMGFMT_440P:
|
||||
case IMGFMT_444P16_LE:
|
||||
case IMGFMT_444P16_BE:
|
||||
case IMGFMT_444P14_LE:
|
||||
case IMGFMT_444P14_BE:
|
||||
case IMGFMT_444P12_LE:
|
||||
case IMGFMT_444P12_BE:
|
||||
case IMGFMT_444P10_LE:
|
||||
case IMGFMT_444P10_BE:
|
||||
case IMGFMT_444P9_LE:
|
||||
case IMGFMT_444P9_BE:
|
||||
case IMGFMT_422P16_LE:
|
||||
case IMGFMT_422P16_BE:
|
||||
case IMGFMT_422P14_LE:
|
||||
case IMGFMT_422P14_BE:
|
||||
case IMGFMT_422P12_LE:
|
||||
case IMGFMT_422P12_BE:
|
||||
case IMGFMT_422P10_LE:
|
||||
case IMGFMT_422P10_BE:
|
||||
case IMGFMT_422P9_LE:
|
||||
case IMGFMT_422P9_BE:
|
||||
case IMGFMT_420P16_LE:
|
||||
case IMGFMT_420P16_BE:
|
||||
case IMGFMT_420P14_LE:
|
||||
case IMGFMT_420P14_BE:
|
||||
case IMGFMT_420P12_LE:
|
||||
case IMGFMT_420P12_BE:
|
||||
case IMGFMT_420P10_LE:
|
||||
case IMGFMT_420P10_BE:
|
||||
case IMGFMT_420P9_LE:
|
||||
case IMGFMT_420P9_BE:
|
||||
return;
|
||||
case IMGFMT_Y16_LE:
|
||||
case IMGFMT_Y16_BE:
|
||||
mpi->bpp=16;
|
||||
case IMGFMT_Y800:
|
||||
case IMGFMT_Y8:
|
||||
/* they're planar ones, but for easier handling use them as packed */
|
||||
mpi->flags&=~MP_IMGFLAG_PLANAR;
|
||||
mpi->num_planes=1;
|
||||
return;
|
||||
case IMGFMT_Y8A:
|
||||
mpi->num_planes=2;
|
||||
return;
|
||||
case IMGFMT_UYVY:
|
||||
mpi->flags|=MP_IMGFLAG_SWAPPED;
|
||||
case IMGFMT_YUY2:
|
||||
mpi->chroma_x_shift = 1;
|
||||
mpi->bpp=16;
|
||||
mpi->num_planes=1;
|
||||
return;
|
||||
case IMGFMT_NV12:
|
||||
mpi->flags|=MP_IMGFLAG_SWAPPED;
|
||||
case IMGFMT_NV21:
|
||||
mpi->flags|=MP_IMGFLAG_PLANAR;
|
||||
mpi->bpp=12;
|
||||
mpi->num_planes=2;
|
||||
mpi->chroma_width=(mpi->width>>0);
|
||||
mpi->chroma_height=(mpi->height>>1);
|
||||
mpi->chroma_x_shift=0;
|
||||
mpi->chroma_y_shift=1;
|
||||
return;
|
||||
}
|
||||
ff_mp_msg(MSGT_DECVIDEO,MSGL_WARN,"mp_image: unknown out_fmt: 0x%X\n",out_fmt);
|
||||
mpi->bpp=0;
|
||||
}
|
||||
|
||||
mp_image_t* ff_new_mp_image(int w,int h){
|
||||
mp_image_t* mpi = malloc(sizeof(mp_image_t));
|
||||
if(!mpi) return NULL; // error!
|
||||
memset(mpi,0,sizeof(mp_image_t));
|
||||
mpi->width=mpi->w=w;
|
||||
mpi->height=mpi->h=h;
|
||||
return mpi;
|
||||
}
|
||||
|
||||
void ff_free_mp_image(mp_image_t* mpi){
|
||||
if(!mpi) return;
|
||||
if(mpi->flags&MP_IMGFLAG_ALLOCATED){
|
||||
/* becouse we allocate the whole image in once */
|
||||
av_free(mpi->planes[0]);
|
||||
if (mpi->flags & MP_IMGFLAG_RGB_PALETTE)
|
||||
av_free(mpi->planes[1]);
|
||||
}
|
||||
free(mpi);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_MP_IMAGE_H
|
||||
#define MPLAYER_MP_IMAGE_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#undef printf //FIXME
|
||||
#undef fprintf //FIXME
|
||||
#include "mp_msg.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#undef realloc
|
||||
#undef malloc
|
||||
#undef free
|
||||
#undef rand
|
||||
#undef srand
|
||||
#undef printf
|
||||
#undef strncpy
|
||||
#define ASMALIGN(ZEROBITS) ".p2align " #ZEROBITS "\n\t"
|
||||
#define CODEC_FLAG2_MEMC_ONLY 0x00001000 ///< Only do ME/MC (I frames -> ref, P frame -> ME+MC).
|
||||
|
||||
enum AVPixelFormat ff_mp2ff_pix_fmt(int mp);
|
||||
|
||||
//--------- codec's requirements (filled by the codec/vf) ---------
|
||||
|
||||
//--- buffer content restrictions:
|
||||
// set if buffer content shouldn't be modified:
|
||||
#define MP_IMGFLAG_PRESERVE 0x01
|
||||
// set if buffer content will be READ.
|
||||
// This can be e.g. for next frame's MC: (I/P mpeg frames) -
|
||||
// then in combination with MP_IMGFLAG_PRESERVE - or it
|
||||
// can be because a video filter or codec will read a significant
|
||||
// amount of data while processing that frame (e.g. blending something
|
||||
// onto the frame, MV based intra prediction).
|
||||
// A frame marked like this should not be placed in to uncachable
|
||||
// video RAM for example.
|
||||
#define MP_IMGFLAG_READABLE 0x02
|
||||
|
||||
//--- buffer width/stride/plane restrictions: (used for direct rendering)
|
||||
// stride _have_to_ be aligned to MB boundary: [for DR restrictions]
|
||||
#define MP_IMGFLAG_ACCEPT_ALIGNED_STRIDE 0x4
|
||||
// stride should be aligned to MB boundary: [for buffer allocation]
|
||||
#define MP_IMGFLAG_PREFER_ALIGNED_STRIDE 0x8
|
||||
// codec accept any stride (>=width):
|
||||
#define MP_IMGFLAG_ACCEPT_STRIDE 0x10
|
||||
// codec accept any width (width*bpp=stride -> stride%bpp==0) (>=width):
|
||||
#define MP_IMGFLAG_ACCEPT_WIDTH 0x20
|
||||
//--- for planar formats only:
|
||||
// uses only stride[0], and stride[1]=stride[2]=stride[0]>>mpi->chroma_x_shift
|
||||
#define MP_IMGFLAG_COMMON_STRIDE 0x40
|
||||
// uses only planes[0], and calculates planes[1,2] from width,height,imgfmt
|
||||
#define MP_IMGFLAG_COMMON_PLANE 0x80
|
||||
|
||||
#define MP_IMGFLAGMASK_RESTRICTIONS 0xFF
|
||||
|
||||
//--------- color info (filled by ff_mp_image_setfmt() ) -----------
|
||||
// set if number of planes > 1
|
||||
#define MP_IMGFLAG_PLANAR 0x100
|
||||
// set if it's YUV colorspace
|
||||
#define MP_IMGFLAG_YUV 0x200
|
||||
// set if it's swapped (BGR or YVU) plane/byteorder
|
||||
#define MP_IMGFLAG_SWAPPED 0x400
|
||||
// set if you want memory for palette allocated and managed by ff_vf_get_image etc.
|
||||
#define MP_IMGFLAG_RGB_PALETTE 0x800
|
||||
|
||||
#define MP_IMGFLAGMASK_COLORS 0xF00
|
||||
|
||||
// codec uses drawing/rendering callbacks (draw_slice()-like thing, DR method 2)
|
||||
// [the codec will set this flag if it supports callbacks, and the vo _may_
|
||||
// clear it in get_image() if draw_slice() not implemented]
|
||||
#define MP_IMGFLAG_DRAW_CALLBACK 0x1000
|
||||
// set if it's in video buffer/memory: [set by vo/vf's get_image() !!!]
|
||||
#define MP_IMGFLAG_DIRECT 0x2000
|
||||
// set if buffer is allocated (used in destination images):
|
||||
#define MP_IMGFLAG_ALLOCATED 0x4000
|
||||
|
||||
// buffer type was printed (do NOT set this flag - it's for INTERNAL USE!!!)
|
||||
#define MP_IMGFLAG_TYPE_DISPLAYED 0x8000
|
||||
|
||||
// codec doesn't support any form of direct rendering - it has own buffer
|
||||
// allocation. so we just export its buffer pointers:
|
||||
#define MP_IMGTYPE_EXPORT 0
|
||||
// codec requires a static WO buffer, but it does only partial updates later:
|
||||
#define MP_IMGTYPE_STATIC 1
|
||||
// codec just needs some WO memory, where it writes/copies the whole frame to:
|
||||
#define MP_IMGTYPE_TEMP 2
|
||||
// I+P type, requires 2+ independent static R/W buffers
|
||||
#define MP_IMGTYPE_IP 3
|
||||
// I+P+B type, requires 2+ independent static R/W and 1+ temp WO buffers
|
||||
#define MP_IMGTYPE_IPB 4
|
||||
// Upper 16 bits give desired buffer number, -1 means get next available
|
||||
#define MP_IMGTYPE_NUMBERED 5
|
||||
// Doesn't need any buffer, incomplete image (probably a first field only)
|
||||
// we need this type to be able to differentiate between half frames and
|
||||
// all other cases
|
||||
#define MP_IMGTYPE_INCOMPLETE 6
|
||||
|
||||
#define MP_MAX_PLANES 4
|
||||
|
||||
#define MP_IMGFIELD_ORDERED 0x01
|
||||
#define MP_IMGFIELD_TOP_FIRST 0x02
|
||||
#define MP_IMGFIELD_REPEAT_FIRST 0x04
|
||||
#define MP_IMGFIELD_TOP 0x08
|
||||
#define MP_IMGFIELD_BOTTOM 0x10
|
||||
#define MP_IMGFIELD_INTERLACED 0x20
|
||||
|
||||
typedef struct mp_image {
|
||||
unsigned int flags;
|
||||
unsigned char type;
|
||||
int number;
|
||||
unsigned char bpp; // bits/pixel. NOT depth! for RGB it will be n*8
|
||||
unsigned int imgfmt;
|
||||
int width,height; // stored dimensions
|
||||
int x,y,w,h; // visible dimensions
|
||||
unsigned char* planes[MP_MAX_PLANES];
|
||||
int stride[MP_MAX_PLANES];
|
||||
char * qscale;
|
||||
int qstride;
|
||||
int pict_type; // 0->unknown, 1->I, 2->P, 3->B
|
||||
int fields;
|
||||
int qscale_type; // 0->mpeg1/4/h263, 1->mpeg2
|
||||
int num_planes;
|
||||
/* these are only used by planar formats Y,U(Cb),V(Cr) */
|
||||
int chroma_width;
|
||||
int chroma_height;
|
||||
int chroma_x_shift; // horizontal
|
||||
int chroma_y_shift; // vertical
|
||||
int usage_count;
|
||||
/* for private use by filter or vo driver (to store buffer id or dmpi) */
|
||||
void* priv;
|
||||
} mp_image_t;
|
||||
|
||||
void ff_mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt);
|
||||
mp_image_t* ff_new_mp_image(int w,int h);
|
||||
void ff_free_mp_image(mp_image_t* mpi);
|
||||
|
||||
mp_image_t* ff_alloc_mpi(int w, int h, unsigned long int fmt);
|
||||
void ff_mp_image_alloc_planes(mp_image_t *mpi);
|
||||
void ff_copy_mpi(mp_image_t *dmpi, mp_image_t *mpi);
|
||||
|
||||
#endif /* MPLAYER_MP_IMAGE_H */
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_MP_MSG_H
|
||||
#define MPLAYER_MP_MSG_H
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
// defined in mplayer.c and mencoder.c
|
||||
extern int verbose;
|
||||
|
||||
// verbosity elevel:
|
||||
|
||||
/* Only messages level MSGL_FATAL-MSGL_STATUS should be translated,
|
||||
* messages level MSGL_V and above should not be translated. */
|
||||
|
||||
#define MSGL_FATAL 0 // will exit/abort
|
||||
#define MSGL_ERR 1 // continues
|
||||
#define MSGL_WARN 2 // only warning
|
||||
#define MSGL_HINT 3 // short help message
|
||||
#define MSGL_INFO 4 // -quiet
|
||||
#define MSGL_STATUS 5 // v=0
|
||||
#define MSGL_V 6 // v=1
|
||||
#define MSGL_DBG2 7 // v=2
|
||||
#define MSGL_DBG3 8 // v=3
|
||||
#define MSGL_DBG4 9 // v=4
|
||||
#define MSGL_DBG5 10 // v=5
|
||||
|
||||
#define MSGL_FIXME 1 // for conversions from printf where the appropriate MSGL is not known; set equal to ERR for obtrusiveness
|
||||
#define MSGT_FIXME 0 // for conversions from printf where the appropriate MSGT is not known; set equal to GLOBAL for obtrusiveness
|
||||
|
||||
// code/module:
|
||||
|
||||
#define MSGT_GLOBAL 0 // common player stuff errors
|
||||
#define MSGT_CPLAYER 1 // console player (mplayer.c)
|
||||
#define MSGT_GPLAYER 2 // gui player
|
||||
|
||||
#define MSGT_VO 3 // libvo
|
||||
#define MSGT_AO 4 // libao
|
||||
|
||||
#define MSGT_DEMUXER 5 // demuxer.c (general stuff)
|
||||
#define MSGT_DS 6 // demux stream (add/read packet etc)
|
||||
#define MSGT_DEMUX 7 // fileformat-specific stuff (demux_*.c)
|
||||
#define MSGT_HEADER 8 // fileformat-specific header (*header.c)
|
||||
|
||||
#define MSGT_AVSYNC 9 // mplayer.c timer stuff
|
||||
#define MSGT_AUTOQ 10 // mplayer.c auto-quality stuff
|
||||
|
||||
#define MSGT_CFGPARSER 11 // cfgparser.c
|
||||
|
||||
#define MSGT_DECAUDIO 12 // av decoder
|
||||
#define MSGT_DECVIDEO 13
|
||||
|
||||
#define MSGT_SEEK 14 // seeking code
|
||||
#define MSGT_WIN32 15 // win32 dll stuff
|
||||
#define MSGT_OPEN 16 // open.c (stream opening)
|
||||
#define MSGT_DVD 17 // open.c (DVD init/read/seek)
|
||||
|
||||
#define MSGT_PARSEES 18 // parse_es.c (mpeg stream parser)
|
||||
#define MSGT_LIRC 19 // lirc_mp.c and input lirc driver
|
||||
|
||||
#define MSGT_STREAM 20 // stream.c
|
||||
#define MSGT_CACHE 21 // cache2.c
|
||||
|
||||
#define MSGT_MENCODER 22
|
||||
|
||||
#define MSGT_XACODEC 23 // XAnim codecs
|
||||
|
||||
#define MSGT_TV 24 // TV input subsystem
|
||||
|
||||
#define MSGT_OSDEP 25 // OS-dependent parts
|
||||
|
||||
#define MSGT_SPUDEC 26 // spudec.c
|
||||
|
||||
#define MSGT_PLAYTREE 27 // Playtree handeling (playtree.c, playtreeparser.c)
|
||||
|
||||
#define MSGT_INPUT 28
|
||||
|
||||
#define MSGT_VFILTER 29
|
||||
|
||||
#define MSGT_OSD 30
|
||||
|
||||
#define MSGT_NETWORK 31
|
||||
|
||||
#define MSGT_CPUDETECT 32
|
||||
|
||||
#define MSGT_CODECCFG 33
|
||||
|
||||
#define MSGT_SWS 34
|
||||
|
||||
#define MSGT_VOBSUB 35
|
||||
#define MSGT_SUBREADER 36
|
||||
|
||||
#define MSGT_AFILTER 37 // Audio filter messages
|
||||
|
||||
#define MSGT_NETST 38 // Netstream
|
||||
|
||||
#define MSGT_MUXER 39 // muxer layer
|
||||
|
||||
#define MSGT_OSD_MENU 40
|
||||
|
||||
#define MSGT_IDENTIFY 41 // -identify output
|
||||
|
||||
#define MSGT_RADIO 42
|
||||
|
||||
#define MSGT_ASS 43 // libass messages
|
||||
|
||||
#define MSGT_LOADER 44 // dll loader messages
|
||||
|
||||
#define MSGT_STATUSLINE 45 // playback/encoding status line
|
||||
|
||||
#define MSGT_TELETEXT 46 // Teletext decoder
|
||||
|
||||
#define MSGT_MAX 64
|
||||
|
||||
|
||||
extern char *ff_mp_msg_charset;
|
||||
extern int ff_mp_msg_color;
|
||||
extern int ff_mp_msg_module;
|
||||
|
||||
extern int ff_mp_msg_levels[MSGT_MAX];
|
||||
extern int ff_mp_msg_level_all;
|
||||
|
||||
|
||||
void ff_mp_msg_init(void);
|
||||
int ff_mp_msg_test(int mod, int lev);
|
||||
|
||||
#include "config.h"
|
||||
|
||||
void ff_mp_msg_va(int mod, int lev, const char *format, va_list va);
|
||||
#ifdef __GNUC__
|
||||
void ff_mp_msg(int mod, int lev, const char *format, ... ) __attribute__ ((format (printf, 3, 4)));
|
||||
# ifdef MP_DEBUG
|
||||
# define mp_dbg(mod,lev, args... ) ff_mp_msg(mod, lev, ## args )
|
||||
# else
|
||||
// only useful for developers, disable but check syntax
|
||||
# define mp_dbg(mod,lev, args... ) do { if (0) ff_mp_msg(mod, lev, ## args ); } while (0)
|
||||
# endif
|
||||
#else // not GNU C
|
||||
void ff_mp_msg(int mod, int lev, const char *format, ... );
|
||||
# ifdef MP_DEBUG
|
||||
# define mp_dbg(mod,lev, ... ) ff_mp_msg(mod, lev, __VA_ARGS__)
|
||||
# else
|
||||
// only useful for developers, disable but check syntax
|
||||
# define mp_dbg(mod,lev, ... ) do { if (0) ff_mp_msg(mod, lev, __VA_ARGS__); } while (0)
|
||||
# endif
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
const char* ff_filename_recode(const char* filename);
|
||||
|
||||
#endif /* MPLAYER_MP_MSG_H */
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_MPC_INFO_H
|
||||
#define MPLAYER_MPC_INFO_H
|
||||
|
||||
typedef struct mp_codec_info_s
|
||||
{
|
||||
/* codec long name ("Autodesk FLI/FLC Animation decoder" */
|
||||
const char *name;
|
||||
/* short name (same as driver name in codecs.conf) ("dshow") */
|
||||
const char *short_name;
|
||||
/* interface author/maintainer */
|
||||
const char *maintainer;
|
||||
/* codec author ("Aaron Holtzman <[email protected]>") */
|
||||
const char *author;
|
||||
/* any additional comments */
|
||||
const char *comment;
|
||||
} mp_codec_info_t;
|
||||
|
||||
#define CONTROL_OK 1
|
||||
#define CONTROL_TRUE 1
|
||||
#define CONTROL_FALSE 0
|
||||
#define CONTROL_UNKNOWN -1
|
||||
#define CONTROL_ERROR -2
|
||||
#define CONTROL_NA -3
|
||||
|
||||
#endif /* MPLAYER_MPC_INFO_H */
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* MPlayer 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_VF_H
|
||||
#define MPLAYER_VF_H
|
||||
|
||||
//#include "m_option.h"
|
||||
#include "mp_image.h"
|
||||
|
||||
//extern m_obj_settings_t* vf_settings;
|
||||
//extern const m_obj_list_t vf_obj_list;
|
||||
|
||||
struct vf_instance;
|
||||
struct vf_priv_s;
|
||||
|
||||
typedef struct vf_info_s {
|
||||
const char *info;
|
||||
const char *name;
|
||||
const char *author;
|
||||
const char *comment;
|
||||
int (*vf_open)(struct vf_instance *vf,char* args);
|
||||
// Ptr to a struct dscribing the options
|
||||
const void* opts;
|
||||
} vf_info_t;
|
||||
|
||||
#define NUM_NUMBERED_MPI 50
|
||||
|
||||
typedef struct vf_image_context_s {
|
||||
mp_image_t* static_images[2];
|
||||
mp_image_t* temp_images[1];
|
||||
mp_image_t* export_images[1];
|
||||
mp_image_t* numbered_images[NUM_NUMBERED_MPI];
|
||||
int static_idx;
|
||||
} vf_image_context_t;
|
||||
|
||||
typedef struct vf_format_context_t {
|
||||
int have_configured;
|
||||
int orig_width, orig_height, orig_fmt;
|
||||
} vf_format_context_t;
|
||||
|
||||
typedef struct vf_instance {
|
||||
const vf_info_t* info;
|
||||
// funcs:
|
||||
int (*config)(struct vf_instance *vf,
|
||||
int width, int height, int d_width, int d_height,
|
||||
unsigned int flags, unsigned int outfmt);
|
||||
int (*control)(struct vf_instance *vf,
|
||||
int request, void* data);
|
||||
int (*query_format)(struct vf_instance *vf,
|
||||
unsigned int fmt);
|
||||
void (*get_image)(struct vf_instance *vf,
|
||||
mp_image_t *mpi);
|
||||
int (*put_image)(struct vf_instance *vf,
|
||||
mp_image_t *mpi, double pts);
|
||||
void (*start_slice)(struct vf_instance *vf,
|
||||
mp_image_t *mpi);
|
||||
void (*draw_slice)(struct vf_instance *vf,
|
||||
unsigned char** src, int* stride, int w,int h, int x, int y);
|
||||
void (*uninit)(struct vf_instance *vf);
|
||||
|
||||
int (*continue_buffered_image)(struct vf_instance *vf);
|
||||
// caps:
|
||||
unsigned int default_caps; // used by default query_format()
|
||||
unsigned int default_reqs; // used by default config()
|
||||
// data:
|
||||
int w, h;
|
||||
vf_image_context_t imgctx;
|
||||
vf_format_context_t fmt;
|
||||
struct vf_instance *next;
|
||||
mp_image_t *dmpi;
|
||||
struct vf_priv_s* priv;
|
||||
} vf_instance_t;
|
||||
|
||||
// control codes:
|
||||
#include "mpc_info.h"
|
||||
|
||||
typedef struct vf_seteq_s
|
||||
{
|
||||
const char *item;
|
||||
int value;
|
||||
} vf_equalizer_t;
|
||||
|
||||
#define VFCTRL_QUERY_MAX_PP_LEVEL 4 /* test for postprocessing support (max level) */
|
||||
#define VFCTRL_SET_PP_LEVEL 5 /* set postprocessing level */
|
||||
#define VFCTRL_SET_EQUALIZER 6 /* set color options (brightness,contrast etc) */
|
||||
#define VFCTRL_GET_EQUALIZER 8 /* gset color options (brightness,contrast etc) */
|
||||
#define VFCTRL_DRAW_OSD 7
|
||||
#define VFCTRL_CHANGE_RECTANGLE 9 /* Change the rectangle boundaries */
|
||||
#define VFCTRL_FLIP_PAGE 10 /* Tell the vo to flip pages */
|
||||
#define VFCTRL_DUPLICATE_FRAME 11 /* For encoding - encode zero-change frame */
|
||||
#define VFCTRL_SKIP_NEXT_FRAME 12 /* For encoding - drop the next frame that passes thru */
|
||||
#define VFCTRL_FLUSH_FRAMES 13 /* For encoding - flush delayed frames */
|
||||
#define VFCTRL_SCREENSHOT 14 /* Make a screenshot */
|
||||
#define VFCTRL_INIT_EOSD 15 /* Select EOSD renderer */
|
||||
#define VFCTRL_DRAW_EOSD 16 /* Render EOSD */
|
||||
#define VFCTRL_GET_PTS 17 /* Return last pts value that reached vf_vo*/
|
||||
#define VFCTRL_SET_DEINTERLACE 18 /* Set deinterlacing status */
|
||||
#define VFCTRL_GET_DEINTERLACE 19 /* Get deinterlacing status */
|
||||
|
||||
#include "vfcap.h"
|
||||
|
||||
//FIXME this should be in a common header, but i dunno which
|
||||
#define MP_NOPTS_VALUE (-1LL<<63) //both int64_t and double should be able to represent this exactly
|
||||
|
||||
|
||||
// functions:
|
||||
void ff_vf_mpi_clear(mp_image_t* mpi,int x0,int y0,int w,int h);
|
||||
mp_image_t* ff_vf_get_image(vf_instance_t* vf, unsigned int outfmt, int mp_imgtype, int mp_imgflag, int w, int h);
|
||||
|
||||
vf_instance_t* vf_open_plugin(const vf_info_t* const* filter_list, vf_instance_t* next, const char *name, char **args);
|
||||
vf_instance_t* vf_open_filter(vf_instance_t* next, const char *name, char **args);
|
||||
vf_instance_t* ff_vf_add_before_vo(vf_instance_t **vf, char *name, char **args);
|
||||
vf_instance_t* vf_open_encoder(vf_instance_t* next, const char *name, char *args);
|
||||
|
||||
unsigned int ff_vf_match_csp(vf_instance_t** vfp,const unsigned int* list,unsigned int preferred);
|
||||
void ff_vf_clone_mpi_attributes(mp_image_t* dst, mp_image_t* src);
|
||||
void ff_vf_queue_frame(vf_instance_t *vf, int (*)(vf_instance_t *));
|
||||
int ff_vf_output_queued_frame(vf_instance_t *vf);
|
||||
|
||||
// default wrappers:
|
||||
int ff_vf_next_config(struct vf_instance *vf,
|
||||
int width, int height, int d_width, int d_height,
|
||||
unsigned int flags, unsigned int outfmt);
|
||||
int ff_vf_next_control(struct vf_instance *vf, int request, void* data);
|
||||
void ff_vf_extra_flip(struct vf_instance *vf);
|
||||
int ff_vf_next_query_format(struct vf_instance *vf, unsigned int fmt);
|
||||
int ff_vf_next_put_image(struct vf_instance *vf,mp_image_t *mpi, double pts);
|
||||
void ff_vf_next_draw_slice (struct vf_instance *vf, unsigned char** src, int* stride, int w,int h, int x, int y);
|
||||
|
||||
vf_instance_t* ff_append_filters(vf_instance_t* last);
|
||||
|
||||
void ff_vf_uninit_filter(vf_instance_t* vf);
|
||||
void ff_vf_uninit_filter_chain(vf_instance_t* vf);
|
||||
|
||||
int ff_vf_config_wrapper(struct vf_instance *vf,
|
||||
int width, int height, int d_width, int d_height,
|
||||
unsigned int flags, unsigned int outfmt);
|
||||
|
||||
static inline int norm_qscale(int qscale, int type)
|
||||
{
|
||||
switch (type) {
|
||||
case 0: // MPEG-1
|
||||
return qscale;
|
||||
case 1: // MPEG-2
|
||||
return qscale >> 1;
|
||||
case 2: // H264
|
||||
return qscale >> 2;
|
||||
case 3: // VP56
|
||||
return (63 - qscale + 2) >> 2;
|
||||
}
|
||||
return qscale;
|
||||
}
|
||||
|
||||
#endif /* MPLAYER_VF_H */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user