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