[FFmpeg-devel] [PATCH] lavfi: add asendcmd and sendcmd filters

Stefano Sabatini stefasab at gmail.com
Tue Aug 14 12:28:53 CEST 2012


---
 doc/filters.texi         |   70 ++++++++++
 libavfilter/Makefile     |    2 +
 libavfilter/allfilters.c |    2 +
 libavfilter/f_sendcmd.c  |  332 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 406 insertions(+), 0 deletions(-)
 create mode 100644 libavfilter/f_sendcmd.c

diff --git a/doc/filters.texi b/doc/filters.texi
index 763085c..ae5af7c 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -4064,6 +4064,76 @@ tools.
 
 Below is a description of the currently available multimedia filters.
 
+ at section asendcmd, sendcmd
+
+Send commands to filters in the filtergraph.
+
+These filters read commands to be sent to other filters in the
+filtergraph.
+
+ at code{asendcmd} must be inserted between two audio filters,
+ at code{sendcmd} must be inserted between two video filters, but apart
+from that they act the same.
+
+The specification of commands can be specified in the filter arguments
+with the @var{commands} option, or in a file specified with the
+ at var{filename} option.
+
+Commands are sent the first time when a frame with time greater or
+equal to the specified command time is processed by the filter.
+
+These filters accept the following options:
+ at table @option
+ at item commands, c
+Set the commands to be read and sent to the other filters.
+ at item filename, f
+Set the filename of the commands to be read and sent to the other
+filters.
+ at end table
+
+ at subsection Commands syntax
+
+Commands are specified one per line. Empty lines or lines starting
+with @code{#} are ignored.
+
+A command line has the syntax:
+ at example
+ at var{target} @var{time} @var{command} @var{arg}
+ at end example
+
+ at var{target} specify the target of the command, usually the name of
+the filter class or of the specific filter instance.
+
+ at var{time} specify the time when the filter command is sent, expressed
+as a time duration.
+
+ at var{command} specify the name of the command, and depends on the
+target filter.
+
+ at var{arg} specify the optional list of argument for the given command.
+
+ at subsection Examples
+
+ at itemize
+ at item
+Specify audio tempo change at second 4:
+ at example
+asendcmd=c='atempo 4 tempo 1.5',atempo
+ at end example
+
+ at item
+Specify a list of drawtext commands in a file.
+ at example
+drawtext 5 reinit fontsize=25:fontfile=FreeSerif.ttf:x=100+30*(t-5):text='hello world 5'
+drawtext 10 reinit fontsize=25:fontfile=FreeSerif.ttf:x=100+30*(t-10):text='hello world 10'
+ at end example
+
+The file containing the list of commands can be specified with:
+ at example
+sendcmd=f=test.cmd,drawtext=fontfile=FreeSerif.ttf:text='hello world 0'
+ at end example
+ at end itemize
+
 @section concat
 
 Concatenate audio and video streams, joining them together one after the
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index b6aeb76..422124c 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -51,6 +51,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)                += vf_setpts.o
 OBJS-$(CONFIG_ASETTB_FILTER)                 += f_settb.o
