mirror of
https://github.com/FFmpeg/FFmpeg.git
synced 2024-11-21 10:55:51 +02:00
ffprobe: add -read_intervals option
This is also useful to test seeking on an input file. This also addresses trac ticket #1437.
This commit is contained in:
parent
6230a95659
commit
f0606a28de
@ -25,6 +25,7 @@ version <next>
|
||||
more consistent with other muxers.
|
||||
- adelay filter
|
||||
- pullup filter ported from libmpcodecs
|
||||
- ffprobe -read_intervals option
|
||||
|
||||
|
||||
version 2.0:
|
||||
|
@ -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.
|
||||
|
257
ffprobe.c
257
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++)
|
||||
|
Loading…
Reference in New Issue
Block a user