yading@10: /* yading@10: * Copyright (c) 2012 Stefano Sabatini yading@10: * yading@10: * This file is part of FFmpeg. yading@10: * yading@10: * FFmpeg is free software; you can redistribute it and/or yading@10: * modify it under the terms of the GNU Lesser General Public yading@10: * License as published by the Free Software Foundation; either yading@10: * version 2.1 of the License, or (at your option) any later version. yading@10: * yading@10: * FFmpeg is distributed in the hope that it will be useful, yading@10: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@10: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@10: * Lesser General Public License for more details. yading@10: * yading@10: * You should have received a copy of the GNU Lesser General Public yading@10: * License along with FFmpeg; if not, write to the Free Software yading@10: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@10: */ yading@10: yading@10: /** yading@10: * @file yading@10: * send commands filter yading@10: */ yading@10: yading@10: #include "libavutil/avstring.h" yading@10: #include "libavutil/bprint.h" yading@10: #include "libavutil/file.h" yading@10: #include "libavutil/opt.h" yading@10: #include "libavutil/parseutils.h" yading@10: #include "avfilter.h" yading@10: #include "internal.h" yading@10: #include "avfiltergraph.h" yading@10: #include "audio.h" yading@10: #include "video.h" yading@10: yading@10: #define COMMAND_FLAG_ENTER 1 yading@10: #define COMMAND_FLAG_LEAVE 2 yading@10: yading@10: static inline char *make_command_flags_str(AVBPrint *pbuf, int flags) yading@10: { yading@10: const char *flag_strings[] = { "enter", "leave" }; yading@10: int i, is_first = 1; yading@10: yading@10: av_bprint_init(pbuf, 0, AV_BPRINT_SIZE_AUTOMATIC); yading@10: for (i = 0; i < FF_ARRAY_ELEMS(flag_strings); i++) { yading@10: if (flags & 1<str; yading@10: } yading@10: yading@10: typedef struct { yading@10: int flags; yading@10: char *target, *command, *arg; yading@10: int index; yading@10: } Command; yading@10: yading@10: typedef struct { yading@10: int64_t start_ts; ///< start timestamp expressed as microseconds units yading@10: int64_t end_ts; ///< end timestamp expressed as microseconds units yading@10: int index; ///< unique index for these interval commands yading@10: Command *commands; yading@10: int nb_commands; yading@10: int enabled; ///< current time detected inside this interval yading@10: } Interval; yading@10: yading@10: typedef struct { yading@10: const AVClass *class; yading@10: Interval *intervals; yading@10: int nb_intervals; yading@10: yading@10: char *commands_filename; yading@10: char *commands_str; yading@10: } SendCmdContext; yading@10: yading@10: #define OFFSET(x) offsetof(SendCmdContext, x) yading@10: #define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM yading@10: static const AVOption options[] = { yading@10: { "commands", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, yading@10: { "c", "set commands", OFFSET(commands_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, yading@10: { "filename", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, yading@10: { "f", "set commands file", OFFSET(commands_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, yading@10: {NULL}, yading@10: }; yading@10: yading@10: #define SPACES " \f\t\n\r" yading@10: yading@10: static void skip_comments(const char **buf) yading@10: { yading@10: while (**buf) { yading@10: /* skip leading spaces */ yading@10: *buf += strspn(*buf, SPACES); yading@10: if (**buf != '#') yading@10: break; yading@10: yading@10: (*buf)++; yading@10: yading@10: /* skip comment until the end of line */ yading@10: *buf += strcspn(*buf, "\n"); yading@10: if (**buf) yading@10: (*buf)++; yading@10: } yading@10: } yading@10: yading@10: #define COMMAND_DELIMS " \f\t\n\r,;" yading@10: yading@10: static int parse_command(Command *cmd, int cmd_count, int interval_count, yading@10: const char **buf, void *log_ctx) yading@10: { yading@10: int ret; yading@10: yading@10: memset(cmd, 0, sizeof(Command)); yading@10: cmd->index = cmd_count; yading@10: yading@10: /* format: [FLAGS] target command arg */ yading@10: *buf += strspn(*buf, SPACES); yading@10: yading@10: /* parse flags */ yading@10: if (**buf == '[') { yading@10: (*buf)++; /* skip "[" */ yading@10: yading@10: while (**buf) { yading@10: int len = strcspn(*buf, "|+]"); yading@10: yading@10: if (!strncmp(*buf, "enter", strlen("enter"))) cmd->flags |= COMMAND_FLAG_ENTER; yading@10: else if (!strncmp(*buf, "leave", strlen("leave"))) cmd->flags |= COMMAND_FLAG_LEAVE; yading@10: else { yading@10: char flag_buf[64]; yading@10: av_strlcpy(flag_buf, *buf, sizeof(flag_buf)); yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Unknown flag '%s' in in interval #%d, command #%d\n", yading@10: flag_buf, interval_count, cmd_count); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: *buf += len; yading@10: if (**buf == ']') yading@10: break; yading@10: if (!strspn(*buf, "+|")) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Invalid flags char '%c' in interval #%d, command #%d\n", yading@10: **buf, interval_count, cmd_count); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: if (**buf) yading@10: (*buf)++; yading@10: } yading@10: yading@10: if (**buf != ']') { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Missing flag terminator or extraneous data found at the end of flags " yading@10: "in interval #%d, command #%d\n", interval_count, cmd_count); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: (*buf)++; /* skip "]" */ yading@10: } else { yading@10: cmd->flags = COMMAND_FLAG_ENTER; yading@10: } yading@10: yading@10: *buf += strspn(*buf, SPACES); yading@10: cmd->target = av_get_token(buf, COMMAND_DELIMS); yading@10: if (!cmd->target || !cmd->target[0]) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "No target specified in in interval #%d, command #%d\n", yading@10: interval_count, cmd_count); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: yading@10: *buf += strspn(*buf, SPACES); yading@10: cmd->command = av_get_token(buf, COMMAND_DELIMS); yading@10: if (!cmd->command || !cmd->command[0]) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "No command specified in in interval #%d, command #%d\n", yading@10: interval_count, cmd_count); yading@10: ret = AVERROR(EINVAL); yading@10: goto fail; yading@10: } yading@10: yading@10: *buf += strspn(*buf, SPACES); yading@10: cmd->arg = av_get_token(buf, COMMAND_DELIMS); yading@10: yading@10: return 1; yading@10: yading@10: fail: yading@10: av_freep(&cmd->target); yading@10: av_freep(&cmd->command); yading@10: av_freep(&cmd->arg); yading@10: return ret; yading@10: } yading@10: yading@10: static int parse_commands(Command **cmds, int *nb_cmds, int interval_count, yading@10: const char **buf, void *log_ctx) yading@10: { yading@10: int cmd_count = 0; yading@10: int ret, n = 0; yading@10: AVBPrint pbuf; yading@10: yading@10: *cmds = NULL; yading@10: *nb_cmds = 0; yading@10: yading@10: while (**buf) { yading@10: Command cmd; yading@10: yading@10: if ((ret = parse_command(&cmd, cmd_count, interval_count, buf, log_ctx)) < 0) yading@10: return ret; yading@10: cmd_count++; yading@10: yading@10: /* (re)allocate commands array if required */ yading@10: if (*nb_cmds == n) { yading@10: n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ yading@10: *cmds = av_realloc_f(*cmds, n, 2*sizeof(Command)); yading@10: if (!*cmds) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Could not (re)allocate command array\n"); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: } yading@10: yading@10: (*cmds)[(*nb_cmds)++] = cmd; yading@10: yading@10: *buf += strspn(*buf, SPACES); yading@10: if (**buf && **buf != ';' && **buf != ',') { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Missing separator or extraneous data found at the end of " yading@10: "interval #%d, in command #%d\n", yading@10: interval_count, cmd_count); yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Command was parsed as: flags:[%s] target:%s command:%s arg:%s\n", yading@10: make_command_flags_str(&pbuf, cmd.flags), cmd.target, cmd.command, cmd.arg); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: if (**buf == ';') yading@10: break; yading@10: if (**buf == ',') yading@10: (*buf)++; yading@10: } yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: #define DELIMS " \f\t\n\r,;" yading@10: yading@10: static int parse_interval(Interval *interval, int interval_count, yading@10: const char **buf, void *log_ctx) yading@10: { yading@10: char *intervalstr; yading@10: int ret; yading@10: yading@10: *buf += strspn(*buf, SPACES); yading@10: if (!**buf) yading@10: return 0; yading@10: yading@10: /* reset data */ yading@10: memset(interval, 0, sizeof(Interval)); yading@10: interval->index = interval_count; yading@10: yading@10: /* format: INTERVAL COMMANDS */ yading@10: yading@10: /* parse interval */ yading@10: intervalstr = av_get_token(buf, DELIMS); yading@10: if (intervalstr && intervalstr[0]) { yading@10: char *start, *end; yading@10: yading@10: start = av_strtok(intervalstr, "-", &end); yading@10: if ((ret = av_parse_time(&interval->start_ts, start, 1)) < 0) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Invalid start time specification '%s' in interval #%d\n", yading@10: start, interval_count); yading@10: goto end; yading@10: } yading@10: yading@10: if (end) { yading@10: if ((ret = av_parse_time(&interval->end_ts, end, 1)) < 0) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Invalid end time specification '%s' in interval #%d\n", yading@10: end, interval_count); yading@10: goto end; yading@10: } yading@10: } else { yading@10: interval->end_ts = INT64_MAX; yading@10: } yading@10: if (interval->end_ts < interval->start_ts) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Invalid end time '%s' in interval #%d: " yading@10: "cannot be lesser than start time '%s'\n", yading@10: end, interval_count, start); yading@10: ret = AVERROR(EINVAL); yading@10: goto end; yading@10: } yading@10: } else { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "No interval specified for interval #%d\n", interval_count); yading@10: ret = AVERROR(EINVAL); yading@10: goto end; yading@10: } yading@10: yading@10: /* parse commands */ yading@10: ret = parse_commands(&interval->commands, &interval->nb_commands, yading@10: interval_count, buf, log_ctx); yading@10: yading@10: end: yading@10: av_free(intervalstr); yading@10: return ret; yading@10: } yading@10: yading@10: static int parse_intervals(Interval **intervals, int *nb_intervals, yading@10: const char *buf, void *log_ctx) yading@10: { yading@10: int interval_count = 0; yading@10: int ret, n = 0; yading@10: yading@10: *intervals = NULL; yading@10: *nb_intervals = 0; yading@10: yading@10: while (1) { yading@10: Interval interval; yading@10: yading@10: skip_comments(&buf); yading@10: if (!(*buf)) yading@10: break; yading@10: yading@10: if ((ret = parse_interval(&interval, interval_count, &buf, log_ctx)) < 0) yading@10: return ret; yading@10: yading@10: buf += strspn(buf, SPACES); yading@10: if (*buf) { yading@10: if (*buf != ';') { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Missing terminator or extraneous data found at the end of interval #%d\n", yading@10: interval_count); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: buf++; /* skip ';' */ yading@10: } yading@10: interval_count++; yading@10: yading@10: /* (re)allocate commands array if required */ yading@10: if (*nb_intervals == n) { yading@10: n = FFMAX(16, 2*n); /* first allocation = 16, or double the number */ yading@10: *intervals = av_realloc_f(*intervals, n, 2*sizeof(Interval)); yading@10: if (!*intervals) { yading@10: av_log(log_ctx, AV_LOG_ERROR, yading@10: "Could not (re)allocate intervals array\n"); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: } yading@10: yading@10: (*intervals)[(*nb_intervals)++] = interval; yading@10: } yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static int cmp_intervals(const void *a, const void *b) yading@10: { yading@10: const Interval *i1 = a; yading@10: const Interval *i2 = b; yading@10: int64_t ts_diff = i1->start_ts - i2->start_ts; yading@10: int ret; yading@10: yading@10: ret = ts_diff > 0 ? 1 : ts_diff < 0 ? -1 : 0; yading@10: return ret == 0 ? i1->index - i2->index : ret; yading@10: } yading@10: yading@10: static av_cold int init(AVFilterContext *ctx) yading@10: { yading@10: SendCmdContext *sendcmd = ctx->priv; yading@10: int ret, i, j; yading@10: yading@10: if (sendcmd->commands_filename && sendcmd->commands_str) { yading@10: av_log(ctx, AV_LOG_ERROR, yading@10: "Only one of the filename or commands options must be specified\n"); yading@10: return AVERROR(EINVAL); yading@10: } yading@10: yading@10: if (sendcmd->commands_filename) { yading@10: uint8_t *file_buf, *buf; yading@10: size_t file_bufsize; yading@10: ret = av_file_map(sendcmd->commands_filename, yading@10: &file_buf, &file_bufsize, 0, ctx); yading@10: if (ret < 0) yading@10: return ret; yading@10: yading@10: /* create a 0-terminated string based on the read file */ yading@10: buf = av_malloc(file_bufsize + 1); yading@10: if (!buf) { yading@10: av_file_unmap(file_buf, file_bufsize); yading@10: return AVERROR(ENOMEM); yading@10: } yading@10: memcpy(buf, file_buf, file_bufsize); yading@10: buf[file_bufsize] = 0; yading@10: av_file_unmap(file_buf, file_bufsize); yading@10: sendcmd->commands_str = buf; yading@10: } yading@10: yading@10: if ((ret = parse_intervals(&sendcmd->intervals, &sendcmd->nb_intervals, yading@10: sendcmd->commands_str, ctx)) < 0) yading@10: return ret; yading@10: yading@10: qsort(sendcmd->intervals, sendcmd->nb_intervals, sizeof(Interval), cmp_intervals); yading@10: yading@10: av_log(ctx, AV_LOG_DEBUG, "Parsed commands:\n"); yading@10: for (i = 0; i < sendcmd->nb_intervals; i++) { yading@10: AVBPrint pbuf; yading@10: Interval *interval = &sendcmd->intervals[i]; yading@10: av_log(ctx, AV_LOG_VERBOSE, "start_time:%f end_time:%f index:%d\n", yading@10: (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, interval->index); yading@10: for (j = 0; j < interval->nb_commands; j++) { yading@10: Command *cmd = &interval->commands[j]; yading@10: av_log(ctx, AV_LOG_VERBOSE, yading@10: " [%s] target:%s command:%s arg:%s index:%d\n", yading@10: make_command_flags_str(&pbuf, cmd->flags), cmd->target, cmd->command, cmd->arg, cmd->index); yading@10: } yading@10: } yading@10: yading@10: return 0; yading@10: } yading@10: yading@10: static void av_cold uninit(AVFilterContext *ctx) yading@10: { yading@10: SendCmdContext *sendcmd = ctx->priv; yading@10: int i, j; yading@10: yading@10: for (i = 0; i < sendcmd->nb_intervals; i++) { yading@10: Interval *interval = &sendcmd->intervals[i]; yading@10: for (j = 0; j < interval->nb_commands; j++) { yading@10: Command *cmd = &interval->commands[j]; yading@10: av_free(cmd->target); yading@10: av_free(cmd->command); yading@10: av_free(cmd->arg); yading@10: } yading@10: av_free(interval->commands); yading@10: } yading@10: av_freep(&sendcmd->intervals); yading@10: } yading@10: yading@10: static int filter_frame(AVFilterLink *inlink, AVFrame *ref) yading@10: { yading@10: AVFilterContext *ctx = inlink->dst; yading@10: SendCmdContext *sendcmd = ctx->priv; yading@10: int64_t ts; yading@10: int i, j, ret; yading@10: yading@10: if (ref->pts == AV_NOPTS_VALUE) yading@10: goto end; yading@10: yading@10: ts = av_rescale_q(ref->pts, inlink->time_base, AV_TIME_BASE_Q); yading@10: yading@10: #define WITHIN_INTERVAL(ts, start_ts, end_ts) ((ts) >= (start_ts) && (ts) < (end_ts)) yading@10: yading@10: for (i = 0; i < sendcmd->nb_intervals; i++) { yading@10: Interval *interval = &sendcmd->intervals[i]; yading@10: int flags = 0; yading@10: yading@10: if (!interval->enabled && WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { yading@10: flags += COMMAND_FLAG_ENTER; yading@10: interval->enabled = 1; yading@10: } yading@10: if (interval->enabled && !WITHIN_INTERVAL(ts, interval->start_ts, interval->end_ts)) { yading@10: flags += COMMAND_FLAG_LEAVE; yading@10: interval->enabled = 0; yading@10: } yading@10: yading@10: if (flags) { yading@10: AVBPrint pbuf; yading@10: av_log(ctx, AV_LOG_VERBOSE, yading@10: "[%s] interval #%d start_ts:%f end_ts:%f ts:%f\n", yading@10: make_command_flags_str(&pbuf, flags), interval->index, yading@10: (double)interval->start_ts/1000000, (double)interval->end_ts/1000000, yading@10: (double)ts/1000000); yading@10: yading@10: for (j = 0; flags && j < interval->nb_commands; j++) { yading@10: Command *cmd = &interval->commands[j]; yading@10: char buf[1024]; yading@10: yading@10: if (cmd->flags & flags) { yading@10: av_log(ctx, AV_LOG_VERBOSE, yading@10: "Processing command #%d target:%s command:%s arg:%s\n", yading@10: cmd->index, cmd->target, cmd->command, cmd->arg); yading@10: ret = avfilter_graph_send_command(inlink->graph, yading@10: cmd->target, cmd->command, cmd->arg, yading@10: buf, sizeof(buf), yading@10: AVFILTER_CMD_FLAG_ONE); yading@10: av_log(ctx, AV_LOG_VERBOSE, yading@10: "Command reply for command #%d: ret:%s res:%s\n", yading@10: cmd->index, av_err2str(ret), buf); yading@10: } yading@10: } yading@10: } yading@10: } yading@10: yading@10: end: yading@10: switch (inlink->type) { yading@10: case AVMEDIA_TYPE_VIDEO: yading@10: case AVMEDIA_TYPE_AUDIO: yading@10: return ff_filter_frame(inlink->dst->outputs[0], ref); yading@10: } yading@10: yading@10: return AVERROR(ENOSYS); yading@10: } yading@10: yading@10: #if CONFIG_SENDCMD_FILTER yading@10: yading@10: #define sendcmd_options options yading@10: AVFILTER_DEFINE_CLASS(sendcmd); yading@10: yading@10: static av_cold int sendcmd_init(AVFilterContext *ctx) yading@10: { yading@10: return init(ctx); yading@10: } yading@10: yading@10: static const AVFilterPad sendcmd_inputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: .get_video_buffer = ff_null_get_video_buffer, yading@10: .filter_frame = filter_frame, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: static const AVFilterPad sendcmd_outputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_VIDEO, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFilter avfilter_vf_sendcmd = { yading@10: .name = "sendcmd", yading@10: .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), yading@10: yading@10: .init = sendcmd_init, yading@10: .uninit = uninit, yading@10: .priv_size = sizeof(SendCmdContext), yading@10: .inputs = sendcmd_inputs, yading@10: .outputs = sendcmd_outputs, yading@10: .priv_class = &sendcmd_class, yading@10: }; yading@10: yading@10: #endif yading@10: yading@10: #if CONFIG_ASENDCMD_FILTER yading@10: yading@10: #define asendcmd_options options yading@10: AVFILTER_DEFINE_CLASS(asendcmd); yading@10: yading@10: static av_cold int asendcmd_init(AVFilterContext *ctx) yading@10: { yading@10: return init(ctx); yading@10: } yading@10: yading@10: static const AVFilterPad asendcmd_inputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_AUDIO, yading@10: .get_audio_buffer = ff_null_get_audio_buffer, yading@10: .filter_frame = filter_frame, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: static const AVFilterPad asendcmd_outputs[] = { yading@10: { yading@10: .name = "default", yading@10: .type = AVMEDIA_TYPE_AUDIO, yading@10: }, yading@10: { NULL } yading@10: }; yading@10: yading@10: AVFilter avfilter_af_asendcmd = { yading@10: .name = "asendcmd", yading@10: .description = NULL_IF_CONFIG_SMALL("Send commands to filters."), yading@10: yading@10: .init = asendcmd_init, yading@10: .uninit = uninit, yading@10: .priv_size = sizeof(SendCmdContext), yading@10: .inputs = asendcmd_inputs, yading@10: .outputs = asendcmd_outputs, yading@10: .priv_class = &asendcmd_class, yading@10: }; yading@10: yading@10: #endif