lavf/concatdec: support seeking.
authorNicolas George <nicolas.george@normalesup.org>
Thu, 14 Feb 2013 15:00:06 +0000 (16:00 +0100)
committerNicolas George <nicolas.george@normalesup.org>
Wed, 20 Mar 2013 19:59:24 +0000 (20:59 +0100)
doc/demuxers.texi
libavformat/concatdec.c

index 0861287..fc50871 100644 (file)
@@ -78,6 +78,9 @@ Duration of the file. This information can be specified from the file;
 specifying it here may be more efficient or help if the information from the
 file is not available or accurate.
 
+If the duration is set for all files, then it is possible to seek in the
+whole concatenated video.
+
 @end table
 
 @subsection Options
index 380ad43..5359ad1 100644 (file)
@@ -37,6 +37,7 @@ typedef struct {
     unsigned nb_files;
     AVFormatContext *avf;
     int safe;
+    int seekable;
 } ConcatContext;
 
 static int concat_probe(AVProbeData *probe)
@@ -128,6 +129,8 @@ static int open_file(AVFormatContext *avf, unsigned fileno)
     ConcatFile *file = &cat->files[fileno];
     int ret;
 
+    if (cat->avf)
+        avformat_close_input(&cat->avf);
     if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 ||
         (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
         av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
@@ -223,8 +226,10 @@ static int concat_read_header(AVFormatContext *avf)
             break;
         time += cat->files[i].duration;
     }
-    if (i == cat->nb_files)
+    if (i == cat->nb_files) {
         avf->duration = time;
+        cat->seekable = 1;
+    }
 
     if ((ret = open_file(avf, 0)) < 0)
         FAIL(ret);
@@ -257,7 +262,6 @@ static int open_next_file(AVFormatContext *avf)
 
     if (++fileno >= cat->nb_files)
         return AVERROR_EOF;
-    avformat_close_input(&cat->avf);
     return open_file(avf, fileno);
 }
 
@@ -282,6 +286,95 @@ static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
     return ret;
 }
 
+static void rescale_interval(AVRational tb_in, AVRational tb_out,
+                             int64_t *min_ts, int64_t *ts, int64_t *max_ts)
+{
+    *ts     = av_rescale_q    (*    ts, tb_in, tb_out);
+    *min_ts = av_rescale_q_rnd(*min_ts, tb_in, tb_out,
+                               AV_ROUND_UP   | AV_ROUND_PASS_MINMAX);
+    *max_ts = av_rescale_q_rnd(*max_ts, tb_in, tb_out,
+                               AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX);
+}
+
+static int try_seek(AVFormatContext *avf, int stream,
+                    int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
+{
+    ConcatContext *cat = avf->priv_data;
+    int64_t t0 = cat->cur_file->start_time - cat->avf->start_time;
+
+    ts -= t0;
+    min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0;
+    max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0;
+    if (stream >= 0) {
+        if (stream >= cat->avf->nb_streams)
+            return AVERROR(EIO);
+        rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base,
+                         &min_ts, &ts, &max_ts);
+    }
+    return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags);
+}
+
+static int real_seek(AVFormatContext *avf, int stream,
+                     int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
+{
+    ConcatContext *cat = avf->priv_data;
+    int ret, left, right;
+
+    if (stream >= 0) {
+        if (stream >= avf->nb_streams)
+            return AVERROR(EINVAL);
+        rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q,
+                         &min_ts, &ts, &max_ts);
+    }
+
+    left  = 0;
+    right = cat->nb_files;
+    while (right - left > 1) {
+        int mid = (left + right) / 2;
+        if (ts < cat->files[mid].start_time)
+            right = mid;
+        else
+            left  = mid;
+    }
+
+    if ((ret = open_file(avf, left)) < 0)
+        return ret;
+
+    ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
+    if (ret < 0 && !(flags & AVSEEK_FLAG_BACKWARD) &&
+        left < cat->nb_files - 1 &&
+        cat->files[left + 1].start_time < max_ts) {
+        if ((ret = open_file(avf, left + 1)) < 0)
+            return ret;
+        ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
+    }
+    return ret;
+}
+
+static int concat_seek(AVFormatContext *avf, int stream,
+                       int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
+{
+    ConcatContext *cat = avf->priv_data;
+    ConcatFile *cur_file_saved = cat->cur_file;
+    AVFormatContext *cur_avf_saved = cat->avf;
+    int ret;
+
+    if (!cat->seekable)
+        return AVERROR(ESPIPE); /* XXX: can we use it? */
+    if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
+        return AVERROR(ENOSYS);
+    cat->avf = NULL;
+    if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags)) < 0) {
+        if (cat->avf)
+            avformat_close_input(&cat->avf);
+        cat->avf      = cur_avf_saved;
+        cat->cur_file = cur_file_saved;
+    } else {
+        avformat_close_input(&cur_avf_saved);
+    }
+    return ret;
+}
+
 #define OFFSET(x) offsetof(ConcatContext, x)
 #define DEC AV_OPT_FLAG_DECODING_PARAM
 
@@ -307,5 +400,6 @@ AVInputFormat ff_concat_demuxer = {
     .read_header    = concat_read_header,
     .read_packet    = concat_read_packet,
     .read_close     = concat_read_close,
+    .read_seek2     = concat_seek,
     .priv_class     = &concat_class,
 };