diff --git a/Changelog b/Changelog index 3e4653b746..24fe407719 100644 --- a/Changelog +++ b/Changelog @@ -25,6 +25,7 @@ version more consistent with other muxers. - adelay filter - pullup filter ported from libmpcodecs +- ffprobe -read_intervals option version 2.0: diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi index 20f5f4acc7..777dbe7506 100644 --- a/doc/ffprobe.texi +++ b/doc/ffprobe.texi @@ -230,6 +230,70 @@ corresponding stream section. Count the number of packets per stream and report it in the corresponding stream section. +@item -read_intervals @var{read_intervals} + +Read only the specified intervals. @var{read_intervals} must be a +sequence of interval specifications separated by ",". +@command{ffprobe} will seek to the interval starting point, and will +continue reading from that. + +Each interval is specified by two optional parts, separated by "%". + +The first part specifies the interval start position. It is +interpreted as an abolute position, or as a relative offset from the +current position if it is preceded by the "+" character. If this first +part is not specified, no seeking will be performed when reading this +interval. + +The second part specifies the interval end position. It is interpreted +as an absolute position, or as a relative offset from the current +position if it is preceded by the "+" character. If the offset +specification starts with "#", it is interpreted as the number of +packets to read (not including the flushing packets) from the interval +start. If no second part is specified, the program will read until the +end of the input. + +Note that seeking is not accurate, thus the actual interval start +point may be different from the specified position. Also, when an +interval duration is specified, the absolute end time will be computed +by adding the duration to the interval start point found by seeking +the file, rather than to the specified start value. + +The formal syntax is given by: +@example +@var{INTERVAL} ::= [@var{START}|+@var{START_OFFSET}][%[@var{END}|+@var{END_OFFSET}]] +@var{INTERVALS} ::= @var{INTERVAL}[,@var{INTERVALS}] +@end example + +A few examples follow. +@itemize +@item +Seek to time 10, read packets until 20 seconds after the found seek +point, then seek to position @code{01:30} (1 minute and thirty +seconds) and read packets until position @code{01:45}. +@example +10%+20,01:30%01:45 +@end example + +@item +Read only 42 packets after seeking to position @code{01:23}: +@example +01:23%+#42 +@end example + +@item +Read only the first 20 seconds from the start: +@example +%+20 +@end example + +@item +Read from the start until position @code{02:30}: +@example +%02:30 +@end example +@end itemize + @item -show_private_data, -private Show private data, that is data depending on the format of the particular shown element. diff --git a/ffprobe.c b/ffprobe.c index 54fef04f0d..bbcdc975e5 100644 --- a/ffprobe.c +++ b/ffprobe.c @@ -37,7 +37,9 @@ #include "libavutil/pixdesc.h" #include "libavutil/dict.h" #include "libavutil/libm.h" +#include "libavutil/parseutils.h" #include "libavutil/timecode.h" +#include "libavutil/timestamp.h" #include "libavdevice/avdevice.h" #include "libswscale/swscale.h" #include "libswresample/swresample.h" @@ -73,6 +75,17 @@ static int show_private_data = 1; static char *print_format; static char *stream_specifier; +typedef struct { + int id; ///< identifier + int64_t start, end; ///< start, end in second/AV_TIME_BASE units + int has_start, has_end; + int start_is_offset, end_is_offset; + int duration_frames; +} ReadInterval; + +static ReadInterval *read_intervals; +static int read_intervals_nb = 0; + /* section structure definition */ #define SECTION_MAX_NB_CHILDREN 10 @@ -1593,16 +1606,93 @@ static av_always_inline int process_frame(WriterContext *w, return got_frame; } -static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) +static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level) +{ + av_log(log_ctx, log_level, "id:%d", interval->id); + + if (interval->has_start) { + av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "", + av_ts2timestr(interval->start, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " start:N/A"); + } + + if (interval->has_end) { + av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : ""); + if (interval->duration_frames) + av_log(log_ctx, log_level, "#%"PRId64, interval->end); + else + av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q)); + } else { + av_log(log_ctx, log_level, " end:N/A"); + } + + av_log(log_ctx, log_level, "\n"); +} + +static int read_interval_packets(WriterContext *w, AVFormatContext *fmt_ctx, + const ReadInterval *interval, int64_t *cur_ts) { AVPacket pkt, pkt1; AVFrame frame; - int i = 0; + int ret = 0, i = 0, frame_count = 0; + int64_t start, end = interval->end; + int has_start = 0, has_end = interval->has_end && !interval->end_is_offset; av_init_packet(&pkt); + av_log(NULL, AV_LOG_VERBOSE, "Processing read interval "); + log_read_interval(interval, NULL, AV_LOG_VERBOSE); + + if (interval->has_start) { + int64_t target; + if (interval->start_is_offset) { + if (*cur_ts == AV_NOPTS_VALUE) { + av_log(NULL, AV_LOG_ERROR, + "Could not seek to relative position since current " + "timestamp is not defined\n"); + ret = AVERROR(EINVAL); + goto end; + } + target = *cur_ts + interval->start; + } else { + target = interval->start; + } + + av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n", + av_ts2timestr(target, &AV_TIME_BASE_Q)); + if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n", + interval->start, av_err2str(ret)); + goto end; + } + } + while (!av_read_frame(fmt_ctx, &pkt)) { if (selected_streams[pkt.stream_index]) { + AVRational tb = fmt_ctx->streams[pkt.stream_index]->time_base; + + if (pkt.pts != AV_NOPTS_VALUE) + *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q); + + if (!has_start && *cur_ts != AV_NOPTS_VALUE) { + start = *cur_ts; + has_start = 1; + } + + if (has_start && !has_end && interval->end_is_offset) { + end = start + interval->end; + has_end = 1; + } + + if (interval->end_is_offset && interval->duration_frames) { + if (frame_count >= interval->end) + break; + } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) { + break; + } + + frame_count++; if (do_read_packets) { if (do_show_packets) show_packet(w, fmt_ctx, &pkt, i++); @@ -1624,6 +1714,30 @@ static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) if (do_read_frames) while (process_frame(w, fmt_ctx, &frame, &pkt) > 0); } + +end: + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval "); + log_read_interval(interval, NULL, AV_LOG_ERROR); + } + return ret; +} + +static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) +{ + int i, ret = 0; + int64_t cur_ts = fmt_ctx->start_time; + + if (read_intervals_nb == 0) { + ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; + ret = read_interval_packets(w, fmt_ctx, &interval, &cur_ts); + } else { + for (i = 0; i < read_intervals_nb; i++) { + ret = read_interval_packets(w, fmt_ctx, &read_intervals[i], &cur_ts); + if (ret < 0) + break; + } + } } static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program) @@ -2229,6 +2343,143 @@ void show_help_default(const char *opt, const char *arg) show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); } +/** + * Parse interval specification, according to the format: + * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]] + * INTERVALS ::= INTERVAL[,INTERVALS] +*/ +static int parse_read_interval(const char *interval_spec, + ReadInterval *interval) +{ + int ret = 0; + char *next, *p, *spec = av_strdup(interval_spec); + if (!spec) + return AVERROR(ENOMEM); + + if (!*spec) { + av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n"); + ret = AVERROR(EINVAL); + goto end; + } + + p = spec; + next = strchr(spec, '%'); + if (next) + *next++ = 0; + + /* parse first part */ + if (*p) { + interval->has_start = 1; + + if (*p == '+') { + interval->start_is_offset = 1; + p++; + } else { + interval->start_is_offset = 0; + } + + ret = av_parse_time(&interval->start, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p); + goto end; + } + } else { + interval->has_start = 0; + } + + /* parse second part */ + p = next; + if (p && *p) { + int64_t us; + interval->has_end = 1; + + if (*p == '+') { + interval->end_is_offset = 1; + p++; + } else { + interval->end_is_offset = 0; + } + + if (interval->end_is_offset && *p == '#') { + long long int lli; + char *tail; + interval->duration_frames = 1; + p++; + lli = strtoll(p, &tail, 10); + if (*tail || lli < 0) { + av_log(NULL, AV_LOG_ERROR, + "Invalid or negative value '%s' for duration number of frames\n", p); + goto end; + } + interval->end = lli; + } else { + ret = av_parse_time(&us, p, 1); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p); + goto end; + } + interval->end = us; + } + } else { + interval->has_end = 0; + } + +end: + av_free(spec); + return ret; +} + +static int parse_read_intervals(const char *intervals_spec) +{ + int ret, n, i; + char *p, *spec = av_strdup(intervals_spec); + if (!spec) + return AVERROR(ENOMEM); + + /* preparse specification, get number of intervals */ + for (n = 0, p = spec; *p; p++) + if (*p == ',') + n++; + n++; + + read_intervals = av_malloc(n * sizeof(*read_intervals)); + if (!read_intervals) { + ret = AVERROR(ENOMEM); + goto end; + } + read_intervals_nb = n; + + /* parse intervals */ + p = spec; + for (i = 0; i < n; i++) { + char *next = strchr(p, ','); + if (next) + *next++ = 0; + + read_intervals[i].id = i; + ret = parse_read_interval(p, &read_intervals[i]); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n", + i, p); + goto end; + } + av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval "); + log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE); + p = next; + av_assert0(i <= read_intervals_nb); + } + av_assert0(i == read_intervals_nb); + +end: + av_free(spec); + return ret; +} + +static int opt_read_intervals(void *optctx, const char *opt, const char *arg) +{ + return parse_read_intervals(arg); +} + static int opt_pretty(void *optctx, const char *opt, const char *arg) { show_value_unit = 1; @@ -2327,6 +2578,7 @@ static const OptionDef real_options[] = { { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, { "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, + { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" }, { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, { NULL, }, @@ -2439,6 +2691,7 @@ int main(int argc, char **argv) end: av_freep(&print_format); + av_freep(&read_intervals); uninit_opts(); for (i = 0; i < FF_ARRAY_ELEMS(sections); i++)