GCC Code Coverage Report
Directory: ../../../ffmpeg/ Exec Total Coverage
File: src/libavfilter/f_sendcmd.c Lines: 0 257 0.0 %
Date: 2020-10-23 17:01:47 Branches: 0 152 0.0 %

Line Branch Exec Source
1
/*
2
 * Copyright (c) 2012 Stefano Sabatini
3
 *
4
 * This file is part of FFmpeg.
5
 *
6
 * FFmpeg is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * FFmpeg is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with FFmpeg; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
 */
20
21
/**
22
 * @file
23
 * send commands filter
24
 */
25
26
#include "libavutil/avstring.h"
27
#include "libavutil/bprint.h"
28
#include "libavutil/eval.h"
29
#include "libavutil/file.h"
30
#include "libavutil/opt.h"
31
#include "libavutil/parseutils.h"
32
#include "avfilter.h"
33
#include "internal.h"
34
#include "audio.h"
35
#include "video.h"
36
37
#define COMMAND_FLAG_ENTER 1
38
#define COMMAND_FLAG_LEAVE 2
39
#define COMMAND_FLAG_EXPR  4
40
41
static const char *const var_names[] = {
42
    "N",     /* frame number */
43
    "T",     /* frame time in seconds */
44
    "POS",   /* original position in the file of the frame */
45
    "PTS",   /* frame pts */
46
    "TS",    /* interval start time in seconds */
47
    "TE",    /* interval end time in seconds */
48
    "TI",    /* interval interpolated value: TI = (T - TS) / (TE - TS) */
49
    NULL
50
};
51
52
enum var_name {
53
    VAR_N,
54
    VAR_T,
55
    VAR_POS,
56
    VAR_PTS,
57
    VAR_TS,
58
    VAR_TE,
59
    VAR_TI,
60
    VAR_VARS_NB
61
};
62
63
static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
64
{
65
    static const char * const flag_strings[] = { "enter", "leave", "expr" };
66
    int i, is_first = 1;
67
68
    av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
69
    for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
70
        if (flags & 1<<i) {
71
            if (!is_first)
72
                av_bprint_chars(pbuf, '+', 1);
73
            av_bprintf(pbuf, "%s", flag_strings[i]);
74
            is_first = 0;
75
        }
76
    }
77
78
    return pbuf->str;
