FFmpeg coverage


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