lavfi: add asendcmd and sendcmd filters
[ffmpeg.git] / 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     char *buf;
378
379     sendcmd->class = &sendcmd_class;
380     av_opt_set_defaults(sendcmd);
381
382     if ((ret = av_set_options_string(sendcmd, args, "=", ":")) < 0)
383         return ret;
384
385     if (sendcmd->commands_filename && sendcmd->commands_str) {
386         av_log(ctx, AV_LOG_ERROR,
387                "Only one of the filename or commands options must be specified\n");
388         return AVERROR(EINVAL);
389     }
390
391     if (sendcmd->commands_filename) {
392         uint8_t *file_buf;
393         size_t file_bufsize;
394         ret = av_file_map(sendcmd->commands_filename,
395                           &file_buf, &file_bufsize, 0, ctx);
396         if (ret < 0)
397             return ret;
398
399         /* create a 0-terminated string based on the read file */
400         buf = av_malloc(file_bufsize + 1);
401         if (!buf)
402             return AVERROR(ENOMEM);
403         memcpy(buf, file_buf, file_bufsize);
404         buf[file_bufsize] = 0;
405         av_file_unmap(file_buf, file_bufsize);
406         sendcmd->commands_str = buf;
407     }
408
409     if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
410                                sendcmd->commands_str, ctx)) < 0)
411         return ret;
412
413     qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
414
415     av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
416     for (i = 0; i < sendcmd->nb_intervals; i++) {
417         AVBPrint pbuf;
418         Interval *interval = &sendcmd->intervals[i];
419         av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
420                (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
421         for (j = 0; j < interval->nb_commands; j++) {
422             Command *cmd = &interval->commands[j];
423             av_log(ctx, AV_LOG_VERBOSE,
424                    "    [%s] target:%s command:%s arg:%s index:%d\n",
425                    make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
426         }
427     }
428
429     return 0;
430 }
431
432 static void av_cold uninit(AVFilterContext *ctx)
433 {
434     SendCmdContext *sendcmd = ctx->priv;
435     int i, j;
436
437     av_opt_free(sendcmd);
438
439     for (i = 0; i < sendcmd->nb_intervals; i++) {
440         Interval *interval = &sendcmd->intervals[i];
441         for (j = 0; j < interval->nb_commands; j++) {
442             Command *cmd = &interval->commands[j];
443             av_free(cmd->target);
444             av_free(cmd->command);
445             av_free(cmd->arg);
446         }
447         av_free(interval->commands);
448     }
449     av_freep(&sendcmd->intervals);
450 }
451
452 static int process_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
453 {
454     AVFilterContext *ctx = inlink->dst;
455     SendCmdContext *sendcmd = ctx->priv;
456     int64_t ts;
457     int i, j, ret;
458
459     if (ref->pts == AV_NOPTS_VALUE)
460         goto end;
461
462     ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
463
464 #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
465
466     for (i = 0; i < sendcmd->nb_intervals; i++) {
467         Interval *interval = &sendcmd->intervals[i];
468         int flags = 0;
469
470         if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
471             flags += COMMAND_FLAG_ENTER;
472             interval->enabled = 1;
473         }
474         if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
475             flags += COMMAND_FLAG_LEAVE;
476             interval->enabled = 0;
477         }
478
479         if (flags) {
480             AVBPrint pbuf;
481             av_log(ctx, AV_LOG_VERBOSE,
482                    "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
483                    make_command_flags_str(&pbuf, flags), interval->index,
484                    (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
485                    (double)ts/1000000);
486
487             for (j = 0; flags && j < interval->nb_commands; j++) {
488                 Command *cmd = &interval->commands[j];
489                 char buf[1024];
490
491                 if (cmd->flags & flags) {
492                     av_log(ctx, AV_LOG_VERBOSE,
493                            "Processing command #%d target:%s command:%s arg:%s\n",
494                            cmd->index, cmd->target, cmd->command, cmd->arg);
495                     ret = avfilter_graph_send_command(inlink->graph,
496                                                       cmd->target, cmd->command, cmd->arg,
497                                                       buf, sizeof(buf),
498                                                       AVFILTER_CMD_FLAG_ONE);
499                     av_log(ctx, AV_LOG_VERBOSE,
500                            "Command reply for command #%d: ret:%s res:%s\n",
501                            cmd->index, av_err2str(ret), buf);
502                 }
503             }
504         }
505     }
506
507 end:
508     /* give the reference away, do not store in cur_buf */
509     inlink->cur_buf = NULL;
510
511     switch (inlink->type) {
512     case AVMEDIA_TYPE_VIDEO: return ff_start_frame   (inlink->dst->outputs[0], ref);
513     case AVMEDIA_TYPE_AUDIO: return ff_filter_samples(inlink->dst->outputs[0], ref);
514     }
515     return AVERROR(ENOSYS);
516 }
517
518 #if CONFIG_SENDCMD_FILTER
519
520 AVFilter avfilter_vf_sendcmd = {
521     .name      = "sendcmd",
522     .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
523
524     .init = init,
525     .uninit = uninit,
526     .priv_size = sizeof(SendCmdContext),
527
528     .inputs = (const AVFilterPad[]) {
529         {
530             .name             = "default",
531             .type             = AVMEDIA_TYPE_VIDEO,
532             .get_video_buffer = ff_null_get_video_buffer,
533             .start_frame      = process_frame,
534             .end_frame        = ff_null_end_frame,
535         },
536         { .name = NULL }
537     },
538     .outputs = (const AVFilterPad[]) {
539         {
540             .name             = "default",
541             .type             = AVMEDIA_TYPE_VIDEO,
542         },
543         { .name = NULL }
544     },
545 };
546
547 #endif
548
549 #if CONFIG_ASENDCMD_FILTER
550
551 AVFilter avfilter_af_asendcmd = {
552     .name      = "asendcmd",
553     .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
554
555     .init = init,
556     .uninit = uninit,
557     .priv_size = sizeof(SendCmdContext),
558
559     .inputs = (const AVFilterPad[]) {
560         {
561             .name             = "default",
562             .type             = AVMEDIA_TYPE_AUDIO,
563             .get_audio_buffer = ff_null_get_audio_buffer,
564             .filter_samples   = process_frame,
565         },
566         { .name = NULL }
567     },
568     .outputs = (const AVFilterPad[]) {
569         {
570             .name             = "default",
571             .type             = AVMEDIA_TYPE_AUDIO,
572         },
573         { .name = NULL }
574     },
575 };
576
577 #endif