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