This is also useful to test seeking on an input file.
This also addresses trac ticket #1437.
... | ... |
@@ -230,6 +230,70 @@ corresponding stream section. |
230 | 230 |
Count the number of packets per stream and report it in the |
231 | 231 |
corresponding stream section. |
232 | 232 |
|
233 |
+@item -read_intervals @var{read_intervals} |
|
234 |
+ |
|
235 |
+Read only the specified intervals. @var{read_intervals} must be a |
|
236 |
+sequence of interval specifications separated by ",". |
|
237 |
+@command{ffprobe} will seek to the interval starting point, and will |
|
238 |
+continue reading from that. |
|
239 |
+ |
|
240 |
+Each interval is specified by two optional parts, separated by "%". |
|
241 |
+ |
|
242 |
+The first part specifies the interval start position. It is |
|
243 |
+interpreted as an abolute position, or as a relative offset from the |
|
244 |
+current position if it is preceded by the "+" character. If this first |
|
245 |
+part is not specified, no seeking will be performed when reading this |
|
246 |
+interval. |
|
247 |
+ |
|
248 |
+The second part specifies the interval end position. It is interpreted |
|
249 |
+as an absolute position, or as a relative offset from the current |
|
250 |
+position if it is preceded by the "+" character. If the offset |
|
251 |
+specification starts with "#", it is interpreted as the number of |
|
252 |
+packets to read (not including the flushing packets) from the interval |
|
253 |
+start. If no second part is specified, the program will read until the |
|
254 |
+end of the input. |
|
255 |
+ |
|
256 |
+Note that seeking is not accurate, thus the actual interval start |
|
257 |
+point may be different from the specified position. Also, when an |
|
258 |
+interval duration is specified, the absolute end time will be computed |
|
259 |
+by adding the duration to the interval start point found by seeking |
|
260 |
+the file, rather than to the specified start value. |
|
261 |
+ |
|
262 |
+The formal syntax is given by: |
|
263 |
+@example |
|
264 |
+@var{INTERVAL} ::= [@var{START}|+@var{START_OFFSET}][%[@var{END}|+@var{END_OFFSET}]] |
|
265 |
+@var{INTERVALS} ::= @var{INTERVAL}[,@var{INTERVALS}] |
|
266 |
+@end example |
|
267 |
+ |
|
268 |
+A few examples follow. |
|
269 |
+@itemize |
|
270 |
+@item |
|
271 |
+Seek to time 10, read packets until 20 seconds after the found seek |
|
272 |
+point, then seek to position @code{01:30} (1 minute and thirty |
|
273 |
+seconds) and read packets until position @code{01:45}. |
|
274 |
+@example |
|
275 |
+10%+20,01:30%01:45 |
|
276 |
+@end example |
|
277 |
+ |
|
278 |
+@item |
|
279 |
+Read only 42 packets after seeking to position @code{01:23}: |
|
280 |
+@example |
|
281 |
+01:23%+#42 |
|
282 |
+@end example |
|
283 |
+ |
|
284 |
+@item |
|
285 |
+Read only the first 20 seconds from the start: |
|
286 |
+@example |
|
287 |
+%+20 |
|
288 |
+@end example |
|
289 |
+ |
|
290 |
+@item |
|
291 |
+Read from the start until position @code{02:30}: |
|
292 |
+@example |
|
293 |
+%02:30 |
|
294 |
+@end example |
|
295 |
+@end itemize |
|
296 |
+ |
|
233 | 297 |
@item -show_private_data, -private |
234 | 298 |
Show private data, that is data depending on the format of the |
235 | 299 |
particular shown element. |
... | ... |
@@ -37,7 +37,9 @@ |
37 | 37 |
#include "libavutil/pixdesc.h" |
38 | 38 |
#include "libavutil/dict.h" |
39 | 39 |
#include "libavutil/libm.h" |
40 |
+#include "libavutil/parseutils.h" |
|
40 | 41 |
#include "libavutil/timecode.h" |
42 |
+#include "libavutil/timestamp.h" |
|
41 | 43 |
#include "libavdevice/avdevice.h" |
42 | 44 |
#include "libswscale/swscale.h" |
43 | 45 |
#include "libswresample/swresample.h" |
... | ... |
@@ -73,6 +75,17 @@ static int show_private_data = 1; |
73 | 73 |
static char *print_format; |
74 | 74 |
static char *stream_specifier; |
75 | 75 |
|
76 |
+typedef struct { |
|
77 |
+ int id; ///< identifier |
|
78 |
+ int64_t start, end; ///< start, end in second/AV_TIME_BASE units |
|
79 |
+ int has_start, has_end; |
|
80 |
+ int start_is_offset, end_is_offset; |
|
81 |
+ int duration_frames; |
|
82 |
+} ReadInterval; |
|
83 |
+ |
|
84 |
+static ReadInterval *read_intervals; |
|
85 |
+static int read_intervals_nb = 0; |
|
86 |
+ |
|
76 | 87 |
/* section structure definition */ |
77 | 88 |
|
78 | 89 |
#define SECTION_MAX_NB_CHILDREN 10 |
... | ... |
@@ -1593,16 +1606,93 @@ static av_always_inline int process_frame(WriterContext *w, |
1593 | 1593 |
return got_frame; |
1594 | 1594 |
} |
1595 | 1595 |
|
1596 |
-static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) |
|
1596 |
+static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level) |
|
1597 |
+{ |
|
1598 |
+ av_log(log_ctx, log_level, "id:%d", interval->id); |
|
1599 |
+ |
|
1600 |
+ if (interval->has_start) { |
|
1601 |
+ av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "", |
|
1602 |
+ av_ts2timestr(interval->start, &AV_TIME_BASE_Q)); |
|
1603 |
+ } else { |
|
1604 |
+ av_log(log_ctx, log_level, " start:N/A"); |
|
1605 |
+ } |
|
1606 |
+ |
|
1607 |
+ if (interval->has_end) { |
|
1608 |
+ av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : ""); |
|
1609 |
+ if (interval->duration_frames) |
|
1610 |
+ av_log(log_ctx, log_level, "#%"PRId64, interval->end); |
|
1611 |
+ else |
|
1612 |
+ av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q)); |
|
1613 |
+ } else { |
|
1614 |
+ av_log(log_ctx, log_level, " end:N/A"); |
|
1615 |
+ } |
|
1616 |
+ |
|
1617 |
+ av_log(log_ctx, log_level, "\n"); |
|
1618 |
+} |
|
1619 |
+ |
|
1620 |
+static int read_interval_packets(WriterContext *w, AVFormatContext *fmt_ctx, |
|
1621 |
+ const ReadInterval *interval, int64_t *cur_ts) |
|
1597 | 1622 |
{ |
1598 | 1623 |
AVPacket pkt, pkt1; |
1599 | 1624 |
AVFrame frame; |
1600 |
- int i = 0; |
|
1625 |
+ int ret = 0, i = 0, frame_count = 0; |
|
1626 |
+ int64_t start, end = interval->end; |
|
1627 |
+ int has_start = 0, has_end = interval->has_end && !interval->end_is_offset; |
|
1601 | 1628 |
|
1602 | 1629 |
av_init_packet(&pkt); |
1603 | 1630 |
|
1631 |
+ av_log(NULL, AV_LOG_VERBOSE, "Processing read interval "); |
|
1632 |
+ log_read_interval(interval, NULL, AV_LOG_VERBOSE); |
|
1633 |
+ |
|
1634 |
+ if (interval->has_start) { |
|
1635 |
+ int64_t target; |
|
1636 |
+ if (interval->start_is_offset) { |
|
1637 |
+ if (*cur_ts == AV_NOPTS_VALUE) { |
|
1638 |
+ av_log(NULL, AV_LOG_ERROR, |
|
1639 |
+ "Could not seek to relative position since current " |
|
1640 |
+ "timestamp is not defined\n"); |
|
1641 |
+ ret = AVERROR(EINVAL); |
|
1642 |
+ goto end; |
|
1643 |
+ } |
|
1644 |
+ target = *cur_ts + interval->start; |
|
1645 |
+ } else { |
|
1646 |
+ target = interval->start; |
|
1647 |
+ } |
|
1648 |
+ |
|
1649 |
+ av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n", |
|
1650 |
+ av_ts2timestr(target, &AV_TIME_BASE_Q)); |
|
1651 |
+ if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) { |
|
1652 |
+ av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n", |
|
1653 |
+ interval->start, av_err2str(ret)); |
|
1654 |
+ goto end; |
|
1655 |
+ } |
|
1656 |
+ } |
|
1657 |
+ |
|
1604 | 1658 |
while (!av_read_frame(fmt_ctx, &pkt)) { |
1605 | 1659 |
if (selected_streams[pkt.stream_index]) { |
1660 |
+ AVRational tb = fmt_ctx->streams[pkt.stream_index]->time_base; |
|
1661 |
+ |
|
1662 |
+ if (pkt.pts != AV_NOPTS_VALUE) |
|
1663 |
+ *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q); |
|
1664 |
+ |
|
1665 |
+ if (!has_start && *cur_ts != AV_NOPTS_VALUE) { |
|
1666 |
+ start = *cur_ts; |
|
1667 |
+ has_start = 1; |
|
1668 |
+ } |
|
1669 |
+ |
|
1670 |
+ if (has_start && !has_end && interval->end_is_offset) { |
|
1671 |
+ end = start + interval->end; |
|
1672 |
+ has_end = 1; |
|
1673 |
+ } |
|
1674 |
+ |
|
1675 |
+ if (interval->end_is_offset && interval->duration_frames) { |
|
1676 |
+ if (frame_count >= interval->end) |
|
1677 |
+ break; |
|
1678 |
+ } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) { |
|
1679 |
+ break; |
|
1680 |
+ } |
|
1681 |
+ |
|
1682 |
+ frame_count++; |
|
1606 | 1683 |
if (do_read_packets) { |
1607 | 1684 |
if (do_show_packets) |
1608 | 1685 |
show_packet(w, fmt_ctx, &pkt, i++); |
... | ... |
@@ -1624,6 +1714,30 @@ static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) |
1624 | 1624 |
if (do_read_frames) |
1625 | 1625 |
while (process_frame(w, fmt_ctx, &frame, &pkt) > 0); |
1626 | 1626 |
} |
1627 |
+ |
|
1628 |
+end: |
|
1629 |
+ if (ret < 0) { |
|
1630 |
+ av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval "); |
|
1631 |
+ log_read_interval(interval, NULL, AV_LOG_ERROR); |
|
1632 |
+ } |
|
1633 |
+ return ret; |
|
1634 |
+} |
|
1635 |
+ |
|
1636 |
+static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) |
|
1637 |
+{ |
|
1638 |
+ int i, ret = 0; |
|
1639 |
+ int64_t cur_ts = fmt_ctx->start_time; |
|
1640 |
+ |
|
1641 |
+ if (read_intervals_nb == 0) { |
|
1642 |
+ ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; |
|
1643 |
+ ret = read_interval_packets(w, fmt_ctx, &interval, &cur_ts); |
|
1644 |
+ } else { |
|
1645 |
+ for (i = 0; i < read_intervals_nb; i++) { |
|
1646 |
+ ret = read_interval_packets(w, fmt_ctx, &read_intervals[i], &cur_ts); |
|
1647 |
+ if (ret < 0) |
|
1648 |
+ break; |
|
1649 |
+ } |
|
1650 |
+ } |
|
1627 | 1651 |
} |
1628 | 1652 |
|
1629 | 1653 |
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) |
2229 | 2229 |
show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); |
2230 | 2230 |
} |
2231 | 2231 |
|
2232 |
+/** |
|
2233 |
+ * Parse interval specification, according to the format: |
|
2234 |
+ * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]] |
|
2235 |
+ * INTERVALS ::= INTERVAL[,INTERVALS] |
|
2236 |
+*/ |
|
2237 |
+static int parse_read_interval(const char *interval_spec, |
|
2238 |
+ ReadInterval *interval) |
|
2239 |
+{ |
|
2240 |
+ int ret = 0; |
|
2241 |
+ char *next, *p, *spec = av_strdup(interval_spec); |
|
2242 |
+ if (!spec) |
|
2243 |
+ return AVERROR(ENOMEM); |
|
2244 |
+ |
|
2245 |
+ if (!*spec) { |
|
2246 |
+ av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n"); |
|
2247 |
+ ret = AVERROR(EINVAL); |
|
2248 |
+ goto end; |
|
2249 |
+ } |
|
2250 |
+ |
|
2251 |
+ p = spec; |
|
2252 |
+ next = strchr(spec, '%'); |
|
2253 |
+ if (next) |
|
2254 |
+ *next++ = 0; |
|
2255 |
+ |
|
2256 |
+ /* parse first part */ |
|
2257 |
+ if (*p) { |
|
2258 |
+ interval->has_start = 1; |
|
2259 |
+ |
|
2260 |
+ if (*p == '+') { |
|
2261 |
+ interval->start_is_offset = 1; |
|
2262 |
+ p++; |
|
2263 |
+ } else { |
|
2264 |
+ interval->start_is_offset = 0; |
|
2265 |
+ } |
|
2266 |
+ |
|
2267 |
+ ret = av_parse_time(&interval->start, p, 1); |
|
2268 |
+ if (ret < 0) { |
|
2269 |
+ av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p); |
|
2270 |
+ goto end; |
|
2271 |
+ } |
|
2272 |
+ } else { |
|
2273 |
+ interval->has_start = 0; |
|
2274 |
+ } |
|
2275 |
+ |
|
2276 |
+ /* parse second part */ |
|
2277 |
+ p = next; |
|
2278 |
+ if (p && *p) { |
|
2279 |
+ int64_t us; |
|
2280 |
+ interval->has_end = 1; |
|
2281 |
+ |
|
2282 |
+ if (*p == '+') { |
|
2283 |
+ interval->end_is_offset = 1; |
|
2284 |
+ p++; |
|
2285 |
+ } else { |
|
2286 |
+ interval->end_is_offset = 0; |
|
2287 |
+ } |
|
2288 |
+ |
|
2289 |
+ if (interval->end_is_offset && *p == '#') { |
|
2290 |
+ long long int lli; |
|
2291 |
+ char *tail; |
|
2292 |
+ interval->duration_frames = 1; |
|
2293 |
+ p++; |
|
2294 |
+ lli = strtoll(p, &tail, 10); |
|
2295 |
+ if (*tail || lli < 0) { |
|
2296 |
+ av_log(NULL, AV_LOG_ERROR, |
|
2297 |
+ "Invalid or negative value '%s' for duration number of frames\n", p); |
|
2298 |
+ goto end; |
|
2299 |
+ } |
|
2300 |
+ interval->end = lli; |
|
2301 |
+ } else { |
|
2302 |
+ ret = av_parse_time(&us, p, 1); |
|
2303 |
+ if (ret < 0) { |
|
2304 |
+ av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p); |
|
2305 |
+ goto end; |
|
2306 |
+ } |
|
2307 |
+ interval->end = us; |
|
2308 |
+ } |
|
2309 |
+ } else { |
|
2310 |
+ interval->has_end = 0; |
|
2311 |
+ } |
|
2312 |
+ |
|
2313 |
+end: |
|
2314 |
+ av_free(spec); |
|
2315 |
+ return ret; |
|
2316 |
+} |
|
2317 |
+ |
|
2318 |
+static int parse_read_intervals(const char *intervals_spec) |
|
2319 |
+{ |
|
2320 |
+ int ret, n, i; |
|
2321 |
+ char *p, *spec = av_strdup(intervals_spec); |
|
2322 |
+ if (!spec) |
|
2323 |
+ return AVERROR(ENOMEM); |
|
2324 |
+ |
|
2325 |
+ /* preparse specification, get number of intervals */ |
|
2326 |
+ for (n = 0, p = spec; *p; p++) |
|
2327 |
+ if (*p == ',') |
|
2328 |
+ n++; |
|
2329 |
+ n++; |
|
2330 |
+ |
|
2331 |
+ read_intervals = av_malloc(n * sizeof(*read_intervals)); |
|
2332 |
+ if (!read_intervals) { |
|
2333 |
+ ret = AVERROR(ENOMEM); |
|
2334 |
+ goto end; |
|
2335 |
+ } |
|
2336 |
+ read_intervals_nb = n; |
|
2337 |
+ |
|
2338 |
+ /* parse intervals */ |
|
2339 |
+ p = spec; |
|
2340 |
+ for (i = 0; i < n; i++) { |
|
2341 |
+ char *next = strchr(p, ','); |
|
2342 |
+ if (next) |
|
2343 |
+ *next++ = 0; |
|
2344 |
+ |
|
2345 |
+ read_intervals[i].id = i; |
|
2346 |
+ ret = parse_read_interval(p, &read_intervals[i]); |
|
2347 |
+ if (ret < 0) { |
|
2348 |
+ av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n", |
|
2349 |
+ i, p); |
|
2350 |
+ goto end; |
|
2351 |
+ } |
|
2352 |
+ av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval "); |
|
2353 |
+ log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE); |
|
2354 |
+ p = next; |
|
2355 |
+ av_assert0(i <= read_intervals_nb); |
|
2356 |
+ } |
|
2357 |
+ av_assert0(i == read_intervals_nb); |
|
2358 |
+ |
|
2359 |
+end: |
|
2360 |
+ av_free(spec); |
|
2361 |
+ return ret; |
|
2362 |
+} |
|
2363 |
+ |
|
2364 |
+static int opt_read_intervals(void *optctx, const char *opt, const char *arg) |
|
2365 |
+{ |
|
2366 |
+ return parse_read_intervals(arg); |
|
2367 |
+} |
|
2368 |
+ |
|
2232 | 2369 |
static int opt_pretty(void *optctx, const char *opt, const char *arg) |
2233 | 2370 |
{ |
2234 | 2371 |
show_value_unit = 1; |
... | ... |
@@ -2327,6 +2578,7 @@ static const OptionDef real_options[] = { |
2327 | 2327 |
{ "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, |
2328 | 2328 |
{ "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, |
2329 | 2329 |
{ "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, |
2330 |
+ { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, |
|
2330 | 2331 |
{ "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" }, |
2331 | 2332 |
{ "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, |
2332 | 2333 |
{ NULL, }, |
... | ... |
@@ -2439,6 +2691,7 @@ int main(int argc, char **argv) |
2439 | 2439 |
|
2440 | 2440 |
end: |
2441 | 2441 |
av_freep(&print_format); |
2442 |
+ av_freep(&read_intervals); |
|
2442 | 2443 |
|
2443 | 2444 |
uninit_opts(); |
2444 | 2445 |
for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) |