[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