| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * Copyright (c) 2012 Stefano Sabatini | ||
| 3 | * | ||
| 4 | * This file is part of FFmpeg. | ||
| 5 | * | ||
| 6 | * FFmpeg is free software; you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU Lesser General Public | ||
| 8 | * License as published by the Free Software Foundation; either | ||
| 9 | * version 2.1 of the License, or (at your option) any later version. | ||
| 10 | * | ||
| 11 | * FFmpeg is distributed in the hope that it will be useful, | ||
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 14 | * Lesser General Public License for more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU Lesser General Public | ||
| 17 | * License along with FFmpeg; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| 19 | */ | ||
| 20 | |||
| 21 | /** | ||
| 22 | * @file | ||
| 23 | * send commands filter | ||
| 24 | */ | ||
| 25 | |||
| 26 | #include "config_components.h" | ||
| 27 | |||
| 28 | #include "libavutil/avstring.h" | ||
| 29 | #include "libavutil/bprint.h" | ||
| 30 | #include "libavutil/eval.h" | ||
| 31 | #include "libavutil/file.h" | ||
| 32 | #include "libavutil/mem.h" | ||
| 33 | #include "libavutil/opt.h" | ||
| 34 | #include "libavutil/parseutils.h" | ||
| 35 | #include "avfilter.h" | ||
| 36 | #include "filters.h" | ||
| 37 | #include "audio.h" | ||
| 38 | #include "video.h" | ||
| 39 | |||
| 40 | #define COMMAND_FLAG_ENTER 1 | ||
| 41 | #define COMMAND_FLAG_LEAVE 2 | ||
| 42 | #define COMMAND_FLAG_EXPR 4 | ||
| 43 | |||
| 44 | static const char *const var_names[] = { | ||
| 45 | "N", /* frame number */ | ||
| 46 | "T", /* frame time in seconds */ | ||
| 47 | "PTS", /* frame pts */ | ||
| 48 | "TS", /* interval start time in seconds */ | ||
| 49 | "TE", /* interval end time in seconds */ | ||
| 50 | "TI", /* interval interpolated value: TI = (T - TS) / (TE - TS) */ | ||
| 51 | "W", /* width for video frames */ | ||
| 52 | "H", /* height for video frames */ | ||
| 53 | NULL | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum var_name { | ||
| 57 | VAR_N, | ||
| 58 | VAR_T, | ||
| 59 | VAR_PTS, | ||
| 60 | VAR_TS, | ||
| 61 | VAR_TE, | ||
| 62 | VAR_TI, | ||
| 63 | VAR_W, | ||
| 64 | VAR_H, | ||
| 65 | VAR_VARS_NB | ||
| 66 | }; | ||
| 67 | |||
| 68 | ✗ | static inline char *make_command_flags_str(AVBPrint *pbuf, int flags) | |
| 69 | { | ||
| 70 | static const char * const flag_strings[] = { "enter", "leave", "expr" }; | ||
| 71 | ✗ | int i, is_first = 1; | |
| 72 | |||
| 73 | ✗ | av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
| 74 | ✗ | for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) { | |
| 75 | ✗ | if (flags & 1<<i) { | |
| 76 | ✗ | if (!is_first) | |
| 77 | ✗ | av_bprint_chars(pbuf, '+', 1); | |
| 78 | ✗ | av_bprintf(pbuf, "%s", flag_strings[i]); | |
| 79 | ✗ | is_first = 0; | |
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | ✗ | return pbuf->str; | |
| 84 | } | ||
| 85 | |||
| 86 | typedef struct Command { | ||
| 87 | int flags; | ||
| 88 | char *target, *command, *arg; | ||
| 89 | int index; | ||
| 90 | } Command; | ||
| 91 | |||
| 92 | typedef struct Interval { | ||
| 93 | int64_t start_ts; ///< start timestamp expressed as microseconds units | ||
| 94 | int64_t end_ts; ///< end timestamp expressed as microseconds units | ||
| 95 | int index; ///< unique index for these interval commands | ||
| 96 | Command *commands; | ||
| 97 | int nb_commands; | ||
| 98 | int enabled; ///< current time detected inside this interval | ||
| 99 | } Interval; | ||
| 100 | |||
| 101 | typedef struct SendCmdContext { | ||
| 102 | const AVClass *class; | ||
| 103 | Interval *intervals; | ||
| 104 | int nb_intervals; | ||
| 105 | |||
| 106 | char *commands_filename; | ||
| 107 | char *commands_str; | ||
| 108 | } SendCmdContext; | ||
| 109 | |||
| 110 | #define OFFSET(x) offsetof(SendCmdContext, x) | ||
| 111 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM | ||
| 112 | static const AVOption options[] = { | ||
| 113 | { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, | ||
| 114 | { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, | ||
| 115 | { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, | ||
| 116 | { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, | ||
| 117 | { NULL } | ||
| 118 | }; | ||
| 119 | |||
| 120 | #define SPACES " \f\t\n\r" | ||
| 121 | |||
| 122 | ✗ | static void skip_comments(const char **buf) | |
| 123 | { | ||
| 124 | ✗ | while (**buf) { | |
| 125 | /* skip leading spaces */ | ||
| 126 | ✗ | *buf += strspn(*buf, SPACES); | |
| 127 | ✗ | if (**buf != '#') | |
| 128 | ✗ | break; | |
| 129 | |||
| 130 | ✗ | (*buf)++; | |
| 131 | |||
| 132 | /* skip comment until the end of line */ | ||
| 133 | ✗ | *buf += strcspn(*buf, "\n"); | |
| 134 | ✗ | if (**buf) | |
| 135 | ✗ | (*buf)++; | |
| 136 | } | ||
| 137 | ✗ | } | |
| 138 | |||
| 139 | #define COMMAND_DELIMS " \f\t\n\r,;" | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Clears fields and frees the buffers used by @p cmd | ||
| 143 | */ | ||
| 144 | ✗ | static void clear_command(Command *cmd) | |
| 145 | { | ||
| 146 | ✗ | cmd->flags = 0; | |
| 147 | ✗ | cmd->index = 0; | |
| 148 | ✗ | av_freep(&cmd->target); | |
| 149 | ✗ | av_freep(&cmd->command); | |
| 150 | ✗ | av_freep(&cmd->arg); | |
| 151 | ✗ | } | |
| 152 | |||
| 153 | ✗ | static int parse_command(Command *cmd, int cmd_count, int interval_count, | |
| 154 | const char **buf, void *log_ctx) | ||
| 155 | { | ||
| 156 | int ret; | ||
| 157 | |||
| 158 | ✗ | memset(cmd, 0, sizeof(Command)); | |
| 159 | ✗ | cmd->index = cmd_count; | |
| 160 | |||
| 161 | /* format: [FLAGS] target command arg */ | ||
| 162 | ✗ | *buf += strspn(*buf, SPACES); | |
| 163 | |||
| 164 | /* parse flags */ | ||
| 165 | ✗ | if (**buf == '[') { | |
| 166 | ✗ | (*buf)++; /* skip "[" */ | |
| 167 | |||
| 168 | ✗ | while (**buf) { | |
| 169 | ✗ | int len = strcspn(*buf, "|+]"); | |
| 170 | |||
| 171 | ✗ | if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER; | |
| 172 | ✗ | else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE; | |
| 173 | ✗ | else if (!strncmp(*buf, "expr", strlen("expr"))) cmd->flags |= COMMAND_FLAG_EXPR; | |
| 174 | else { | ||
| 175 | char flag_buf[64]; | ||
| 176 | ✗ | av_strlcpy(flag_buf, *buf, sizeof(flag_buf)); | |
| 177 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 178 | "Unknown flag '%s' in interval #%d, command #%d\n", | ||
| 179 | flag_buf, interval_count, cmd_count); | ||
| 180 | ✗ | return AVERROR(EINVAL); | |
| 181 | } | ||
| 182 | ✗ | *buf += len; | |
| 183 | ✗ | if (**buf == ']') | |
| 184 | ✗ | break; | |
| 185 | ✗ | if (!strspn(*buf, "+|")) { | |
| 186 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 187 | "Invalid flags char '%c' in interval #%d, command #%d\n", | ||
| 188 | ✗ | **buf, interval_count, cmd_count); | |
| 189 | ✗ | return AVERROR(EINVAL); | |
| 190 | } | ||
| 191 | ✗ | if (**buf) | |
| 192 | ✗ | (*buf)++; | |
| 193 | } | ||
| 194 | |||
| 195 | ✗ | if (**buf != ']') { | |
| 196 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 197 | "Missing flag terminator or extraneous data found at the end of flags " | ||
| 198 | "in interval #%d, command #%d\n", interval_count, cmd_count); | ||
| 199 | ✗ | return AVERROR(EINVAL); | |
| 200 | } | ||
| 201 | ✗ | (*buf)++; /* skip "]" */ | |
| 202 | } else { | ||
| 203 | ✗ | cmd->flags = COMMAND_FLAG_ENTER; | |
| 204 | } | ||
| 205 | |||
| 206 | ✗ | *buf += strspn(*buf, SPACES); | |
| 207 | ✗ | cmd->target = av_get_token(buf, COMMAND_DELIMS); | |
| 208 | ✗ | if (!cmd->target || !cmd->target[0]) { | |
| 209 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 210 | "No target specified in interval #%d, command #%d\n", | ||
| 211 | interval_count, cmd_count); | ||
| 212 | ✗ | ret = AVERROR(EINVAL); | |
| 213 | ✗ | goto fail; | |
| 214 | } | ||
| 215 | |||
| 216 | ✗ | *buf += strspn(*buf, SPACES); | |
| 217 | ✗ | cmd->command = av_get_token(buf, COMMAND_DELIMS); | |
| 218 | ✗ | if (!cmd->command || !cmd->command[0]) { | |
| 219 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 220 | "No command specified in interval #%d, command #%d\n", | ||
| 221 | interval_count, cmd_count); | ||
| 222 | ✗ | ret = AVERROR(EINVAL); | |
| 223 | ✗ | goto fail; | |
| 224 | } | ||
| 225 | |||
| 226 | ✗ | *buf += strspn(*buf, SPACES); | |
| 227 | ✗ | cmd->arg = av_get_token(buf, COMMAND_DELIMS); | |
| 228 | |||
| 229 | ✗ | return 1; | |
| 230 | |||
| 231 | ✗ | fail: | |
| 232 | ✗ | clear_command(cmd); | |
| 233 | ✗ | return ret; | |
| 234 | } | ||
| 235 | |||
| 236 | ✗ | static int parse_commands(Command **cmds, int *nb_cmds, int interval_count, | |
| 237 | const char **buf, void *log_ctx) | ||
| 238 | { | ||
| 239 | ✗ | int cmd_count = 0; | |
| 240 | ✗ | int ret, n = 0; | |
| 241 | AVBPrint pbuf; | ||
| 242 | |||
| 243 | ✗ | *cmds = NULL; | |
| 244 | ✗ | *nb_cmds = 0; | |
| 245 | |||
| 246 | ✗ | while (**buf) { | |
| 247 | Command cmd; | ||
| 248 | |||
| 249 | ✗ | if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0) | |
| 250 | ✗ | return ret; | |
| 251 | ✗ | cmd_count++; | |
| 252 | |||
| 253 | /* (re)allocate commands array if required */ | ||
| 254 | ✗ | if (*nb_cmds == n) { | |
| 255 | ✗ | n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ | |
| 256 | ✗ | *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command)); | |
| 257 | ✗ | if (!*cmds) { | |
| 258 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 259 | "Could not (re)allocate command array\n"); | ||
| 260 | ✗ | clear_command(&cmd); | |
| 261 | ✗ | return AVERROR(ENOMEM); | |
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | ✗ | (*cmds)[(*nb_cmds)++] = cmd; | |
| 266 | |||
| 267 | ✗ | *buf += strspn(*buf, SPACES); | |
| 268 | ✗ | if (**buf && **buf != ';' && **buf != ',') { | |
| 269 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 270 | "Missing separator or extraneous data found at the end of " | ||
| 271 | "interval #%d, in command #%d\n", | ||
| 272 | interval_count, cmd_count); | ||
| 273 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 274 | "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n", | ||
| 275 | make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg); | ||
| 276 | ✗ | return AVERROR(EINVAL); | |
| 277 | } | ||
| 278 | ✗ | if (**buf == ';') | |
| 279 | ✗ | break; | |
| 280 | ✗ | if (**buf == ',') | |
| 281 | ✗ | (*buf)++; | |
| 282 | } | ||
| 283 | |||
| 284 | ✗ | return 0; | |
| 285 | } | ||
| 286 | |||
| 287 | #define DELIMS " \f\t\n\r,;" | ||
| 288 | |||
| 289 | ✗ | static int parse_interval(Interval *interval, int interval_count, | |
| 290 | const char **buf, void *log_ctx) | ||
| 291 | { | ||
| 292 | char *intervalstr; | ||
| 293 | int ret; | ||
| 294 | |||
| 295 | ✗ | *buf += strspn(*buf, SPACES); | |
| 296 | ✗ | if (!**buf) | |
| 297 | ✗ | return 0; | |
| 298 | |||
| 299 | /* reset data */ | ||
| 300 | ✗ | memset(interval, 0, sizeof(Interval)); | |
| 301 | ✗ | interval->index = interval_count; | |
| 302 | |||
| 303 | /* format: INTERVAL COMMANDS */ | ||
| 304 | |||
| 305 | /* parse interval */ | ||
| 306 | ✗ | intervalstr = av_get_token(buf, DELIMS); | |
| 307 | ✗ | if (intervalstr && intervalstr[0]) { | |
| 308 | char *start, *end; | ||
| 309 | |||
| 310 | ✗ | start = av_strtok(intervalstr, "-", &end); | |
| 311 | ✗ | if (!start) { | |
| 312 | ✗ | ret = AVERROR(EINVAL); | |
| 313 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 314 | "Invalid interval specification '%s' in interval #%d\n", | ||
| 315 | intervalstr, interval_count); | ||
| 316 | ✗ | goto end; | |
| 317 | } | ||
| 318 | ✗ | if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) { | |
| 319 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 320 | "Invalid start time specification '%s' in interval #%d\n", | ||
| 321 | start, interval_count); | ||
| 322 | ✗ | goto end; | |
| 323 | } | ||
| 324 | |||
| 325 | ✗ | if (end) { | |
| 326 | ✗ | if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) { | |
| 327 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 328 | "Invalid end time specification '%s' in interval #%d\n", | ||
| 329 | end, interval_count); | ||
| 330 | ✗ | goto end; | |
| 331 | } | ||
| 332 | } else { | ||
| 333 | ✗ | interval->end_ts = INT64_MAX; | |
| 334 | } | ||
| 335 | ✗ | if (interval->end_ts < interval->start_ts) { | |
| 336 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 337 | "Invalid end time '%s' in interval #%d: " | ||
| 338 | "cannot be lesser than start time '%s'\n", | ||
| 339 | end, interval_count, start); | ||
| 340 | ✗ | ret = AVERROR(EINVAL); | |
| 341 | ✗ | goto end; | |
| 342 | } | ||
| 343 | } else { | ||
| 344 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 345 | "No interval specified for interval #%d\n", interval_count); | ||
| 346 | ✗ | ret = AVERROR(EINVAL); | |
| 347 | ✗ | goto end; | |
| 348 | } | ||
| 349 | |||
| 350 | /* parse commands */ | ||
| 351 | ✗ | ret = parse_commands(&interval->commands, &interval->nb_commands, | |
| 352 | interval_count, buf, log_ctx); | ||
| 353 | |||
| 354 | ✗ | end: | |
| 355 | ✗ | av_free(intervalstr); | |
| 356 | ✗ | return ret; | |
| 357 | } | ||
| 358 | |||
| 359 | ✗ | static int parse_intervals(Interval **intervals, int *nb_intervals, | |
| 360 | const char *buf, void *log_ctx) | ||
| 361 | { | ||
| 362 | ✗ | int interval_count = 0; | |
| 363 | ✗ | int ret, n = 0; | |
| 364 | |||
| 365 | ✗ | *intervals = NULL; | |
| 366 | ✗ | *nb_intervals = 0; | |
| 367 | |||
| 368 | ✗ | if (!buf) | |
| 369 | ✗ | return 0; | |
| 370 | |||
| 371 | ✗ | while (1) { | |
| 372 | Interval interval; | ||
| 373 | |||
| 374 | ✗ | skip_comments(&buf); | |
| 375 | ✗ | if (!(*buf)) | |
| 376 | ✗ | break; | |
| 377 | |||
| 378 | ✗ | if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0) | |
| 379 | ✗ | return ret; | |
| 380 | |||
| 381 | ✗ | buf += strspn(buf, SPACES); | |
| 382 | ✗ | if (*buf) { | |
| 383 | ✗ | if (*buf != ';') { | |
| 384 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 385 | "Missing terminator or extraneous data found at the end of interval #%d\n", | ||
| 386 | interval_count); | ||
| 387 | ✗ | return AVERROR(EINVAL); | |
| 388 | } | ||
| 389 | ✗ | buf++; /* skip ';' */ | |
| 390 | } | ||
| 391 | ✗ | interval_count++; | |
| 392 | |||
| 393 | /* (re)allocate commands array if required */ | ||
| 394 | ✗ | if (*nb_intervals == n) { | |
| 395 | ✗ | n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ | |
| 396 | ✗ | *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval)); | |
| 397 | ✗ | if (!*intervals) { | |
| 398 | ✗ | av_log(log_ctx, AV_LOG_ERROR, | |
| 399 | "Could not (re)allocate intervals array\n"); | ||
| 400 | ✗ | return AVERROR(ENOMEM); | |
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | ✗ | (*intervals)[(*nb_intervals)++] = interval; | |
| 405 | } | ||
| 406 | |||
| 407 | ✗ | return 0; | |
| 408 | } | ||
| 409 | |||
| 410 | ✗ | static int cmp_intervals(const void *a, const void *b) | |
| 411 | { | ||
| 412 | ✗ | const Interval *i1 = a; | |
| 413 | ✗ | const Interval *i2 = b; | |
| 414 | ✗ | return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index); | |
| 415 | } | ||
| 416 | |||
| 417 | ✗ | static av_cold int init(AVFilterContext *ctx) | |
| 418 | { | ||
| 419 | ✗ | SendCmdContext *s = ctx->priv; | |
| 420 | int ret, i, j; | ||
| 421 | |||
| 422 | ✗ | if ((!!s->commands_filename + !!s->commands_str) != 1) { | |
| 423 | ✗ | av_log(ctx, AV_LOG_ERROR, | |
| 424 | "One and only one of the filename or commands options must be specified\n"); | ||
| 425 | ✗ | return AVERROR(EINVAL); | |
| 426 | } | ||
| 427 | |||
| 428 | ✗ | if (s->commands_filename) { | |
| 429 | uint8_t *file_buf, *buf; | ||
| 430 | size_t file_bufsize; | ||
| 431 | ✗ | ret = av_file_map(s->commands_filename, | |
| 432 | &file_buf, &file_bufsize, 0, ctx); | ||
| 433 | ✗ | if (ret < 0) | |
| 434 | ✗ | return ret; | |
| 435 | |||
| 436 | /* create a 0-terminated string based on the read file */ | ||
| 437 | ✗ | buf = av_malloc(file_bufsize + 1); | |
| 438 | ✗ | if (!buf) { | |
| 439 | ✗ | av_file_unmap(file_buf, file_bufsize); | |
| 440 | ✗ | return AVERROR(ENOMEM); | |
| 441 | } | ||
| 442 | ✗ | memcpy(buf, file_buf, file_bufsize); | |
| 443 | ✗ | buf[file_bufsize] = 0; | |
| 444 | ✗ | av_file_unmap(file_buf, file_bufsize); | |
| 445 | ✗ | s->commands_str = buf; | |
| 446 | } | ||
| 447 | |||
| 448 | ✗ | if ((ret = parse_intervals(&s->intervals, &s->nb_intervals, | |
| 449 | ✗ | s->commands_str, ctx)) < 0) | |
| 450 | ✗ | return ret; | |
| 451 | |||
| 452 | ✗ | if (s->nb_intervals == 0) { | |
| 453 | ✗ | av_log(ctx, AV_LOG_ERROR, "No commands were specified\n"); | |
| 454 | ✗ | return AVERROR(EINVAL); | |
| 455 | } | ||
| 456 | |||
| 457 | ✗ | qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals); | |
| 458 | |||
| 459 | ✗ | av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n"); | |
| 460 | ✗ | for (i = 0; i < s->nb_intervals; i++) { | |
| 461 | AVBPrint pbuf; | ||
| 462 | ✗ | Interval *interval = &s->intervals[i]; | |
| 463 | ✗ | av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n", | |
| 464 | ✗ | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index); | |
| 465 | ✗ | for (j = 0; j < interval->nb_commands; j++) { | |
| 466 | ✗ | Command *cmd = &interval->commands[j]; | |
| 467 | ✗ | av_log(ctx, AV_LOG_VERBOSE, | |
| 468 | " [%s] target:%s command:%s arg:%s index:%d\n", | ||
| 469 | make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index); | ||
| 470 | } | ||
| 471 | } | ||
| 472 | |||
| 473 | ✗ | return 0; | |
| 474 | } | ||
| 475 | |||
| 476 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 477 | { | ||
| 478 | ✗ | SendCmdContext *s = ctx->priv; | |
| 479 | int i, j; | ||
| 480 | |||
| 481 | ✗ | for (i = 0; i < s->nb_intervals; i++) { | |
| 482 | ✗ | Interval *interval = &s->intervals[i]; | |
| 483 | ✗ | for (j = 0; j < interval->nb_commands; j++) { | |
| 484 | ✗ | Command *cmd = &interval->commands[j]; | |
| 485 | ✗ | clear_command(cmd); | |
| 486 | } | ||
| 487 | ✗ | av_freep(&interval->commands); | |
| 488 | } | ||
| 489 | ✗ | av_freep(&s->intervals); | |
| 490 | ✗ | } | |
| 491 | |||
| 492 | ✗ | static int filter_frame(AVFilterLink *inlink, AVFrame *ref) | |
| 493 | { | ||
| 494 | ✗ | FilterLink *inl = ff_filter_link(inlink); | |
| 495 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 496 | ✗ | SendCmdContext *s = ctx->priv; | |
| 497 | int64_t ts; | ||
| 498 | int i, j, ret; | ||
| 499 | |||
| 500 | ✗ | if (ref->pts == AV_NOPTS_VALUE) | |
| 501 | ✗ | goto end; | |
| 502 | |||
| 503 | ✗ | ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q); | |
| 504 | |||
| 505 | #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts)) | ||
| 506 | |||
| 507 | ✗ | for (i = 0; i < s->nb_intervals; i++) { | |
| 508 | ✗ | Interval *interval = &s->intervals[i]; | |
| 509 | ✗ | int flags = 0; | |
| 510 | |||
| 511 | ✗ | if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { | |
| 512 | ✗ | flags += COMMAND_FLAG_ENTER; | |
| 513 | ✗ | interval->enabled = 1; | |
| 514 | } | ||
| 515 | ✗ | if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { | |
| 516 | ✗ | flags += COMMAND_FLAG_LEAVE; | |
| 517 | ✗ | interval->enabled = 0; | |
| 518 | } | ||
| 519 | ✗ | if (interval->enabled) | |
| 520 | ✗ | flags += COMMAND_FLAG_EXPR; | |
| 521 | |||
| 522 | ✗ | if (flags) { | |
| 523 | AVBPrint pbuf; | ||
| 524 | ✗ | av_log(ctx, AV_LOG_VERBOSE, | |
| 525 | "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n", | ||
| 526 | make_command_flags_str(&pbuf, flags), interval->index, | ||
| 527 | ✗ | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, | |
| 528 | ✗ | (double)ts/1000000); | |
| 529 | |||
| 530 | ✗ | for (j = 0; flags && j < interval->nb_commands; j++) { | |
| 531 | ✗ | Command *cmd = &interval->commands[j]; | |
| 532 | ✗ | char *cmd_arg = cmd->arg; | |
| 533 | char buf[1024]; | ||
| 534 | |||
| 535 | ✗ | if (cmd->flags & flags) { | |
| 536 | ✗ | if (cmd->flags & COMMAND_FLAG_EXPR) { | |
| 537 | double var_values[VAR_VARS_NB], res; | ||
| 538 | ✗ | double start = TS2T(interval->start_ts, AV_TIME_BASE_Q); | |
| 539 | ✗ | double end = TS2T(interval->end_ts, AV_TIME_BASE_Q); | |
| 540 | ✗ | double current = TS2T(ref->pts, inlink->time_base); | |
| 541 | |||
| 542 | ✗ | var_values[VAR_N] = inl->frame_count_in; | |
| 543 | ✗ | var_values[VAR_PTS] = TS2D(ref->pts); | |
| 544 | ✗ | var_values[VAR_T] = current; | |
| 545 | ✗ | var_values[VAR_TS] = start; | |
| 546 | ✗ | var_values[VAR_TE] = end; | |
| 547 | ✗ | var_values[VAR_TI] = (current - start) / (end - start); | |
| 548 | ✗ | var_values[VAR_W] = ref->width; | |
| 549 | ✗ | var_values[VAR_H] = ref->height; | |
| 550 | |||
| 551 | ✗ | if ((ret = av_expr_parse_and_eval(&res, cmd->arg, var_names, var_values, | |
| 552 | NULL, NULL, NULL, NULL, NULL, 0, NULL)) < 0) { | ||
| 553 | ✗ | av_log(ctx, AV_LOG_ERROR, "Invalid expression '%s' for command argument.\n", cmd->arg); | |
| 554 | ✗ | av_frame_free(&ref); | |
| 555 | ✗ | return AVERROR(EINVAL); | |
| 556 | } | ||
| 557 | |||
| 558 | ✗ | cmd_arg = av_asprintf("%g", res); | |
| 559 | ✗ | if (!cmd_arg) { | |
| 560 | ✗ | av_frame_free(&ref); | |
| 561 | ✗ | return AVERROR(ENOMEM); | |
| 562 | } | ||
| 563 | } | ||
| 564 | ✗ | av_log(ctx, AV_LOG_VERBOSE, | |
| 565 | "Processing command #%d target:%s command:%s arg:%s\n", | ||
| 566 | cmd->index, cmd->target, cmd->command, cmd_arg); | ||
| 567 | ✗ | ret = avfilter_graph_send_command(inl->graph, | |
| 568 | ✗ | cmd->target, cmd->command, cmd_arg, | |
| 569 | buf, sizeof(buf), | ||
| 570 | AVFILTER_CMD_FLAG_ONE); | ||
| 571 | ✗ | av_log(ctx, AV_LOG_VERBOSE, | |
| 572 | "Command reply for command #%d: ret:%s res:%s\n", | ||
| 573 | ✗ | cmd->index, av_err2str(ret), buf); | |
| 574 | ✗ | if (cmd->flags & COMMAND_FLAG_EXPR) | |
| 575 | ✗ | av_freep(&cmd_arg); | |
| 576 | } | ||
| 577 | } | ||
| 578 | } | ||
| 579 | } | ||
| 580 | |||
| 581 | ✗ | end: | |
| 582 | ✗ | switch (inlink->type) { | |
| 583 | ✗ | case AVMEDIA_TYPE_VIDEO: | |
| 584 | case AVMEDIA_TYPE_AUDIO: | ||
| 585 | ✗ | return ff_filter_frame(inlink->dst->outputs[0], ref); | |
| 586 | } | ||
| 587 | |||
| 588 | ✗ | return AVERROR(ENOSYS); | |
| 589 | } | ||
| 590 | |||
| 591 | AVFILTER_DEFINE_CLASS_EXT(sendcmd, "(a)sendcmd", options); | ||
| 592 | |||
| 593 | #if CONFIG_SENDCMD_FILTER | ||
| 594 | |||
| 595 | static const AVFilterPad sendcmd_inputs[] = { | ||
| 596 | { | ||
| 597 | .name = "default", | ||
| 598 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 599 | .filter_frame = filter_frame, | ||
| 600 | }, | ||
| 601 | }; | ||
| 602 | |||
| 603 | const FFFilter ff_vf_sendcmd = { | ||
| 604 | .p.name = "sendcmd", | ||
| 605 | .p.description = NULL_IF_CONFIG_SMALL("Send commands to filters."), | ||
| 606 | .p.flags = AVFILTER_FLAG_METADATA_ONLY, | ||
| 607 | .p.priv_class = &sendcmd_class, | ||
| 608 | .init = init, | ||
| 609 | .uninit = uninit, | ||
| 610 | .priv_size = sizeof(SendCmdContext), | ||
| 611 | FILTER_INPUTS(sendcmd_inputs), | ||
| 612 | FILTER_OUTPUTS(ff_video_default_filterpad), | ||
| 613 | }; | ||
| 614 | |||
| 615 | #endif | ||
| 616 | |||
| 617 | #if CONFIG_ASENDCMD_FILTER | ||
| 618 | |||
| 619 | static const AVFilterPad asendcmd_inputs[] = { | ||
| 620 | { | ||
| 621 | .name = "default", | ||
| 622 | .type = AVMEDIA_TYPE_AUDIO, | ||
| 623 | .filter_frame = filter_frame, | ||
| 624 | }, | ||
| 625 | }; | ||
| 626 | |||
| 627 | const FFFilter ff_af_asendcmd = { | ||
| 628 | .p.name = "asendcmd", | ||
| 629 | .p.description = NULL_IF_CONFIG_SMALL("Send commands to filters."), | ||
| 630 | .p.priv_class = &sendcmd_class, | ||
| 631 | .p.flags = AVFILTER_FLAG_METADATA_ONLY, | ||
| 632 | .init = init, | ||
| 633 | .uninit = uninit, | ||
| 634 | .priv_size = sizeof(SendCmdContext), | ||
| 635 | FILTER_INPUTS(asendcmd_inputs), | ||
| 636 | FILTER_OUTPUTS(ff_audio_default_filterpad), | ||
| 637 | }; | ||
| 638 | |||
| 639 | #endif | ||
| 640 |