79
}
80
81
typedef struct Command {
82
    int flags;
83
    char *target, *command, *arg;
84
    int index;
85
} Command;
86
87
typedef struct Interval {
88
    int64_t start_ts;          ///< start timestamp expressed as microseconds units
89
    int64_t end_ts;            ///< end   timestamp expressed as microseconds units
90
    int index;                 ///< unique index for these interval commands
91
    Command *commands;
92
    int   nb_commands;
93
    int enabled;               ///< current time detected inside this interval
94
} Interval;
95
96
typedef struct SendCmdContext {
97
    const AVClass *class;
98
    Interval *intervals;
99
    int   nb_intervals;
100
101
    char *commands_filename;
102
    char *commands_str;
103
} SendCmdContext;
104
105
#define OFFSET(x) offsetof(SendCmdContext, x)
106
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
107
static const AVOption options[] = {
108
    { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
109
    { "c",        "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
110
    { "filename", "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
111
    { "f",        "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
112
    { NULL }
113
};
114
115
#define SPACES " \f\t\n\r"
116
117
static void skip_comments(const char **buf)
118
{
119
    while (**buf) {
120
        /* skip leading spaces */
121
        *buf += strspn(*buf, SPACES);
122
        if (**buf != '#')
123
            break;
124
125
        (*buf)++;
126
127
        /* skip comment until the end of line */
128
        *buf += strcspn(*buf, "\n");
129
        if (**buf)
130
            (*buf)++;
131
    }
132
}
133
134
#define COMMAND_DELIMS " \f\t\n\r,;"
135
136
static int parse_command(Command *cmd, int cmd_count, int interval_count,
137
                         const char **buf, void *log_ctx)
138
{
139
    int ret;
140
141
    memset(cmd, 0, sizeof(Command));
142
    cmd->index = cmd_count;
143
144
    /* format: [FLAGS] target command arg */
145
    *buf += strspn(*buf, SPACES);
146
147
    /* parse flags */
148
    if (**buf == '[') {
149
        (*buf)++; /* skip "[" */
150
151
        while (**buf) {
152
            int len = strcspn(*buf, "|+]");
153
154
            if      (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
155
            else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
156
            else if (!strncmp(*buf, "expr",  strlen("expr")))  cmd->flags |= COMMAND_FLAG_EXPR;
157
            else {
158
                char flag_buf[64];
159
                av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
160
                av_log(log_ctx, AV_LOG_ERROR,
161
                       "Unknown flag '%s' in interval #%d, command #%d\n",
162
                       flag_buf, interval_count, cmd_count);
163
                return AVERROR(EINVAL);
164
            }
165
            *buf += len;
166
            if (**buf == ']')
167
                break;
168
            if (!strspn(*buf, "+|")) {
169
                av_log(log_ctx, AV_LOG_ERROR,
170
                       "Invalid flags char '%c' in interval #%d, command #%d\n",
171
                       **buf, interval_count, cmd_count);
172
                return AVERROR(EINVAL);
173
            }
174
            if (**buf)
175
                (*buf)++;
176
        }
177
178
        if (**buf != ']') {
179
            av_log(log_ctx, AV_LOG_ERROR,
180
                   "Missing flag terminator or extraneous data found at the end of flags "
181
                   "in interval #%d, command #%d\n", interval_count, cmd_count);
182
            return AVERROR(EINVAL);
183
        }
184
        (*buf)++; /* skip "]" */
185
    } else {
186
        cmd->flags = COMMAND_FLAG_ENTER;
187
    }
188
189
    *buf += strspn(*buf, SPACES);
190
    cmd->target = av_get_token(buf, COMMAND_DELIMS);
191
    if (!cmd->target || !cmd->target[0]) {
192
        av_log(log_ctx, AV_LOG_ERROR,
193
               "No target specified in interval #%d, command #%d\n",
194
               interval_count, cmd_count);
195
        ret = AVERROR(EINVAL);
196
        goto fail;
197
    }
198
199
    *buf += strspn(*buf, SPACES);
200
    cmd->command = av_get_token(buf, COMMAND_DELIMS);
201
    if (!cmd->command || !cmd->command[0]) {
202
        av_log(log_ctx, AV_LOG_ERROR,
203
               "No command specified in interval #%d, command #%d\n",
204
               interval_count, cmd_count);
205
        ret = AVERROR(EINVAL);
206
        goto fail;
207
    }
208
209
    *buf += strspn(*buf, SPACES);
210
    cmd->arg = av_get_token(buf, COMMAND_DELIMS);
211
212
    return 1;
213
214
fail:
215
    av_freep(&cmd->target);
216
    av_freep(&cmd->command);
217
    av_freep(&cmd->arg);
218
    return ret;
219
}
220
221
static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
222
                          const char **buf, void *log_ctx)
223
{
224
    int cmd_count = 0;
225
    int ret, n = 0;
226
    AVBPrint pbuf;
227
228
    *cmds = NULL;
229
    *nb_cmds = 0;
230
231
    while (**buf) {
232
        Command cmd;
233
234
        if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
235
            return ret;
236
        cmd_count++;
237
238
        /* (re)allocate commands array if required */
239
        if (*nb_cmds == n) {
240
            n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
241
            *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
242
            if (!*cmds) {
243
                av_log(log_ctx, AV_LOG_ERROR,
244
                       "Could not (re)allocate command array\n");
245
                return AVERROR(ENOMEM);
246
            }
247
        }
248
249
        (*cmds)[(*nb_cmds)++] = cmd;
250
251
        *buf += strspn(*buf, SPACES);
252
        if (**buf && **buf != ';' && **buf != ',') {
253
            av_log(log_ctx, AV_LOG_ERROR,
254
                   "Missing separator or extraneous data found at the end of "
255
                   "interval #%d, in command #%d\n",
256
                   interval_count, cmd_count);
257
            av_log(log_ctx, AV_LOG_ERROR,
258
                   "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
259
                   make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
260
            return AVERROR(EINVAL);
261
        }
262
        if (**buf == ';')
263
            break;
264
        if (**buf == ',')
265
            (*buf)++;
266
    }
267
268
    return 0;
269
}
270
271
#define DELIMS " \f\t\n\r,;"
272
273
static int parse_interval(Interval *interval, int interval_count,
274
                          const char **buf, void *log_ctx)
275
{
276
    char *intervalstr;
277
    int ret;
278
279
    *buf += strspn(*buf, SPACES);
280
    if (!**buf)
281
        return 0;
282
283
    /* reset data */
284
    memset(interval, 0, sizeof(Interval));
285
    interval->index = interval_count;
286
287
    /* format: INTERVAL COMMANDS */
288
289
    /* parse interval */
290
    intervalstr = av_get_token(buf, DELIMS);
291
    if (intervalstr && intervalstr[0]) {
292
        char *start, *end;
293
294
        start = av_strtok(intervalstr, "-", &end);
295
        if (!start) {
296
            ret = AVERROR(EINVAL);
297
            av_log(log_ctx, AV_LOG_ERROR,
298
                   "Invalid interval specification '%s' in interval #%d\n",
299
                   intervalstr, interval_count);
300
            goto end;
301
        }
302
        if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
303
            av_log(log_ctx, AV_LOG_ERROR,
304
                   "Invalid start time specification '%s' in interval #%d\n",
305
                   start, interval_count);
306
            goto end;
307
        }
308
309
        if (end) {
310
            if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
311
                av_log(log_ctx, AV_LOG_ERROR,
312
                       "Invalid end time specification '%s' in interval #%d\n",
313
                       end, interval_count);
314
                goto end;
315
            }
316
        } else {
317
            interval->end_ts = INT64_MAX;
318
        }
319
        if (interval->end_ts < interval->start_ts) {
320
            av_log(log_ctx, AV_LOG_ERROR,
321
                   "Invalid end time '%s' in interval #%d: "
322
                   "cannot be lesser than start time '%s'\n",
323
                   end, interval_count, start);
324
            ret = AVERROR(EINVAL);
325
            goto end;
326
        }
327
    } else {
328
        av_log(log_ctx, AV_LOG_ERROR,
329
               "No interval specified for interval #%d\n", interval_count);
330
        ret = AVERROR(EINVAL);
331
        goto end;
332
    }
333
334
    /* parse commands */
335
    ret = parse_commands(&interval->commands, &interval->nb_commands,
336
                         interval_count, buf, log_ctx);
337
338
end:
339
    av_free(intervalstr);
340
    return ret;
341
}
342
343
static int parse_intervals(Interval **intervals, int *nb_intervals,
344
                           const char *buf, void *log_ctx)
345
{
346
    int interval_count = 0;
347
    int ret, n = 0;
348
349
    *intervals = NULL;
350
    *nb_intervals = 0;
351
352
    if (!buf)
353
        return 0;
354
355
    while (1) {
356
        Interval interval;
357
358
        skip_comments(&buf);
359
        if (!(*buf))
360
            break;
361
362
        if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
363
            return ret;
364
365
        buf += strspn(buf, SPACES);
366
        if (*buf) {
367
            if (*buf != ';') {
368
                av_log(log_ctx, AV_LOG_ERROR,
369
                       "Missing terminator or extraneous data found at the end of interval #%d\n",
370
                       interval_count);
371
                return AVERROR(EINVAL);
372
            }
373
            buf++; /* skip ';' */
374
        }
375
        interval_count++;
376
377
        /* (re)allocate commands array if required */
378
        if (*nb_intervals == n) {
379
            n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
380
            *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
381
            if (!*intervals) {
382
                av_log(log_ctx, AV_LOG_ERROR,
383
                       "Could not (re)allocate intervals array\n");
384
                return AVERROR(ENOMEM);
385
            }
386
        }
387
388
        (*intervals)[(*nb_intervals)++] = interval;
389
    }
390
391
    return 0;
392
}
393
394
static int cmp_intervals(const void *a, const void *b)
395
{
396
    const Interval *i1 = a;
397
    const Interval *i2 = b;
398
    return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index);
399
}
400
401
static av_cold int init(AVFilterContext *ctx)
402
{
403
    SendCmdContext *s = ctx->priv;
404
    int ret, i, j;
405
406
    if ((!!s->commands_filename + !!s->commands_str) != 1) {
407
        av_log(ctx, AV_LOG_ERROR,
408
               "One and only one of the filename or commands options must be specified\n");
409
        return AVERROR(EINVAL);
410
    }
411
412
    if (s->commands_filename) {
413
        uint8_t *file_buf, *buf;
414
        size_t file_bufsize;
415
        ret = av_file_map(s->commands_filename,
416
                          &file_buf, &file_bufsize, 0, ctx);
417
        if (ret < 0)
418
            return ret;
419
420
        /* create a 0-terminated string based on the read file */
421
        buf = av_malloc(file_bufsize + 1);
422
        if (!buf) {
423
            av_file_unmap(file_buf, file_bufsize);
424
            return AVERROR(ENOMEM);
425
        }
426
        memcpy(buf, file_buf, file_bufsize);
427
        buf[file_bufsize] = 0;
428
        av_file_unmap(file_buf, file_bufsize);
429
        s->commands_str = buf;
430
    }
431
432
    if ((ret = parse_intervals(&s->intervals, &s->nb_intervals,
433
                               s->commands_str, ctx)) < 0)
434
        return ret;
435
436
    if (s->nb_intervals == 0) {
437
        av_log(ctx, AV_LOG_ERROR, "No commands were specified\n");
438
        return AVERROR(EINVAL);
439
    }
440
441
    qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals);
442
443
    av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
444
    for (i = 0; i < s->nb_intervals; i++) {
445
        AVBPrint pbuf;
446
        Interval *interval = &s->intervals[i];
447
        av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
448
               (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
449
        for (j = 0; j < interval->nb_commands; j++) {
450
            Command *cmd = &interval->commands[j];
451
            av_log(ctx, AV_LOG_VERBOSE,
452
                   "    [%s] target:%s command:%s arg:%s index:%d\n",
453
                   make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
454
        }
455
    }
456
457
    return 0;
458
}
459
460
static av_cold void uninit(AVFilterContext *ctx)
461
{
462
    SendCmdContext *s = ctx->priv;
463
    int i, j;
464
465
    for (i = 0; i < s->nb_intervals; i++) {
466
        Interval *interval = &s->intervals[i];
467
        for (j = 0; j < interval->nb_commands; j++) {
468
            Command *cmd = &interval->commands[j];
469
            av_freep(&cmd->target);
470
            av_freep(&cmd->command);
471
            av_freep(&cmd->arg);
472
        }
473
        av_freep(&interval->commands);
474
    }
475
    av_freep(&s->intervals);
476
}
477
478
static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
479
{
480
    AVFilterContext *ctx = inlink->dst;
481
    SendCmdContext *s = ctx->priv;
482
    int64_t ts;
483
    int i, j, ret;
484
485
    if (ref->pts == AV_NOPTS_VALUE)
486
        goto end;
487
488
    ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
489
490
#define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
491
492
    for (i = 0; i < s->nb_intervals; i++) {
493
        Interval *interval = &s->intervals[i];
494
        int flags = 0;
495
496
        if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
497
            flags += COMMAND_FLAG_ENTER;
498
            interval->enabled = 1;
499
        }
500
        if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
501
            flags += COMMAND_FLAG_LEAVE;
502
            interval->enabled = 0;
503
        }
504
        if (interval->enabled)
505
            flags += COMMAND_FLAG_EXPR;
506
507
        if (flags) {
508
            AVBPrint pbuf;
509
            av_log(ctx, AV_LOG_VERBOSE,
510
                   "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
511
                   make_command_flags_str(&pbuf, flags), interval->index,
512
                   (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
513
                   (double)ts/1000000);
514
515
            for (j = 0; flags && j < interval->nb_commands; j++) {
516
                Command *cmd = &interval->commands[j];
517
                char *cmd_arg = cmd->arg;
518
                char buf[1024];
519
520
                if (cmd->flags & flags) {
521
                    if (cmd->flags & COMMAND_FLAG_EXPR) {
522
                        double var_values[VAR_VARS_NB], res;
523
                        double start = TS2T(interval->start_ts, AV_TIME_BASE_Q);
524
                        double end = TS2T(interval->end_ts, AV_TIME_BASE_Q);
525
                        double current = TS2T(ref->pts, inlink->time_base);
526
527
                        var_values[VAR_N]   = inlink->frame_count_in;
528
                        var_values[VAR_POS] = ref->pkt_pos == -1 ? NAN : ref->pkt_pos;
529
                        var_values[VAR_PTS] = TS2D(ref->pts);
530
                        var_values[VAR_T]   = current;
531
                        var_values[VAR_TS]  = start;
532
                        var_values[VAR_TE]  = end;
533
                        var_values[VAR_TI]  = (current - start) / (end - start);
534
535
                        if ((ret = av_expr_parse_and_eval(&res, cmd->arg, var_names, var_values,
536
                                                          NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) {
537
                            av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for command argument.\n", cmd->arg);
538
                            av_frame_free(&ref);
539
                            return AVERROR(EINVAL);
540
                        }
541
542
                        cmd_arg = av_asprintf("%g", res);
543
                        if (!cmd_arg) {
544
                            av_frame_free(&ref);
545
                            return AVERROR(ENOMEM);
546
                        }
547
                    }
548
                    av_log(ctx, AV_LOG_VERBOSE,
549
                           "Processing command #%d target:%s command:%s arg:%s\n",
550
                           cmd->index, cmd->target, cmd->command, cmd_arg);
551
                    ret = avfilter_graph_send_command(inlink->graph,
552
                                                      cmd->target, cmd->command, cmd_arg,
553
                                                      buf, sizeof(buf),
554
                                                      AVFILTER_CMD_FLAG_ONE);
555
                    av_log(ctx, AV_LOG_VERBOSE,
556
                           "Command reply for command #%d: ret:%s res:%s\n",
557
                           cmd->index, av_err2str(ret), buf);
558
                    if (cmd->flags & COMMAND_FLAG_EXPR)
559
                        av_freep(&cmd_arg);
560
                }
561
            }
562
        }
563
    }
564
565
end:
566
    switch (inlink->type) {
567
    case AVMEDIA_TYPE_VIDEO:
568
    case AVMEDIA_TYPE_AUDIO:
569
        return ff_filter_frame(inlink->dst->outputs[0], ref);
570
    }
571
572
    return AVERROR(ENOSYS);
573
}
574
575
#if CONFIG_SENDCMD_FILTER
576
577
#define sendcmd_options options
578
AVFILTER_DEFINE_CLASS(sendcmd);
579
580
static const AVFilterPad sendcmd_inputs[] = {
581
    {
582
        .name         = "default",
583
        .type         = AVMEDIA_TYPE_VIDEO,
584
        .filter_frame = filter_frame,
585
    },
586
    { NULL }
587
};
588
589
static const AVFilterPad sendcmd_outputs[] = {
590
    {
591
        .name = "default",
592
        .type = AVMEDIA_TYPE_VIDEO,
593
    },
594
    { NULL }
595
};
596
597
AVFilter ff_vf_sendcmd = {
598
    .name        = "sendcmd",
599
    .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
600
    .init        = init,
601
    .uninit      = uninit,
602
    .priv_size   = sizeof(SendCmdContext),
603
    .inputs      = sendcmd_inputs,
604
    .outputs     = sendcmd_outputs,
605
    .priv_class  = &sendcmd_class,
606
};
607
608
#endif
609
610
#if CONFIG_ASENDCMD_FILTER
611
612
#define asendcmd_options options
613
AVFILTER_DEFINE_CLASS(asendcmd);
614
615
static const AVFilterPad asendcmd_inputs[] = {
616
    {
617
        .name         = "default",
618
        .type         = AVMEDIA_TYPE_AUDIO,
619
        .filter_frame = filter_frame,
620
    },
621
    { NULL }
622
};
623
624
static const AVFilterPad asendcmd_outputs[] = {
625
    {
626
        .name = "default",
627
        .type = AVMEDIA_TYPE_AUDIO,
628
    },
629
    { NULL }
630
};
631
632
AVFilter ff_af_asendcmd = {
633
    .name        = "asendcmd",
634
    .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
635
    .init        = init,
636
    .uninit      = uninit,
637
    .priv_size   = sizeof(SendCmdContext),
638
    .inputs      = asendcmd_inputs,
639
    .outputs     = asendcmd_outputs,
640
    .priv_class  = &asendcmd_class,
641
};
642
643
#endif