2012-08-13 21:13:26 +03:00
|
|
|
/*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2022-02-23 14:56:49 +02:00
|
|
|
#include "config_components.h"
|
|
|
|
|
2012-08-13 21:13:26 +03:00
|
|
|
#include "libavutil/avstring.h"
|
|
|
|
#include "libavutil/bprint.h"
|
2020-02-29 00:11:32 +02:00
|
|
|
#include "libavutil/eval.h"
|
2012-08-13 21:13:26 +03:00
|
|
|
#include "libavutil/file.h"
|
2024-03-25 02:30:37 +02:00
|
|
|
#include "libavutil/mem.h"
|
2012-08-13 21:13:26 +03:00
|
|
|
#include "libavutil/opt.h"
|
|
|
|
#include "libavutil/parseutils.h"
|
|
|
|
#include "avfilter.h"
|
2024-08-06 08:50:21 +02:00
|
|
|
#include "filters.h"
|
2012-08-13 21:13:26 +03:00
|
|
|
#include "audio.h"
|
|
|
|
#include "video.h"
|
|
|
|
|
|
|
|
#define COMMAND_FLAG_ENTER 1
|
|
|
|
#define COMMAND_FLAG_LEAVE 2
|
2020-02-29 00:11:32 +02:00
|
|
|
#define COMMAND_FLAG_EXPR 4
|
|
|
|
|
|
|
|
static const char *const var_names[] = {
|
|
|
|
"N", /* frame number */
|
|
|
|
"T", /* frame time in seconds */
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
#if FF_API_FRAME_PKT
|
2020-02-29 00:11:32 +02:00
|
|
|
"POS", /* original position in the file of the frame */
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
#endif
|
2020-02-29 00:11:32 +02:00
|
|
|
"PTS", /* frame pts */
|
2020-03-13 18:19:33 +02:00
|
|
|
"TS", /* interval start time in seconds */
|
|
|
|
"TE", /* interval end time in seconds */
|
|
|
|
"TI", /* interval interpolated value: TI = (T - TS) / (TE - TS) */
|
2022-03-07 17:57:30 +02:00
|
|
|
"W", /* width for video frames */
|
|
|
|
"H", /* height for video frames */
|
2020-02-29 00:11:32 +02:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
enum var_name {
|
|
|
|
VAR_N,
|
|
|
|
VAR_T,
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
#if FF_API_FRAME_PKT
|
2020-02-29 00:11:32 +02:00
|
|
|
VAR_POS,
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
#endif
|
2020-02-29 00:11:32 +02:00
|
|
|
VAR_PTS,
|
2020-03-13 18:19:33 +02:00
|
|
|
VAR_TS,
|
|
|
|
VAR_TE,
|
|
|
|
VAR_TI,
|
2022-03-07 17:57:30 +02:00
|
|
|
VAR_W,
|
|
|
|
VAR_H,
|
2020-02-29 00:11:32 +02:00
|
|
|
VAR_VARS_NB
|
|
|
|
};
|
2012-08-13 21:13:26 +03:00
|
|
|
|
|
|
|
static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
|
|
|
|
{
|
2020-02-29 00:11:32 +02:00
|
|
|
static const char * const flag_strings[] = { "enter", "leave", "expr" };
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-05-12 20:00:49 +02:00
|
|
|
typedef struct Command {
|
2012-08-13 21:13:26 +03:00
|
|
|
int flags;
|
|
|
|
char *target, *command, *arg;
|
|
|
|
int index;
|
|
|
|
} Command;
|
|
|
|
|
2017-05-12 20:00:49 +02:00
|
|
|
typedef struct Interval {
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
|
|
|
|
2017-05-12 20:00:49 +02:00
|
|
|
typedef struct SendCmdContext {
|
2012-08-13 21:13:26 +03:00
|
|
|
const AVClass *class;
|
|
|
|
Interval *intervals;
|
|
|
|
int nb_intervals;
|
|
|
|
|
|
|
|
char *commands_filename;
|
|
|
|
char *commands_str;
|
|
|
|
} SendCmdContext;
|
|
|
|
|
|
|
|
#define OFFSET(x) offsetof(SendCmdContext, x)
|
2013-04-12 16:45:28 +03:00
|
|
|
#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
|
2012-11-29 02:13:29 +03:00
|
|
|
static const AVOption options[] = {
|
|
|
|
{ "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
|
|
|
{ "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
|
|
|
{ "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
|
|
|
{ "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
|
2013-09-07 15:13:50 +03:00
|
|
|
{ NULL }
|
2012-08-13 21:13:26 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
#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;
|
2020-02-29 00:11:32 +02:00
|
|
|
else if (!strncmp(*buf, "expr", strlen("expr"))) cmd->flags |= COMMAND_FLAG_EXPR;
|
2012-08-13 21:13:26 +03:00
|
|
|
else {
|
|
|
|
char flag_buf[64];
|
|
|
|
av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
|
|
|
|
av_log(log_ctx, AV_LOG_ERROR,
|
2013-04-21 14:35:29 +03:00
|
|
|
"Unknown flag '%s' in interval #%d, command #%d\n",
|
2012-08-13 21:13:26 +03:00
|
|
|
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,
|
2013-04-21 14:35:29 +03:00
|
|
|
"No target specified in interval #%d, command #%d\n",
|
2012-08-13 21:13:26 +03:00
|
|
|
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,
|
2013-04-21 14:35:29 +03:00
|
|
|
"No command specified in interval #%d, command #%d\n",
|
2012-08-13 21:13:26 +03:00
|
|
|
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);
|
2017-02-09 01:03:21 +02:00
|
|
|
if (!start) {
|
|
|
|
ret = AVERROR(EINVAL);
|
|
|
|
av_log(log_ctx, AV_LOG_ERROR,
|
|
|
|
"Invalid interval specification '%s' in interval #%d\n",
|
|
|
|
intervalstr, interval_count);
|
|
|
|
goto end;
|
|
|
|
}
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
|
|
|
|
2015-03-16 11:41:19 +02:00
|
|
|
if (!buf)
|
|
|
|
return 0;
|
|
|
|
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
2015-11-01 17:43:56 +02:00
|
|
|
return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
|
|
|
|
2013-04-11 15:39:37 +03:00
|
|
|
static av_cold int init(AVFilterContext *ctx)
|
2012-08-13 21:13:26 +03:00
|
|
|
{
|
2015-09-09 20:30:35 +02:00
|
|
|
SendCmdContext *s = ctx->priv;
|
2012-08-13 21:13:26 +03:00
|
|
|
int ret, i, j;
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
if ((!!s->commands_filename + !!s->commands_str) != 1) {
|
2012-08-13 21:13:26 +03:00
|
|
|
av_log(ctx, AV_LOG_ERROR,
|
2015-03-16 11:41:19 +02:00
|
|
|
"One and only one of the filename or commands options must be specified\n");
|
2012-08-13 21:13:26 +03:00
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
if (s->commands_filename) {
|
2012-10-23 22:42:14 +03:00
|
|
|
uint8_t *file_buf, *buf;
|
2012-08-13 21:13:26 +03:00
|
|
|
size_t file_bufsize;
|
2015-09-09 20:30:35 +02:00
|
|
|
ret = av_file_map(s->commands_filename,
|
2012-08-13 21:13:26 +03:00
|
|
|
&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);
|
2012-10-23 02:42:24 +03:00
|
|
|
if (!buf) {
|
|
|
|
av_file_unmap(file_buf, file_bufsize);
|
2012-08-13 21:13:26 +03:00
|
|
|
return AVERROR(ENOMEM);
|
2012-10-23 02:42:24 +03:00
|
|
|
}
|
2012-08-13 21:13:26 +03:00
|
|
|
memcpy(buf, file_buf, file_bufsize);
|
|
|
|
buf[file_bufsize] = 0;
|
|
|
|
av_file_unmap(file_buf, file_bufsize);
|
2015-09-09 20:30:35 +02:00
|
|
|
s->commands_str = buf;
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
if ((ret = parse_intervals(&s->intervals, &s->nb_intervals,
|
|
|
|
s->commands_str, ctx)) < 0)
|
2012-08-13 21:13:26 +03:00
|
|
|
return ret;
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
if (s->nb_intervals == 0) {
|
2015-03-16 11:41:19 +02:00
|
|
|
av_log(ctx, AV_LOG_ERROR, "No commands were specified\n");
|
2015-01-16 01:08:36 +02:00
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals);
|
2012-08-13 21:13:26 +03:00
|
|
|
|
|
|
|
av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
|
2015-09-09 20:30:35 +02:00
|
|
|
for (i = 0; i < s->nb_intervals; i++) {
|
2012-08-13 21:13:26 +03:00
|
|
|
AVBPrint pbuf;
|
2015-09-09 20:30:35 +02:00
|
|
|
Interval *interval = &s->intervals[i];
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-05-05 14:13:00 +03:00
|
|
|
static av_cold void uninit(AVFilterContext *ctx)
|
2012-08-13 21:13:26 +03:00
|
|
|
{
|
2015-09-09 20:30:35 +02:00
|
|
|
SendCmdContext *s = ctx->priv;
|
2012-08-13 21:13:26 +03:00
|
|
|
int i, j;
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
for (i = 0; i < s->nb_intervals; i++) {
|
|
|
|
Interval *interval = &s->intervals[i];
|
2012-08-13 21:13:26 +03:00
|
|
|
for (j = 0; j < interval->nb_commands; j++) {
|
|
|
|
Command *cmd = &interval->commands[j];
|
2014-12-14 16:54:45 +02:00
|
|
|
av_freep(&cmd->target);
|
|
|
|
av_freep(&cmd->command);
|
|
|
|
av_freep(&cmd->arg);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
2014-12-14 16:54:45 +02:00
|
|
|
av_freep(&interval->commands);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
2015-09-09 20:30:35 +02:00
|
|
|
av_freep(&s->intervals);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
|
|
|
|
2013-03-10 03:30:30 +03:00
|
|
|
static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
|
2012-08-13 21:13:26 +03:00
|
|
|
{
|
2024-08-06 08:50:21 +02:00
|
|
|
FilterLink *inl = ff_filter_link(inlink);
|
2012-08-13 21:13:26 +03:00
|
|
|
AVFilterContext *ctx = inlink->dst;
|
2015-09-09 20:30:35 +02:00
|
|
|
SendCmdContext *s = ctx->priv;
|
2012-08-13 21:13:26 +03:00
|
|
|
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))
|
|
|
|
|
2015-09-09 20:30:35 +02:00
|
|
|
for (i = 0; i < s->nb_intervals; i++) {
|
|
|
|
Interval *interval = &s->intervals[i];
|
2012-08-13 21:13:26 +03:00
|
|
|
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;
|
|
|
|
}
|
2020-02-29 00:11:32 +02:00
|
|
|
if (interval->enabled)
|
|
|
|
flags += COMMAND_FLAG_EXPR;
|
2012-08-13 21:13:26 +03:00
|
|
|
|
|
|
|
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];
|
2020-02-29 00:11:32 +02:00
|
|
|
char *cmd_arg = cmd->arg;
|
2012-08-13 21:13:26 +03:00
|
|
|
char buf[1024];
|
|
|
|
|
|
|
|
if (cmd->flags & flags) {
|
2020-02-29 00:11:32 +02:00
|
|
|
if (cmd->flags & COMMAND_FLAG_EXPR) {
|
|
|
|
double var_values[VAR_VARS_NB], res;
|
2020-03-13 18:19:33 +02:00
|
|
|
double start = TS2T(interval->start_ts, AV_TIME_BASE_Q);
|
|
|
|
double end = TS2T(interval->end_ts, AV_TIME_BASE_Q);
|
|
|
|
double current = TS2T(ref->pts, inlink->time_base);
|
2020-02-29 00:11:32 +02:00
|
|
|
|
2024-08-06 08:50:21 +02:00
|
|
|
var_values[VAR_N] = inl->frame_count_in;
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
#if FF_API_FRAME_PKT
|
|
|
|
FF_DISABLE_DEPRECATION_WARNINGS
|
2020-02-29 00:11:32 +02:00
|
|
|
var_values[VAR_POS] = ref->pkt_pos == -1 ? NAN : ref->pkt_pos;
|
lavu/frame: deprecate AVFrame.pkt_{pos,size}
These fields are supposed to store information about the packet the
frame was decoded from, specifically the byte offset it was stored at
and its size.
However,
- the fields are highly ad-hoc - there is no strong reason why
specifically those (and not any other) packet properties should have a
dedicated field in AVFrame; unlike e.g. the timestamps, there is no
fundamental link between coded packet offset/size and decoded frames
- they only make sense for frames produced by decoding demuxed packets,
and even then it is not always the case that the encoded data was
stored in the file as a contiguous sequence of bytes (in order for pos
to be well-defined)
- pkt_pos was added without much explanation, apparently to allow
passthrough of this information through lavfi in order to handle byte
seeking in ffplay. That is now implemented using arbitrary user data
passthrough in AVFrame.opaque_ref.
- several filters use pkt_pos as a variable available to user-supplied
expressions, but there seems to be no established motivation for using them.
- pkt_size was added for use in ffprobe, but that too is now handled
without using this field. Additonally, the values of this field
produced by libavcodec are flawed, as described in the previous
ffprobe conversion commit.
In summary - these fields are ill-defined and insufficiently motivated,
so deprecate them.
2023-03-10 11:48:34 +02:00
|
|
|
FF_ENABLE_DEPRECATION_WARNINGS
|
|
|
|
#endif
|
2020-02-29 00:11:32 +02:00
|
|
|
var_values[VAR_PTS] = TS2D(ref->pts);
|
2020-03-13 18:19:33 +02:00
|
|
|
var_values[VAR_T] = current;
|
|
|
|
var_values[VAR_TS] = start;
|
|
|
|
var_values[VAR_TE] = end;
|
|
|
|
var_values[VAR_TI] = (current - start) / (end - start);
|
2022-03-07 17:57:30 +02:00
|
|
|
var_values[VAR_W] = ref->width;
|
|
|
|
var_values[VAR_H] = ref->height;
|
2020-02-29 00:11:32 +02:00
|
|
|
|
|
|
|
if ((ret = av_expr_parse_and_eval(&res, cmd->arg, var_names, var_values,
|
|
|
|
NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) {
|
|
|
|
av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for command argument.\n", cmd->arg);
|
|
|
|
av_frame_free(&ref);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd_arg = av_asprintf("%g", res);
|
|
|
|
if (!cmd_arg) {
|
|
|
|
av_frame_free(&ref);
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
}
|
|
|
|
}
|
2012-08-13 21:13:26 +03:00
|
|
|
av_log(ctx, AV_LOG_VERBOSE,
|
|
|
|
"Processing command #%d target:%s command:%s arg:%s\n",
|
2020-02-29 00:11:32 +02:00
|
|
|
cmd->index, cmd->target, cmd->command, cmd_arg);
|
2024-08-11 16:35:52 +02:00
|
|
|
ret = avfilter_graph_send_command(inl->graph,
|
2020-02-29 00:11:32 +02:00
|
|
|
cmd->target, cmd->command, cmd_arg,
|
2012-08-13 21:13:26 +03:00
|
|
|
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);
|
2020-02-29 00:11:32 +02:00
|
|
|
if (cmd->flags & COMMAND_FLAG_EXPR)
|
|
|
|
av_freep(&cmd_arg);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
switch (inlink->type) {
|
2012-12-08 13:27:29 +03:00
|
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
|
|
return ff_filter_frame(inlink->dst->outputs[0], ref);
|
2012-08-13 21:13:26 +03:00
|
|
|
}
|
2012-12-08 13:27:29 +03:00
|
|
|
|
2012-08-13 21:13:26 +03:00
|
|
|
return AVERROR(ENOSYS);
|
|
|
|
}
|
|
|
|
|
2021-09-10 22:29:00 +02:00
|
|
|
AVFILTER_DEFINE_CLASS_EXT(sendcmd, "(a)sendcmd", options);
|
2012-08-13 21:13:26 +03:00
|
|
|
|
2021-09-10 22:29:00 +02:00
|
|
|
#if CONFIG_SENDCMD_FILTER
|
2012-11-29 02:13:47 +03:00
|
|
|
|
2012-11-28 22:01:59 +03:00
|
|
|
static const AVFilterPad sendcmd_inputs[] = {
|
|
|
|
{
|
2013-09-07 15:13:50 +03:00
|
|
|
.name = "default",
|
|
|
|
.type = AVMEDIA_TYPE_VIDEO,
|
|
|
|
.filter_frame = filter_frame,
|
2012-11-28 22:01:59 +03:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-04-19 18:33:56 +02:00
|
|
|
const AVFilter ff_vf_sendcmd = {
|
2013-09-07 15:13:50 +03:00
|
|
|
.name = "sendcmd",
|
2012-08-13 21:13:26 +03:00
|
|
|
.description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
|
2013-09-07 15:13:50 +03:00
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.priv_size = sizeof(SendCmdContext),
|
2021-11-22 15:39:11 +02:00
|
|
|
.flags = AVFILTER_FLAG_METADATA_ONLY,
|
2021-08-12 13:05:31 +02:00
|
|
|
FILTER_INPUTS(sendcmd_inputs),
|
2023-08-03 14:37:51 +02:00
|
|
|
FILTER_OUTPUTS(ff_video_default_filterpad),
|
2013-09-07 15:13:50 +03:00
|
|
|
.priv_class = &sendcmd_class,
|
2012-08-13 21:13:26 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if CONFIG_ASENDCMD_FILTER
|
|
|
|
|
2012-11-28 22:01:59 +03:00
|
|
|
static const AVFilterPad asendcmd_inputs[] = {
|
|
|
|
{
|
2013-09-07 15:13:50 +03:00
|
|
|
.name = "default",
|
|
|
|
.type = AVMEDIA_TYPE_AUDIO,
|
|
|
|
.filter_frame = filter_frame,
|
2012-11-28 22:01:59 +03:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-04-19 18:33:56 +02:00
|
|
|
const AVFilter ff_af_asendcmd = {
|
2013-09-07 15:13:50 +03:00
|
|
|
.name = "asendcmd",
|
2012-08-13 21:13:26 +03:00
|
|
|
.description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
|
2021-09-10 22:29:00 +02:00
|
|
|
.priv_class = &sendcmd_class,
|
2013-09-07 15:13:50 +03:00
|
|
|
.init = init,
|
|
|
|
.uninit = uninit,
|
|
|
|
.priv_size = sizeof(SendCmdContext),
|
2021-11-22 15:39:11 +02:00
|
|
|
.flags = AVFILTER_FLAG_METADATA_ONLY,
|
2021-08-12 13:05:31 +02:00
|
|
|
FILTER_INPUTS(asendcmd_inputs),
|
2023-08-03 00:59:02 +02:00
|
|
|
FILTER_OUTPUTS(ff_audio_default_filterpad),
|
2012-08-13 21:13:26 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|