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