Browse code

ffprobe: add -read_intervals option

This is also useful to test seeking on an input file.

This also addresses trac ticket #1437.

Stefano Sabatini authored on 2012/10/22 23:01:29
Showing 3 changed files
... ...
@@ -25,6 +25,7 @@ version <next>
25 25
   more consistent with other muxers.
26 26
 - adelay filter
27 27
 - pullup filter ported from libmpcodecs
28
+- ffprobe -read_intervals option
28 29
 
29 30
 
30 31
 version 2.0:
... ...
@@ -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++)