aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas George <nicolas.george@normalesup.org>2013-02-14 16:00:06 +0100
committerNicolas George <nicolas.george@normalesup.org>2013-03-20 20:59:24 +0100
commitde1a71595de778000611abc0ee3941df2248a990 (patch)
treee8dde9e54c5066a895a51918d633b8c722a8ae15
parent710cd0fddf39d4eea68f7dcdd3e98da179a4cd7b (diff)
downloadffmpeg-de1a71595de778000611abc0ee3941df2248a990.tar.gz
lavf/concatdec: support seeking.
-rw-r--r--doc/demuxers.texi3
-rw-r--r--libavformat/concatdec.c98
2 files changed, 99 insertions, 2 deletions
diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 0861287e76..fc5087188f 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -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
diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c
index 380ad4383f..5359ad149d 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)
@@ -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,
};