• 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 "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 
make_command_flags_str(AVBPrint * pbuf,int flags)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 
skip_comments(const char ** buf)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 
parse_command(Command * cmd,int cmd_count,int interval_count,const char ** buf,void * log_ctx)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 
parse_commands(Command ** cmds,int * nb_cmds,int interval_count,const char ** buf,void * log_ctx)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 
parse_interval(Interval * interval,int interval_count,const char ** buf,void * log_ctx)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 
parse_intervals(Interval ** intervals,int * nb_intervals,const char * buf,void * log_ctx)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 
cmp_intervals(const void * a,const void * b)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 
init(AVFilterContext * ctx)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 
uninit(AVFilterContext * ctx)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 
filter_frame(AVFilterLink * inlink,AVFrame * ref)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
644