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;
378 sendcmd->class = &sendcmd_class;
379 av_opt_set_defaults(sendcmd);
381 if ((ret = av_set_options_string(sendcmd, args, "=", ":")) < 0)
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);
390 if (sendcmd->commands_filename) {
391 uint8_t *file_buf, *buf;
393 ret = av_file_map(sendcmd->commands_filename,
394 &file_buf, &file_bufsize, 0, ctx);
398 /* create a 0-terminated string based on the read file */
399 buf = av_malloc(file_bufsize + 1);
401 av_file_unmap(file_buf, file_bufsize);
402 return AVERROR(ENOMEM);
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;
410 if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
411 sendcmd->commands_str, ctx)) < 0)
414 qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
416 av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
417 for (i = 0; i < sendcmd->nb_intervals; i++) {
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);
433 static void av_cold uninit(AVFilterContext *ctx)
435 SendCmdContext *sendcmd = ctx->priv;
438 av_opt_free(sendcmd);
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);
448 av_free(interval->commands);
450 av_freep(&sendcmd->intervals);
453 static int process_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
455 AVFilterContext *ctx = inlink->dst;
456 SendCmdContext *sendcmd = 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 < sendcmd->nb_intervals; i++) {
468 Interval *interval = &sendcmd->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 /* give the reference away, do not store in cur_buf */
510 inlink->cur_buf = NULL;
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);
516 return AVERROR(ENOSYS);
519 #if CONFIG_SENDCMD_FILTER
521 AVFilter avfilter_vf_sendcmd = {
523 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
527 .priv_size = sizeof(SendCmdContext),
529 .inputs = (const AVFilterPad[]) {
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,
539 .outputs = (const AVFilterPad[]) {
542 .type = AVMEDIA_TYPE_VIDEO,
550 #if CONFIG_ASENDCMD_FILTER
552 AVFilter avfilter_af_asendcmd = {
554 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
558 .priv_size = sizeof(SendCmdContext),
560 .inputs = (const AVFilterPad[]) {
563 .type = AVMEDIA_TYPE_AUDIO,
564 .get_audio_buffer = ff_null_get_audio_buffer,
565 .filter_samples = process_frame,
569 .outputs = (const AVFilterPad[]) {
572 .type = AVMEDIA_TYPE_AUDIO,