FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/f_sendcmd.c
Date: 2022-12-05 20:26:17
Exec Total Coverage
Lines: 0 259 0.0%
Functions: 0 10 0.0%
Branches: 0 152 0.0%

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