2 * This file is part of FFmpeg.
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 #include "libavutil/avstring.h"
20 #include "libavutil/internal.h"
21 #include "libavutil/mem.h"
22 #include "libavutil/opt.h"
27 #include "framesync.h"
30 typedef struct StreamSelectContext
{
40 } StreamSelectContext
;
42 #define OFFSET(x) offsetof(StreamSelectContext, x)
43 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM
44 #define TFLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM
45 static const AVOption streamselect_options
[] = {
46 { "inputs", "number of input streams", OFFSET(nb_inputs
), AV_OPT_TYPE_INT
, {.i64
=2}, 2, INT_MAX
, .flags
=FLAGS
},
47 { "map", "input indexes to remap to outputs", OFFSET(map_str
), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
=TFLAGS
},
51 AVFILTER_DEFINE_CLASS_EXT(streamselect
, "(a)streamselect", streamselect_options
);
53 static int process_frame(FFFrameSync
*fs
)
55 AVFilterContext
*ctx
= fs
->parent
;
56 StreamSelectContext
*s
= fs
->opaque
;
57 AVFrame
**in
= s
->frames
;
58 int i
, j
, ret
= 0, have_out
= 0;
60 for (i
= 0; i
< ctx
->nb_inputs
; i
++) {
61 if ((ret
= ff_framesync_get_frame(&s
->fs
, i
, &in
[i
], 0)) < 0)
65 for (j
= 0; j
< ctx
->nb_inputs
; j
++) {
66 for (i
= 0; i
< s
->nb_map
; i
++) {
67 FilterLink
*outl
= ff_filter_link(ctx
->outputs
[i
]);
71 if (s
->is_audio
&& s
->last_pts
[j
] == in
[j
]->pts
&&
72 outl
->frame_count_in
> 0)
74 out
= av_frame_clone(in
[j
]);
76 return AVERROR(ENOMEM
);
78 out
->pts
= av_rescale_q(s
->fs
.pts
, s
->fs
.time_base
, ctx
->outputs
[i
]->time_base
);
79 s
->last_pts
[j
] = in
[j
]->pts
;
80 ret
= ff_filter_frame(ctx
->outputs
[i
], out
);
89 ff_filter_set_ready(ctx
, 100);
93 static int activate(AVFilterContext
*ctx
)
95 StreamSelectContext
*s
= ctx
->priv
;
96 return ff_framesync_activate(&s
->fs
);
99 static int config_output(AVFilterLink
*outlink
)
101 FilterLink
*outl
= ff_filter_link(outlink
);
102 AVFilterContext
*ctx
= outlink
->src
;
103 StreamSelectContext
*s
= ctx
->priv
;
104 const int outlink_idx
= FF_OUTLINK_IDX(outlink
);
105 const int inlink_idx
= s
->map
[outlink_idx
];
106 AVFilterLink
*inlink
= ctx
->inputs
[inlink_idx
];
107 FilterLink
*inl
= ff_filter_link(inlink
);
111 av_log(ctx
, AV_LOG_VERBOSE
, "config output link %d "
112 "with settings from input link %d\n",
113 outlink_idx
, inlink_idx
);
115 switch (outlink
->type
) {
116 case AVMEDIA_TYPE_VIDEO
:
117 outlink
->w
= inlink
->w
;
118 outlink
->h
= inlink
->h
;
119 outlink
->sample_aspect_ratio
= inlink
->sample_aspect_ratio
;
120 outl
->frame_rate
= inl
->frame_rate
;
122 case AVMEDIA_TYPE_AUDIO
:
123 outlink
->sample_rate
= inlink
->sample_rate
;
124 outlink
->ch_layout
.nb_channels
= inlink
->ch_layout
.nb_channels
;
128 outlink
->time_base
= inlink
->time_base
;
129 outlink
->format
= inlink
->format
;
131 if (s
->fs
.opaque
== s
)
134 if ((ret
= ff_framesync_init(&s
->fs
, ctx
, ctx
->nb_inputs
)) < 0)
139 s
->fs
.on_event
= process_frame
;
141 for (i
= 0; i
< ctx
->nb_inputs
; i
++) {
142 in
[i
].time_base
= ctx
->inputs
[i
]->time_base
;
144 in
[i
].before
= EXT_STOP
;
145 in
[i
].after
= EXT_STOP
;
148 s
->frames
= av_calloc(ctx
->nb_inputs
, sizeof(*s
->frames
));
150 return AVERROR(ENOMEM
);
152 return ff_framesync_configure(&s
->fs
);
155 static int parse_definition(AVFilterContext
*ctx
, int nb_pads
, int is_input
, int is_audio
)
157 const char *padtype
= is_input
? "in" : "out";
160 for (i
= 0; i
< nb_pads
; i
++) {
161 AVFilterPad pad
= { 0 };
163 pad
.type
= is_audio
? AVMEDIA_TYPE_AUDIO
: AVMEDIA_TYPE_VIDEO
;
165 pad
.name
= av_asprintf("%sput%d", padtype
, i
);
167 return AVERROR(ENOMEM
);
169 av_log(ctx
, AV_LOG_DEBUG
, "Add %s pad %s\n", padtype
, pad
.name
);
172 ret
= ff_append_inpad_free_name(ctx
, &pad
);
174 pad
.config_props
= config_output
;
175 ret
= ff_append_outpad_free_name(ctx
, &pad
);
184 static int parse_mapping(AVFilterContext
*ctx
, const char *map
)
186 StreamSelectContext
*s
= ctx
->priv
;
191 av_log(ctx
, AV_LOG_ERROR
, "mapping definition is not set\n");
192 return AVERROR(EINVAL
);
195 new_map
= av_calloc(s
->nb_inputs
, sizeof(*new_map
));
197 return AVERROR(ENOMEM
);
201 const int n
= strtol(map
, &p
, 0);
203 av_log(ctx
, AV_LOG_DEBUG
, "n=%d map=%p p=%p\n", n
, map
, p
);
209 if (new_nb_map
>= s
->nb_inputs
) {
210 av_log(ctx
, AV_LOG_ERROR
, "Unable to map more than the %d "
211 "input pads available\n", s
->nb_inputs
);
213 return AVERROR(EINVAL
);
216 if (n
< 0 || n
>= ctx
->nb_inputs
) {
217 av_log(ctx
, AV_LOG_ERROR
, "Input stream index %d doesn't exist "
218 "(there is only %d input streams defined)\n",
221 return AVERROR(EINVAL
);
224 av_log(ctx
, AV_LOG_VERBOSE
, "Map input stream %d to output stream %d\n", n
, new_nb_map
);
225 new_map
[new_nb_map
++] = n
;
229 av_log(ctx
, AV_LOG_ERROR
, "invalid mapping\n");
231 return AVERROR(EINVAL
);
236 s
->nb_map
= new_nb_map
;
238 av_log(ctx
, AV_LOG_VERBOSE
, "%d map set\n", s
->nb_map
);
243 static int process_command(AVFilterContext
*ctx
, const char *cmd
, const char *args
,
244 char *res
, int res_len
, int flags
)
246 if (!strcmp(cmd
, "map")) {
247 int ret
= parse_mapping(ctx
, args
);
253 return AVERROR(ENOSYS
);
256 static av_cold
int init(AVFilterContext
*ctx
)
258 StreamSelectContext
*s
= ctx
->priv
;
259 int ret
, nb_outputs
= 0;
260 char *map
= s
->map_str
;
262 if (!strcmp(ctx
->filter
->name
, "astreamselect"))
275 s
->last_pts
= av_calloc(s
->nb_inputs
, sizeof(*s
->last_pts
));
277 return AVERROR(ENOMEM
);
279 if ((ret
= parse_definition(ctx
, s
->nb_inputs
, 1, s
->is_audio
)) < 0 ||
280 (ret
= parse_definition(ctx
, nb_outputs
, 0, s
->is_audio
)) < 0)
283 av_log(ctx
, AV_LOG_DEBUG
, "Configured with %d inpad and %d outpad\n",
284 ctx
->nb_inputs
, ctx
->nb_outputs
);
286 return parse_mapping(ctx
, s
->map_str
);
289 static av_cold
void uninit(AVFilterContext
*ctx
)
291 StreamSelectContext
*s
= ctx
->priv
;
293 av_freep(&s
->last_pts
);
295 av_freep(&s
->frames
);
296 ff_framesync_uninit(&s
->fs
);
299 const FFFilter ff_vf_streamselect
= {
300 .p
.name
= "streamselect",
301 .p
.description
= NULL_IF_CONFIG_SMALL("Select video streams"),
302 .p
.priv_class
= &streamselect_class
,
303 .p
.flags
= AVFILTER_FLAG_DYNAMIC_INPUTS
| AVFILTER_FLAG_DYNAMIC_OUTPUTS
,
305 .process_command
= process_command
,
307 .activate
= activate
,
308 .priv_size
= sizeof(StreamSelectContext
),
311 const FFFilter ff_af_astreamselect
= {
312 .p
.name
= "astreamselect",
313 .p
.description
= NULL_IF_CONFIG_SMALL("Select audio streams"),
314 .p
.priv_class
= &streamselect_class
,
315 .p
.flags
= AVFILTER_FLAG_DYNAMIC_INPUTS
| AVFILTER_FLAG_DYNAMIC_OUTPUTS
,
317 .process_command
= process_command
,
319 .activate
= activate
,
320 .priv_size
= sizeof(StreamSelectContext
),