LCOV - code coverage report
Current view: top level - libavfilter - af_join.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 158 241 65.6 %
Date: 2017-12-14 01:15:32 Functions: 9 9 100.0 %

          Line data    Source code
       1             : /*
       2             :  * This file is part of FFmpeg.
       3             :  *
       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.
       8             :  *
       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.
      13             :  *
      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
      17             :  */
      18             : 
      19             : /**
      20             :  * @file
      21             :  * Audio join filter
      22             :  *
      23             :  * Join multiple audio inputs as different channels in
      24             :  * a single output
      25             :  */
      26             : 
      27             : #include "libavutil/avassert.h"
      28             : #include "libavutil/channel_layout.h"
      29             : #include "libavutil/common.h"
      30             : #include "libavutil/opt.h"
      31             : 
      32             : #include "audio.h"
      33             : #include "avfilter.h"
      34             : #include "formats.h"
      35             : #include "filters.h"
      36             : #include "internal.h"
      37             : 
      38             : typedef struct ChannelMap {
      39             :     int input;                ///< input stream index
      40             :     int       in_channel_idx; ///< index of in_channel in the input stream data
      41             :     uint64_t  in_channel;     ///< layout describing the input channel
      42             :     uint64_t out_channel;     ///< layout describing the output channel
      43             : } ChannelMap;
      44             : 
      45             : typedef struct JoinContext {
      46             :     const AVClass *class;
      47             : 
      48             :     int inputs;
      49             :     char *map;
      50             :     char    *channel_layout_str;
      51             :     uint64_t channel_layout;
      52             : 
      53             :     int      nb_channels;
      54             :     ChannelMap *channels;
      55             : 
      56             :     /**
      57             :      * Temporary storage for input frames, until we get one on each input.
      58             :      */
      59             :     AVFrame **input_frames;
      60             : 
      61             :     /**
      62             :      *  Temporary storage for buffer references, for assembling the output frame.
      63             :      */
      64             :     AVBufferRef **buffers;
      65             : } JoinContext;
      66             : 
      67             : #define OFFSET(x) offsetof(JoinContext, x)
      68             : #define A AV_OPT_FLAG_AUDIO_PARAM
      69             : #define F AV_OPT_FLAG_FILTERING_PARAM
      70             : static const AVOption join_options[] = {
      71             :     { "inputs",         "Number of input streams.", OFFSET(inputs),             AV_OPT_TYPE_INT,    { .i64 = 2 }, 1, INT_MAX,       A|F },
      72             :     { "channel_layout", "Channel layout of the "
      73             :                         "output stream.",           OFFSET(channel_layout_str), AV_OPT_TYPE_STRING, {.str = "stereo"}, 0, 0, A|F },
      74             :     { "map",            "A comma-separated list of channels maps in the format "
      75             :                         "'input_stream.input_channel-output_channel.",
      76             :                                                     OFFSET(map),                AV_OPT_TYPE_STRING,                 .flags = A|F },
      77             :     { NULL }
      78             : };
      79             : 
      80             : AVFILTER_DEFINE_CLASS(join);
      81             : 
      82           2 : static int parse_maps(AVFilterContext *ctx)
      83             : {
      84           2 :     JoinContext *s = ctx->priv;
      85           2 :     char separator = '|';
      86           2 :     char *cur      = s->map;
      87             : 
      88           4 :     while (cur && *cur) {
      89             :         char *sep, *next, *p;
      90           0 :         uint64_t in_channel = 0, out_channel = 0;
      91             :         int input_idx, out_ch_idx, in_ch_idx;
      92             : 
      93           0 :         next = strchr(cur, separator);
      94           0 :         if (next)
      95           0 :             *next++ = 0;
      96             : 
      97             :         /* split the map into input and output parts */
      98           0 :         if (!(sep = strchr(cur, '-'))) {
      99           0 :             av_log(ctx, AV_LOG_ERROR, "Missing separator '-' in channel "
     100             :                    "map '%s'\n", cur);
     101           0 :             return AVERROR(EINVAL);
     102             :         }
     103           0 :         *sep++ = 0;
     104             : 
     105             : #define PARSE_CHANNEL(str, var, inout)                                         \
     106             :         if (!(var = av_get_channel_layout(str))) {                             \
     107             :             av_log(ctx, AV_LOG_ERROR, "Invalid " inout " channel: %s.\n", str);\
     108             :             return AVERROR(EINVAL);                                            \
     109             :         }                                                                      \
     110             :         if (av_get_channel_layout_nb_channels(var) != 1) {                     \
     111             :             av_log(ctx, AV_LOG_ERROR, "Channel map describes more than one "   \
     112             :                    inout " channel.\n");                                       \
     113             :             return AVERROR(EINVAL);                                            \
     114             :         }
     115             : 
     116             :         /* parse output channel */
     117           0 :         PARSE_CHANNEL(sep, out_channel, "output");
     118           0 :         if (!(out_channel & s->channel_layout)) {
     119           0 :             av_log(ctx, AV_LOG_ERROR, "Output channel '%s' is not present in "
     120             :                    "requested channel layout.\n", sep);
     121           0 :             return AVERROR(EINVAL);
     122             :         }
     123             : 
     124           0 :         out_ch_idx = av_get_channel_layout_channel_index(s->channel_layout,
     125             :                                                          out_channel);
     126           0 :         if (s->channels[out_ch_idx].input >= 0) {
     127           0 :             av_log(ctx, AV_LOG_ERROR, "Multiple maps for output channel "
     128             :                    "'%s'.\n", sep);
     129           0 :             return AVERROR(EINVAL);
     130             :         }
     131             : 
     132             :         /* parse input channel */
     133           0 :         input_idx = strtol(cur, &cur, 0);
     134           0 :         if (input_idx < 0 || input_idx >= s->inputs) {
     135           0 :             av_log(ctx, AV_LOG_ERROR, "Invalid input stream index: %d.\n",
     136             :                    input_idx);
     137           0 :             return AVERROR(EINVAL);
     138             :         }
     139             : 
     140           0 :         if (*cur)
     141           0 :             cur++;
     142             : 
     143           0 :         in_ch_idx = strtol(cur, &p, 0);
     144           0 :         if (p == cur) {
     145             :             /* channel specifier is not a number,
     146             :              * try to parse as channel name */
     147           0 :             PARSE_CHANNEL(cur, in_channel, "input");
     148             :         }
     149             : 
     150           0 :         s->channels[out_ch_idx].input      = input_idx;
     151           0 :         if (in_channel)
     152           0 :             s->channels[out_ch_idx].in_channel = in_channel;
     153             :         else
     154           0 :             s->channels[out_ch_idx].in_channel_idx = in_ch_idx;
     155             : 
     156           0 :         cur = next;
     157             :     }
     158           2 :     return 0;
     159             : }
     160             : 
     161           2 : static av_cold int join_init(AVFilterContext *ctx)
     162             : {
     163           2 :     JoinContext *s = ctx->priv;
     164             :     int ret, i;
     165             : 
     166           2 :     if (!(s->channel_layout = av_get_channel_layout(s->channel_layout_str))) {
     167           0 :         av_log(ctx, AV_LOG_ERROR, "Error parsing channel layout '%s'.\n",
     168             :                s->channel_layout_str);
     169           0 :         return AVERROR(EINVAL);
     170             :     }
     171             : 
     172           2 :     s->nb_channels  = av_get_channel_layout_nb_channels(s->channel_layout);
     173           2 :     s->channels     = av_mallocz_array(s->nb_channels, sizeof(*s->channels));
     174           2 :     s->buffers      = av_mallocz_array(s->nb_channels, sizeof(*s->buffers));
     175           2 :     s->input_frames = av_mallocz_array(s->inputs, sizeof(*s->input_frames));
     176           2 :     if (!s->channels || !s->buffers|| !s->input_frames)
     177           0 :         return AVERROR(ENOMEM);
     178             : 
     179          12 :     for (i = 0; i < s->nb_channels; i++) {
     180          10 :         s->channels[i].out_channel = av_channel_layout_extract_channel(s->channel_layout, i);
     181          10 :         s->channels[i].input       = -1;
     182             :     }
     183             : 
     184           2 :     if ((ret = parse_maps(ctx)) < 0)
     185           0 :         return ret;
     186             : 
     187          12 :     for (i = 0; i < s->inputs; i++) {
     188             :         char name[32];
     189           4 :         AVFilterPad pad = { 0 };
     190             : 
     191           4 :         snprintf(name, sizeof(name), "input%d", i);
     192           4 :         pad.type           = AVMEDIA_TYPE_AUDIO;
     193           4 :         pad.name           = av_strdup(name);
     194           4 :         if (!pad.name)
     195           0 :             return AVERROR(ENOMEM);
     196             : 
     197           4 :         if ((ret = ff_insert_inpad(ctx, i, &pad)) < 0) {
     198           0 :             av_freep(&pad.name);
     199           0 :             return ret;
     200             :         }
     201             :     }
     202             : 
     203           2 :     return 0;
     204             : }
     205             : 
     206           2 : static av_cold void join_uninit(AVFilterContext *ctx)
     207             : {
     208           2 :     JoinContext *s = ctx->priv;
     209             :     int i;
     210             : 
     211           6 :     for (i = 0; i < ctx->nb_inputs; i++) {
     212           4 :         av_freep(&ctx->input_pads[i].name);
     213           4 :         av_frame_free(&s->input_frames[i]);
     214             :     }
     215             : 
     216           2 :     av_freep(&s->channels);
     217           2 :     av_freep(&s->buffers);
     218           2 :     av_freep(&s->input_frames);
     219           2 : }
     220             : 
     221           1 : static int join_query_formats(AVFilterContext *ctx)
     222             : {
     223           1 :     JoinContext *s = ctx->priv;
     224           1 :     AVFilterChannelLayouts *layouts = NULL;
     225             :     int i, ret;
     226             : 
     227           2 :     if ((ret = ff_add_channel_layout(&layouts, s->channel_layout)) < 0 ||
     228           1 :         (ret = ff_channel_layouts_ref(layouts, &ctx->outputs[0]->in_channel_layouts)) < 0)
     229           0 :         return ret;
     230             : 
     231           3 :     for (i = 0; i < ctx->nb_inputs; i++) {
     232           2 :         layouts = ff_all_channel_layouts();
     233           2 :         if ((ret = ff_channel_layouts_ref(layouts, &ctx->inputs[i]->out_channel_layouts)) < 0)
     234           0 :             return ret;
     235             :     }
     236             : 
     237           2 :     if ((ret = ff_set_common_formats(ctx, ff_planar_sample_fmts())) < 0 ||
     238           1 :         (ret = ff_set_common_samplerates(ctx, ff_all_samplerates())) < 0)
     239           0 :         return ret;
     240             : 
     241           1 :     return 0;
     242             : }
     243             : 
     244           5 : static void guess_map_matching(AVFilterContext *ctx, ChannelMap *ch,
     245             :                                uint64_t *inputs)
     246             : {
     247             :     int i;
     248             : 
     249          11 :     for (i = 0; i < ctx->nb_inputs; i++) {
     250           8 :         AVFilterLink *link = ctx->inputs[i];
     251             : 
     252          10 :         if (ch->out_channel & link->channel_layout &&
     253           2 :             !(ch->out_channel & inputs[i])) {
     254           2 :             ch->input      = i;
     255           2 :             ch->in_channel = ch->out_channel;
     256           2 :             inputs[i]     |= ch->out_channel;
     257           2 :             return;
     258             :         }
     259             :     }
     260             : }
     261             : 
     262           3 : static void guess_map_any(AVFilterContext *ctx, ChannelMap *ch,
     263             :                           uint64_t *inputs)
     264             : {
     265             :     int i;
     266             : 
     267           6 :     for (i = 0; i < ctx->nb_inputs; i++) {
     268           6 :         AVFilterLink *link = ctx->inputs[i];
     269             : 
     270           6 :         if ((inputs[i] & link->channel_layout) != link->channel_layout) {
     271           3 :             uint64_t unused = link->channel_layout & ~inputs[i];
     272             : 
     273           3 :             ch->input      = i;
     274           3 :             ch->in_channel = av_channel_layout_extract_channel(unused, 0);
     275           3 :             inputs[i]     |= ch->in_channel;
     276           3 :             return;
     277             :         }
     278             :     }
     279             : }
     280             : 
     281           1 : static int join_config_output(AVFilterLink *outlink)
     282             : {
     283           1 :     AVFilterContext *ctx = outlink->src;
     284           1 :     JoinContext       *s = ctx->priv;
     285             :     uint64_t *inputs;   // nth element tracks which channels are used from nth input
     286           1 :     int i, ret = 0;
     287             : 
     288             :     /* initialize inputs to user-specified mappings */
     289           1 :     if (!(inputs = av_mallocz_array(ctx->nb_inputs, sizeof(*inputs))))
     290           0 :         return AVERROR(ENOMEM);
     291           6 :     for (i = 0; i < s->nb_channels; i++) {
     292           5 :         ChannelMap *ch = &s->channels[i];
     293             :         AVFilterLink *inlink;
     294             : 
     295           5 :         if (ch->input < 0)
     296           5 :             continue;
     297             : 
     298           0 :         inlink = ctx->inputs[ch->input];
     299             : 
     300           0 :         if (!ch->in_channel)
     301           0 :             ch->in_channel = av_channel_layout_extract_channel(inlink->channel_layout,
     302             :                                                                ch->in_channel_idx);
     303             : 
     304           0 :         if (!(ch->in_channel & inlink->channel_layout)) {
     305           0 :             av_log(ctx, AV_LOG_ERROR, "Requested channel %s is not present in "
     306             :                    "input stream #%d.\n", av_get_channel_name(ch->in_channel),
     307             :                    ch->input);
     308           0 :             ret = AVERROR(EINVAL);
     309           0 :             goto fail;
     310             :         }
     311             : 
     312           0 :         inputs[ch->input] |= ch->in_channel;
     313             :     }
     314             : 
     315             :     /* guess channel maps when not explicitly defined */
     316             :     /* first try unused matching channels */
     317           6 :     for (i = 0; i < s->nb_channels; i++) {
     318           5 :         ChannelMap *ch = &s->channels[i];
     319             : 
     320           5 :         if (ch->input < 0)
     321           5 :             guess_map_matching(ctx, ch, inputs);
     322             :     }
     323             : 
     324             :     /* if the above failed, try to find _any_ unused input channel */
     325           6 :     for (i = 0; i < s->nb_channels; i++) {
     326           5 :         ChannelMap *ch = &s->channels[i];
     327             : 
     328           5 :         if (ch->input < 0)
     329           3 :             guess_map_any(ctx, ch, inputs);
     330             : 
     331           5 :         if (ch->input < 0) {
     332           0 :             av_log(ctx, AV_LOG_ERROR, "Could not find input channel for "
     333             :                    "output channel '%s'.\n",
     334             :                    av_get_channel_name(ch->out_channel));
     335           0 :             goto fail;
     336             :         }
     337             : 
     338           5 :         ch->in_channel_idx = av_get_channel_layout_channel_index(ctx->inputs[ch->input]->channel_layout,
     339             :                                                                  ch->in_channel);
     340             :     }
     341             : 
     342             :     /* print mappings */
     343           1 :     av_log(ctx, AV_LOG_VERBOSE, "mappings: ");
     344           6 :     for (i = 0; i < s->nb_channels; i++) {
     345           5 :         ChannelMap *ch = &s->channels[i];
     346           5 :         av_log(ctx, AV_LOG_VERBOSE, "%d.%s => %s ", ch->input,
     347             :                av_get_channel_name(ch->in_channel),
     348             :                av_get_channel_name(ch->out_channel));
     349             :     }
     350           1 :     av_log(ctx, AV_LOG_VERBOSE, "\n");
     351             : 
     352           3 :     for (i = 0; i < ctx->nb_inputs; i++) {
     353           2 :         if (!inputs[i])
     354           0 :             av_log(ctx, AV_LOG_WARNING, "No channels are used from input "
     355             :                    "stream %d.\n", i);
     356             :     }
     357             : 
     358           1 : fail:
     359           1 :     av_freep(&inputs);
     360           1 :     return ret;
     361             : }
     362             : 
     363         261 : static int try_push_frame(AVFilterContext *ctx)
     364             : {
     365         261 :     AVFilterLink *outlink = ctx->outputs[0];
     366         261 :     JoinContext *s       = ctx->priv;
     367             :     AVFrame *frame;
     368         261 :     int linesize   = INT_MAX;
     369         261 :     int nb_samples = INT_MAX;
     370         261 :     int nb_buffers = 0;
     371             :     int i, j, ret;
     372             : 
     373         781 :     for (i = 0; i < ctx->nb_inputs; i++) {
     374         522 :         if (!s->input_frames[i])
     375           2 :             return 0;
     376         520 :         nb_samples = FFMIN(nb_samples, s->input_frames[i]->nb_samples);
     377             :     }
     378         259 :     if (!nb_samples)
     379           0 :         return 0;
     380             : 
     381             :     /* setup the output frame */
     382         259 :     frame = av_frame_alloc();
     383         259 :     if (!frame)
     384           0 :         return AVERROR(ENOMEM);
     385         259 :     if (s->nb_channels > FF_ARRAY_ELEMS(frame->data)) {
     386           0 :         frame->extended_data = av_mallocz_array(s->nb_channels,
     387             :                                           sizeof(*frame->extended_data));
     388           0 :         if (!frame->extended_data) {
     389           0 :             ret = AVERROR(ENOMEM);
     390           0 :             goto fail;
     391             :         }
     392             :     }
     393             : 
     394             :     /* copy the data pointers */
     395        1554 :     for (i = 0; i < s->nb_channels; i++) {
     396        1295 :         ChannelMap *ch = &s->channels[i];
     397        1295 :         AVFrame *cur   = s->input_frames[ch->input];
     398             :         AVBufferRef *buf;
     399             : 
     400        1295 :         frame->extended_data[i] = cur->extended_data[ch->in_channel_idx];
     401        1295 :         linesize = FFMIN(linesize, cur->linesize[0]);
     402             : 
     403             :         /* add the buffer where this plan is stored to the list if it's
     404             :          * not already there */
     405        1295 :         buf = av_frame_get_plane_buffer(cur, ch->in_channel_idx);
     406        1295 :         if (!buf) {
     407           0 :             ret = AVERROR(EINVAL);
     408           0 :             goto fail;
     409             :         }
     410        3885 :         for (j = 0; j < nb_buffers; j++)
     411        2590 :             if (s->buffers[j]->buffer == buf->buffer)
     412           0 :                 break;
     413        1295 :         if (j == i)
     414        1295 :             s->buffers[nb_buffers++] = buf;
     415             :     }
     416             : 
     417             :     /* create references to the buffers we copied to output */
     418         259 :     if (nb_buffers > FF_ARRAY_ELEMS(frame->buf)) {
     419           0 :         frame->nb_extended_buf = nb_buffers - FF_ARRAY_ELEMS(frame->buf);
     420           0 :         frame->extended_buf = av_mallocz_array(frame->nb_extended_buf,
     421             :                                                sizeof(*frame->extended_buf));
     422           0 :         if (!frame->extended_buf) {
     423           0 :             frame->nb_extended_buf = 0;
     424           0 :             ret = AVERROR(ENOMEM);
     425           0 :             goto fail;
     426             :         }
     427             :     }
     428        1554 :     for (i = 0; i < FFMIN(FF_ARRAY_ELEMS(frame->buf), nb_buffers); i++) {
     429        1295 :         frame->buf[i] = av_buffer_ref(s->buffers[i]);
     430        1295 :         if (!frame->buf[i]) {
     431           0 :             ret = AVERROR(ENOMEM);
     432           0 :             goto fail;
     433             :         }
     434             :     }
     435         259 :     for (i = 0; i < frame->nb_extended_buf; i++) {
     436           0 :         frame->extended_buf[i] = av_buffer_ref(s->buffers[i +
     437             :                                                FF_ARRAY_ELEMS(frame->buf)]);
     438           0 :         if (!frame->extended_buf[i]) {
     439           0 :             ret = AVERROR(ENOMEM);
     440           0 :             goto fail;
     441             :         }
     442             :     }
     443             : 
     444         259 :     frame->nb_samples     = nb_samples;
     445         259 :     frame->channel_layout = outlink->channel_layout;
     446         259 :     frame->channels       = outlink->channels;
     447         259 :     frame->sample_rate    = outlink->sample_rate;
     448         259 :     frame->format         = outlink->format;
     449         259 :     frame->pts            = s->input_frames[0]->pts;
     450         259 :     frame->linesize[0]    = linesize;
     451         259 :     if (frame->data != frame->extended_data) {
     452           0 :         memcpy(frame->data, frame->extended_data, sizeof(*frame->data) *
     453           0 :                FFMIN(FF_ARRAY_ELEMS(frame->data), s->nb_channels));
     454             :     }
     455             : 
     456         259 :     ret = ff_filter_frame(outlink, frame);
     457             : 
     458         777 :     for (i = 0; i < ctx->nb_inputs; i++)
     459         518 :         av_frame_free(&s->input_frames[i]);
     460             : 
     461         259 :     return ret;
     462             : 
     463           0 : fail:
     464           0 :     av_frame_free(&frame);
     465           0 :     return ret;
     466             : }
     467             : 
     468         910 : static int activate(AVFilterContext *ctx)
     469             : {
     470         910 :     JoinContext *s = ctx->priv;
     471             :     int i, ret, status;
     472         910 :     int nb_samples = 0;
     473             :     int64_t pts;
     474             : 
     475         910 :     if (!s->input_frames[0]) {
     476         521 :         ret = ff_inlink_consume_frame(ctx->inputs[0], &s->input_frames[0]);
     477         521 :         if (ret < 0) {
     478           0 :             return ret;
     479         521 :         } else if (ff_inlink_acknowledge_status(ctx->inputs[0], &status, &pts)) {
     480           3 :             ff_outlink_set_status(ctx->outputs[0], status, pts);
     481           3 :             return 0;
     482             :         } else {
     483         518 :             if (ff_outlink_frame_wanted(ctx->outputs[0]) && !s->input_frames[0]) {
     484         259 :                 ff_inlink_request_frame(ctx->inputs[0]);
     485         259 :                 return 0;
     486             :             }
     487             :         }
     488             :     }
     489             : 
     490         648 :     nb_samples = s->input_frames[0]->nb_samples;
     491             : 
     492         909 :     for (i = 1; i < ctx->nb_inputs && nb_samples > 0; i++) {
     493         648 :         if (s->input_frames[i])
     494           0 :             continue;
     495             : 
     496         648 :         if (ff_inlink_check_available_samples(ctx->inputs[i], nb_samples) > 0) {
     497         259 :             ret = ff_inlink_consume_samples(ctx->inputs[i], nb_samples, nb_samples, &s->input_frames[i]);
     498         259 :             if (ret < 0) {
     499           0 :                 return ret;
     500         259 :             } else if (ff_inlink_acknowledge_status(ctx->inputs[i], &status, &pts)) {
     501           0 :                 ff_outlink_set_status(ctx->outputs[0], status, pts);
     502           0 :                 return 0;
     503             :             }
     504             :         } else {
     505         389 :             if (ff_outlink_frame_wanted(ctx->outputs[0])) {
     506         387 :                 ff_inlink_request_frame(ctx->inputs[i]);
     507         387 :                 return 0;
     508             :             }
     509             :         }
     510             :     }
     511             : 
     512         261 :     return try_push_frame(ctx);
     513             : }
     514             : 
     515             : static const AVFilterPad avfilter_af_join_outputs[] = {
     516             :     {
     517             :         .name          = "default",
     518             :         .type          = AVMEDIA_TYPE_AUDIO,
     519             :         .config_props  = join_config_output,
     520             :     },
     521             :     { NULL }
     522             : };
     523             : 
     524             : AVFilter ff_af_join = {
     525             :     .name           = "join",
     526             :     .description    = NULL_IF_CONFIG_SMALL("Join multiple audio streams into "
     527             :                                            "multi-channel output."),
     528             :     .priv_size      = sizeof(JoinContext),
     529             :     .priv_class     = &join_class,
     530             :     .init           = join_init,
     531             :     .uninit         = join_uninit,
     532             :     .activate       = activate,
     533             :     .query_formats  = join_query_formats,
     534             :     .inputs         = NULL,
     535             :     .outputs        = avfilter_af_join_outputs,
     536             :     .flags          = AVFILTER_FLAG_DYNAMIC_INPUTS,
     537             : };

Generated by: LCOV version 1.13