| /* |
| * 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 |
| #define TFLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_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=TFLAGS }, |
| { 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, |
| }; |