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