@@ -115,6 +116,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)                 += vf_setpts.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 2949a4c..dd376d8 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -41,6 +41,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);
@@ -105,6 +106,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
index 0000000..46704ef
--- /dev/null
+++ b/libavfilter/f_sendcmd.c
@@ -0,0 +1,332 @@
+/*
+ * 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/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"
+
+typedef struct {
+    char *buf;                  ///< allocated buffer
+    char *target, *command, *arg;
+    double t;
+} Command;
+
+static int cmp_commands(const void *a, const void *b)
+{
+    const Command *cmd1 = a;
+    const Command *cmd2 = b;
+
+    return cmd1->t - cmd2->t;
+}
+
+typedef struct {
+    const AVClass *class;
+    Command *commands;
+    int   nb_commands;
+    int next_command_idx;       ///< the index of the next command to process
+
+    char *commands_filename;
+    char *commands_str;
+
+    uint8_t *commands_file_buf;
+    size_t   commands_file_bufsize;
+} 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 DELIMS " \f\t"
+static int parse_command(Command *cmd, const char *cmd_string, void *log_ctx)
+{
+    char *timestr, *buf = strdup(cmd_string);
+    int ret;
+
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    /* format: target time command arg */
+    cmd->buf    = buf;
+    cmd->target = av_strtok(buf, DELIMS, &buf);
+    timestr     = av_strtok(NULL, DELIMS, &buf);
+    if (timestr) {
+        int64_t us;
+        if ((ret = av_parse_time(&us, timestr, 1)) < 0) {
+            av_log(log_ctx, AV_LOG_ERROR, "Invalid time specification '%s'\n", timestr);
+            return ret;
+        }
+        cmd->t = (double)us / 1000000;
+    }
+    cmd->command = av_strtok(NULL, DELIMS, &buf);
+    cmd->arg     = buf;
+
+    if (!cmd->command) {
+        av_log(log_ctx, AV_LOG_ERROR,
+               "Parse error, command not specified in string '%s'\n", cmd_string);
+        return AVERROR(EINVAL);
+    }
+
+    return 0;
+}
+
+static int parse_commands(Command **cmds, int *nb_cmds,
+                          const char *cmds_buf, size_t cmds_bufsize,
+                          void *log_ctx)
+{
+    char *line = NULL;
+    size_t line_size = 0;
+    int line_count;
+    int ret, n = 0;
+    const char *p = cmds_buf;
+    const char *pend = cmds_buf + cmds_bufsize;
+
+    *nb_cmds = 0;
+    if (!p)
+        return 0;
+    for (line_count = 0; *p; line_count++) {
+        Command cmd;
+        int i = 0;
+
+        /* skip empty lines && # comments */
+        while (p[i] != '\n' && p+i < pend)
+            i++;
+        line_size = i;
+
+        if (*p == '\n' || *p == '#') {
+            p += line_size+1;
+            continue;
+        }
+
+        line = av_realloc_f(line, line_size+1, 1);
+        if (!line) {
+            ret = AVERROR(ENOMEM);
+            goto end;
+        }
+        memcpy(line, p, line_size);
+        line[line_size] = 0;
+
+        if ((ret = parse_command(&cmd, line, log_ctx)) < 0) {
+            av_log(log_ctx, AV_LOG_ERROR,
+                   "Error occurred parsing line %d: %s\n", line_count, line);
+            goto end;
+        }
+
+        /* (re)allocate commands array if required*/
+        if (*nb_cmds == n) {
+            n = FFMAX(16, 2*n);
+            *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
+            if (!*cmds) {
+                ret = AVERROR(ENOMEM);
+            }
+        }
+        (*cmds)[(*nb_cmds)++] = cmd;
+        p += line_size+1;
+    }
+
+end:
+    av_free(line);
+    return ret;
+}
+
+static av_cold int init(AVFilterContext *ctx, const char *args)
+{
+    SendCmdContext *sendcmd = ctx->priv;
+    int ret, i;
+    char *buf;
+    size_t bufsize;
+
+    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) {
+        ret = av_file_map(sendcmd->commands_filename,
+                          &sendcmd->commands_file_buf,
+                          &sendcmd->commands_file_bufsize, 0, ctx);
+        if (ret < 0)
+            return ret;
+        buf     = sendcmd->commands_file_buf;
+        bufsize = sendcmd->commands_file_bufsize;
+    } else {
+        buf     = sendcmd->commands_str;
+        bufsize = strlen(sendcmd->commands_str) + 1;
+    }
+
+    if ((ret = parse_commands(&sendcmd->commands, &sendcmd->nb_commands,
+                              buf, bufsize, ctx)) < 0)
+        return ret;
+
+    qsort(sendcmd->commands, sendcmd->nb_commands, sizeof(Command), cmp_commands);
+
+    av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
+    for (i = 0; i < sendcmd->nb_commands; i++) {
+        Command cmd = sendcmd->commands[i];
+        av_log(ctx, AV_LOG_DEBUG, "target:%s time:%f command:%s arg:%s\n",
+               cmd.target, cmd.t, cmd.command, cmd.arg);
+    }
+
+    return 0;
+}
+
+static void av_cold uninit(AVFilterContext *ctx)
+{
+    SendCmdContext *sendcmd = ctx->priv;
+    int i;
+
+    av_opt_free(sendcmd);
+    for (i = 0; i < sendcmd->nb_commands; i++)
+        av_free(sendcmd->commands[i].buf);
+
+    av_freep(&sendcmd->commands);
+    av_file_unmap(sendcmd->commands_file_buf,
+                  sendcmd->commands_file_bufsize);
+}
+
+static int process_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
+{
+    AVFilterContext *ctx = inlink->dst;
+    SendCmdContext *sendcmd = ctx->priv;
+    double t;
+    int ret;
+
+    if (ref->pts == AV_NOPTS_VALUE)
+        goto end;
+
+    t = ref->pts * av_q2d(inlink->time_base);
+
+    while (sendcmd->next_command_idx < sendcmd->nb_commands &&
+           t >= (sendcmd->commands[sendcmd->next_command_idx]).t) {
+        char buf[1024];
+        Command *cmd = &sendcmd->commands[sendcmd->next_command_idx];
+
+        av_log(ctx, AV_LOG_VERBOSE,
+               "Processing command target:%s time:%f command:%s arg:%s\n",
+               cmd->target, cmd->t, 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 '%s': ret:%s res:%s\n", cmd->buf, av_err2str(ret), buf);
+        sendcmd->next_command_idx++;
+    }
+
+end:
+    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);
+}
+
+static int end_frame(AVFilterLink *inlink)
+{
+    inlink->cur_buf = NULL;
+    return ff_end_frame(inlink->dst->outputs[0]);
+}
+
+#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        = 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
-- 
1.7.5.4



More information about the ffmpeg-devel mailing list