lavfi: add asendcmd and sendcmd filters
authorStefano Sabatini <stefasab@gmail.com>
Mon, 13 Aug 2012 18:13:26 +0000 (20:13 +0200)
committerStefano Sabatini <stefasab@gmail.com>
Sat, 22 Sep 2012 22:50:06 +0000 (00:50 +0200)
Changelog
doc/filters.texi
libavfilter/Makefile
libavfilter/allfilters.c
libavfilter/f_sendcmd.c [new file with mode: 0644]
libavfilter/version.h

index bd37f87..e8ea679 100644 (file)
--- a/Changelog
+++ b/Changelog
@@ -62,6 +62,7 @@ version next:
 - RTP depacketization of JPEG
 - Smooth Streaming live segmenter muxer
 - F4V muxer
+- sendcmd and asendcmd filters
 
 
 version 0.11:
index e202d38..916aa6f 100644 (file)
@@ -4248,6 +4248,137 @@ tools.
 
 Below is a description of the currently available multimedia filters.
 
+@section asendcmd, sendcmd
+
+Send commands to filters in the filtergraph.
+
+These filters read commands to be sent to other filters in the
+filtergraph.
+
+@code{asendcmd} must be inserted between two audio filters,
+@code{sendcmd} must be inserted between two video filters, but apart
+from that they act the same way.
+
+The specification of commands can be provided in the filter arguments
+with the @var{commands} option, or in a file specified by the
+@var{filename} option.
+
+These filters accept the following options:
+@table @option
+@item commands, c
+Set the commands to be read and sent to the other filters.
+@item filename, f
+Set the filename of the commands to be read and sent to the other
+filters.
+@end table
+
+@subsection Commands syntax
+
+A commands description consists of a sequence of interval
+specifications, comprising a list of commands to be executed when a
+particular event related to that interval occurs. The occurring event
+is typically the current frame time entering or leaving a given time
+interval.
+
+An interval is specified by the following syntax:
+@example
+@var{START}[-@var{END}] @var{COMMANDS};
+@end example
+
+The time interval is specified by the @var{START} and @var{END} times.
+@var{END} is optional and defaults to the maximum time.
+
+The current frame time is considered within the specified interval if
+it is included in the interval [@var{START}, @var{END}), that is when
+the time is greater or equal to @var{START} and is lesser than
+@var{END}.
+
+@var{COMMANDS} consists of a sequence of one or more command
+specifications, separated by ",", relating to that interval.  The
+syntax of a command specification is given by:
+@example
+[@var{FLAGS}] @var{TARGET} @var{COMMAND} @var{ARG}
+@end example
+
+@var{FLAGS} is optional and specifies the type of events relating to
+the time interval which enable sending the specified command, and must
+be a non-null sequence of identifier flags separated by "+" or "|" and
+enclosed between "[" and "]".
+
+The following flags are recognized:
+@table @option
+@item enter
+The command is sent when the current frame timestamp enters the
+specified interval. In other words, the command is sent when the
+previous frame timestamp was not in the given interval, and the
+current is.
+
+@item leave
+The command is sent when the current frame timestamp leaves the
+specified interval. In other words, the command is sent when the
+previous frame timestamp was in the given interval, and the
+current is not.
+@end table
+
+If @var{FLAGS} is not specified, a default value of @code{[enter]} is
+assumed.
+
+@var{TARGET} specifies the target of the command, usually the name of
+the filter class or a specific filter instance name.
+
+@var{COMMAND} specifies the name of the command for the target filter.
+
+@var{ARG} is optional and specifies the optional list of argument for
+the given @var{COMMAND}.
+
+Between one interval specification and another, whitespaces, or
+sequences of characters starting with @code{#} until the end of line,
+are ignored and can be used to annotate comments.
+
+A simplified BNF description of the commands specification syntax
+follows:
+@example
+@var{COMMAND_FLAG}  ::= "enter" | "leave"
+@var{COMMAND_FLAGS} ::= @var{COMMAND_FLAG} [(+|"|")@var{COMMAND_FLAG}]
+@var{COMMAND}       ::= ["[" @var{COMMAND_FLAGS} "]"] @var{TARGET} @var{COMMAND} [@var{ARG}]
+@var{COMMANDS}      ::= @var{COMMAND} [,@var{COMMANDS}]
+@var{INTERVAL}      ::= @var{START}[-@var{END}] @var{COMMANDS}
+@var{INTERVALS}     ::= @var{INTERVAL}[;@var{INTERVALS}]
+@end example
+
+@subsection Examples
+
+@itemize
+@item
+Specify audio tempo change at second 4:
+@example
+asendcmd=c='4.0 atempo tempo 1.5',atempo
+@end example
+
+@item
+Specify a list of drawtext and hue commands in a file.
+@example
+# show text in the interval 5-10
+5.0-10.0 [enter] drawtext reinit 'fontfile=FreeSerif.ttf:text=hello world',
+         [leave] drawtext reinit 'fontfile=FreeSerif.ttf:text=';
+
+# desaturate the image in the interval 15-20
+15.0-20.0 [enter] hue reinit s=0,
+          [enter] drawtext reinit 'fontfile=FreeSerif.ttf:text=nocolor',
+          [leave] hue reinit s=1,
+          [leave] drawtext reinit 'fontfile=FreeSerif.ttf:text=color';
+
+# apply an exponential saturation fade-out effect, starting from time 25
+25 [enter] hue s=exp(t-25)
+@end example
+
+A filtergraph allowing to read and process the above command list
+stored in a file @file{test.cmd}, can be specified with:
+@example
+sendcmd=f=test.cmd,drawtext=fontfile=FreeSerif.ttf:text='',hue
+@end example
+@end itemize
+
 @section asetpts, setpts
 
 Change the PTS (presentation timestamp) of the input frames.
index 82d39e4..3477eca 100644 (file)
@@ -53,6 +53,7 @@ OBJS-$(CONFIG_AMERGE_FILTER)                 += af_amerge.o
 OBJS-$(CONFIG_AMIX_FILTER)                   += af_amix.o
 OBJS-$(CONFIG_ANULL_FILTER)                  += af_anull.o
 OBJS-$(CONFIG_ARESAMPLE_FILTER)              += af_aresample.o
+OBJS-$(CONFIG_ASENDCMD_FILTER)               += f_sendcmd.o
 OBJS-$(CONFIG_ASETNSAMPLES_FILTER)           += af_asetnsamples.o
 OBJS-$(CONFIG_ASETPTS_FILTER)                += f_setpts.o
 OBJS-$(CONFIG_ASETTB_FILTER)                 += f_settb.o
@@ -122,6 +123,7 @@ OBJS-$(CONFIG_PIXDESCTEST_FILTER)            += vf_pixdesctest.o
 OBJS-$(CONFIG_REMOVELOGO_FILTER)             += bbox.o lswsutils.o lavfutils.o vf_removelogo.o
 OBJS-$(CONFIG_SCALE_FILTER)                  += vf_scale.o
 OBJS-$(CONFIG_SELECT_FILTER)                 += vf_select.o
+OBJS-$(CONFIG_SENDCMD_FILTER)                += f_sendcmd.o
 OBJS-$(CONFIG_SETDAR_FILTER)                 += vf_aspect.o
 OBJS-$(CONFIG_SETFIELD_FILTER)               += vf_setfield.o
 OBJS-$(CONFIG_SETPTS_FILTER)                 += f_setpts.o
index 6842ec9..b28c024 100644 (file)
@@ -42,6 +42,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER (AMIX,        amix,        af);
     REGISTER_FILTER (ANULL,       anull,       af);
     REGISTER_FILTER (ARESAMPLE,   aresample,   af);
+    REGISTER_FILTER (ASENDCMD,    asendcmd,    af);
     REGISTER_FILTER (ASETNSAMPLES, asetnsamples, af);
     REGISTER_FILTER (ASETPTS,     asetpts,     af);
     REGISTER_FILTER (ASETTB,      asettb,      af);
@@ -113,6 +114,7 @@ void avfilter_register_all(void)
     REGISTER_FILTER (REMOVELOGO,  removelogo,  vf);
     REGISTER_FILTER (SCALE,       scale,       vf);
     REGISTER_FILTER (SELECT,      select,      vf);
+    REGISTER_FILTER (SENDCMD,     sendcmd,     vf);
     REGISTER_FILTER (SETDAR,      setdar,      vf);
     REGISTER_FILTER (SETFIELD,    setfield,    vf);
     REGISTER_FILTER (SETPTS,      setpts,      vf);
diff --git a/libavfilter/f_sendcmd.c b/libavfilter/f_sendcmd.c
new file mode 100644 (file)
index 0000000..7081de8
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2012 Stefano Sabatini
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * send commands filter
+ */
+
+#include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
+#include "libavutil/file.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "avfiltergraph.h"
+#include "audio.h"
+#include "video.h"
+
+#define COMMAND_FLAG_ENTER 1
+#define COMMAND_FLAG_LEAVE 2
+
+static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
+{
+    const char *flag_strings[] = { "enter", "leave" };
+    int i, is_first = 1;
+
+    av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
+        if (flags & 1<<i) {
+            if (!is_first)
+                av_bprint_chars(pbuf, '+', 1);
+            av_bprintf(pbuf, "%s", flag_strings[i]);
+            is_first = 0;
+        }
+    }
+
+    return pbuf->str;
+}
+
+typedef struct {
+    int flags;
+    char *target, *command, *arg;
+    int index;
+} Command;
+
+typedef struct {
+    int64_t start_ts;          ///< start timestamp expressed as microseconds units
+    int64_t end_ts;            ///< end   timestamp expressed as microseconds units
+    int index;                 ///< unique index for these interval commands
+    Command *commands;
+    int   nb_commands;
+    int enabled;               ///< current time detected inside this interval
+} Interval;
+
+typedef struct {
+    const AVClass *class;
+    Interval *intervals;
+    int   nb_intervals;
+
+    char *commands_filename;
+    char *commands_str;
+} SendCmdContext;
+
+#define OFFSET(x) offsetof(SendCmdContext, x)
+
+static const AVOption sendcmd_options[]= {
+    { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0 },
+    { "c",        "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0 },
+    { "filename", "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0 },
+    { "f",        "set commands file",  OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0 },
+    {NULL},
+};
+
+AVFILTER_DEFINE_CLASS(sendcmd);
+
+#define SPACES " \f\t\n\r"
+
+static void skip_comments(const char **buf)
+{
+    while (**buf) {
+        /* skip leading spaces */
+        *buf += strspn(*buf, SPACES);
+        if (**buf != '#')
+            break;
+
+        (*buf)++;
+
+        /* skip comment until the end of line */
+        *buf += strcspn(*buf, "\n");
+        if (**buf)
+            (*buf)++;
+    }
+}
+
+#define COMMAND_DELIMS " \f\t\n\r,;"
+
+static int parse_command(Command *cmd, int cmd_count, int interval_count,
+                         const char **buf, void *log_ctx)
+{
+    int ret;
+
+    memset(cmd, 0, sizeof(Command));
+    cmd->index = cmd_count;
+
+    /* format: [FLAGS] target command arg */
+    *buf += strspn(*buf, SPACES);
+
+    /* parse flags */
+    if (**buf == '[') {
+        (*buf)++; /* skip "[" */
+
+        while (**buf) {
+            int len = strcspn(*buf, "|+]");
+
+            if      (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
+            else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
+            else {
+                char flag_buf[64];
+                av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Unknown flag '%s' in in interval #%d, command #%d\n",
+                       flag_buf, interval_count, cmd_count);
+                return AVERROR(EINVAL);
+            }
+            *buf += len;
+            if (**buf == ']')
+                break;
+            if (!strspn(*buf, "+|")) {
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Invalid flags char '%c' in interval #%d, command #%d\n",
+                       **buf, interval_count, cmd_count);
+                return AVERROR(EINVAL);
+            }
+            if (**buf)
+                (*buf)++;
+        }
+
+        if (**buf != ']') {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Missing flag terminator or extraneous data found at the end of flags "
+                   "in interval #%d, command #%d\n", interval_count, cmd_count);
+            return AVERROR(EINVAL);
+        }
+        (*buf)++; /* skip "]" */
+    } else {
+        cmd->flags = COMMAND_FLAG_ENTER;
+    }
+
+    *buf += strspn(*buf, SPACES);
+    cmd->target = av_get_token(buf, COMMAND_DELIMS);
+    if (!cmd->target || !cmd->target[0]) {
+        av_log(log_ctx, AV_LOG_ERROR,
+               "No target specified in in interval #%d, command #%d\n",
+               interval_count, cmd_count);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    *buf += strspn(*buf, SPACES);
+    cmd->command = av_get_token(buf, COMMAND_DELIMS);
+    if (!cmd->command || !cmd->command[0]) {
+        av_log(log_ctx, AV_LOG_ERROR,
+               "No command specified in in interval #%d, command #%d\n",
+               interval_count, cmd_count);
+        ret = AVERROR(EINVAL);
+        goto fail;
+    }
+
+    *buf += strspn(*buf, SPACES);
+    cmd->arg = av_get_token(buf, COMMAND_DELIMS);
+
+    return 1;
+
+fail:
+    av_freep(&cmd->target);
+    av_freep(&cmd->command);
+    av_freep(&cmd->arg);
+    return ret;
+}
+
+static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
+                          const char **buf, void *log_ctx)
+{
+    int cmd_count = 0;
+    int ret, n = 0;
+    AVBPrint pbuf;
+
+    *cmds = NULL;
+    *nb_cmds = 0;
+
+    while (**buf) {
+        Command cmd;
+
+        if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
+            return ret;
+        cmd_count++;
+
+        /* (re)allocate commands array if required */
+        if (*nb_cmds == n) {
+            n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
+            *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
+            if (!*cmds) {
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Could not (re)allocate command array\n");
+                return AVERROR(ENOMEM);
+            }
+        }
+
+        (*cmds)[(*nb_cmds)++] = cmd;
+
+        *buf += strspn(*buf, SPACES);
+        if (**buf && **buf != ';' && **buf != ',') {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Missing separator or extraneous data found at the end of "
+                   "interval #%d, in command #%d\n",
+                   interval_count, cmd_count);
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
+                   make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
+            return AVERROR(EINVAL);
+        }
+        if (**buf == ';')
+            break;
+        if (**buf == ',')
+            (*buf)++;
+    }
+
+    return 0;
+}
+
+#define DELIMS " \f\t\n\r,;"
+
+static int parse_interval(Interval *interval, int interval_count,
+                          const char **buf, void *log_ctx)
+{
+    char *intervalstr;
+    int ret;
+
+    *buf += strspn(*buf, SPACES);
+    if (!**buf)
+        return 0;
+
+    /* reset data */
+    memset(interval, 0, sizeof(Interval));
+    interval->index = interval_count;
+
+    /* format: INTERVAL COMMANDS */
+
+    /* parse interval */
+    intervalstr = av_get_token(buf, DELIMS);
+    if (intervalstr && intervalstr[0]) {
+        char *start, *end;
+
+        start = av_strtok(intervalstr, "-", &end);
+        if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Invalid start time specification '%s' in interval #%d\n",
+                   start, interval_count);
+            goto end;
+        }
+
+        if (end) {
+            if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Invalid end time specification '%s' in interval #%d\n",
+                       end, interval_count);
+                goto end;
+            }
+        } else {
+            interval->end_ts = INT64_MAX;
+        }
+        if (interval->end_ts < interval->start_ts) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Invalid end time '%s' in interval #%d: "
+                   "cannot be lesser than start time '%s'\n",
+                   end, interval_count, start);
+            ret = AVERROR(EINVAL);
+            goto end;
+        }
+    } else {
+        av_log(log_ctx, AV_LOG_ERROR,
+               "No interval specified for interval #%d\n", interval_count);
+        ret = AVERROR(EINVAL);
+        goto end;
+    }
+
+    /* parse commands */
+    ret = parse_commands(&interval->commands, &interval->nb_commands,
+                         interval_count, buf, log_ctx);
+
+end:
+    av_free(intervalstr);
+    return ret;
+}
+
+static int parse_intervals(Interval **intervals, int *nb_intervals,
+                           const char *buf, void *log_ctx)
+{
+    int interval_count = 0;
+    int ret, n = 0;
+
+    *intervals = NULL;
+    *nb_intervals = 0;
+
+    while (1) {
+        Interval interval;
+
+        skip_comments(&buf);
+        if (!(*buf))
+            break;
+
+        if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
+            return ret;
+
+        buf += strspn(buf, SPACES);
+        if (*buf) {
+            if (*buf != ';') {
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Missing terminator or extraneous data found at the end of interval #%d\n",
+                       interval_count);
+                return AVERROR(EINVAL);
+            }
+            buf++; /* skip ';' */
+        }
+        interval_count++;
+
+        /* (re)allocate commands array if required */
+        if (*nb_intervals == n) {
+            n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
+            *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
+            if (!*intervals) {
+                av_log(log_ctx, AV_LOG_ERROR,
+                       "Could not (re)allocate intervals array\n");
+                return AVERROR(ENOMEM);
+            }
+        }
+
+        (*intervals)[(*nb_intervals)++] = interval;
+    }
+
+    return 0;
+}
+
+static int cmp_intervals(const void *a, const void *b)
+{
+    const Interval *i1 = a;
+    const Interval *i2 = b;
+    int64_t ts_diff = i1->start_ts - i2->start_ts;
+    int ret;
+
+    ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0;
+    return ret == 0 ? i1->index - i2->index : ret;
+}
+
+static av_cold int init(AVFilterContext *ctx, const char *args)
+{
+    SendCmdContext *sendcmd = ctx->priv;
+    int ret, i, j;
+    char *buf;
+
+    sendcmd->class = &sendcmd_class;
+    av_opt_set_defaults(sendcmd);
+
+    if ((ret = av_set_options_string(sendcmd, args, "=", ":")) < 0)
+        return ret;
+
+    if (sendcmd->commands_filename && sendcmd->commands_str) {
+        av_log(ctx, AV_LOG_ERROR,
+               "Only one of the filename or commands options must be specified\n");
+        return AVERROR(EINVAL);
+    }
+
+    if (sendcmd->commands_filename) {
+        uint8_t *file_buf;
+        size_t file_bufsize;
+        ret = av_file_map(sendcmd->commands_filename,
+                          &file_buf, &file_bufsize, 0, ctx);
+        if (ret < 0)
+            return ret;
+
+        /* create a 0-terminated string based on the read file */
+        buf = av_malloc(file_bufsize + 1);
+        if (!buf)
+            return AVERROR(ENOMEM);
+        memcpy(buf, file_buf, file_bufsize);
+        buf[file_bufsize] = 0;
+        av_file_unmap(file_buf, file_bufsize);
+        sendcmd->commands_str = buf;
+    }
+
+    if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
+                               sendcmd->commands_str, ctx)) < 0)
+        return ret;
+
+    qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
+
+    av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
+    for (i = 0; i < sendcmd->nb_intervals; i++) {
+        AVBPrint pbuf;
+        Interval *interval = &sendcmd->intervals[i];
+        av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
+               (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
+        for (j = 0; j < interval->nb_commands; j++) {
+            Command *cmd = &interval->commands[j];
+            av_log(ctx, AV_LOG_VERBOSE,
+                   "    [%s] target:%s command:%s arg:%s index:%d\n",
+                   make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
+        }
+    }
+
+    return 0;
+}
+
+static void av_cold uninit(AVFilterContext *ctx)
+{
+    SendCmdContext *sendcmd = ctx->priv;
+    int i, j;
+
+    av_opt_free(sendcmd);
+
+    for (i = 0; i < sendcmd->nb_intervals; i++) {
+        Interval *interval = &sendcmd->intervals[i];
+        for (j = 0; j < interval->nb_commands; j++) {
+            Command *cmd = &interval->commands[j];
+            av_free(cmd->target);
+            av_free(cmd->command);
+            av_free(cmd->arg);
+        }
+        av_free(interval->commands);
+    }
+    av_freep(&sendcmd->intervals);
+}
+
+static int process_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
+{
+    AVFilterContext *ctx = inlink->dst;
+    SendCmdContext *sendcmd = ctx->priv;
+    int64_t ts;
+    int i, j, ret;
+
+    if (ref->pts == AV_NOPTS_VALUE)
+        goto end;
+
+    ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
+
+#define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
+
+    for (i = 0; i < sendcmd->nb_intervals; i++) {
+        Interval *interval = &sendcmd->intervals[i];
+        int flags = 0;
+
+        if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
+            flags += COMMAND_FLAG_ENTER;
+            interval->enabled = 1;
+        }
+        if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
+            flags += COMMAND_FLAG_LEAVE;
+            interval->enabled = 0;
+        }
+
+        if (flags) {
+            AVBPrint pbuf;
+            av_log(ctx, AV_LOG_VERBOSE,
+                   "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
+                   make_command_flags_str(&pbuf, flags), interval->index,
+                   (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
+                   (double)ts/1000000);
+
+            for (j = 0; flags && j < interval->nb_commands; j++) {
+                Command *cmd = &interval->commands[j];
+                char buf[1024];
+
+                if (cmd->flags & flags) {
+                    av_log(ctx, AV_LOG_VERBOSE,
+                           "Processing command #%d target:%s command:%s arg:%s\n",
+                           cmd->index, cmd->target, cmd->command, cmd->arg);
+                    ret = avfilter_graph_send_command(inlink->graph,
+                                                      cmd->target, cmd->command, cmd->arg,
+                                                      buf, sizeof(buf),
+                                                      AVFILTER_CMD_FLAG_ONE);
+                    av_log(ctx, AV_LOG_VERBOSE,
+                           "Command reply for command #%d: ret:%s res:%s\n",
+                           cmd->index, av_err2str(ret), buf);
+                }
+            }
+        }
+    }
+
+end:
+    /* give the reference away, do not store in cur_buf */
+    inlink->cur_buf = NULL;
+
+    switch (inlink->type) {
+    case AVMEDIA_TYPE_VIDEO: return ff_start_frame   (inlink->dst->outputs[0], ref);
+    case AVMEDIA_TYPE_AUDIO: return ff_filter_samples(inlink->dst->outputs[0], ref);
+    }
+    return AVERROR(ENOSYS);
+}
+
+#if CONFIG_SENDCMD_FILTER
+
+AVFilter avfilter_vf_sendcmd = {
+    .name      = "sendcmd",
+    .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
+
+    .init = init,
+    .uninit = uninit,
+    .priv_size = sizeof(SendCmdContext),
+
+    .inputs = (const AVFilterPad[]) {
+        {
+            .name             = "default",
+            .type             = AVMEDIA_TYPE_VIDEO,
+            .get_video_buffer = ff_null_get_video_buffer,
+            .start_frame      = process_frame,
+            .end_frame        = ff_null_end_frame,
+        },
+        { .name = NULL }
+    },
+    .outputs = (const AVFilterPad[]) {
+        {
+            .name             = "default",
+            .type             = AVMEDIA_TYPE_VIDEO,
+        },
+        { .name = NULL }
+    },
+};
+
+#endif
+
+#if CONFIG_ASENDCMD_FILTER
+
+AVFilter avfilter_af_asendcmd = {
+    .name      = "asendcmd",
+    .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
+
+    .init = init,
+    .uninit = uninit,
+    .priv_size = sizeof(SendCmdContext),
+
+    .inputs = (const AVFilterPad[]) {
+        {
+            .name             = "default",
+            .type             = AVMEDIA_TYPE_AUDIO,
+            .get_audio_buffer = ff_null_get_audio_buffer,
+            .filter_samples   = process_frame,
+        },
+        { .name = NULL }
+    },
+    .outputs = (const AVFilterPad[]) {
+        {
+            .name             = "default",
+            .type             = AVMEDIA_TYPE_AUDIO,
+        },
+        { .name = NULL }
+    },
+};
+
+#endif
index 8b7a670..3d3d1c9 100644 (file)
@@ -29,8 +29,8 @@
 #include "libavutil/avutil.h"
 
 #define LIBAVFILTER_VERSION_MAJOR  3
-#define LIBAVFILTER_VERSION_MINOR  16
-#define LIBAVFILTER_VERSION_MICRO 106
+#define LIBAVFILTER_VERSION_MINOR  17
+#define LIBAVFILTER_VERSION_MICRO 100
 
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
                                                LIBAVFILTER_VERSION_MINOR, \