f_sendcmd.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  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. * @file
  22. * send commands filter
  23. */
  24. #include "libavutil/avstring.h"
  25. #include "libavutil/bprint.h"
  26. #include "libavutil/file.h"
  27. #include "libavutil/opt.h"
  28. #include "libavutil/parseutils.h"
  29. #include "avfilter.h"
  30. #include "internal.h"
  31. #include "audio.h"
  32. #include "video.h"
  33. #define COMMAND_FLAG_ENTER 1
  34. #define COMMAND_FLAG_LEAVE 2
  35. static inline char *make_command_flags_str(AVBPrint *pbuf, int flags)
  36. {
  37. static const char * const flag_strings[] = { "enter", "leave" };
  38. int i, is_first = 1;
  39. av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC);
  40. for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) {
  41. if (flags & 1<<i) {
  42. if (!is_first)
  43. av_bprint_chars(pbuf, '+', 1);
  44. av_bprintf(pbuf, "%s", flag_strings[i]);
  45. is_first = 0;
  46. }
  47. }
  48. return pbuf->str;
  49. }
  50. typedef struct Command {
  51. int flags;
  52. char *target, *command, *arg;
  53. int index;
  54. } Command;
  55. typedef struct Interval {
  56. int64_t start_ts; ///< start timestamp expressed as microseconds units
  57. int64_t end_ts; ///< end timestamp expressed as microseconds units
  58. int index; ///< unique index for these interval commands
  59. Command *commands;
  60. int nb_commands;
  61. int enabled; ///< current time detected inside this interval
  62. } Interval;
  63. typedef struct SendCmdContext {
  64. const AVClass *class;
  65. Interval *intervals;
  66. int nb_intervals;
  67. char *commands_filename;
  68. char *commands_str;
  69. } SendCmdContext;
  70. #define OFFSET(x) offsetof(SendCmdContext, x)
  71. #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
  72. static const AVOption options[] = {
  73. { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
  74. { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
  75. { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
  76. { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS },
  77. { NULL }
  78. };
  79. #define SPACES " \f\t\n\r"
  80. static void skip_comments(const char **buf)
  81. {
  82. while (**buf) {
  83. /* skip leading spaces */
  84. *buf += strspn(*buf, SPACES);
  85. if (**buf != '#')
  86. break;
  87. (*buf)++;
  88. /* skip comment until the end of line */
  89. *buf += strcspn(*buf, "\n");
  90. if (**buf)
  91. (*buf)++;
  92. }
  93. }
  94. #define COMMAND_DELIMS " \f\t\n\r,;"
  95. static int parse_command(Command *cmd, int cmd_count, int interval_count,
  96. const char **buf, void *log_ctx)
  97. {
  98. int ret;
  99. memset(cmd, 0, sizeof(Command));
  100. cmd->index = cmd_count;
  101. /* format: [FLAGS] target command arg */
  102. *buf += strspn(*buf, SPACES);
  103. /* parse flags */
  104. if (**buf == '[') {
  105. (*buf)++; /* skip "[" */
  106. while (**buf) {
  107. int len = strcspn(*buf, "|+]");
  108. if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER;
  109. else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE;
  110. else {
  111. char flag_buf[64];
  112. av_strlcpy(flag_buf, *buf, sizeof(flag_buf));
  113. av_log(log_ctx, AV_LOG_ERROR,
  114. "Unknown flag '%s' in interval #%d, command #%d\n",
  115. flag_buf, interval_count, cmd_count);
  116. return AVERROR(EINVAL);
  117. }
  118. *buf += len;
  119. if (**buf == ']')
  120. break;
  121. if (!strspn(*buf, "+|")) {
  122. av_log(log_ctx, AV_LOG_ERROR,
  123. "Invalid flags char '%c' in interval #%d, command #%d\n",
  124. **buf, interval_count, cmd_count);
  125. return AVERROR(EINVAL);
  126. }
  127. if (**buf)
  128. (*buf)++;
  129. }
  130. if (**buf != ']') {
  131. av_log(log_ctx, AV_LOG_ERROR,
  132. "Missing flag terminator or extraneous data found at the end of flags "
  133. "in interval #%d, command #%d\n", interval_count, cmd_count);
  134. return AVERROR(EINVAL);
  135. }
  136. (*buf)++; /* skip "]" */
  137. } else {
  138. cmd->flags = COMMAND_FLAG_ENTER;
  139. }
  140. *buf += strspn(*buf, SPACES);
  141. cmd->target = av_get_token(buf, COMMAND_DELIMS);
  142. if (!cmd->target || !cmd->target[0]) {
  143. av_log(log_ctx, AV_LOG_ERROR,
  144. "No target specified in interval #%d, command #%d\n",
  145. interval_count, cmd_count);
  146. ret = AVERROR(EINVAL);
  147. goto fail;
  148. }
  149. *buf += strspn(*buf, SPACES);
  150. cmd->command = av_get_token(buf, COMMAND_DELIMS);
  151. if (!cmd->command || !cmd->command[0]) {
  152. av_log(log_ctx, AV_LOG_ERROR,
  153. "No command specified in interval #%d, command #%d\n",
  154. interval_count, cmd_count);
  155. ret = AVERROR(EINVAL);
  156. goto fail;
  157. }
  158. *buf += strspn(*buf, SPACES);
  159. cmd->arg = av_get_token(buf, COMMAND_DELIMS);
  160. return 1;
  161. fail:
  162. av_freep(&cmd->target);
  163. av_freep(&cmd->command);
  164. av_freep(&cmd->arg);
  165. return ret;
  166. }
  167. static int parse_commands(Command **cmds, int *nb_cmds, int interval_count,
  168. const char **buf, void *log_ctx)
  169. {
  170. int cmd_count = 0;
  171. int ret, n = 0;
  172. AVBPrint pbuf;
  173. *cmds = NULL;
  174. *nb_cmds = 0;
  175. while (**buf) {
  176. Command cmd;
  177. if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0)
  178. return ret;
  179. cmd_count++;
  180. /* (re)allocate commands array if required */
  181. if (*nb_cmds == n) {
  182. n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
  183. *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command));
  184. if (!*cmds) {
  185. av_log(log_ctx, AV_LOG_ERROR,
  186. "Could not (re)allocate command array\n");
  187. return AVERROR(ENOMEM);
  188. }
  189. }
  190. (*cmds)[(*nb_cmds)++] = cmd;
  191. *buf += strspn(*buf, SPACES);
  192. if (**buf && **buf != ';' && **buf != ',') {
  193. av_log(log_ctx, AV_LOG_ERROR,
  194. "Missing separator or extraneous data found at the end of "
  195. "interval #%d, in command #%d\n",
  196. interval_count, cmd_count);
  197. av_log(log_ctx, AV_LOG_ERROR,
  198. "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n",
  199. make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg);
  200. return AVERROR(EINVAL);
  201. }
  202. if (**buf == ';')
  203. break;
  204. if (**buf == ',')
  205. (*buf)++;
  206. }
  207. return 0;
  208. }
  209. #define DELIMS " \f\t\n\r,;"
  210. static int parse_interval(Interval *interval, int interval_count,
  211. const char **buf, void *log_ctx)
  212. {
  213. char *intervalstr;
  214. int ret;
  215. *buf += strspn(*buf, SPACES);
  216. if (!**buf)
  217. return 0;
  218. /* reset data */
  219. memset(interval, 0, sizeof(Interval));
  220. interval->index = interval_count;
  221. /* format: INTERVAL COMMANDS */
  222. /* parse interval */
  223. intervalstr = av_get_token(buf, DELIMS);
  224. if (intervalstr && intervalstr[0]) {
  225. char *start, *end;
  226. start = av_strtok(intervalstr, "-", &end);
  227. if (!start) {
  228. ret = AVERROR(EINVAL);
  229. av_log(log_ctx, AV_LOG_ERROR,
  230. "Invalid interval specification '%s' in interval #%d\n",
  231. intervalstr, interval_count);
  232. goto end;
  233. }
  234. if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) {
  235. av_log(log_ctx, AV_LOG_ERROR,
  236. "Invalid start time specification '%s' in interval #%d\n",
  237. start, interval_count);
  238. goto end;
  239. }
  240. if (end) {
  241. if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) {
  242. av_log(log_ctx, AV_LOG_ERROR,
  243. "Invalid end time specification '%s' in interval #%d\n",
  244. end, interval_count);
  245. goto end;
  246. }
  247. } else {
  248. interval->end_ts = INT64_MAX;
  249. }
  250. if (interval->end_ts < interval->start_ts) {
  251. av_log(log_ctx, AV_LOG_ERROR,
  252. "Invalid end time '%s' in interval #%d: "
  253. "cannot be lesser than start time '%s'\n",
  254. end, interval_count, start);
  255. ret = AVERROR(EINVAL);
  256. goto end;
  257. }
  258. } else {
  259. av_log(log_ctx, AV_LOG_ERROR,
  260. "No interval specified for interval #%d\n", interval_count);
  261. ret = AVERROR(EINVAL);
  262. goto end;
  263. }
  264. /* parse commands */
  265. ret = parse_commands(&interval->commands, &interval->nb_commands,
  266. interval_count, buf, log_ctx);
  267. end:
  268. av_free(intervalstr);
  269. return ret;
  270. }
  271. static int parse_intervals(Interval **intervals, int *nb_intervals,
  272. const char *buf, void *log_ctx)
  273. {
  274. int interval_count = 0;
  275. int ret, n = 0;
  276. *intervals = NULL;
  277. *nb_intervals = 0;
  278. if (!buf)
  279. return 0;
  280. while (1) {
  281. Interval interval;
  282. skip_comments(&buf);
  283. if (!(*buf))
  284. break;
  285. if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0)
  286. return ret;
  287. buf += strspn(buf, SPACES);
  288. if (*buf) {
  289. if (*buf != ';') {
  290. av_log(log_ctx, AV_LOG_ERROR,
  291. "Missing terminator or extraneous data found at the end of interval #%d\n",
  292. interval_count);
  293. return AVERROR(EINVAL);
  294. }
  295. buf++; /* skip ';' */
  296. }
  297. interval_count++;
  298. /* (re)allocate commands array if required */
  299. if (*nb_intervals == n) {
  300. n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */
  301. *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval));
  302. if (!*intervals) {
  303. av_log(log_ctx, AV_LOG_ERROR,
  304. "Could not (re)allocate intervals array\n");
  305. return AVERROR(ENOMEM);
  306. }
  307. }
  308. (*intervals)[(*nb_intervals)++] = interval;
  309. }
  310. return 0;
  311. }
  312. static int cmp_intervals(const void *a, const void *b)
  313. {
  314. const Interval *i1 = a;
  315. const Interval *i2 = b;
  316. return 2 * FFDIFFSIGN(i1->start_ts, i2->start_ts) + FFDIFFSIGN(i1->index, i2->index);
  317. }
  318. static av_cold int init(AVFilterContext *ctx)
  319. {
  320. SendCmdContext *s = ctx->priv;
  321. int ret, i, j;
  322. if ((!!s->commands_filename + !!s->commands_str) != 1) {
  323. av_log(ctx, AV_LOG_ERROR,
  324. "One and only one of the filename or commands options must be specified\n");
  325. return AVERROR(EINVAL);
  326. }
  327. if (s->commands_filename) {
  328. uint8_t *file_buf, *buf;
  329. size_t file_bufsize;
  330. ret = av_file_map(s->commands_filename,
  331. &file_buf, &file_bufsize, 0, ctx);
  332. if (ret < 0)
  333. return ret;
  334. /* create a 0-terminated string based on the read file */
  335. buf = av_malloc(file_bufsize + 1);
  336. if (!buf) {
  337. av_file_unmap(file_buf, file_bufsize);
  338. return AVERROR(ENOMEM);
  339. }
  340. memcpy(buf, file_buf, file_bufsize);
  341. buf[file_bufsize] = 0;
  342. av_file_unmap(file_buf, file_bufsize);
  343. s->commands_str = buf;
  344. }
  345. if ((ret = parse_intervals(&s->intervals, &s->nb_intervals,
  346. s->commands_str, ctx)) < 0)
  347. return ret;
  348. if (s->nb_intervals == 0) {
  349. av_log(ctx, AV_LOG_ERROR, "No commands were specified\n");
  350. return AVERROR(EINVAL);
  351. }
  352. qsort(s->intervals, s->nb_intervals, sizeof(Interval), cmp_intervals);
  353. av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n");
  354. for (i = 0; i < s->nb_intervals; i++) {
  355. AVBPrint pbuf;
  356. Interval *interval = &s->intervals[i];
  357. av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n",
  358. (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index);
  359. for (j = 0; j < interval->nb_commands; j++) {
  360. Command *cmd = &interval->commands[j];
  361. av_log(ctx, AV_LOG_VERBOSE,
  362. " [%s] target:%s command:%s arg:%s index:%d\n",
  363. make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index);
  364. }
  365. }
  366. return 0;
  367. }
  368. static av_cold void uninit(AVFilterContext *ctx)
  369. {
  370. SendCmdContext *s = ctx->priv;
  371. int i, j;
  372. for (i = 0; i < s->nb_intervals; i++) {
  373. Interval *interval = &s->intervals[i];
  374. for (j = 0; j < interval->nb_commands; j++) {
  375. Command *cmd = &interval->commands[j];
  376. av_freep(&cmd->target);
  377. av_freep(&cmd->command);
  378. av_freep(&cmd->arg);
  379. }
  380. av_freep(&interval->commands);
  381. }
  382. av_freep(&s->intervals);
  383. }
  384. static int filter_frame(AVFilterLink *inlink, AVFrame *ref)
  385. {
  386. AVFilterContext *ctx = inlink->dst;
  387. SendCmdContext *s = ctx->priv;
  388. int64_t ts;
  389. int i, j, ret;
  390. if (ref->pts == AV_NOPTS_VALUE)
  391. goto end;
  392. ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q);
  393. #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts))
  394. for (i = 0; i < s->nb_intervals; i++) {
  395. Interval *interval = &s->intervals[i];
  396. int flags = 0;
  397. if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
  398. flags += COMMAND_FLAG_ENTER;
  399. interval->enabled = 1;
  400. }
  401. if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) {
  402. flags += COMMAND_FLAG_LEAVE;
  403. interval->enabled = 0;
  404. }
  405. if (flags) {
  406. AVBPrint pbuf;
  407. av_log(ctx, AV_LOG_VERBOSE,
  408. "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n",
  409. make_command_flags_str(&pbuf, flags), interval->index,
  410. (double)interval->start_ts/1000000, (double)interval->end_ts/1000000,
  411. (double)ts/1000000);
  412. for (j = 0; flags && j < interval->nb_commands; j++) {
  413. Command *cmd = &interval->commands[j];
  414. char buf[1024];
  415. if (cmd->flags & flags) {
  416. av_log(ctx, AV_LOG_VERBOSE,
  417. "Processing command #%d target:%s command:%s arg:%s\n",
  418. cmd->index, cmd->target, cmd->command, cmd->arg);
  419. ret = avfilter_graph_send_command(inlink->graph,
  420. cmd->target, cmd->command, cmd->arg,
  421. buf, sizeof(buf),
  422. AVFILTER_CMD_FLAG_ONE);
  423. av_log(ctx, AV_LOG_VERBOSE,
  424. "Command reply for command #%d: ret:%s res:%s\n",
  425. cmd->index, av_err2str(ret), buf);
  426. }
  427. }
  428. }
  429. }
  430. end:
  431. switch (inlink->type) {
  432. case AVMEDIA_TYPE_VIDEO:
  433. case AVMEDIA_TYPE_AUDIO:
  434. return ff_filter_frame(inlink->dst->outputs[0], ref);
  435. }
  436. return AVERROR(ENOSYS);
  437. }
  438. #if CONFIG_SENDCMD_FILTER
  439. #define sendcmd_options options
  440. AVFILTER_DEFINE_CLASS(sendcmd);
  441. static const AVFilterPad sendcmd_inputs[] = {
  442. {
  443. .name = "default",
  444. .type = AVMEDIA_TYPE_VIDEO,
  445. .filter_frame = filter_frame,
  446. },
  447. { NULL }
  448. };
  449. static const AVFilterPad sendcmd_outputs[] = {
  450. {
  451. .name = "default",
  452. .type = AVMEDIA_TYPE_VIDEO,
  453. },
  454. { NULL }
  455. };
  456. AVFilter ff_vf_sendcmd = {
  457. .name = "sendcmd",
  458. .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
  459. .init = init,
  460. .uninit = uninit,
  461. .priv_size = sizeof(SendCmdContext),
  462. .inputs = sendcmd_inputs,
  463. .outputs = sendcmd_outputs,
  464. .priv_class = &sendcmd_class,
  465. };
  466. #endif
  467. #if CONFIG_ASENDCMD_FILTER
  468. #define asendcmd_options options
  469. AVFILTER_DEFINE_CLASS(asendcmd);
  470. static const AVFilterPad asendcmd_inputs[] = {
  471. {
  472. .name = "default",
  473. .type = AVMEDIA_TYPE_AUDIO,
  474. .filter_frame = filter_frame,
  475. },
  476. { NULL }
  477. };
  478. static const AVFilterPad asendcmd_outputs[] = {
  479. {
  480. .name = "default",
  481. .type = AVMEDIA_TYPE_AUDIO,
  482. },
  483. { NULL }
  484. };
  485. AVFilter ff_af_asendcmd = {
  486. .name = "asendcmd",
  487. .description = NULL_IF_CONFIG_SMALL("Send commands to filters."),
  488. .init = init,
  489. .uninit = uninit,
  490. .priv_size = sizeof(SendCmdContext),
  491. .inputs = asendcmd_inputs,
  492. .outputs = asendcmd_outputs,
  493. .priv_class = &asendcmd_class,
  494. };
  495. #endif