blob: 522d6adb907a2bf6d67aa9c9dcf6220fb95ee697
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 (!start) { |
272 | ret = AVERROR(EINVAL); |
273 | av_log(log_ctx, AV_LOG_ERROR, |
274 | "Invalid interval specification '%s' in interval #%d\n", |
275 | intervalstr, interval_count); |
276 | goto end; |
277 | } |
278 | if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) { |
279 | av_log(log_ctx, AV_LOG_ERROR, |
280 | "Invalid start time specification '%s' in interval #%d\n", |
281 | start, interval_count); |
282 | goto end; |
283 | } |
284 | |
285 | if (end) { |
286 | if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) { |
287 | av_log(log_ctx, AV_LOG_ERROR, |
288 | "Invalid end time specification '%s' in interval #%d\n", |
289 | end, interval_count); |
290 | goto end; |
291 | } |
292 | } else { |
293 | interval->end_ts = INT64_MAX; |
294 | } |
295 | if (interval->end_ts < interval->start_ts) { |
296 | av_log(log_ctx, AV_LOG_ERROR, |
297 | "Invalid end time '%s' in interval #%d: " |
298 | "cannot be lesser than start time '%s'\n", |
299 | end, interval_count, start); |
300 | ret = AVERROR(EINVAL); |
301 | goto end; |
302 | } |
303 | } else { |
304 | av_log(log_ctx, AV_LOG_ERROR, |
305 | "No interval specified for interval #%d\n", interval_count); |
306 | ret = AVERROR(EINVAL); |
307 | goto end; |
308 | } |
309 | |
310 | /* parse commands */ |
311 | ret = parse_commands(&interval->commands, &interval->nb_commands, |
312 | interval_count, buf, log_ctx); |
313 | |
314 | end: |
315 | av_free(intervalstr); |
316 | return ret; |
317 | } |
318 | |
319 | static int parse_intervals(Interval **intervals, int *nb_intervals, |
320 | const char *buf, void *log_ctx) |
321 | { |
322 | int interval_count = 0; |
323 | int ret, n = 0; |
324 | |
325 | *intervals = NULL; |
326 | *nb_intervals = 0; |
327 | |
328 | if (!buf) |
329 | return 0; |
330 | |
331 | while (1) { |
332 | Interval interval; |
333 | |
334 | skip_comments(&buf); |
335 | if (!(*buf)) |
336 | break; |
337 | |
338 | if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0) |
339 | return ret; |
340 | |
341 | buf += strspn(buf, SPACES); |
342 | if (*buf) { |
343 | if (*buf != ';') { |
344 | av_log(log_ctx, AV_LOG_ERROR, |
345 | "Missing terminator or extraneous data found at the end of interval #%d\n", |
346 | interval_count); |
347 | return AVERROR(EINVAL); |
348 | } |
349 | buf++; /* skip ';' */ |
350 | } |
351 | interval_count++; |
352 | |
353 | /* (re)allocate commands array if required */ |
354 | if (*nb_intervals == n) { |
355 | n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ |
356 | *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval)); |
357 | if (!*intervals) { |
358 | av_log(log_ctx, AV_LOG_ERROR, |
359 | "Could not (re)allocate intervals array\n"); |
360 | return AVERROR(ENOMEM); |
361 | } |
362 | } |
363 | |
364 | (*intervals)[(*nb_intervals)++] = interval; |
365 | } |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | static int cmp_intervals(const void *a, const void *b) |
371 | { |
372 | const Interval *i1 = a; |
373 | const Interval *i2 = b; |
374 | return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index); |
375 | } |
376 | |
377 | static av_cold int init(AVFilterContext *ctx) |
378 | { |
379 | SendCmdContext *s = ctx->priv; |
380 | int ret, i, j; |
381 | |
382 | if ((!!s->commands_filename + !!s->commands_str) != 1) { |
383 | av_log(ctx, AV_LOG_ERROR, |
384 | "One and only one of the filename or commands options must be specified\n"); |
385 | return AVERROR(EINVAL); |
386 | } |
387 | |
388 | if (s->commands_filename) { |
389 | uint8_t *file_buf, *buf; |
390 | size_t file_bufsize; |
391 | ret = av_file_map(s->commands_filename, |
392 | &file_buf, &file_bufsize, 0, ctx); |
393 | if (ret < 0) |
394 | return ret; |
395 | |
396 | /* create a 0-terminated string based on the read file */ |
397 | buf = av_malloc(file_bufsize + 1); |
398 | if (!buf) { |
399 | av_file_unmap(file_buf, file_bufsize); |
400 | return AVERROR(ENOMEM); |
401 | } |
402 | memcpy(buf, file_buf, file_bufsize); |
403 | buf[file_bufsize] = 0; |
404 | av_file_unmap(file_buf, file_bufsize); |
405 | s->commands_str = buf; |
406 | } |
407 | |
408 | if ((ret = parse_intervals(&s->intervals, &s->nb_intervals, |
409 | s->commands_str, ctx)) < 0) |
410 | return ret; |
411 | |
412 | if (s->nb_intervals == 0) { |
413 | av_log(ctx, AV_LOG_ERROR, "No commands were specified\n"); |
414 | return AVERROR(EINVAL); |
415 | } |
416 | |
417 | qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals); |
418 | |
419 | av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n"); |
420 | for (i = 0; i < s->nb_intervals; i++) { |
421 | AVBPrint pbuf; |
422 | Interval *interval = &s->intervals[i]; |
423 | av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n", |
424 | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index); |
425 | for (j = 0; j < interval->nb_commands; j++) { |
426 | Command *cmd = &interval->commands[j]; |
427 | av_log(ctx, AV_LOG_VERBOSE, |
428 | " [%s] target:%s command:%s arg:%s index:%d\n", |
429 | make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index); |
430 | } |
431 | } |
432 | |
433 | return 0; |
434 | } |
435 | |
436 | static av_cold void uninit(AVFilterContext *ctx) |
437 | { |
438 | SendCmdContext *s = ctx->priv; |
439 | int i, j; |
440 | |
441 | for (i = 0; i < s->nb_intervals; i++) { |
442 | Interval *interval = &s->intervals[i]; |
443 | for (j = 0; j < interval->nb_commands; j++) { |
444 | Command *cmd = &interval->commands[j]; |
445 | av_freep(&cmd->target); |
446 | av_freep(&cmd->command); |
447 | av_freep(&cmd->arg); |
448 | } |
449 | av_freep(&interval->commands); |
450 | } |
451 | av_freep(&s->intervals); |
452 | } |
453 | |
454 | static int filter_frame(AVFilterLink *inlink, AVFrame *ref) |
455 | { |
456 | AVFilterContext *ctx = inlink->dst; |
457 | SendCmdContext *s = ctx->priv; |
458 | int64_t ts; |
459 | int i, j, ret; |
460 | |
461 | if (ref->pts == AV_NOPTS_VALUE) |
462 | goto end; |
463 | |
464 | ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q); |
465 | |
466 | #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts)) |
467 | |
468 | for (i = 0; i < s->nb_intervals; i++) { |
469 | Interval *interval = &s->intervals[i]; |
470 | int flags = 0; |
471 | |
472 | if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { |
473 | flags += COMMAND_FLAG_ENTER; |
474 | interval->enabled = 1; |
475 | } |
476 | if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { |
477 | flags += COMMAND_FLAG_LEAVE; |
478 | interval->enabled = 0; |
479 | } |
480 | |
481 | if (flags) { |
482 | AVBPrint pbuf; |
483 | av_log(ctx, AV_LOG_VERBOSE, |
484 | "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n", |
485 | make_command_flags_str(&pbuf, flags), interval->index, |
486 | (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, |
487 | (double)ts/1000000); |
488 | |
489 | for (j = 0; flags && j < interval->nb_commands; j++) { |
490 | Command *cmd = &interval->commands[j]; |
491 | char buf[1024]; |
492 | |
493 | if (cmd->flags & flags) { |
494 | av_log(ctx, AV_LOG_VERBOSE, |
495 | "Processing command #%d target:%s command:%s arg:%s\n", |
496 | cmd->index, cmd->target, cmd->command, cmd->arg); |
497 | ret = avfilter_graph_send_command(inlink->graph, |
498 | cmd->target, cmd->command, cmd->arg, |
499 | buf, sizeof(buf), |
500 | AVFILTER_CMD_FLAG_ONE); |
501 | av_log(ctx, AV_LOG_VERBOSE, |
502 | "Command reply for command #%d: ret:%s res:%s\n", |
503 | cmd->index, av_err2str(ret), buf); |
504 | } |
505 | } |
506 | } |
507 | } |
508 | |
509 | end: |
510 | switch (inlink->type) { |
511 | case AVMEDIA_TYPE_VIDEO: |
512 | case AVMEDIA_TYPE_AUDIO: |
513 | return ff_filter_frame(inlink->dst->outputs[0], ref); |
514 | } |
515 | |
516 | return AVERROR(ENOSYS); |
517 | } |
518 | |
519 | #if CONFIG_SENDCMD_FILTER |
520 | |
521 | #define sendcmd_options options |
522 | AVFILTER_DEFINE_CLASS(sendcmd); |
523 | |
524 | static const AVFilterPad sendcmd_inputs[] = { |
525 | { |
526 | .name = "default", |
527 | .type = AVMEDIA_TYPE_VIDEO, |
528 | .filter_frame = filter_frame, |
529 | }, |
530 | { NULL } |
531 | }; |
532 | |
533 | static const AVFilterPad sendcmd_outputs[] = { |
534 | { |
535 | .name = "default", |
536 | .type = AVMEDIA_TYPE_VIDEO, |
537 | }, |
538 | { NULL } |
539 | }; |
540 | |
541 | AVFilter ff_vf_sendcmd = { |
542 | .name = "sendcmd", |
543 | .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), |
544 | .init = init, |
545 | .uninit = uninit, |
546 | .priv_size = sizeof(SendCmdContext), |
547 | .inputs = sendcmd_inputs, |
548 | .outputs = sendcmd_outputs, |
549 | .priv_class = &sendcmd_class, |
550 | }; |
551 | |
552 | #endif |
553 | |
554 | #if CONFIG_ASENDCMD_FILTER |
555 | |
556 | #define asendcmd_options options |
557 | AVFILTER_DEFINE_CLASS(asendcmd); |
558 | |
559 | static const AVFilterPad asendcmd_inputs[] = { |
560 | { |
561 | .name = "default", |
562 | .type = AVMEDIA_TYPE_AUDIO, |
563 | .filter_frame = filter_frame, |
564 | }, |
565 | { NULL } |
566 | }; |
567 | |
568 | static const AVFilterPad asendcmd_outputs[] = { |
569 | { |
570 | .name = "default", |
571 | .type = AVMEDIA_TYPE_AUDIO, |
572 | }, |
573 | { NULL } |
574 | }; |
575 | |
576 | AVFilter ff_af_asendcmd = { |
577 | .name = "asendcmd", |
578 | .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), |
579 | .init = init, |
580 | .uninit = uninit, |
581 | .priv_size = sizeof(SendCmdContext), |
582 | .inputs = asendcmd_inputs, |
583 | .outputs = asendcmd_outputs, |
584 | .priv_class = &asendcmd_class, |
585 | }; |
586 | |
587 | #endif |
588 |