[FFmpeg-devel] [PATCH] lavfi: add asendcmd and sendcmd filters
Nicolas George
nicolas.george at normalesup.org
Fri Sep 7 11:32:51 CEST 2012
Le nonidi 19 fructidor, an CCXX, Stefano Sabatini a écrit :
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 712936d..fcee490 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -4176,6 +4176,130 @@ 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{sendcmd} must be inserted between two video filters.
> + at code{asendcmd} works the same way as @code{sendcmd} but for audio.
I liked the previous version better: "for audio" seems to imply that the
filter cares about the type of data it sees.
> +
> +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.
This paragraph was not updated to correspond to the interval logic.
> +
> +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
> +
> +A commands description consists of a sequence of interval
> +specifications, comprising a list of commands to be specified at
> +particular events relating to that interval.
> +
> +An interval is specified by the following syntax:
> + at example
> + at var{START}[- at var{END}] @var{COMMANDS};
> + at end example
> +
> +The time interval is specified by the @var{START} and @var{END} times.
> + at var{END} is optional and defaults to the maximum time.
> +
> + at var{COMMANDS} consists of a sequence of one or more command
> +descriptions, separated by ",", relating to that interval.
> +The syntax of a command is given by:
> + at example
> +[@var{FLAGS}] @var{TARGET} @var{COMMAND} @var{ARG}
> + at end example
> +
> + at var{FLAGS} is optional and specifies the type of events relating to
> +the time interval which enable to send the specified command, and must
> +be a sequence of identifier flag separated by "+" and enclosed between
> +"[" and "]".
> +
> +The following flags are recognized:
> + at table @option
> + at 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.
> +
> + at 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.
> + at end table
> +
> +If @var{FLAGS} is not specified, a default value of @code{[enter]} is
> +assumed.
> +
> + at var{TARGET} specifies the target of the command, usually the name of
> +the filter class or a specific filter instance.
> +
> + at var{COMMAND} specifies the name of the command for the @var{TARGET}
> +filter instance.
> +
> + at 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:
> + at example
> + at var{COMMAND_FLAG} ::= "enter" | "leave"
> + at var{COMMAND_FLAGS} ::= @var{COMMAND_FLAG} [+ at var{COMMAND_FLAG}]
> + at var{COMMAND} ::= ["[" @var{COMMAND_FLAGS} "]"] @var{TARGET} @var{COMMAND} [@var{ARG}]
> + at var{COMMANDS} ::= @var{COMMAND} [, at var{COMMANDS}]
> + at var{INTERVAL} ::= @var{START}[- at var{END}] @var{COMMANDS}
> + at var{INTERVALS} ::= @var{INTERVAL}[;@var{INTERVALS}]
> + at end example
> +
> + at subsection Examples
> +
> + at itemize
> + at item
> +Specify audio tempo change at second 4:
> + at example
> +asendcmd=c='4.0 atempo tempo 1.5',atempo
> + at end example
> +
> + at item
> +Specify a list of drawtext and hue commands in a file.
> + at 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';
> + at end example
> +
> +A filtergraph allowing to read and process the above command list
> +stored in a file @file{test.cmd}, can be specified with:
> + at example
> +sendcmd=f=test.cmd,drawtext=fontfile=FreeSerif.ttf:text='',hue
> + at end example
> + at end itemize
> +
> @section asetpts, setpts
>
> Change the PTS (presentation timestamp) of the input frames.
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 29b5f0e..0d815f9 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -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
> @@ -121,6 +122,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
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 6842ec9..b28c024 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -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
> index 0000000..8fa3de9
> --- /dev/null
> +++ b/libavfilter/f_sendcmd.c
> @@ -0,0 +1,589 @@
> +/*
> + * 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"
> +
> +#define COMMAND_FLAG_ENTER 1
> +#define COMMAND_FLAG_LEAVE 2
> +
> +static inline char *av_make_command_flags_str(char *buf, size_t buf_size, int flags)
> +{
> + const char *strs[] = { "enter", "leave" };
> + int len, i, j;
> + char *buf0 = buf;
> +
> + for (i = 0; i < FF_ARRAY_ELEMS(strs); i++) {
> + if (flags & 1<<i) {
> + if (buf != buf0) {
> + *(buf++) = '+';
> + buf_size--;
> + }
> + for (j = 0, len = strlen("enter"); j < len && j < buf_size; j++)
> + buf[j] = strs[i][j];
> + buf += j;
> + buf_size -= j;
> + }
> + }
> +
> + return buf0;
> +}
You could use bprint for that, that would probably be simpler.
> +
> +#define flags2str(flags) \
> + av_make_command_flags_str((char[32]){0}, 32, flags)
> +
> +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;
"inside" may be more descriptive.
> +} Interval;
> +
> +typedef struct {
> + const AVClass *class;
> + Interval *intervals;
> + int nb_intervals;
> +
> + 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 SPACES " \f\t\n\r"
> +
> +static void skip_comments(const char **buf)
> +{
> + int len;
> +
> + while (1) {
> + if (!**buf)
> + break;
while (**buf) ?
> +
> + /* skip leading spaces */
> + len = strspn(*buf, SPACES);
> + if (len)
> + *buf += len;
If len == 0, then *buf += len is a nop: you can write that as *buf += strspn
directly.
> + 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 */
> + if (**buf)
> + *buf += strspn(*buf, SPACES);
If **buf is null, strspn will return 0, so the test is unnecessary. There
are a lot of similar constructs that can be simplified that way.
> +
> + /* 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;
With that code, "[enter+enter]" will be parsed as
COMMAND_FLAG_ENTER+COMMAND_FLAG_ENTER = 1+1 = 2. Using |= instead of +=
seems more logical.
> + 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);
> + }
I am not sure what the strlcpy is for. If you want to print only up to 63
chars, you can write "%63s".
> + *buf += len;
> + if (**buf == ']')
> + break;
> + if (**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;
> + }
> +
> + if (**buf)
> + *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;
> + }
> +
> + if (**buf)
> + *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;
> + }
> +
> + if (**buf)
> + *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;
> +
> + *cmds = 0;
NULL?
> + *nb_cmds = 0;
> +
> + while (1) {
> + 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;
> +
> + if (**buf)
> + *buf += strspn(*buf, SPACES);
> + if (**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",
> + flags2str(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;
> +
> + if (!**buf)
> + return 0;
> +
> + /* reset data */
> + memset(interval, 0, sizeof(Interval));
> + interval->index = interval_count;
> +
> + /* format: INTERVAL COMMANDS */
> +
> + /* parse interval */
> + if (**buf)
> + *buf += strspn(*buf, SPACES);
> + intervalstr = av_get_token(buf, DELIMS);
> + if (intervalstr) {
> + 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;
> +
> + if (*buf)
> + buf += strspn(buf, SPACES);
> + 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;
> + int ret;
> +
> + ret = i1->start_ts - i2->start_ts;
> + return ret == 0 ? i1->index - i2->index : ret;
start_ts is an int64_t, the subtraction could overflow.
> +}
> +
> +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) {
> + 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;
Beware: sendcmd->commands_file_buf is not necessarily 0-terminated. It will
not be if the file size happens to be a multiple of 4096, and in that case
the parsing code will likely segfault.
> + } else {
> + buf = sendcmd->commands_str;
> + }
> +
> + if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
> + buf, 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++) {
> + 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",
> + flags2str(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);
> +
> + 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;
> + int64_t ts;
> + int i, j, ret;
> + AVRational tb = {1, 1000000};
There is already AV_TIME_BASE_Q.
> +
> + if (ref->pts == AV_NOPTS_VALUE)
> + goto end;
> +
> + ts = av_rescale_q(ref->pts, inlink->time_base, tb);
> +
> + for (i = 0; i < sendcmd->nb_intervals; i++) {
> + Interval *interval = &sendcmd->intervals[i];
> + int flags = 0;
> +
> + if (!interval->enabled && ts >= interval->start_ts && ts <= interval->end_ts) {
> + flags += COMMAND_FLAG_ENTER;
> + interval->enabled = 1;
> + }
I would suggest to take start_ts inclusively and end_ts exclusively:
ts >= interval->start_ts && ts < interval->end_ts
> + if (interval->enabled && (ts < interval->start_ts || ts > interval->end_ts)) {
> + flags += COMMAND_FLAG_LEAVE;
> + interval->enabled = 0;
> + }
A macro is_inside_interval(interval, ts) would make the block easier to
understand, I believe.
> +
> + if (flags) {
> + av_log(ctx, AV_LOG_VERBOSE,
> + "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
> + flags2str(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
Sorry for the delay. Thanks for the great work.
Regards,
--
Nicolas George
More information about the ffmpeg-devel
mailing list