00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00026 #include "libavutil/avstring.h"
00027 #include "libavutil/bprint.h"
00028 #include "libavutil/file.h"
00029 #include "libavutil/opt.h"
00030 #include "libavutil/parseutils.h"
00031 #include "avfilter.h"
00032 #include "internal.h"
00033 #include "avfiltergraph.h"
00034 #include "audio.h"
00035 #include "video.h"
00036
00037 #define COMMAND_FLAG_ENTER 1
00038 #define COMMAND_FLAG_LEAVE 2
00039
00040 static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
00041 {
00042 const char *flag_strings[] = { "enter", "leave" };
00043 int i, is_first = 1;
00044
00045 av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
00046 for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
00047 if (flags & 1<<i) {
00048 if (!is_first)
00049 av_bprint_chars(pbuf, '+', 1);
00050 av_bprintf(pbuf, "%s", flag_strings[i]);
00051 is_first = 0;
00052 }
00053 }
00054
00055 return pbuf->str;
00056 }
00057
00058 typedef struct {
00059 int flags;
00060 char *target, *command, *arg;
00061 int index;
00062 } Command;
00063
00064 typedef struct {
00065 int64_t start_ts;
00066 int64_t end_ts;
00067 int index;
00068 Command *commands;
00069 int nb_commands;
00070 int enabled;
00071 } Interval;
00072
00073 typedef struct {
00074 const AVClass *class;
00075 Interval *intervals;
00076 int nb_intervals;
00077
00078 char *commands_filename;
00079 char *commands_str;
00080 } SendCmdContext;
00081
00082 #define OFFSET(x) offsetof(SendCmdContext, x)
00083 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM
00084 static const AVOption options[] = {
00085 { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
00086 { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
00087 { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
00088 { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
00089 {NULL},
00090 };
00091
00092 #define SPACES " \f\t\n\r"
00093
00094 static void skip_comments(const char **buf)
00095 {
00096 while (**buf) {
00097
00098 *buf += strspn(*buf, SPACES);
00099 if (**buf != '#')
00100 break;
00101
00102 (*buf)++;
00103
00104
00105 *buf += strcspn(*buf, "\n");
00106 if (**buf)
00107 (*buf)++;
00108 }
00109 }
00110
00111 #define COMMAND_DELIMS " \f\t\n\r,;"
00112
00113 static int parse_command(Command *cmd, int cmd_count, int interval_count,
00114 const char **buf, void *log_ctx)
00115 {
00116 int ret;
00117
00118 memset(cmd, 0, sizeof(Command));
00119 cmd->index = cmd_count;
00120
00121
00122 *buf += strspn(*buf, SPACES);
00123
00124
00125 if (**buf == '[') {
00126 (*buf)++;
00127
00128 while (**buf) {
00129 int len = strcspn(*buf, "|+]");
00130
00131 if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
00132 else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
00133 else {
00134 char flag_buf[64];
00135 av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
00136 av_log(log_ctx, AV_LOG_ERROR,
00137 "Unknown flag '%s' in in interval #%d, command #%d\n",
00138 flag_buf, interval_count, cmd_count);
00139 return AVERROR(EINVAL);
00140 }
00141 *buf += len;
00142 if (**buf == ']')
00143 break;
00144 if (!strspn(*buf, "+|")) {
00145 av_log(log_ctx, AV_LOG_ERROR,
00146 "Invalid flags char '%c' in interval #%d, command #%d\n",
00147 **buf, interval_count, cmd_count);
00148 return AVERROR(EINVAL);
00149 }
00150 if (**buf)
00151 (*buf)++;
00152 }
00153
00154 if (**buf != ']') {
00155 av_log(log_ctx, AV_LOG_ERROR,
00156 "Missing flag terminator or extraneous data found at the end of flags "
00157 "in interval #%d, command #%d\n", interval_count, cmd_count);
00158 return AVERROR(EINVAL);
00159 }
00160 (*buf)++;
00161 } else {
00162 cmd->flags = COMMAND_FLAG_ENTER;
00163 }
00164
00165 *buf += strspn(*buf, SPACES);
00166 cmd->target = av_get_token(buf, COMMAND_DELIMS);
00167 if (!cmd->target || !cmd->target[0]) {
00168 av_log(log_ctx, AV_LOG_ERROR,
00169 "No target specified in in interval #%d, command #%d\n",
00170 interval_count, cmd_count);
00171 ret = AVERROR(EINVAL);
00172 goto fail;
00173 }
00174
00175 *buf += strspn(*buf, SPACES);
00176 cmd->command = av_get_token(buf, COMMAND_DELIMS);
00177 if (!cmd->command || !cmd->command[0]) {
00178 av_log(log_ctx, AV_LOG_ERROR,
00179 "No command specified in in interval #%d, command #%d\n",
00180 interval_count, cmd_count);
00181 ret = AVERROR(EINVAL);
00182 goto fail;
00183 }
00184
00185 *buf += strspn(*buf, SPACES);
00186 cmd->arg = av_get_token(buf, COMMAND_DELIMS);
00187
00188 return 1;
00189
00190 fail:
00191 av_freep(&cmd->target);
00192 av_freep(&cmd->command);
00193 av_freep(&cmd->arg);
00194 return ret;
00195 }
00196
00197 static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
00198 const char **buf, void *log_ctx)
00199 {
00200 int cmd_count = 0;
00201 int ret, n = 0;
00202 AVBPrint pbuf;
00203
00204 *cmds = NULL;
00205 *nb_cmds = 0;
00206
00207 while (**buf) {
00208 Command cmd;
00209
00210 if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
00211 return ret;
00212 cmd_count++;
00213
00214
00215 if (*nb_cmds == n) {
00216 n = FFMAX(16, 2*n);
00217 *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
00218 if (!*cmds) {
00219 av_log(log_ctx, AV_LOG_ERROR,
00220 "Could not (re)allocate command array\n");
00221 return AVERROR(ENOMEM);
00222 }
00223 }
00224
00225 (*cmds)[(*nb_cmds)++] = cmd;
00226
00227 *buf += strspn(*buf, SPACES);
00228 if (**buf && **buf != ';' && **buf != ',') {
00229 av_log(log_ctx, AV_LOG_ERROR,
00230 "Missing separator or extraneous data found at the end of "
00231 "interval #%d, in command #%d\n",
00232 interval_count, cmd_count);
00233 av_log(log_ctx, AV_LOG_ERROR,
00234 "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
00235 make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
00236 return AVERROR(EINVAL);
00237 }
00238 if (**buf == ';')
00239 break;
00240 if (**buf == ',')
00241 (*buf)++;
00242 }
00243
00244 return 0;
00245 }
00246
00247 #define DELIMS " \f\t\n\r,;"
00248
00249 static int parse_interval(Interval *interval, int interval_count,
00250 const char **buf, void *log_ctx)
00251 {
00252 char *intervalstr;
00253 int ret;
00254
00255 *buf += strspn(*buf, SPACES);
00256 if (!**buf)
00257 return 0;
00258
00259
00260 memset(interval, 0, sizeof(Interval));
00261 interval->index = interval_count;
00262
00263
00264
00265
00266 intervalstr = av_get_token(buf, DELIMS);
00267 if (intervalstr && intervalstr[0]) {
00268 char *start, *end;
00269
00270 start = av_strtok(intervalstr, "-", &end);
00271 if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
00272 av_log(log_ctx, AV_LOG_ERROR,
00273 "Invalid start time specification '%s' in interval #%d\n",
00274 start, interval_count);
00275 goto end;
00276 }
00277
00278 if (end) {
00279 if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
00280 av_log(log_ctx, AV_LOG_ERROR,
00281 "Invalid end time specification '%s' in interval #%d\n",
00282 end, interval_count);
00283 goto end;
00284 }
00285 } else {
00286 interval->end_ts = INT64_MAX;
00287 }
00288 if (interval->end_ts < interval->start_ts) {
00289 av_log(log_ctx, AV_LOG_ERROR,
00290 "Invalid end time '%s' in interval #%d: "
00291 "cannot be lesser than start time '%s'\n",
00292 end, interval_count, start);
00293 ret = AVERROR(EINVAL);
00294 goto end;
00295 }
00296 } else {
00297 av_log(log_ctx, AV_LOG_ERROR,
00298 "No interval specified for interval #%d\n", interval_count);
00299 ret = AVERROR(EINVAL);
00300 goto end;
00301 }
00302
00303
00304 ret = parse_commands(&interval->commands, &interval->nb_commands,
00305 interval_count, buf, log_ctx);
00306
00307 end:
00308 av_free(intervalstr);
00309 return ret;
00310 }
00311
00312 static int parse_intervals(Interval **intervals, int *nb_intervals,
00313 const char *buf, void *log_ctx)
00314 {
00315 int interval_count = 0;
00316 int ret, n = 0;
00317
00318 *intervals = NULL;
00319 *nb_intervals = 0;
00320
00321 while (1) {
00322 Interval interval;
00323
00324 skip_comments(&buf);
00325 if (!(*buf))
00326 break;
00327
00328 if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
00329 return ret;
00330
00331 buf += strspn(buf, SPACES);
00332 if (*buf) {
00333 if (*buf != ';') {
00334 av_log(log_ctx, AV_LOG_ERROR,
00335 "Missing terminator or extraneous data found at the end of interval #%d\n",
00336 interval_count);
00337 return AVERROR(EINVAL);
00338 }
00339 buf++;
00340 }
00341 interval_count++;
00342
00343
00344 if (*nb_intervals == n) {
00345 n = FFMAX(16, 2*n);
00346 *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
00347 if (!*intervals) {
00348 av_log(log_ctx, AV_LOG_ERROR,
00349 "Could not (re)allocate intervals array\n");
00350 return AVERROR(ENOMEM);
00351 }
00352 }
00353
00354 (*intervals)[(*nb_intervals)++] = interval;
00355 }
00356
00357 return 0;
00358 }
00359
00360 static int cmp_intervals(const void *a, const void *b)
00361 {
00362 const Interval *i1 = a;
00363 const Interval *i2 = b;
00364 int64_t ts_diff = i1->start_ts - i2->start_ts;
00365 int ret;
00366
00367 ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0;
00368 return ret == 0 ? i1->index - i2->index : ret;
00369 }
00370
00371 static av_cold int init(AVFilterContext *ctx, const char *args, const AVClass *class)
00372 {
00373 SendCmdContext *sendcmd = ctx->priv;
00374 int ret, i, j;
00375
00376 sendcmd->class = class;
00377 av_opt_set_defaults(sendcmd);
00378
00379 if ((ret = av_set_options_string(sendcmd, args, "=", ":")) < 0)
00380 return ret;
00381
00382 if (sendcmd->commands_filename && sendcmd->commands_str) {
00383 av_log(ctx, AV_LOG_ERROR,
00384 "Only one of the filename or commands options must be specified\n");
00385 return AVERROR(EINVAL);
00386 }
00387
00388 if (sendcmd->commands_filename) {
00389 uint8_t *file_buf, *buf;
00390 size_t file_bufsize;
00391 ret = av_file_map(sendcmd->commands_filename,
00392 &file_buf, &file_bufsize, 0, ctx);
00393 if (ret < 0)
00394 return ret;
00395
00396
00397 buf = av_malloc(file_bufsize + 1);
00398 if (!buf) {
00399 av_file_unmap(file_buf, file_bufsize);
00400 return AVERROR(ENOMEM);
00401 }
00402 memcpy(buf, file_buf, file_bufsize);
00403 buf[file_bufsize] = 0;
00404 av_file_unmap(file_buf, file_bufsize);
00405 sendcmd->commands_str = buf;
00406 }
00407
00408 if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals,
00409 sendcmd->commands_str, ctx)) < 0)
00410 return ret;
00411
00412 qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals);
00413
00414 av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
00415 for (i = 0; i < sendcmd->nb_intervals; i++) {
00416 AVBPrint pbuf;
00417 Interval *interval = &sendcmd->intervals[i];
00418 av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
00419 (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
00420 for (j = 0; j < interval->nb_commands; j++) {
00421 Command *cmd = &interval->commands[j];
00422 av_log(ctx, AV_LOG_VERBOSE,
00423 " [%s] target:%s command:%s arg:%s index:%d\n",
00424 make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
00425 }
00426 }
00427
00428 return 0;
00429 }
00430
00431 static void av_cold uninit(AVFilterContext *ctx)
00432 {
00433 SendCmdContext *sendcmd = ctx->priv;
00434 int i, j;
00435
00436 av_opt_free(sendcmd);
00437
00438 for (i = 0; i < sendcmd->nb_intervals; i++) {
00439 Interval *interval = &sendcmd->intervals[i];
00440 for (j = 0; j < interval->nb_commands; j++) {
00441 Command *cmd = &interval->commands[j];
00442 av_free(cmd->target);
00443 av_free(cmd->command);
00444 av_free(cmd->arg);
00445 }
00446 av_free(interval->commands);
00447 }
00448 av_freep(&sendcmd->intervals);
00449 }
00450
00451 static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *ref)
00452 {
00453 AVFilterContext *ctx = inlink->dst;
00454 SendCmdContext *sendcmd = ctx->priv;
00455 int64_t ts;
00456 int i, j, ret;
00457
00458 if (ref->pts == AV_NOPTS_VALUE)
00459 goto end;
00460
00461 ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
00462
00463 #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
00464
00465 for (i = 0; i < sendcmd->nb_intervals; i++) {
00466 Interval *interval = &sendcmd->intervals[i];
00467 int flags = 0;
00468
00469 if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
00470 flags += COMMAND_FLAG_ENTER;
00471 interval->enabled = 1;
00472 }
00473 if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
00474 flags += COMMAND_FLAG_LEAVE;
00475 interval->enabled = 0;
00476 }
00477
00478 if (flags) {
00479 AVBPrint pbuf;
00480 av_log(ctx, AV_LOG_VERBOSE,
00481 "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
00482 make_command_flags_str(&pbuf, flags), interval->index,
00483 (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
00484 (double)ts/1000000);
00485
00486 for (j = 0; flags && j < interval->nb_commands; j++) {
00487 Command *cmd = &interval->commands[j];
00488 char buf[1024];
00489
00490 if (cmd->flags & flags) {
00491 av_log(ctx, AV_LOG_VERBOSE,
00492 "Processing command #%d target:%s command:%s arg:%s\n",
00493 cmd->index, cmd->target, cmd->command, cmd->arg);
00494 ret = avfilter_graph_send_command(inlink->graph,
00495 cmd->target, cmd->command, cmd->arg,
00496 buf, sizeof(buf),
00497 AVFILTER_CMD_FLAG_ONE);
00498 av_log(ctx, AV_LOG_VERBOSE,
00499 "Command reply for command #%d: ret:%s res:%s\n",
00500 cmd->index, av_err2str(ret), buf);
00501 }
00502 }
00503 }
00504 }
00505
00506 end:
00507 switch (inlink->type) {
00508 case AVMEDIA_TYPE_VIDEO:
00509 case AVMEDIA_TYPE_AUDIO:
00510 return ff_filter_frame(inlink->dst->outputs[0], ref);
00511 }
00512
00513 return AVERROR(ENOSYS);
00514 }
00515
00516 #if CONFIG_SENDCMD_FILTER
00517
00518 #define sendcmd_options options
00519 AVFILTER_DEFINE_CLASS(sendcmd);
00520
00521 static av_cold int sendcmd_init(AVFilterContext *ctx, const char *args)
00522 {
00523 return init(ctx, args, &sendcmd_class);
00524 }
00525
00526 static const AVFilterPad sendcmd_inputs[] = {
00527 {
00528 .name = "default",
00529 .type = AVMEDIA_TYPE_VIDEO,
00530 .get_video_buffer = ff_null_get_video_buffer,
00531 .filter_frame = filter_frame,
00532 },
00533 { NULL }
00534 };
00535
00536 static const AVFilterPad sendcmd_outputs[] = {
00537 {
00538 .name = "default",
00539 .type = AVMEDIA_TYPE_VIDEO,
00540 },
00541 { NULL }
00542 };
00543
00544 AVFilter avfilter_vf_sendcmd = {
00545 .name = "sendcmd",
00546 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
00547
00548 .init = sendcmd_init,
00549 .uninit = uninit,
00550 .priv_size = sizeof(SendCmdContext),
00551 .inputs = sendcmd_inputs,
00552 .outputs = sendcmd_outputs,
00553 .priv_class = &sendcmd_class,
00554 };
00555
00556 #endif
00557
00558 #if CONFIG_ASENDCMD_FILTER
00559
00560 #define asendcmd_options options
00561 AVFILTER_DEFINE_CLASS(asendcmd);
00562
00563 static av_cold int asendcmd_init(AVFilterContext *ctx, const char *args)
00564 {
00565 return init(ctx, args, &asendcmd_class);
00566 }
00567
00568 static const AVFilterPad asendcmd_inputs[] = {
00569 {
00570 .name = "default",
00571 .type = AVMEDIA_TYPE_AUDIO,
00572 .get_audio_buffer = ff_null_get_audio_buffer,
00573 .filter_frame = filter_frame,
00574 },
00575 { NULL }
00576 };
00577
00578 static const AVFilterPad asendcmd_outputs[] = {
00579 {
00580 .name = "default",
00581 .type = AVMEDIA_TYPE_AUDIO,
00582 },
00583 { NULL }
00584 };
00585
00586 AVFilter avfilter_af_asendcmd = {
00587 .name = "asendcmd",
00588 .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
00589
00590 .init = asendcmd_init,
00591 .uninit = uninit,
00592 .priv_size = sizeof(SendCmdContext),
00593 .inputs = asendcmd_inputs,
00594 .outputs = asendcmd_outputs,
00595 .priv_class = &asendcmd_class,
00596 };
00597
00598 #endif