2 * Copyright (c) 2012 Stefano Sabatini
4 * This file is part of FFmpeg.
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.
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.
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
23 * send commands filter
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"
36 #define COMMAND_FLAG_ENTER 1
37 #define COMMAND_FLAG_LEAVE 2
39 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
41 static const char * const flag_strings[] = { "enter", "leave" };
44 av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
45 for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
48 av_bprint_chars(pbuf, '+', 1);
49 av_bprintf(pbuf, "%s", flag_strings[i]);
57 typedef struct Command {
59 char *target, *command, *arg;
63 typedef struct Interval {
64 int64_t start_ts; ///< start timestamp expressed as microseconds units
65 int64_t end_ts; ///< end timestamp expressed as microseconds units
66 int index; ///< unique index for these interval commands
69 int enabled; ///< current time detected inside this interval
72 typedef struct SendCmdContext {
77 char *commands_filename;
81 #define OFFSET(x) offsetof(SendCmdContext, x)
82 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
83 static const AVOption options[] = {
84 { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
85 { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
86 { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
87 { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
91 #define SPACES " \f\t\n\r"
93 static void skip_comments(const char **buf)
96 /* skip leading spaces */
97 *buf += strspn(*buf, SPACES);
103 /* skip comment until the end of line */
104 *buf += strcspn(*buf, "\n");
110 #define COMMAND_DELIMS " \f\t\n\r,;"
112 static int parse_command(Command *cmd, int cmd_count, int interval_count,
113 const char **buf, void *log_ctx)
117 memset(cmd, 0, sizeof(Command));
118 cmd->index = cmd_count;
120 /* format: [FLAGS] target command arg */
121 *buf += strspn(*buf, SPACES);
125 (*buf)++; /* skip "[" */
128 int len = strcspn(*buf, "|+]");
130 if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
131 else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
134 av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
135 av_log(log_ctx, AV_LOG_ERROR,
136 "Unknown flag '%s' in interval #%d, command #%d\n",
137 flag_buf, interval_count, cmd_count);
138 return AVERROR(EINVAL);
143 if (!strspn(*buf, "+|")) {
144 av_log(log_ctx, AV_LOG_ERROR,
145 "Invalid flags char '%c' in interval #%d, command #%d\n",
146 **buf, interval_count, cmd_count);
147 return AVERROR(EINVAL);
154 av_log(log_ctx, AV_LOG_ERROR,
155 "Missing flag terminator or extraneous data found at the end of flags "
156 "in interval #%d, command #%d\n", interval_count, cmd_count);
157 return AVERROR(EINVAL);
159 (*buf)++; /* skip "]" */
161 cmd->flags = COMMAND_FLAG_ENTER;
164 *buf += strspn(*buf, SPACES);
165 cmd->target = av_get_token(buf, COMMAND_DELIMS);
166 if (!cmd->target || !cmd->target[0]) {
167 av_log(log_ctx, AV_LOG_ERROR,
168 "No target specified in interval #%d, command #%d\n",
169 interval_count, cmd_count);
170 ret = AVERROR(EINVAL);
174 *buf += strspn(*buf, SPACES);
175 cmd->command = av_get_token(buf, COMMAND_DELIMS);
176 if (!cmd->command || !cmd->command[0]) {
177 av_log(log_ctx, AV_LOG_ERROR,
178 "No command specified in interval #%d, command #%d\n",
179 interval_count, cmd_count);
180 ret = AVERROR(EINVAL);
184 *buf += strspn(*buf, SPACES);
185 cmd->arg = av_get_token(buf, COMMAND_DELIMS);
190 av_freep(&cmd->target);
191 av_freep(&cmd->command);
196 static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
197 const char **buf, void *log_ctx)
209 if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
213 /* (re)allocate commands array if required */
215 n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
216 *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
218 av_log(log_ctx, AV_LOG_ERROR,
219 "Could not (re)allocate command array\n");
220 return AVERROR(ENOMEM);
224 (*cmds)[(*nb_cmds)++] = cmd;
226 *buf += strspn(*buf, SPACES);
227 if (**buf && **buf != ';' && **buf != ',') {
228 av_log(log_ctx, AV_LOG_ERROR,
229 "Missing separator or extraneous data found at the end of "
230 "interval #%d, in command #%d\n",
231 interval_count, cmd_count);
232 av_log(log_ctx, AV_LOG_ERROR,
233 "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
234 make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
235 return AVERROR(EINVAL);
246 #define DELIMS " \f\t\n\r,;"
248 static int parse_interval(Interval *interval, int interval_count,
249 const char **buf, void *log_ctx)
254 *buf += strspn(*buf, SPACES);
259 memset(interval, 0, sizeof(Interval));
260 interval->index = interval_count;
262 /* format: INTERVAL COMMANDS */
265 intervalstr = av_get_token(buf, DELIMS);
266 if (intervalstr && intervalstr[0]) {
269 start = av_strtok(intervalstr, "-", &end);
271 ret = AVERROR(EINVAL);
272 av_log(log_ctx, AV_LOG_ERROR,
273 "Invalid interval specification '%s' in interval #%d\n",
274 intervalstr, interval_count);
277 if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
278 av_log(log_ctx, AV_LOG_ERROR,
279 "Invalid start time specification '%s' in interval #%d\n",
280 start, interval_count);
285 if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
286 av_log(log_ctx, AV_LOG_ERROR,
287 "Invalid end time specification '%s' in interval #%d\n",
288 end, interval_count);
292 interval->end_ts = INT64_MAX;
294 if (interval->end_ts < interval->start_ts) {
295 av_log(log_ctx, AV_LOG_ERROR,
296 "Invalid end time '%s' in interval #%d: "
297 "cannot be lesser than start time '%s'\n",
298 end, interval_count, start);
299 ret = AVERROR(EINVAL);
303 av_log(log_ctx, AV_LOG_ERROR,
304 "No interval specified for interval #%d\n", interval_count);
305 ret = AVERROR(EINVAL);
310 ret = parse_commands(&interval->commands, &interval->nb_commands,
311 interval_count, buf, log_ctx);
314 av_free(intervalstr);
318 static int parse_intervals(Interval **intervals, int *nb_intervals,
319 const char *buf, void *log_ctx)
321 int interval_count = 0;
337 if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
340 buf += strspn(buf, SPACES);
343 av_log(log_ctx, AV_LOG_ERROR,
344 "Missing terminator or extraneous data found at the end of interval #%d\n",
346 return AVERROR(EINVAL);
348 buf++; /* skip ';' */
352 /* (re)allocate commands array if required */
353 if (*nb_intervals == n) {
354 n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
355 *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
357 av_log(log_ctx, AV_LOG_ERROR,
358 "Could not (re)allocate intervals array\n");
359 return AVERROR(ENOMEM);
363 (*intervals)[(*nb_intervals)++] = interval;
369 static int cmp_intervals(const void *a, const void *b)
371 const Interval *i1 = a;
372 const Interval *i2 = b;
373 return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index);
376 static av_cold int init(AVFilterContext *ctx)
378 SendCmdContext *s = ctx->priv;
381 if ((!!s->commands_filename + !!s->commands_str) != 1) {
382 av_log(ctx, AV_LOG_ERROR,
383 "One and only one of the filename or commands options must be specified\n");
384 return AVERROR(EINVAL);
387 if (s->commands_filename) {
388 uint8_t *file_buf, *buf;
390 ret = av_file_map(s->commands_filename,
391 &file_buf, &file_bufsize, 0, ctx);
395 /* create a 0-terminated string based on the read file */
396 buf = av_malloc(file_bufsize + 1);
398 av_file_unmap(file_buf, file_bufsize);
399 return AVERROR(ENOMEM);
401 memcpy(buf, file_buf, file_bufsize);
402 buf[file_bufsize] = 0;
403 av_file_unmap(file_buf, file_bufsize);
404 s->commands_str = buf;
407 if ((ret = parse_intervals(&s->intervals, &s->nb_intervals,
408 s->commands_str, ctx)) < 0)
411 if (s->nb_intervals == 0) {
412 av_log(ctx, AV_LOG_ERROR, "No commands were specified\n");
413 return AVERROR(EINVAL);
416 qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals);
418 av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
419 for (i = 0; i < s->nb_intervals; i++) {
421 Interval *interval = &s->intervals[i];
422 av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
423 (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
424 for (j = 0; j < interval->nb_commands; j++) {
425 Command *cmd = &interval->commands[j];
426 av_log(ctx, AV_LOG_VERBOSE,
427 " [%s] target:%s command:%s arg:%s index:%d\n",
428 make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
435 static av_cold void uninit(AVFilterContext *ctx)
437 SendCmdContext *s = ctx->priv;
440 for (i = 0; i < s->nb_intervals; i++) {
441 Interval *interval = &s->intervals[i];
442 for (j = 0; j < interval->nb_commands; j++) {
443 Command *cmd = &interval->commands[j];
444 av_freep(&cmd->target);
445 av_freep(&cmd->command);
448 av_freep(&interval->commands);
450 av_freep(&s->intervals);
453 static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
455 AVFilterContext *ctx = inlink->dst;
456 SendCmdContext *s = ctx->priv;
460 if (ref->pts == AV_NOPTS_VALUE)
463 ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
465 #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
467 for (i = 0; i < s->nb_intervals; i++) {
468 Interval *interval = &s->intervals[i];
471 if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
472 flags += COMMAND_FLAG_ENTER;
473 interval->enabled = 1;
475 if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
476 flags += COMMAND_FLAG_LEAVE;
477 interval->enabled = 0;
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,
488 for (j = 0; flags && j < interval->nb_commands; j++) {
489 Command *cmd = &interval->commands[j];
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,
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);
509 switch (inlink->type) {
510 case AVMEDIA_TYPE_VIDEO:
511 case AVMEDIA_TYPE_AUDIO:
512 return ff_filter_frame(inlink->dst->outputs[0], ref);
515 return AVERROR(ENOSYS);
518 #if CONFIG_SENDCMD_FILTER
520 #define sendcmd_options options
521 AVFILTER_DEFINE_CLASS(sendcmd);
523 static const AVFilterPad sendcmd_inputs[] = {
526 .type = AVMEDIA_TYPE_VIDEO,
527 .filter_frame = filter_frame,
532 static const AVFilterPad sendcmd_outputs[] = {
535 .type = AVMEDIA_TYPE_VIDEO,
540 AVFilter ff_vf_sendcmd = {
542 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
545 .priv_size = sizeof(SendCmdContext),
546 .inputs = sendcmd_inputs,
547 .outputs = sendcmd_outputs,
548 .priv_class = &sendcmd_class,
553 #if CONFIG_ASENDCMD_FILTER
555 #define asendcmd_options options
556 AVFILTER_DEFINE_CLASS(asendcmd);
558 static const AVFilterPad asendcmd_inputs[] = {
561 .type = AVMEDIA_TYPE_AUDIO,
562 .filter_frame = filter_frame,
567 static const AVFilterPad asendcmd_outputs[] = {
570 .type = AVMEDIA_TYPE_AUDIO,
575 AVFilter ff_af_asendcmd = {
577 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
580 .priv_size = sizeof(SendCmdContext),
581 .inputs = asendcmd_inputs,
582 .outputs = asendcmd_outputs,
583 .priv_class = &asendcmd_class,