[FFmpeg-devel] [PATCH 4/4] lavf/concatdec: support seeking.

Nicolas George nicolas.george at normalesup.org
Thu Feb 14 16:30:59 CET 2013


Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---
 doc/demuxers.texi       |    3 ++
 libavformat/concatdec.c |   98 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 99 insertions(+), 2 deletions(-)


Seeking works with ffplay.
Nothing works with mplayer, since it uses lavf for demuxing but nut
protocol, and tries to do smart things at the stream level, which fail
spectacularly in this case.


diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index c8eec21..d887c46 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -71,6 +71,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
diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index a9fcc76..675445e 100644
--- a/libavformat/concatdec.c
+++ b/libavformat/concatdec.c
@@ -37,6 +37,7 @@ typedef struct {
     unsigned nb_files;
     AVFormatContext *avf;
     int safe;
+    int seekable;
 } ConcatContext;
 
 static int concat_probe(AVProbeData *probe)
@@ -122,6 +123,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);
@@ -217,8 +220,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);
@@ -251,7 +256,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);
 }
 
@@ -276,6 +280,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
 
@@ -301,5 +394,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,
 };
-- 
1.7.10.4



More information about the ffmpeg-devel mailing list