| 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 "libavutil/avstring.h" |
| 27 | #include "libavutil/bprint.h" |
| 28 | #include "libavutil/file.h" |
| 29 | #include "libavutil/opt.h" |
| 30 | #include "libavutil/parseutils.h" |
| 31 | #include "avfilter.h" |
| 32 | #include "internal.h" |
| 33 | #include "avfiltergraph.h" |
| 34 | #include "audio.h" |
| 35 | #include "video.h" |
| 36 | |
| 37 | #define COMMAND_FLAG_ENTER 1 |
| 38 | #define COMMAND_FLAG_LEAVE 2 |
| 39 | |
| 40 | static inline char *make_command_flags_str(AVBPrint *pbuf, int flags) |
| 41 | { |
| 42 | static const char * const flag_strings[] = { "enter", "leave" }; |
| 43 | int i, is_first = 1; |
| 44 | |
| 45 | av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC); |
| 46 | for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) { |
| 47 | if (flags & 1<<i) { |
| 48 | if (!is_first) |
| 49 | av_bprint_chars(pbuf, '+', 1); |
| 50 | av_bprintf(pbuf, "%s", flag_strings[i]); |
| 51 | is_first = 0; |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | return pbuf->str; |
| 56 | } |
| 57 | |
| 58 | typedef struct { |
| 59 | int flags; |
| 60 | char *target, *command, *arg; |
| 61 | int index; |
| 62 | } Command; |
| 63 | |
| 64 | typedef struct { |
| 65 | int64_t start_ts; ///< start timestamp expressed as microseconds units |
| 66 | int64_t end_ts; ///< end timestamp expressed as microseconds units |
| 67 | int index; ///< unique index for these interval commands |
| 68 | Command *commands; |
| 69 | int nb_commands; |
| 70 | int enabled; ///< current time detected inside this interval |
| 71 | } Interval; |
| 72 | |
| 73 | typedef struct { |
| 74 | const AVClass *class; |
| 75 | Interval *intervals; |
| 76 | int nb_intervals; |
| 77 | |
| 78 | char *commands_filename; |
| 79 | char *commands_str; |
| 80 | } SendCmdContext; |
| 81 | |
| 82 | #define OFFSET(x) offsetof(SendCmdContext, x) |
| 83 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM |
| 84 | static const AVOption options[] = { |
| 85 | { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, |
| 86 | { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, |
| 87 | { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, |
| 88 | { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, |
| 89 | { NULL } |
| 90 | }; |
| 91 | |
| 92 | #define SPACES " \f\t\n\r" |
| 93 | |
| 94 | static void skip_comments(const char **buf) |
| 95 | { |
| 96 | while (**buf) { |
| 97 | /* skip leading spaces */ |
| 98 | *buf += strspn(*buf, SPACES); |
| 99 | if (**buf != '#') |
| 100 | break; |
| 101 | |
| 102 | (*buf)++; |
| 103 | |
| 104 | /* skip comment until the end of line */ |
| 105 | *buf += strcspn(*buf, "\n"); |
| 106 | if (**buf) |
| 107 | (*buf)++; |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | #define COMMAND_DELIMS " \f\t\n\r,;" |
| 112 | |
| 113 | static int parse_command(Command *cmd, int cmd_count, int interval_count, |
| 114 | const char **buf, void *log_ctx) |
| 115 | { |
| 116 | int ret; |
| 117 | |
| 118 | memset(cmd, 0, sizeof(Command)); |
| 119 | cmd->index = cmd_count; |
| 120 | |
| 121 | /* format: [FLAGS] target command arg */ |
| 122 | *buf += strspn(*buf, SPACES); |
| 123 | |
| 124 | /* parse flags */ |
| 125 | if (**buf == '[') { |
| 126 | (*buf)++; /* skip "[" */ |
| 127 | |
| 128 | while (**buf) { |
| 129 | int len = strcspn(*buf, "|+]"); |
| 130 | |
| 131 | if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER; |
| 132 | else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE; |
| 133 | else { |
| 134 | char flag_buf[64]; |
| 135 | av_strlcpy(flag_buf, *buf, sizeof(flag_buf)); |
| 136 | av_log(log_ctx, AV_LOG_ERROR, |
| 137 | "Unknown flag '%s' in interval #%d, command #%d\n", |
| 138 | flag_buf, interval_count, cmd_count); |
| 139 | return AVERROR(EINVAL); |
| 140 | } |
| 141 | *buf += len; |
| 142 | if (**buf == ']') |
| 143 | break; |
| 144 | if (!strspn(*buf, "+|")) { |
| 145 | av_log(log_ctx, AV_LOG_ERROR, |
| 146 | "Invalid flags char '%c' in interval #%d, command #%d\n", |
| 147 | **buf, interval_count, cmd_count); |
| 148 | return AVERROR(EINVAL); |
| 149 | } |
| 150 | if (**buf) |
| 151 | (*buf)++; |
| 152 | } |
| 153 | |
| 154 | if (**buf != ']') { |
| 155 | av_log(log_ctx, AV_LOG_ERROR, |
| 156 | "Missing flag terminator or extraneous data found at the end of flags " |
| 157 | "in interval #%d, command #%d\n", interval_count, cmd_count); |
| 158 | return AVERROR(EINVAL); |
| 159 | } |
| 160 | (*buf)++; /* skip "]" */ |
| 161 | } else { |
| 162 | cmd->flags = COMMAND_FLAG_ENTER; |
| 163 | } |
| 164 | |
| 165 | *buf += strspn(*buf, SPACES); |
| 166 | cmd->target = av_get_token(buf, COMMAND_DELIMS); |
| 167 | if (!cmd->target || !cmd->target[0]) { |
| 168 | av_log(log_ctx, AV_LOG_ERROR, |
| 169 | "No target specified in interval #%d, command #%d\n", |
| 170 | interval_count, cmd_count); |
| 171 | ret = AVERROR(EINVAL); |
| 172 | goto fail; |
| 173 | } |
| 174 | |
| 175 | *buf += strspn(*buf, SPACES); |
| 176 | cmd->command = av_get_token(buf, COMMAND_DELIMS); |
| 177 | if (!cmd->command || !cmd->command[0]) { |
| 178 | av_log(log_ctx, AV_LOG_ERROR, |
| 179 | "No command specified in interval #%d, command #%d\n", |
| 180 | interval_count, cmd_count); |
| 181 | ret = AVERROR(EINVAL); |
| 182 | goto fail; |
| 183 | } |
| 184 | |
| 185 | *buf += strspn(*buf, SPACES); |
| 186 | cmd->arg = av_get_token(buf, COMMAND_DELIMS); |
| 187 | |
| 188 | return 1; |
| 189 | |
| 190 | fail: |
| 191 | av_freep(&cmd->target); |
| 192 | av_freep(&cmd->command); |
| 193 | av_freep(&cmd->arg); |
| 194 | return ret; |
| 195 | } |
| 196 | |
| 197 | static int parse_commands(Command **cmds, int *nb_cmds, int interval_count, |
| 198 | const char **buf, void *log_ctx) |
| 199 | { |
| 200 | int cmd_count = 0; |
| 201 | int ret, n = 0; |
| 202 | AVBPrint pbuf; |
| 203 | |
| 204 | *cmds = NULL; |
| 205 | *nb_cmds = 0; |
| 206 | |
| 207 | while (**buf) { |
| 208 | Command cmd; |
| 209 | |
| 210 | if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0) |
| 211 | return ret; |
| 212 | cmd_count++; |
| 213 | |
| 214 | /* (re)allocate commands array if required */ |
| 215 | if (*nb_cmds == n) { |
| 216 | n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ |
| 217 | *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command)); |
| 218 | if (!*cmds) { |
| 219 | av_log(log_ctx, AV_LOG_ERROR, |
| 220 | "Could not (re)allocate command array\n"); |
| 221 | return AVERROR(ENOMEM); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | (*cmds)[(*nb_cmds)++] = cmd; |
| 226 | |
| 227 | *buf += strspn(*buf, SPACES); |
| 228 | if (**buf && **buf != ';' && **buf != ',') { |
| 229 | av_log(log_ctx, AV_LOG_ERROR, |
| 230 | "Missing separator or extraneous data found at the end of " |
| 231 | "interval #%d, in command #%d\n", |
| 232 | interval_count, cmd_count); |
| 233 | av_log(log_ctx, AV_LOG_ERROR, |
| 234 | "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n", |
| 235 | make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg); |
| 236 | return AVERROR(EINVAL); |
| 237 | } |
| 238 | if (**buf == ';') |
| 239 | break; |
| 240 | if (**buf == ',') |
| 241 | (*buf)++; |
| 242 | } |
| 243 | |
| 244 | return 0; |
| 245 | } |
| 246 | |
| 247 | #define DELIMS " \f\t\n\r,;" |
| 248 | |
| 249 | static int parse_interval(Interval *interval, int interval_count, |
| 250 | const char **buf, void *log_ctx) |
| 251 | { |
| 252 | char *intervalstr; |
| 253 | int ret; |
| 254 | |
| 255 | *buf += strspn(*buf, SPACES); |
| 256 | if (!**buf) |
| 257 | return 0; |
| 258 | |
| 259 | /* reset data */ |
| 260 | memset(interval, 0, sizeof(Interval)); |
| 261 | interval->index = interval_count; |
| 262 | |
| 263 | /* format: INTERVAL COMMANDS */ |
| 264 | |
| 265 | /* parse interval */ |
| 266 | intervalstr = av_get_token(buf, DELIMS); |
| 267 | if (intervalstr && intervalstr[0]) { |
| 268 | char *start, *end; |
| 269 | |
| 270 | start = av_strtok(intervalstr, "-", &end); |
| 271 | if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) { |
| 272 | av_log(log_ctx, AV_LOG_ERROR, |
| 273 | "Invalid start time specification '%s' in interval #%d\n", |
| 274 | start, interval_count); |
| 275 | goto end; |
| 276 | } |
| 277 | |
| 278 | if (end) { |
| 279 | if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) { |
| 280 | av_log(log_ctx, AV_LOG_ERROR, |
| 281 | "Invalid end time specification '%s' in interval #%d\n", |
| 282 | end, interval_count); |
| 283 | goto end; |
| 284 | } |
| 285 | } else { |
| 286 | interval->end_ts = INT64_MAX; |
| 287 | } |
| 288 | if (interval->end_ts < interval->start_ts) { |
| 289 | av_log(log_ctx, AV_LOG_ERROR, |
| 290 | "Invalid end time '%s' in interval #%d: " |
| 291 | "cannot be lesser than start time '%s'\n", |
| 292 | end, interval_count, start); |
| 293 | ret = AVERROR(EINVAL); |
| 294 | goto end; |
| 295 | } |
| 296 | } else { |
| 297 | av_log(log_ctx, AV_LOG_ERROR, |
| 298 | "No interval specified for interval #%d\n", interval_count); |
| 299 | ret = AVERROR(EINVAL); |
| 300 | goto end; |
| 301 | } |
| 302 | |
| 303 | /* parse commands */ |
| 304 | ret = parse_commands(&interval->commands, &interval->nb_commands, |
| 305 | interval_count, buf, log_ctx); |
| 306 | |
| 307 | end: |
| 308 | av_free(intervalstr); |
| 309 | return ret; |
| 310 | } |
| 311 | |
| 312 | static int parse_intervals(Interval **intervals, int *nb_intervals, |
| 313 | const char *buf, void *log_ctx) |
| 314 | { |
| 315 | int interval_count = 0; |
| 316 | int ret, n = 0; |
| 317 | |
| 318 | *intervals = NULL; |
| 319 | *nb_intervals = 0; |
| 320 | |
| 321 | while (1) { |
| 322 | Interval interval; |
| 323 | |
| 324 | skip_comments(&buf); |
| 325 | if (!(*buf)) |
| 326 | break; |
| 327 | |
| 328 | if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0) |
| 329 | return ret; |
| 330 | |
| 331 | buf += strspn(buf, SPACES); |
| 332 | if (*buf) { |
| 333 | if (*buf != ';') { |
| 334 | av_log(log_ctx, AV_LOG_ERROR, |
| 335 | "Missing terminator or extraneous data found at the end of interval #%d\n", |
| 336 | interval_count); |
| 337 | return AVERROR(EINVAL); |
| 338 | } |
| 339 | buf++; /* skip ';' */ |
| 340 | } |
| 341 | interval_count++; |
| 342 | |
| 343 | /* (re)allocate commands array if required */ |
| 344 | if (*nb_intervals == n) { |
| 345 | n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ |
| 346 | *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval)); |
| 347 | if (!*intervals) { |
| 348 | av_log(log_ctx, AV_LOG_ERROR, |
| 349 | "Could not (re)allocate intervals array\n"); |
| 350 | return AVERROR(ENOMEM); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | (*intervals)[(*nb_intervals)++] = interval; |
| 355 | } |
| 356 | |
| 357 | return 0; |
| 358 | } |
| 359 | |
| 360 | static int cmp_intervals(const void *a, const void *b) |
| 361 | { |
| 362 | const Interval *i1 = a; |
| 363 | const Interval *i2 = b; |
| 364 | int64_t ts_diff = i1->start_ts - i2->start_ts; |
| 365 | int ret; |
| 366 | |
| 367 | ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0; |
| 368 | return ret == 0 ? i1->index - i2->index : ret; |
| 369 | } |
| 370 | |
| 371 | static av_cold int init(AVFilterContext *ctx) |
| 372 | { |
| 373 | SendCmdContext *sendcmd = ctx->priv; |
| 374 | int ret, i, j; |
| 375 | |
| 376 | if (sendcmd->commands_filename && sendcmd->commands_str) { |
| 377 | av_log(ctx, AV_LOG_ERROR, |
| 378 | "Only one of the filename or commands options must be specified\n"); |
| 379 | return AVERROR(EINVAL); |
| 380 | } |
| 381 | |
| 382 | if (sendcmd->commands_filename) { |
| 383 | uint8_t *file_buf, *buf; |
| 384 | size_t file_bufsize; |
| 385 | ret = av_file_map(sendcmd->commands_filename, |
| 386 | &file_buf, &file_bufsize, 0, ctx); |
| 387 | if (ret < 0) |
| 388 | return ret; |
| 389 | |
| 390 | /* create a 0-terminated string based on the read file */ |
| 391 | buf = av_malloc(file_bufsize + 1); |
| 392 | if (!buf) { |
| 393 | av_file_unmap(file_buf, file_bufsize); |
| 394 | return AVERROR(ENOMEM); |
| 395 | } |
| 396 | memcpy(buf, file_buf, file_bufsize); |
| 397 | buf[file_bufsize] = 0; |
| 398 | av_file_unmap(file_buf, file_bufsize); |
| 399 | sendcmd->commands_str = buf; |
| 400 | } |
| 401 | |
| 402 | if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals, |
| 403 | sendcmd->commands_str, ctx)) < 0) |
| 404 | return ret; |
| 405 | |
| 406 | qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals); |
| 407 | |
| 408 | av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n"); |
| 409 | for (i = 0; i < sendcmd->nb_intervals; i++) { |
| 410 | AVBPrint pbuf; |
| 411 | Interval *interval = &sendcmd->intervals[i]; |
| 412 | av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n", |
| 413 | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index); |
| 414 | for (j = 0; j < interval->nb_commands; j++) { |
| 415 | Command *cmd = &interval->commands[j]; |
| 416 | av_log(ctx, AV_LOG_VERBOSE, |
| 417 | " [%s] target:%s command:%s arg:%s index:%d\n", |
| 418 | make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index); |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | return 0; |
| 423 | } |
| 424 | |
| 425 | static av_cold void uninit(AVFilterContext *ctx) |
| 426 | { |
| 427 | SendCmdContext *sendcmd = ctx->priv; |
| 428 | int i, j; |
| 429 | |
| 430 | for (i = 0; i < sendcmd->nb_intervals; i++) { |
| 431 | Interval *interval = &sendcmd->intervals[i]; |
| 432 | for (j = 0; j < interval->nb_commands; j++) { |
| 433 | Command *cmd = &interval->commands[j]; |
| 434 | av_free(cmd->target); |
| 435 | av_free(cmd->command); |
| 436 | av_free(cmd->arg); |
| 437 | } |
| 438 | av_free(interval->commands); |
| 439 | } |
| 440 | av_freep(&sendcmd->intervals); |
| 441 | } |
| 442 | |
| 443 | static int filter_frame(AVFilterLink *inlink, AVFrame *ref) |
| 444 | { |
| 445 | AVFilterContext *ctx = inlink->dst; |
| 446 | SendCmdContext *sendcmd = ctx->priv; |
| 447 | int64_t ts; |
| 448 | int i, j, ret; |
| 449 | |
| 450 | if (ref->pts == AV_NOPTS_VALUE) |
| 451 | goto end; |
| 452 | |
| 453 | ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q); |
| 454 | |
| 455 | #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts)) |
| 456 | |
| 457 | for (i = 0; i < sendcmd->nb_intervals; i++) { |
| 458 | Interval *interval = &sendcmd->intervals[i]; |
| 459 | int flags = 0; |
| 460 | |
| 461 | if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { |
| 462 | flags += COMMAND_FLAG_ENTER; |
| 463 | interval->enabled = 1; |
| 464 | } |
| 465 | if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { |
| 466 | flags += COMMAND_FLAG_LEAVE; |
| 467 | interval->enabled = 0; |
| 468 | } |
| 469 | |
| 470 | if (flags) { |
| 471 | AVBPrint pbuf; |
| 472 | av_log(ctx, AV_LOG_VERBOSE, |
| 473 | "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n", |
| 474 | make_command_flags_str(&pbuf, flags), interval->index, |
| 475 | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, |
| 476 | (double)ts/1000000); |
| 477 | |
| 478 | for (j = 0; flags && j < interval->nb_commands; j++) { |
| 479 | Command *cmd = &interval->commands[j]; |
| 480 | char buf[1024]; |
| 481 | |
| 482 | if (cmd->flags & flags) { |
| 483 | av_log(ctx, AV_LOG_VERBOSE, |
| 484 | "Processing command #%d target:%s command:%s arg:%s\n", |
| 485 | cmd->index, cmd->target, cmd->command, cmd->arg); |
| 486 | ret = avfilter_graph_send_command(inlink->graph, |
| 487 | cmd->target, cmd->command, cmd->arg, |
| 488 | buf, sizeof(buf), |
| 489 | AVFILTER_CMD_FLAG_ONE); |
| 490 | av_log(ctx, AV_LOG_VERBOSE, |
| 491 | "Command reply for command #%d: ret:%s res:%s\n", |
| 492 | cmd->index, av_err2str(ret), buf); |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | end: |
| 499 | switch (inlink->type) { |
| 500 | case AVMEDIA_TYPE_VIDEO: |
| 501 | case AVMEDIA_TYPE_AUDIO: |
| 502 | return ff_filter_frame(inlink->dst->outputs[0], ref); |
| 503 | } |
| 504 | |
| 505 | return AVERROR(ENOSYS); |
| 506 | } |
| 507 | |
| 508 | #if CONFIG_SENDCMD_FILTER |
| 509 | |
| 510 | #define sendcmd_options options |
| 511 | AVFILTER_DEFINE_CLASS(sendcmd); |
| 512 | |
| 513 | static const AVFilterPad sendcmd_inputs[] = { |
| 514 | { |
| 515 | .name = "default", |
| 516 | .type = AVMEDIA_TYPE_VIDEO, |
| 517 | .filter_frame = filter_frame, |
| 518 | }, |
| 519 | { NULL } |
| 520 | }; |
| 521 | |
| 522 | static const AVFilterPad sendcmd_outputs[] = { |
| 523 | { |
| 524 | .name = "default", |
| 525 | .type = AVMEDIA_TYPE_VIDEO, |
| 526 | }, |
| 527 | { NULL } |
| 528 | }; |
| 529 | |
| 530 | AVFilter ff_vf_sendcmd = { |
| 531 | .name = "sendcmd", |
| 532 | .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), |
| 533 | .init = init, |
| 534 | .uninit = uninit, |
| 535 | .priv_size = sizeof(SendCmdContext), |
| 536 | .inputs = sendcmd_inputs, |
| 537 | .outputs = sendcmd_outputs, |
| 538 | .priv_class = &sendcmd_class, |
| 539 | }; |
| 540 | |
| 541 | #endif |
| 542 | |
| 543 | #if CONFIG_ASENDCMD_FILTER |
| 544 | |
| 545 | #define asendcmd_options options |
| 546 | AVFILTER_DEFINE_CLASS(asendcmd); |
| 547 | |
| 548 | static const AVFilterPad asendcmd_inputs[] = { |
| 549 | { |
| 550 | .name = "default", |
| 551 | .type = AVMEDIA_TYPE_AUDIO, |
| 552 | .filter_frame = filter_frame, |
| 553 | }, |
| 554 | { NULL } |
| 555 | }; |
| 556 | |
| 557 | static const AVFilterPad asendcmd_outputs[] = { |
| 558 | { |
| 559 | .name = "default", |
| 560 | .type = AVMEDIA_TYPE_AUDIO, |
| 561 | }, |
| 562 | { NULL } |
| 563 | }; |
| 564 | |
| 565 | AVFilter ff_af_asendcmd = { |
| 566 | .name = "asendcmd", |
| 567 | .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), |
| 568 | .init = init, |
| 569 | .uninit = uninit, |
| 570 | .priv_size = sizeof(SendCmdContext), |
| 571 | .inputs = asendcmd_inputs, |
| 572 | .outputs = asendcmd_outputs, |
| 573 | .priv_class = &asendcmd_class, |
| 574 | }; |
| 575 | |
| 576 | #endif |