mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-25 16:52:31 +00:00
94ebf55658
Be more careful when an input stream encounters EOF when its filtergraph
has not been configured yet. The current code would immediately mark the
corresponding output streams as finished, while there may still be
buffered frames waiting for frames to appear on other filtergraph
inputs.
This should fix the random FATE failures for complex filtergraph tests
after a3a0230a98
830 lines
29 KiB
C
830 lines
29 KiB
C
/*
|
|
* avconv filter configuration
|
|
*
|
|
* This file is part of Libav.
|
|
*
|
|
* Libav 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.
|
|
*
|
|
* Libav 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 Libav; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "avconv.h"
|
|
|
|
#include "libavfilter/avfilter.h"
|
|
#include "libavfilter/buffersrc.h"
|
|
|
|
#include "libavresample/avresample.h"
|
|
|
|
#include "libavutil/avassert.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/channel_layout.h"
|
|
#include "libavutil/display.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/pixdesc.h"
|
|
#include "libavutil/pixfmt.h"
|
|
#include "libavutil/samplefmt.h"
|
|
|
|
/* Define a function for building a string containing a list of
|
|
* allowed formats. */
|
|
#define DEF_CHOOSE_FORMAT(suffix, type, var, supported_list, none, get_name) \
|
|
static char *choose_ ## suffix (OutputFilter *ofilter) \
|
|
{ \
|
|
if (ofilter->var != none) { \
|
|
get_name(ofilter->var); \
|
|
return av_strdup(name); \
|
|
} else if (ofilter->supported_list) { \
|
|
const type *p; \
|
|
AVIOContext *s = NULL; \
|
|
uint8_t *ret; \
|
|
int len; \
|
|
\
|
|
if (avio_open_dyn_buf(&s) < 0) \
|
|
exit(1); \
|
|
\
|
|
for (p = ofilter->supported_list; *p != none; p++) { \
|
|
get_name(*p); \
|
|
avio_printf(s, "%s|", name); \
|
|
} \
|
|
len = avio_close_dyn_buf(s, &ret); \
|
|
ret[len - 1] = 0; \
|
|
return ret; \
|
|
} else \
|
|
return NULL; \
|
|
}
|
|
|
|
DEF_CHOOSE_FORMAT(pix_fmts, enum AVPixelFormat, format, formats, AV_PIX_FMT_NONE,
|
|
GET_PIX_FMT_NAME)
|
|
|
|
DEF_CHOOSE_FORMAT(sample_fmts, enum AVSampleFormat, format, formats,
|
|
AV_SAMPLE_FMT_NONE, GET_SAMPLE_FMT_NAME)
|
|
|
|
DEF_CHOOSE_FORMAT(sample_rates, int, sample_rate, sample_rates, 0,
|
|
GET_SAMPLE_RATE_NAME)
|
|
|
|
DEF_CHOOSE_FORMAT(channel_layouts, uint64_t, channel_layout, channel_layouts, 0,
|
|
GET_CH_LAYOUT_NAME)
|
|
|
|
int init_simple_filtergraph(InputStream *ist, OutputStream *ost)
|
|
{
|
|
FilterGraph *fg = av_mallocz(sizeof(*fg));
|
|
|
|
if (!fg)
|
|
exit(1);
|
|
fg->index = nb_filtergraphs;
|
|
|
|
GROW_ARRAY(fg->outputs, fg->nb_outputs);
|
|
if (!(fg->outputs[0] = av_mallocz(sizeof(*fg->outputs[0]))))
|
|
exit(1);
|
|
fg->outputs[0]->ost = ost;
|
|
fg->outputs[0]->graph = fg;
|
|
fg->outputs[0]->format = -1;
|
|
|
|
ost->filter = fg->outputs[0];
|
|
|
|
GROW_ARRAY(fg->inputs, fg->nb_inputs);
|
|
if (!(fg->inputs[0] = av_mallocz(sizeof(*fg->inputs[0]))))
|
|
exit(1);
|
|
fg->inputs[0]->ist = ist;
|
|
fg->inputs[0]->graph = fg;
|
|
fg->inputs[0]->format = -1;
|
|
|
|
fg->inputs[0]->frame_queue = av_fifo_alloc(8 * sizeof(AVFrame*));
|
|
if (!fg->inputs[0])
|
|
exit_program(1);
|
|
|
|
GROW_ARRAY(ist->filters, ist->nb_filters);
|
|
ist->filters[ist->nb_filters - 1] = fg->inputs[0];
|
|
|
|
GROW_ARRAY(filtergraphs, nb_filtergraphs);
|
|
filtergraphs[nb_filtergraphs - 1] = fg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void init_input_filter(FilterGraph *fg, AVFilterInOut *in)
|
|
{
|
|
InputStream *ist = NULL;
|
|
enum AVMediaType type = avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx);
|
|
int i;
|
|
|
|
// TODO: support other filter types
|
|
if (type != AVMEDIA_TYPE_VIDEO && type != AVMEDIA_TYPE_AUDIO) {
|
|
av_log(NULL, AV_LOG_FATAL, "Only video and audio filters supported "
|
|
"currently.\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (in->name) {
|
|
AVFormatContext *s;
|
|
AVStream *st = NULL;
|
|
char *p;
|
|
int file_idx = strtol(in->name, &p, 0);
|
|
|
|
if (file_idx < 0 || file_idx >= nb_input_files) {
|
|
av_log(NULL, AV_LOG_FATAL, "Invalid file index %d in filtegraph description %s.\n",
|
|
file_idx, fg->graph_desc);
|
|
exit(1);
|
|
}
|
|
s = input_files[file_idx]->ctx;
|
|
|
|
for (i = 0; i < s->nb_streams; i++) {
|
|
if (s->streams[i]->codecpar->codec_type != type)
|
|
continue;
|
|
if (check_stream_specifier(s, s->streams[i], *p == ':' ? p + 1 : p) == 1) {
|
|
st = s->streams[i];
|
|
break;
|
|
}
|
|
}
|
|
if (!st) {
|
|
av_log(NULL, AV_LOG_FATAL, "Stream specifier '%s' in filtergraph description %s "
|
|
"matches no streams.\n", p, fg->graph_desc);
|
|
exit(1);
|
|
}
|
|
ist = input_streams[input_files[file_idx]->ist_index + st->index];
|
|
} else {
|
|
/* find the first unused stream of corresponding type */
|
|
for (i = 0; i < nb_input_streams; i++) {
|
|
ist = input_streams[i];
|
|
if (ist->dec_ctx->codec_type == type && ist->discard)
|
|
break;
|
|
}
|
|
if (i == nb_input_streams) {
|
|
av_log(NULL, AV_LOG_FATAL, "Cannot find a matching stream for "
|
|
"unlabeled input pad %d on filter %s\n", in->pad_idx,
|
|
in->filter_ctx->name);
|
|
exit(1);
|
|
}
|
|
}
|
|
av_assert0(ist);
|
|
|
|
ist->discard = 0;
|
|
ist->decoding_needed = 1;
|
|
ist->st->discard = AVDISCARD_NONE;
|
|
|
|
GROW_ARRAY(fg->inputs, fg->nb_inputs);
|
|
if (!(fg->inputs[fg->nb_inputs - 1] = av_mallocz(sizeof(*fg->inputs[0]))))
|
|
exit(1);
|
|
fg->inputs[fg->nb_inputs - 1]->ist = ist;
|
|
fg->inputs[fg->nb_inputs - 1]->graph = fg;
|
|
fg->inputs[fg->nb_inputs - 1]->format = -1;
|
|
|
|
fg->inputs[fg->nb_inputs - 1]->frame_queue = av_fifo_alloc(8 * sizeof(AVFrame*));
|
|
if (!fg->inputs[fg->nb_inputs - 1])
|
|
exit_program(1);
|
|
|
|
GROW_ARRAY(ist->filters, ist->nb_filters);
|
|
ist->filters[ist->nb_filters - 1] = fg->inputs[fg->nb_inputs - 1];
|
|
}
|
|
|
|
int init_complex_filtergraph(FilterGraph *fg)
|
|
{
|
|
AVFilterInOut *inputs, *outputs, *cur;
|
|
AVFilterGraph *graph;
|
|
int ret = 0;
|
|
|
|
/* this graph is only used for determining the kinds of inputs
|
|
* and outputs we have, and is discarded on exit from this function */
|
|
graph = avfilter_graph_alloc();
|
|
if (!graph)
|
|
return AVERROR(ENOMEM);
|
|
|
|
ret = avfilter_graph_parse2(graph, fg->graph_desc, &inputs, &outputs);
|
|
if (ret < 0)
|
|
goto fail;
|
|
|
|
for (cur = inputs; cur; cur = cur->next)
|
|
init_input_filter(fg, cur);
|
|
|
|
for (cur = outputs; cur;) {
|
|
GROW_ARRAY(fg->outputs, fg->nb_outputs);
|
|
fg->outputs[fg->nb_outputs - 1] = av_mallocz(sizeof(*fg->outputs[0]));
|
|
if (!fg->outputs[fg->nb_outputs - 1])
|
|
exit(1);
|
|
|
|
fg->outputs[fg->nb_outputs - 1]->graph = fg;
|
|
fg->outputs[fg->nb_outputs - 1]->out_tmp = cur;
|
|
fg->outputs[fg->nb_outputs - 1]->type = avfilter_pad_get_type(cur->filter_ctx->output_pads,
|
|
cur->pad_idx);
|
|
cur = cur->next;
|
|
fg->outputs[fg->nb_outputs - 1]->out_tmp->next = NULL;
|
|
}
|
|
|
|
fail:
|
|
avfilter_inout_free(&inputs);
|
|
avfilter_graph_free(&graph);
|
|
return ret;
|
|
}
|
|
|
|
static int insert_trim(int64_t start_time, int64_t duration,
|
|
AVFilterContext **last_filter, int *pad_idx,
|
|
const char *filter_name)
|
|
{
|
|
AVFilterGraph *graph = (*last_filter)->graph;
|
|
AVFilterContext *ctx;
|
|
const AVFilter *trim;
|
|
enum AVMediaType type = avfilter_pad_get_type((*last_filter)->output_pads, *pad_idx);
|
|
const char *name = (type == AVMEDIA_TYPE_VIDEO) ? "trim" : "atrim";
|
|
int ret = 0;
|
|
|
|
if (duration == INT64_MAX && start_time == AV_NOPTS_VALUE)
|
|
return 0;
|
|
|
|
trim = avfilter_get_by_name(name);
|
|
if (!trim) {
|
|
av_log(NULL, AV_LOG_ERROR, "%s filter not present, cannot limit "
|
|
"recording time.\n", name);
|
|
return AVERROR_FILTER_NOT_FOUND;
|
|
}
|
|
|
|
ctx = avfilter_graph_alloc_filter(graph, trim, filter_name);
|
|
if (!ctx)
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (duration != INT64_MAX) {
|
|
ret = av_opt_set_double(ctx, "duration", (double)duration / 1e6,
|
|
AV_OPT_SEARCH_CHILDREN);
|
|
}
|
|
if (ret >= 0 && start_time != AV_NOPTS_VALUE) {
|
|
ret = av_opt_set_double(ctx, "start", (double)start_time / 1e6,
|
|
AV_OPT_SEARCH_CHILDREN);
|
|
}
|
|
if (ret < 0) {
|
|
av_log(ctx, AV_LOG_ERROR, "Error configuring the %s filter", name);
|
|
return ret;
|
|
}
|
|
|
|
ret = avfilter_init_str(ctx, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(*last_filter, *pad_idx, ctx, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*last_filter = ctx;
|
|
*pad_idx = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int insert_filter(AVFilterContext **last_filter, int *pad_idx,
|
|
const char *filter_name, const char *args)
|
|
{
|
|
AVFilterGraph *graph = (*last_filter)->graph;
|
|
AVFilterContext *ctx;
|
|
int ret;
|
|
|
|
ret = avfilter_graph_create_filter(&ctx,
|
|
avfilter_get_by_name(filter_name),
|
|
filter_name, args, NULL, graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(*last_filter, *pad_idx, ctx, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*last_filter = ctx;
|
|
*pad_idx = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int configure_output_video_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out)
|
|
{
|
|
char *pix_fmts;
|
|
OutputStream *ost = ofilter->ost;
|
|
OutputFile *of = output_files[ost->file_index];
|
|
AVFilterContext *last_filter = out->filter_ctx;
|
|
int pad_idx = out->pad_idx;
|
|
int ret;
|
|
char name[255];
|
|
|
|
snprintf(name, sizeof(name), "output stream %d:%d", ost->file_index, ost->index);
|
|
ret = avfilter_graph_create_filter(&ofilter->filter,
|
|
avfilter_get_by_name("buffersink"),
|
|
name, NULL, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (ofilter->width || ofilter->height) {
|
|
char args[255];
|
|
AVFilterContext *filter;
|
|
|
|
snprintf(args, sizeof(args), "%d:%d:0x%X",
|
|
ofilter->width, ofilter->height,
|
|
(unsigned)ost->sws_flags);
|
|
snprintf(name, sizeof(name), "scaler for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
if ((ret = avfilter_graph_create_filter(&filter, avfilter_get_by_name("scale"),
|
|
name, args, NULL, fg->graph)) < 0)
|
|
return ret;
|
|
if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0)
|
|
return ret;
|
|
|
|
last_filter = filter;
|
|
pad_idx = 0;
|
|
}
|
|
|
|
if ((pix_fmts = choose_pix_fmts(ofilter))) {
|
|
AVFilterContext *filter;
|
|
snprintf(name, sizeof(name), "pixel format for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
ret = avfilter_graph_create_filter(&filter,
|
|
avfilter_get_by_name("format"),
|
|
"format", pix_fmts, NULL, fg->graph);
|
|
av_freep(&pix_fmts);
|
|
if (ret < 0)
|
|
return ret;
|
|
if ((ret = avfilter_link(last_filter, pad_idx, filter, 0)) < 0)
|
|
return ret;
|
|
|
|
last_filter = filter;
|
|
pad_idx = 0;
|
|
}
|
|
|
|
if (ost->frame_rate.num) {
|
|
AVFilterContext *fps;
|
|
char args[255];
|
|
|
|
snprintf(args, sizeof(args), "fps=%d/%d", ost->frame_rate.num,
|
|
ost->frame_rate.den);
|
|
snprintf(name, sizeof(name), "fps for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
ret = avfilter_graph_create_filter(&fps, avfilter_get_by_name("fps"),
|
|
name, args, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(last_filter, pad_idx, fps, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
last_filter = fps;
|
|
pad_idx = 0;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "trim for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
ret = insert_trim(of->start_time, of->recording_time,
|
|
&last_filter, &pad_idx, name);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
|
|
if ((ret = avfilter_link(last_filter, pad_idx, ofilter->filter, 0)) < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int configure_output_audio_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out)
|
|
{
|
|
OutputStream *ost = ofilter->ost;
|
|
OutputFile *of = output_files[ost->file_index];
|
|
AVCodecContext *codec = ost->enc_ctx;
|
|
AVFilterContext *last_filter = out->filter_ctx;
|
|
int pad_idx = out->pad_idx;
|
|
char *sample_fmts, *sample_rates, *channel_layouts;
|
|
char name[255];
|
|
int ret;
|
|
|
|
|
|
snprintf(name, sizeof(name), "output stream %d:%d", ost->file_index, ost->index);
|
|
ret = avfilter_graph_create_filter(&ofilter->filter,
|
|
avfilter_get_by_name("abuffersink"),
|
|
name, NULL, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (codec->channels && !codec->channel_layout)
|
|
codec->channel_layout = av_get_default_channel_layout(codec->channels);
|
|
|
|
sample_fmts = choose_sample_fmts(ofilter);
|
|
sample_rates = choose_sample_rates(ofilter);
|
|
channel_layouts = choose_channel_layouts(ofilter);
|
|
if (sample_fmts || sample_rates || channel_layouts) {
|
|
AVFilterContext *format;
|
|
char args[256];
|
|
int len = 0;
|
|
|
|
if (sample_fmts)
|
|
len += snprintf(args + len, sizeof(args) - len, "sample_fmts=%s:",
|
|
sample_fmts);
|
|
if (sample_rates)
|
|
len += snprintf(args + len, sizeof(args) - len, "sample_rates=%s:",
|
|
sample_rates);
|
|
if (channel_layouts)
|
|
len += snprintf(args + len, sizeof(args) - len, "channel_layouts=%s:",
|
|
channel_layouts);
|
|
args[len - 1] = 0;
|
|
|
|
av_freep(&sample_fmts);
|
|
av_freep(&sample_rates);
|
|
av_freep(&channel_layouts);
|
|
|
|
snprintf(name, sizeof(name), "audio format for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
ret = avfilter_graph_create_filter(&format,
|
|
avfilter_get_by_name("aformat"),
|
|
name, args, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(last_filter, pad_idx, format, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
last_filter = format;
|
|
pad_idx = 0;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "trim for output stream %d:%d",
|
|
ost->file_index, ost->index);
|
|
ret = insert_trim(of->start_time, of->recording_time,
|
|
&last_filter, &pad_idx, name);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((ret = avfilter_link(last_filter, pad_idx, ofilter->filter, 0)) < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DESCRIBE_FILTER_LINK(f, inout, in) \
|
|
{ \
|
|
AVFilterContext *ctx = inout->filter_ctx; \
|
|
AVFilterPad *pads = in ? ctx->input_pads : ctx->output_pads; \
|
|
int nb_pads = in ? ctx->nb_inputs : ctx->nb_outputs; \
|
|
AVIOContext *pb; \
|
|
\
|
|
if (avio_open_dyn_buf(&pb) < 0) \
|
|
exit(1); \
|
|
\
|
|
avio_printf(pb, "%s", ctx->filter->name); \
|
|
if (nb_pads > 1) \
|
|
avio_printf(pb, ":%s", avfilter_pad_get_name(pads, inout->pad_idx));\
|
|
avio_w8(pb, 0); \
|
|
avio_close_dyn_buf(pb, &f->name); \
|
|
}
|
|
|
|
int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out)
|
|
{
|
|
av_freep(&ofilter->name);
|
|
DESCRIBE_FILTER_LINK(ofilter, out, 0);
|
|
|
|
switch (avfilter_pad_get_type(out->filter_ctx->output_pads, out->pad_idx)) {
|
|
case AVMEDIA_TYPE_VIDEO: return configure_output_video_filter(fg, ofilter, out);
|
|
case AVMEDIA_TYPE_AUDIO: return configure_output_audio_filter(fg, ofilter, out);
|
|
default: av_assert0(0);
|
|
}
|
|
}
|
|
|
|
static int configure_input_video_filter(FilterGraph *fg, InputFilter *ifilter,
|
|
AVFilterInOut *in)
|
|
{
|
|
AVFilterContext *last_filter;
|
|
const AVFilter *buffer_filt = avfilter_get_by_name("buffer");
|
|
InputStream *ist = ifilter->ist;
|
|
InputFile *f = input_files[ist->file_index];
|
|
AVRational tb = ist->framerate.num ? av_inv_q(ist->framerate) :
|
|
ist->st->time_base;
|
|
AVBufferSrcParameters *par;
|
|
char name[255];
|
|
int ret, pad_idx = 0;
|
|
|
|
snprintf(name, sizeof(name), "graph %d input from stream %d:%d", fg->index,
|
|
ist->file_index, ist->st->index);
|
|
|
|
ifilter->filter = avfilter_graph_alloc_filter(fg->graph, buffer_filt, name);
|
|
if (!ifilter->filter)
|
|
return AVERROR(ENOMEM);
|
|
|
|
par = av_buffersrc_parameters_alloc();
|
|
if (!par)
|
|
return AVERROR(ENOMEM);
|
|
|
|
par->sample_aspect_ratio = ifilter->sample_aspect_ratio;
|
|
par->width = ifilter->width;
|
|
par->height = ifilter->height;
|
|
par->format = ifilter->format;
|
|
par->time_base = tb;
|
|
par->hw_frames_ctx = ifilter->hw_frames_ctx;
|
|
|
|
ret = av_buffersrc_parameters_set(ifilter->filter, par);
|
|
av_freep(&par);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_init_str(ifilter->filter, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
last_filter = ifilter->filter;
|
|
|
|
if (ist->autorotate) {
|
|
uint8_t* displaymatrix = av_stream_get_side_data(ist->st,
|
|
AV_PKT_DATA_DISPLAYMATRIX, NULL);
|
|
if (displaymatrix) {
|
|
double rot = av_display_rotation_get((int32_t*) displaymatrix);
|
|
if (rot < -135 || rot > 135) {
|
|
ret = insert_filter(&last_filter, &pad_idx, "vflip", NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = insert_filter(&last_filter, &pad_idx, "hflip", NULL);
|
|
} else if (rot < -45) {
|
|
ret = insert_filter(&last_filter, &pad_idx, "transpose", "dir=clock");
|
|
} else if (rot > 45) {
|
|
ret = insert_filter(&last_filter, &pad_idx, "transpose", "dir=cclock");
|
|
}
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (ist->framerate.num) {
|
|
AVFilterContext *setpts;
|
|
|
|
snprintf(name, sizeof(name), "force CFR for input from stream %d:%d",
|
|
ist->file_index, ist->st->index);
|
|
if ((ret = avfilter_graph_create_filter(&setpts,
|
|
avfilter_get_by_name("setpts"),
|
|
name, "N", NULL,
|
|
fg->graph)) < 0)
|
|
return ret;
|
|
|
|
if ((ret = avfilter_link(last_filter, 0, setpts, 0)) < 0)
|
|
return ret;
|
|
|
|
last_filter = setpts;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "trim for input stream %d:%d",
|
|
ist->file_index, ist->st->index);
|
|
ret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ?
|
|
AV_NOPTS_VALUE : 0, f->recording_time, &last_filter, &pad_idx, name);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((ret = avfilter_link(last_filter, 0, in->filter_ctx, in->pad_idx)) < 0)
|
|
return ret;
|
|
return 0;
|
|
}
|
|
|
|
static int configure_input_audio_filter(FilterGraph *fg, InputFilter *ifilter,
|
|
AVFilterInOut *in)
|
|
{
|
|
AVFilterContext *last_filter;
|
|
const AVFilter *abuffer_filt = avfilter_get_by_name("abuffer");
|
|
InputStream *ist = ifilter->ist;
|
|
InputFile *f = input_files[ist->file_index];
|
|
AVBufferSrcParameters *par;
|
|
char args[255], name[255];
|
|
int ret, pad_idx = 0;
|
|
|
|
snprintf(name, sizeof(name), "graph %d input from stream %d:%d", fg->index,
|
|
ist->file_index, ist->st->index);
|
|
|
|
ifilter->filter = avfilter_graph_alloc_filter(fg->graph, abuffer_filt, name);
|
|
if (!ifilter->filter)
|
|
return AVERROR(ENOMEM);
|
|
|
|
par = av_buffersrc_parameters_alloc();
|
|
if (!par)
|
|
return AVERROR(ENOMEM);
|
|
|
|
par->time_base = (AVRational){ 1, ifilter->sample_rate };
|
|
par->sample_rate = ifilter->sample_rate;
|
|
par->format = ifilter->format;
|
|
par->channel_layout = ifilter->channel_layout;
|
|
|
|
ret = av_buffersrc_parameters_set(ifilter->filter, par);
|
|
av_freep(&par);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_init_str(ifilter->filter, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
last_filter = ifilter->filter;
|
|
|
|
if (audio_sync_method > 0) {
|
|
AVFilterContext *async;
|
|
int len = 0;
|
|
|
|
av_log(NULL, AV_LOG_WARNING, "-async has been deprecated. Used the "
|
|
"asyncts audio filter instead.\n");
|
|
|
|
if (audio_sync_method > 1)
|
|
len += snprintf(args + len, sizeof(args) - len, "compensate=1:"
|
|
"max_comp=%d:", audio_sync_method);
|
|
snprintf(args + len, sizeof(args) - len, "min_delta=%f",
|
|
audio_drift_threshold);
|
|
|
|
snprintf(name, sizeof(name), "graph %d audio sync for input stream %d:%d",
|
|
fg->index, ist->file_index, ist->st->index);
|
|
ret = avfilter_graph_create_filter(&async,
|
|
avfilter_get_by_name("asyncts"),
|
|
name, args, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(last_filter, 0, async, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
last_filter = async;
|
|
}
|
|
if (audio_volume != 256) {
|
|
AVFilterContext *volume;
|
|
|
|
av_log(NULL, AV_LOG_WARNING, "-vol has been deprecated. Use the volume "
|
|
"audio filter instead.\n");
|
|
|
|
snprintf(args, sizeof(args), "volume=%f", audio_volume / 256.0);
|
|
|
|
snprintf(name, sizeof(name), "graph %d volume for input stream %d:%d",
|
|
fg->index, ist->file_index, ist->st->index);
|
|
ret = avfilter_graph_create_filter(&volume,
|
|
avfilter_get_by_name("volume"),
|
|
name, args, NULL, fg->graph);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = avfilter_link(last_filter, 0, volume, 0);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
last_filter = volume;
|
|
}
|
|
|
|
snprintf(name, sizeof(name), "trim for input stream %d:%d",
|
|
ist->file_index, ist->st->index);
|
|
ret = insert_trim(((f->start_time == AV_NOPTS_VALUE) || !f->accurate_seek) ?
|
|
AV_NOPTS_VALUE : 0, f->recording_time, &last_filter, &pad_idx, name);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((ret = avfilter_link(last_filter, 0, in->filter_ctx, in->pad_idx)) < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int configure_input_filter(FilterGraph *fg, InputFilter *ifilter,
|
|
AVFilterInOut *in)
|
|
{
|
|
av_freep(&ifilter->name);
|
|
DESCRIBE_FILTER_LINK(ifilter, in, 1);
|
|
|
|
switch (avfilter_pad_get_type(in->filter_ctx->input_pads, in->pad_idx)) {
|
|
case AVMEDIA_TYPE_VIDEO: return configure_input_video_filter(fg, ifilter, in);
|
|
case AVMEDIA_TYPE_AUDIO: return configure_input_audio_filter(fg, ifilter, in);
|
|
default: av_assert0(0);
|
|
}
|
|
}
|
|
|
|
int configure_filtergraph(FilterGraph *fg)
|
|
{
|
|
AVFilterInOut *inputs, *outputs, *cur;
|
|
int ret, i, simple = filtergraph_is_simple(fg);
|
|
const char *graph_desc = simple ? fg->outputs[0]->ost->avfilter :
|
|
fg->graph_desc;
|
|
|
|
avfilter_graph_free(&fg->graph);
|
|
if (!(fg->graph = avfilter_graph_alloc()))
|
|
return AVERROR(ENOMEM);
|
|
|
|
if (simple) {
|
|
OutputStream *ost = fg->outputs[0]->ost;
|
|
char args[512];
|
|
AVDictionaryEntry *e = NULL;
|
|
|
|
snprintf(args, sizeof(args), "flags=0x%X", (unsigned)ost->sws_flags);
|
|
fg->graph->scale_sws_opts = av_strdup(args);
|
|
|
|
args[0] = '\0';
|
|
while ((e = av_dict_get(fg->outputs[0]->ost->resample_opts, "", e,
|
|
AV_DICT_IGNORE_SUFFIX))) {
|
|
av_strlcatf(args, sizeof(args), "%s=%s:", e->key, e->value);
|
|
}
|
|
if (strlen(args))
|
|
args[strlen(args) - 1] = '\0';
|
|
fg->graph->resample_lavr_opts = av_strdup(args);
|
|
}
|
|
|
|
if ((ret = avfilter_graph_parse2(fg->graph, graph_desc, &inputs, &outputs)) < 0)
|
|
return ret;
|
|
|
|
if (hw_device_ctx) {
|
|
for (i = 0; i < fg->graph->nb_filters; i++) {
|
|
fg->graph->filters[i]->hw_device_ctx = av_buffer_ref(hw_device_ctx);
|
|
}
|
|
}
|
|
|
|
if (simple && (!inputs || inputs->next || !outputs || outputs->next)) {
|
|
av_log(NULL, AV_LOG_ERROR, "Simple filtergraph '%s' does not have "
|
|
"exactly one input and output.\n", graph_desc);
|
|
return AVERROR(EINVAL);
|
|
}
|
|
|
|
for (cur = inputs, i = 0; cur; cur = cur->next, i++)
|
|
if ((ret = configure_input_filter(fg, fg->inputs[i], cur)) < 0)
|
|
return ret;
|
|
avfilter_inout_free(&inputs);
|
|
|
|
for (cur = outputs, i = 0; cur; cur = cur->next, i++) {
|
|
OutputFilter *ofilter = fg->outputs[i];
|
|
if (ofilter->ost)
|
|
configure_output_filter(fg, ofilter, cur);
|
|
}
|
|
|
|
avfilter_inout_free(&outputs);
|
|
|
|
if ((ret = avfilter_graph_config(fg->graph, NULL)) < 0)
|
|
return ret;
|
|
|
|
/* limit the lists of allowed formats to the ones selected, to
|
|
* make sure they stay the same if the filtergraph is reconfigured later */
|
|
for (i = 0; i < fg->nb_outputs; i++) {
|
|
OutputFilter *ofilter = fg->outputs[i];
|
|
AVFilterLink *link = ofilter->filter->inputs[0];
|
|
|
|
ofilter->format = link->format;
|
|
|
|
ofilter->width = link->w;
|
|
ofilter->height = link->h;
|
|
|
|
ofilter->sample_rate = link->sample_rate;
|
|
ofilter->channel_layout = link->channel_layout;
|
|
}
|
|
|
|
for (i = 0; i < fg->nb_inputs; i++) {
|
|
while (av_fifo_size(fg->inputs[i]->frame_queue)) {
|
|
AVFrame *tmp;
|
|
av_fifo_generic_read(fg->inputs[i]->frame_queue, &tmp, sizeof(tmp), NULL);
|
|
ret = av_buffersrc_add_frame(fg->inputs[i]->filter, tmp);
|
|
av_frame_free(&tmp);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* send the EOFs for the finished inputs */
|
|
for (i = 0; i < fg->nb_inputs; i++) {
|
|
if (fg->inputs[i]->eof) {
|
|
ret = av_buffersrc_add_frame(fg->inputs[i]->filter, NULL);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame)
|
|
{
|
|
av_buffer_unref(&ifilter->hw_frames_ctx);
|
|
|
|
ifilter->format = frame->format;
|
|
|
|
ifilter->width = frame->width;
|
|
ifilter->height = frame->height;
|
|
ifilter->sample_aspect_ratio = frame->sample_aspect_ratio;
|
|
|
|
ifilter->sample_rate = frame->sample_rate;
|
|
ifilter->channel_layout = frame->channel_layout;
|
|
|
|
if (frame->hw_frames_ctx) {
|
|
ifilter->hw_frames_ctx = av_buffer_ref(frame->hw_frames_ctx);
|
|
if (!ifilter->hw_frames_ctx)
|
|
return AVERROR(ENOMEM);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ist_in_filtergraph(FilterGraph *fg, InputStream *ist)
|
|
{
|
|
int i;
|
|
for (i = 0; i < fg->nb_inputs; i++)
|
|
if (fg->inputs[i]->ist == ist)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int filtergraph_is_simple(FilterGraph *fg)
|
|
{
|
|
return !fg->graph_desc;
|
|
}
|