@@ -169,11 +169,14 @@ typedef struct MOVStreamContext {
int64_t *chunk_offsets;
unsigned int stts_count;
MOVStts *stts_data;
+ uint32_t min_sample_duration;
unsigned int sdtp_count;
uint8_t *sdtp_data;
unsigned int ctts_count;
unsigned int ctts_allocated_size;
MOVCtts *ctts_data;
+ int32_t *sample_offsets;
+ int sample_offsets_count;
unsigned int stsc_count;
MOVStsc *stsc_data;
unsigned int stsc_index;
@@ -222,6 +225,8 @@ typedef struct MOVStreamContext {
MOVSbgp *sync_group;
uint8_t *sgpd_sync;
uint32_t sgpd_sync_count;
+ int *open_key_samples;
+ int open_key_samples_count;
int nb_frames_for_fps;
int64_t duration_for_fps;
@@ -50,6 +50,7 @@
#include "libavutil/dovi_meta.h"
#include "libavcodec/ac3tab.h"
#include "libavcodec/flac.h"
+#include "libavcodec/hevc.h"
#include "libavcodec/mpegaudiodecheader.h"
#include "libavcodec/mlp_parse.h"
#include "avformat.h"
@@ -2948,6 +2949,7 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
if (entries >= INT_MAX / sizeof(*sc->stts_data))
return AVERROR(ENOMEM);
+ sc->min_sample_duration = INT_MAX;
for (i = 0; i < entries && !pb->eof_reached; i++) {
unsigned int sample_duration;
unsigned int sample_count;
@@ -2967,6 +2969,7 @@ static int mov_read_stts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
sc->stts_data[i].count= sample_count;
sc->stts_data[i].duration= sample_duration;
+ sc->min_sample_duration = FFMIN(sc->min_sample_duration, sample_duration);
av_log(c->fc, AV_LOG_TRACE, "sample_count=%u, sample_duration=%u\n",
sample_count, sample_duration);
@@ -3069,6 +3072,7 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
AVStream *st;
MOVStreamContext *sc;
unsigned int i, entries, ctts_count = 0;
+ int32_t *sample_offsets;
if (c->fc->nb_streams < 1)
return 0;
@@ -3090,6 +3094,7 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
if (!sc->ctts_data)
return AVERROR(ENOMEM);
+ sc->sample_offsets_count = 0;
for (i = 0; i < entries && !pb->eof_reached; i++) {
int count = avio_rb32(pb);
int duration = avio_rb32(pb);
@@ -3101,8 +3106,9 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
continue;
}
- add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size,
- count, duration);
+ if (add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size,
+ count, duration) >= 0)
+ sc->sample_offsets_count += count;
av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n",
count, duration);
@@ -3127,6 +3133,15 @@ static int mov_read_ctts(MOVContext *c, AVIOContext *pb, MOVAtom atom)
av_log(c->fc, AV_LOG_TRACE, "dts shift %d\n", sc->dts_shift);
+ av_freep(&sc->sample_offsets);
+ sc->sample_offsets = av_calloc(sc->sample_offsets_count, sizeof(*sc->sample_offsets));
+ if (!sc->sample_offsets)
+ return AVERROR(ENOMEM);
+ sample_offsets = sc->sample_offsets;
+ for (int i = 0; i < sc->ctts_count; i++)
+ for (int j = 0; j < sc->ctts_data[i].count; j++)
+ *sample_offsets++ = sc->ctts_data[i].duration;
+
return 0;
}
@@ -3882,6 +3897,52 @@ static void mov_fix_index(MOVContext *mov, AVStream *st)
msc->current_index = msc->index_ranges[0].start;
}
+static uint32_t get_sgpd_sync_index(const MOVStreamContext *sc, int nal_unit_type)
+{
+ for (uint32_t i = 0; i < sc->sgpd_sync_count; i++)
+ if (sc->sgpd_sync[i] == HEVC_NAL_CRA_NUT)
+ return i + 1;
+ return 0;
+}
+
+static int build_open_gop_key_points(AVStream *st)
+{
+ int k = 0;
+ int sample_id = 0;
+ int open_sample_count = 0;
+ uint32_t cra_index;
+ MOVStreamContext *sc = st->priv_data;
+
+ if (st->codecpar->codec_id != AV_CODEC_ID_HEVC || !sc->sync_group_count)
+ return 0;
+
+ /* The following HEVC NAL type reveal the use of open GOP sync points
+ * (TODO: BLA types may also be concerned) */
+ cra_index = get_sgpd_sync_index(sc, HEVC_NAL_CRA_NUT); /* Clean Random Access */
+ if (!cra_index)
+ return 0;
+
+ for (uint32_t i = 0; i < sc->sync_group_count; i++)
+ if (sc->sync_group[i].index == cra_index)
+ open_sample_count += sc->sync_group[i].count;
+
+ av_freep(&sc->open_key_samples);
+ sc->open_key_samples_count = open_sample_count;
+ sc->open_key_samples = av_calloc(open_sample_count, sizeof(*sc->open_key_samples));
+ if (!sc->open_key_samples)
+ return AVERROR(ENOMEM);
+
+ for (uint32_t i = 0; i < sc->sync_group_count; i++) {
+ const MOVSbgp *sg = &sc->sync_group[i];
+ if (sg->index == cra_index)
+ for (uint32_t j = 0; j < sg->count; j++)
+ sc->open_key_samples[k++] = sample_id;
+ sample_id += sg->count;
+ }
+
+ return 0;
+}
+
static void mov_build_index(MOVContext *mov, AVStream *st)
{
MOVStreamContext *sc = st->priv_data;
@@ -3897,6 +3958,10 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
MOVCtts *ctts_data_old = sc->ctts_data;
unsigned int ctts_count_old = sc->ctts_count;
+ int ret = build_open_gop_key_points(st);
+ if (ret < 0)
+ return;
+
if (sc->elst_count) {
int i, edit_start_index = 0, multiple_edits = 0;
int64_t empty_duration = 0; // empty duration of the first edit list entry
@@ -7772,6 +7837,8 @@ static int mov_read_close(AVFormatContext *s)
av_freep(&sc->rap_group);
av_freep(&sc->sync_group);
av_freep(&sc->sgpd_sync);
+ av_freep(&sc->open_key_samples);
+ av_freep(&sc->sample_offsets);
av_freep(&sc->display_matrix);
av_freep(&sc->index_ranges);
@@ -8444,6 +8511,49 @@ static int mov_seek_fragment(AVFormatContext *s, AVStream *st, int64_t timestamp
return 0;
}
+static int is_open_key_sample(const MOVStreamContext *sc, int sample)
+{
+ // TODO: a bisect search would scale much better
+ for (int i = 0; i < sc->open_key_samples_count; i++) {
+ const int oks = sc->open_key_samples[i];
+ if (oks == sample)
+ return 1;
+ if (oks > sample) /* list is monotically increasing so we can stop early */
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Some key sample may be key frames but not IDR frames, so a random access to
+ * them may not be allowed.
+ */
+static int can_seek_to_key_sample(AVStream *st, int sample, int64_t requested_pts)
+{
+ MOVStreamContext *sc = st->priv_data;
+ FFStream *const sti = ffstream(st);
+ int64_t key_sample_dts, key_sample_pts;
+
+ if (st->codecpar->codec_id != AV_CODEC_ID_HEVC)
+ return 1;
+
+ if (sample >= sc->sample_offsets_count)
+ return 1;
+
+ key_sample_dts = sti->index_entries[sample].timestamp;
+ key_sample_pts = key_sample_dts + sc->sample_offsets[sample] + sc->dts_shift;
+
+ /*
+ * If the sample needs to be presented before an open key sample, they may
+ * not be decodable properly, even though they come after in decoding
+ * order.
+ */
+ if (is_open_key_sample(sc, sample) && key_sample_pts > requested_pts)
+ return 0;
+
+ return 1;
+}
+
static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp, int flags)
{
MOVStreamContext *sc = st->priv_data;
@@ -8459,12 +8569,19 @@ static int mov_seek_stream(AVFormatContext *s, AVStream *st, int64_t timestamp,
if (ret < 0)
return ret;
+ for (;;) {
sample = av_index_search_timestamp(st, timestamp, flags);
av_log(s, AV_LOG_TRACE, "stream %d, timestamp %"PRId64", sample %d\n", st->index, timestamp, sample);
if (sample < 0 && sti->nb_index_entries && timestamp < sti->index_entries[0].timestamp)
sample = 0;
if (sample < 0) /* not sure what to do */
return AVERROR_INVALIDDATA;
+
+ if (!sample || can_seek_to_key_sample(st, sample, timestamp))
+ break;
+ timestamp -= FFMAX(sc->min_sample_duration, 1);
+ }
+
mov_current_sample_set(sc, sample);
av_log(s, AV_LOG_TRACE, "stream %d, found sample %d\n", st->index, sc->current_sample);
/* adjust ctts index */