/* * 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/internal.h" #include "libavutil/opt.h" #include "avfilter.h" #include "audio.h" #include "filters.h" #include "formats.h" #include "framesync.h" #include "internal.h" #include "video.h" typedef struct StreamSelectContext { const AVClass *class; int nb_inputs; char *map_str; int *map; int nb_map; int is_audio; int64_t *last_pts; AVFrame **frames; FFFrameSync fs; } StreamSelectContext; #define OFFSET(x) offsetof(StreamSelectContext, x) #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM static const AVOption streamselect_options[] = { { "inputs", "number of input streams", OFFSET(nb_inputs), AV_OPT_TYPE_INT, {.i64=2}, 2, INT_MAX, .flags=FLAGS }, { "map", "input indexes to remap to outputs", OFFSET(map_str), AV_OPT_TYPE_STRING, {.str=NULL}, .flags=FLAGS }, { NULL } }; AVFILTER_DEFINE_CLASS(streamselect); static int process_frame(FFFrameSync *fs) { AVFilterContext *ctx = fs->parent; StreamSelectContext *s = fs->opaque; AVFrame **in = s->frames; int i, j, ret = 0, have_out = 0; for (i = 0; i < ctx->nb_inputs; i++) { if ((ret = ff_framesync_get_frame(&s->fs, i, &in[i], 0)) < 0) return ret; } for (j = 0; j < ctx->nb_inputs; j++) { for (i = 0; i < s->nb_map; i++) { if (s->map[i] == j) { AVFrame *out; if (s->is_audio && s->last_pts[j] == in[j]->pts && ctx->outputs[i]->frame_count_in > 0) continue; out = av_frame_clone(in[j]); if (!out) return AVERROR(ENOMEM); out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, ctx->outputs[i]->time_base); s->last_pts[j] = in[j]->pts; ret = ff_filter_frame(ctx->outputs[i], out); have_out = 1; if (ret < 0) return ret; } } } if (!have_out) ff_filter_set_ready(ctx, 100); return ret; } static int activate(AVFilterContext *ctx) { StreamSelectContext *s = ctx->priv; return ff_framesync_activate(&s->fs); } static int config_output(AVFilterLink *outlink) { AVFilterContext *ctx = outlink->src; StreamSelectContext *s = ctx->priv; const int outlink_idx = FF_OUTLINK_IDX(outlink); const int inlink_idx = s->map[outlink_idx]; AVFilterLink *inlink = ctx->inputs[inlink_idx]; FFFrameSyncIn *in; int i, ret; av_log(ctx, AV_LOG_VERBOSE, "config output link %d " "with settings from input link %d\n", outlink_idx, inlink_idx); switch (outlink->type) { case AVMEDIA_TYPE_VIDEO: outlink->w = inlink->w; outlink->h = inlink->h; outlink->sample_aspect_ratio = inlink->sample_aspect_ratio; outlink->frame_rate = inlink->frame_rate; break; case AVMEDIA_TYPE_AUDIO: outlink->sample_rate = inlink->sample_rate; outlink->channels = inlink->channels; outlink->channel_layout = inlink->channel_layout; break; } outlink->time_base = inlink->time_base; outlink->format = inlink->format; if (s->fs.opaque == s) return 0; if ((ret = ff_framesync_init(&s->fs, ctx, ctx->nb_inputs)) < 0) return ret; in = s->fs.in; s->fs.opaque = s; s->fs.on_event = process_frame; for (i = 0; i < ctx->nb_inputs; i++) { in[i].time_base = ctx->inputs[i]->time_base; in[i].sync = 1; in[i].before = EXT_STOP; in[i].after = EXT_STOP; } s->frames = av_calloc(ctx->nb_inputs, sizeof(*s->frames)); if (!s->frames) return AVERROR(ENOMEM); return ff_framesync_configure(&s->fs); } static int parse_definition(AVFilterContext *ctx, int nb_pads, int is_input, int is_audio) { const char *padtype = is_input ? "in" : "out"; int i = 0, ret = 0; for (i = 0; i < nb_pads; i++) { AVFilterPad pad = { 0 }; pad.type = is_audio ? AVMEDIA_TYPE_AUDIO : AVMEDIA_TYPE_VIDEO; pad.name = av_asprintf("%sput%d", padtype, i); if (!pad.name) return AVERROR(ENOMEM); av_log(ctx, AV_LOG_DEBUG, "Add %s pad %s\n", padtype, pad.name); if (is_input) { ret = ff_insert_inpad(ctx, i, &pad); } else { pad.config_props = config_output; ret = ff_insert_outpad(ctx, i, &pad); } if (ret < 0) { av_freep(&pad.name); return ret; } } return 0; } static int parse_mapping(AVFilterContext *ctx, const char *map) { StreamSelectContext *s = ctx->priv; int *new_map; int new_nb_map = 0; if (!map) { av_log(ctx, AV_LOG_ERROR, "mapping definition is not set\n"); return AVERROR(EINVAL); } new_map = av_calloc(s->nb_inputs, sizeof(*new_map)); if (!new_map) return AVERROR(ENOMEM); while (1) { char *p; const int n = strtol(map, &p, 0); av_log(ctx, AV_LOG_DEBUG, "n=%d map=%p p=%p\n", n, map, p); if (map == p) break; map = p; if (new_nb_map >= s->nb_inputs) { av_log(ctx, AV_LOG_ERROR, "Unable to map more than the %d " "input pads available\n", s->nb_inputs); av_free(new_map); return AVERROR(EINVAL); } if (n < 0 || n >= ctx->nb_inputs) { av_log(ctx, AV_LOG_ERROR, "Input stream index %d doesn't exist " "(there is only %d input streams defined)\n", n, s->nb_inputs); av_free(new_map); return AVERROR(EINVAL); } av_log(ctx, AV_LOG_VERBOSE, "Map input stream %d to output stream %d\n", n, new_nb_map); new_map[new_nb_map++] = n; } if (!new_nb_map) { av_log(ctx, AV_LOG_ERROR, "invalid mapping\n"); av_free(new_map); return AVERROR(EINVAL); } av_freep(&s->map); s->map = new_map; s->nb_map = new_nb_map; av_log(ctx, AV_LOG_VERBOSE, "%d map set\n", s->nb_map); return 0; } static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, char *res, int res_len, int flags) { if (!strcmp(cmd, "map")) { int ret = parse_mapping(ctx, args); if (ret < 0) return ret; return avfilter_config_links(ctx); } return AVERROR(ENOSYS); } static av_cold int init(AVFilterContext *ctx) { StreamSelectContext *s = ctx->priv; int ret, nb_outputs = 0; char *map = s->map_str; if (!strcmp(ctx->filter->name, "astreamselect")) s->is_audio = 1; for (; map;) { char *p; strtol(map, &p, 0); if (map == p) break; nb_outputs++; map = p; } s->last_pts = av_calloc(s->nb_inputs, sizeof(*s->last_pts)); if (!s->last_pts) return AVERROR(ENOMEM); if ((ret = parse_definition(ctx, s->nb_inputs, 1, s->is_audio)) < 0 || (ret = parse_definition(ctx, nb_outputs, 0, s->is_audio)) < 0) return ret; av_log(ctx, AV_LOG_DEBUG, "Configured with %d inpad and %d outpad\n", ctx->nb_inputs, ctx->nb_outputs); return parse_mapping(ctx, s->map_str); } static av_cold void uninit(AVFilterContext *ctx) { StreamSelectContext *s = ctx->priv; av_freep(&s->last_pts); av_freep(&s->map); av_freep(&s->frames); ff_framesync_uninit(&s->fs); for (int i = 0; i < ctx->nb_inputs; i++) av_freep(&ctx->input_pads[i].name); for (int i = 0; i < ctx->nb_outputs; i++) av_freep(&ctx->output_pads[i].name); } static int query_formats(AVFilterContext *ctx) { AVFilterFormats *formats, *rates = NULL; AVFilterChannelLayouts *layouts = NULL; int ret, i; for (i = 0; i < ctx->nb_inputs; i++) { formats = ff_all_formats(ctx->inputs[i]->type); if ((ret = ff_set_common_formats(ctx, formats)) < 0) return ret; if (ctx->inputs[i]->type == AVMEDIA_TYPE_AUDIO) { rates = ff_all_samplerates(); if ((ret = ff_set_common_samplerates(ctx, rates)) < 0) return ret; layouts = ff_all_channel_counts(); if ((ret = ff_set_common_channel_layouts(ctx, layouts)) < 0) return ret; } } return 0; } AVFilter ff_vf_streamselect = { .name = "streamselect", .description = NULL_IF_CONFIG_SMALL("Select video streams"), .init = init, .query_formats = query_formats, .process_command = process_command, .uninit = uninit, .activate = activate, .priv_size = sizeof(StreamSelectContext), .priv_class = &streamselect_class, .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, }; #define astreamselect_options streamselect_options AVFILTER_DEFINE_CLASS(astreamselect); AVFilter ff_af_astreamselect = { .name = "astreamselect", .description = NULL_IF_CONFIG_SMALL("Select audio streams"), .init = init, .query_formats = query_formats, .process_command = process_command, .uninit = uninit, .activate = activate, .priv_size = sizeof(StreamSelectContext), .priv_class = &astreamselect_class, .flags = AVFILTER_FLAG_DYNAMIC_INPUTS | AVFILTER_FLAG_DYNAMIC_OUTPUTS, };