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"
33 #include "avfiltergraph.h"
37 #define COMMAND_FLAG_ENTER 1
38 #define COMMAND_FLAG_LEAVE 2
40 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
42 const char *flag_strings[] = { "enter", "leave" };
45 av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
46 for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
49 av_bprint_chars(pbuf, '+', 1);
50 av_bprintf(pbuf, "%s", flag_strings[i]);
60 char *target, *command, *arg;
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
70 int enabled; ///< current time detected inside this interval
78 char *commands_filename;
82 #define OFFSET(x) offsetof(SendCmdContext, x)
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 },
92 AVFILTER_DEFINE_CLASS(sendcmd);
94 #define SPACES " \f\t\n\r"
96 static void skip_comments(const char **buf)
99 /* skip leading spaces */
100 *buf += strspn(*buf, SPACES);
106 /* skip comment until the end of line */
107 *buf += strcspn(*buf, "\n");
113 #define COMMAND_DELIMS " \f\t\n\r,;"
115 static int parse_command(Command *cmd, int cmd_count, int interval_count,
116 const char **buf, void *log_ctx)
120 memset(cmd, 0, sizeof(Command));
121 cmd->index = cmd_count;
123 /* format: [FLAGS] target command arg */
124 *buf += strspn(*buf, SPACES);
128 (*buf)++; /* skip "[" */
131 int len = strcspn(*buf, "|+]");
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;
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);
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);
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);
162 (*buf)++; /* skip "]" */
164 cmd->flags = COMMAND_FLAG_ENTER;
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);
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);
187 *buf += strspn(*buf, SPACES);
188 cmd->arg = av_get_token(buf, COMMAND_DELIMS);
193 av_freep(&cmd->target);
194 av_freep(&cmd->command);
199 static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
200 const char **buf, void *log_ctx)
212 if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
216 /* (re)allocate commands array if required */
218 n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
219 *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
221 av_log(log_ctx, AV_LOG_ERROR,
222 "Could not (re)allocate command array\n");
223 return AVERROR(ENOMEM);
227 (*cmds)[(*nb_cmds)++] = cmd;
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);
249 #define DELIMS " \f\t\n\r,;"
251 static int parse_interval(Interval *interval, int interval_count,
252 const char **buf, void *log_ctx)
257 *buf += strspn(*buf, SPACES);
262 memset(interval, 0, sizeof(Interval));
263 interval->index = interval_count;
265 /* format: INTERVAL COMMANDS */
268 intervalstr = av_get_token(buf, DELIMS);
269 if (intervalstr && intervalstr[0]) {
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);
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);
288 interval->end_ts = INT64_MAX;
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);
299 av_log(log_ctx, AV_LOG_ERROR,
300 "No interval specified for interval #%d\n", interval_count);
301 ret = AVERROR(EINVAL);
306 ret = parse_commands(&interval->commands, &interval->nb_commands,
307 interval_count, buf, log_ctx);
310 av_free(intervalstr);
314 static int parse_intervals(Interval **intervals, int *nb_intervals,
315 const char *buf, void *log_ctx)
317 int interval_count = 0;
330 if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
333 buf += strspn(buf, SPACES);
336 av_log(log_ctx, AV_LOG_ERROR,
337 "Missing terminator or extraneous data found at the end of interval #%d\n",
339 return AVERROR(EINVAL);
341 buf++; /* skip ';' */
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));
350 av_log(log_ctx, AV_LOG_ERROR,
351 "Could not (re)allocate intervals array\n");
352 return AVERROR(ENOMEM);
356 (*intervals)[(*nb_intervals)++] = interval;
362 static int cmp_intervals(const void *a, const void *b)
364 const Interval *i1 = a;
365 const Interval *i2 = b;
366 int64_t ts_diff = i1->start_ts - i2->start_ts;
369 ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0;
370 return ret == 0 ? i1->index - i2->index : ret;
373 static av_cold int init(AVFilterContext *ctx, const char *args)
375 SendCmdContext *sendcmd = ctx->priv;
379 sendcmd->class = &sendcmd_class;
380 av_opt_set_defaults(sendcmd);
382 if ((ret = av_set_options_string(sendcmd, args, "=", ":")) < 0)
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);
391 if (sendcmd->commands_filename) {
394 ret = av_file_map(sendcmd->commands_filename,
395 &file_buf, &file_bufsize, 0, ctx);
399 /* create a 0-terminated string based on the read file */
400 buf = av_malloc(file_bufsize + 1);
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;
409 if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
410 sendcmd->commands_str, ctx)) < 0)
413 qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
415 av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
416 for (i = 0; i < sendcmd->nb_intervals; i++) {
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);
432 static void av_cold uninit(AVFilterContext *ctx)
434 SendCmdContext *sendcmd = ctx->priv;
437 av_opt_free(sendcmd);
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);
447 av_free(interval->commands);
449 av_freep(&sendcmd->intervals);
452 static int process_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
454 AVFilterContext *ctx = inlink->dst;
455 SendCmdContext *sendcmd = ctx->priv;
459 if (ref->pts == AV_NOPTS_VALUE)
462 ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
464 #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
466 for (i = 0; i < sendcmd->nb_intervals; i++) {
467 Interval *interval = &sendcmd->intervals[i];
470 if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
471 flags += COMMAND_FLAG_ENTER;
472 interval->enabled = 1;
474 if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
475 flags += COMMAND_FLAG_LEAVE;
476 interval->enabled = 0;
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,
487 for (j = 0; flags && j < interval->nb_commands; j++) {
488 Command *cmd = &interval->commands[j];
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,
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);
508 /* give the reference away, do not store in cur_buf */
509 inlink->cur_buf = NULL;
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);
515 return AVERROR(ENOSYS);
518 #if CONFIG_SENDCMD_FILTER
520 AVFilter avfilter_vf_sendcmd = {
522 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
526 .priv_size = sizeof(SendCmdContext),
528 .inputs = (const AVFilterPad[]) {
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,
538 .outputs = (const AVFilterPad[]) {
541 .type = AVMEDIA_TYPE_VIDEO,
549 #if CONFIG_ASENDCMD_FILTER
551 AVFilter avfilter_af_asendcmd = {
553 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
557 .priv_size = sizeof(SendCmdContext),
559 .inputs = (const AVFilterPad[]) {
562 .type = AVMEDIA_TYPE_AUDIO,
563 .get_audio_buffer = ff_null_get_audio_buffer,
564 .filter_samples = process_frame,
568 .outputs = (const AVFilterPad[]) {
571 .type = AVMEDIA_TYPE_AUDIO,