static int mov_read_default(MOVContext *c, AVIOContext *pb, MOVAtom atom);
static int mov_read_mfra(MOVContext *c, AVIOContext *f);
+static int64_t add_ctts_entry(MOVStts** ctts_data, unsigned int* ctts_count, unsigned int* allocated_size,
+ int count, int duration);
static int mov_metadata_track_or_disc_number(MOVContext *c, AVIOContext *pb,
unsigned len, const char *key)
init_get_bits(&gb, buf, 8*ddts_size);
if (c->fc->nb_streams < 1) {
+ av_free(buf);
return 0;
}
st = c->fc->streams[c->fc->nb_streams-1];
st->codecpar->sample_rate = get_bits_long(&gb, 32);
if (st->codecpar->sample_rate <= 0) {
av_log(c->fc, AV_LOG_ERROR, "Invalid sample rate %d\n", st->codecpar->sample_rate);
+ av_free(buf);
return AVERROR_INVALIDDATA;
}
skip_bits_long(&gb, 32); /* max bitrate */
((channel_layout_code & 0x8) ? AV_CH_LOW_FREQUENCY : 0);
st->codecpar->channels = av_get_channel_layout_nb_channels(st->codecpar->channel_layout);
+ av_free(buf);
return 0;
}
sc->time_scale = avio_rb32(pb);
if (sc->time_scale <= 0) {
- av_log(c->fc, AV_LOG_ERROR, "Invalid mdhd time scale %d\n", sc->time_scale);
- return AVERROR_INVALIDDATA;
+ av_log(c->fc, AV_LOG_ERROR, "Invalid mdhd time scale %d, defaulting to 1\n", sc->time_scale);
+ sc->time_scale = 1;
}
st->duration = (version == 1) ? avio_rb64(pb) : avio_rb32(pb); /* duration */
mov_metadata_creation_time(&c->fc->metadata, creation_time);
c->time_scale = avio_rb32(pb); /* time scale */
if (c->time_scale <= 0) {
- av_log(c->fc, AV_LOG_ERROR, "Invalid mvhd time scale %d\n", c->time_scale);
- return AVERROR_INVALIDDATA;
+ av_log(c->fc, AV_LOG_ERROR, "Invalid mvhd time scale %d, defaulting to 1\n", c->time_scale);
+ c->time_scale = 1;
}
av_log(c->fc, AV_LOG_TRACE, "time scale = %i\n", c->time_scale);
{
AVStream *st;
MOVStreamContext *sc;
- unsigned int i, entries, ctts_count = 0;
+ unsigned int i, j, entries, ctts_count = 0;
if (c->fc->nb_streams < 1)
return 0;
if (entries >= UINT_MAX / sizeof(*sc->ctts_data))
return AVERROR_INVALIDDATA;
av_freep(&sc->ctts_data);
- sc->ctts_data = av_realloc(NULL, entries * sizeof(*sc->ctts_data));
+ sc->ctts_data = av_fast_realloc(NULL, &sc->ctts_allocated_size, entries * sizeof(*sc->ctts_data));
if (!sc->ctts_data)
return AVERROR(ENOMEM);
continue;
}
- sc->ctts_data[ctts_count].count = count;
- sc->ctts_data[ctts_count].duration = duration;
- ctts_count++;
+ /* Expand entries such that we have a 1-1 mapping with samples. */
+ for (j = 0; j < count; j++)
+ add_ctts_entry(&sc->ctts_data, &ctts_count, &sc->ctts_allocated_size, 1, duration);
av_log(c->fc, AV_LOG_TRACE, "count=%d, duration=%d\n",
count, duration);
int64_t edit_list_dts_entry_end = 0;
int64_t edit_list_start_ctts_sample = 0;
int64_t curr_cts;
+ int64_t curr_ctts = 0;
+ int64_t min_corrected_pts = -1;
+ int64_t empty_edits_sum_duration = 0;
int64_t edit_list_index = 0;
int64_t index;
int64_t index_ctts_count;
int flags;
- unsigned int ctts_allocated_size = 0;
int64_t start_dts = 0;
int64_t edit_list_media_time_dts = 0;
int64_t edit_list_start_encountered = 0;
int first_non_zero_audio_edit = -1;
int packet_skip_samples = 0;
MOVIndexRange *current_index_range;
+ int i;
if (!msc->elst_data || msc->elst_count <= 0 || nb_old <= 0) {
return;
msc->ctts_count = 0;
msc->ctts_index = 0;
msc->ctts_sample = 0;
+ msc->ctts_allocated_size = 0;
// If the dts_shift is positive (in case of negative ctts values in mov),
// then negate the DTS by dts_shift
- if (msc->dts_shift > 0)
+ if (msc->dts_shift > 0) {
edit_list_dts_entry_end -= msc->dts_shift;
-
- // Offset the DTS by ctts[0] to make the PTS of the first frame 0
- if (ctts_data_old && ctts_count_old > 0) {
- edit_list_dts_entry_end -= ctts_data_old[0].duration;
- av_log(mov->fc, AV_LOG_DEBUG, "Offset DTS by ctts[%d].duration: %d\n", 0, ctts_data_old[0].duration);
+ av_log(mov->fc, AV_LOG_DEBUG, "Shifting DTS by %d because of negative CTTS.\n", msc->dts_shift);
}
start_dts = edit_list_dts_entry_end;
edit_list_dts_entry_end += edit_list_duration;
num_discarded_begin = 0;
if (edit_list_media_time == -1) {
+ empty_edits_sum_duration += edit_list_duration;
continue;
}
// frames (pts) before or after edit list
curr_cts = current->timestamp + msc->dts_shift;
+ curr_ctts = 0;
if (ctts_data_old && ctts_index_old < ctts_count_old) {
- av_log(mov->fc, AV_LOG_DEBUG, "shifted frame pts, curr_cts: %"PRId64" @ %"PRId64", ctts: %d, ctts_count: %"PRId64"\n",
- curr_cts, ctts_index_old, ctts_data_old[ctts_index_old].duration, ctts_count_old);
- curr_cts += ctts_data_old[ctts_index_old].duration;
+ curr_ctts = ctts_data_old[ctts_index_old].duration;
+ av_log(mov->fc, AV_LOG_DEBUG, "stts: %"PRId64" ctts: %"PRId64", ctts_index: %"PRId64", ctts_count: %"PRId64"\n",
+ curr_cts, curr_ctts, ctts_index_old, ctts_count_old);
+ curr_cts += curr_ctts;
ctts_sample_old++;
if (ctts_sample_old == ctts_data_old[ctts_index_old].count) {
if (add_ctts_entry(&msc->ctts_data, &msc->ctts_count,
- &ctts_allocated_size,
+ &msc->ctts_allocated_size,
ctts_data_old[ctts_index_old].count - edit_list_start_ctts_sample,
ctts_data_old[ctts_index_old].duration) == -1) {
av_log(mov->fc, AV_LOG_ERROR, "Cannot add CTTS entry %"PRId64" - {%"PRId64", %d}\n",
}
}
}
- } else if (edit_list_start_encountered == 0) {
- edit_list_start_encountered = 1;
- // Make timestamps strictly monotonically increasing for audio, by rewriting timestamps for
- // discarded packets.
- if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && frame_duration_buffer) {
- fix_index_entry_timestamps(st, st->nb_index_entries, edit_list_dts_counter,
- frame_duration_buffer, num_discarded_begin);
- av_freep(&frame_duration_buffer);
+ } else {
+ if (min_corrected_pts < 0) {
+ min_corrected_pts = edit_list_dts_counter + curr_ctts + msc->dts_shift;
+ } else {
+ min_corrected_pts = FFMIN(min_corrected_pts, edit_list_dts_counter + curr_ctts + msc->dts_shift);
+ }
+ if (edit_list_start_encountered == 0) {
+ edit_list_start_encountered = 1;
+ // Make timestamps strictly monotonically increasing for audio, by rewriting timestamps for
+ // discarded packets.
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && frame_duration_buffer) {
+ fix_index_entry_timestamps(st, st->nb_index_entries, edit_list_dts_counter,
+ frame_duration_buffer, num_discarded_begin);
+ av_freep(&frame_duration_buffer);
+ }
}
}
if (ctts_data_old && ctts_sample_old != 0) {
if (add_ctts_entry(&msc->ctts_data, &msc->ctts_count,
- &ctts_allocated_size,
+ &msc->ctts_allocated_size,
ctts_sample_old - edit_list_start_ctts_sample,
ctts_data_old[ctts_index_old].duration) == -1) {
av_log(mov->fc, AV_LOG_ERROR, "Cannot add CTTS entry %"PRId64" - {%"PRId64", %d}\n",
}
}
}
+ // If there are empty edits, then min_corrected_pts might be positive intentionally. So we subtract the
+ // sum duration of emtpy edits here.
+ min_corrected_pts -= empty_edits_sum_duration;
+
+ // If the minimum pts turns out to be greater than zero after fixing the index, then we subtract the
+ // dts by that amount to make the first pts zero.
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && min_corrected_pts > 0) {
+ av_log(mov->fc, AV_LOG_DEBUG, "Offset DTS by %"PRId64" to make first pts zero.\n", min_corrected_pts);
+ for (i = 0; i < st->nb_index_entries; ++i) {
+ st->index_entries[i].timestamp -= min_corrected_pts;
+ }
+ }
+
// Update av stream length
st->duration = edit_list_dts_entry_end - start_dts;
c->trak_index = -1;
/* sanity checks */
- if (sc->chunk_count && (!sc->stts_count || !sc->stsc_count ||
- (!sc->sample_size && !sc->sample_count))) {
+ if ((sc->chunk_count && (!sc->stts_count || !sc->stsc_count ||
+ (!sc->sample_size && !sc->sample_count))) ||
+ (!sc->chunk_count && sc->sample_count)) {
av_log(c->fc, AV_LOG_ERROR, "stream %d, missing mandatory atoms, broken header\n",
st->index);
return 0;
int64_t dts;
int data_offset = 0;
unsigned entries, first_sample_flags = frag->flags;
- int flags, distance, i, err;
+ int flags, distance, i;
for (i = 0; i < c->fc->nb_streams; i++) {
if (c->fc->streams[i]->id == frag->track_id) {
entries = avio_rb32(pb);
av_log(c->fc, AV_LOG_TRACE, "flags 0x%x entries %u\n", flags, entries);
- /* Always assume the presence of composition time offsets.
- * Without this assumption, for instance, we cannot deal with a track in fragmented movies that meet the following.
- * 1) in the initial movie, there are no samples.
- * 2) in the first movie fragment, there is only one sample without composition time offset.
- * 3) in the subsequent movie fragments, there are samples with composition time offset. */
- if (!sc->ctts_count && sc->sample_count)
- {
- /* Complement ctts table if moov atom doesn't have ctts atom. */
- ctts_data = av_realloc(NULL, sizeof(*sc->ctts_data));
- if (!ctts_data)
- return AVERROR(ENOMEM);
- sc->ctts_data = ctts_data;
- sc->ctts_data[sc->ctts_count].count = sc->sample_count;
- sc->ctts_data[sc->ctts_count].duration = 0;
- sc->ctts_count++;
- }
if ((uint64_t)entries+sc->ctts_count >= UINT_MAX/sizeof(*sc->ctts_data))
return AVERROR_INVALIDDATA;
- if ((err = av_reallocp_array(&sc->ctts_data, entries + sc->ctts_count,
- sizeof(*sc->ctts_data))) < 0) {
- sc->ctts_count = 0;
- return err;
- }
if (flags & MOV_TRUN_DATA_OFFSET) data_offset = avio_rb32(pb);
if (flags & MOV_TRUN_FIRST_SAMPLE_FLAGS) first_sample_flags = avio_rb32(pb);
dts = sc->track_end - sc->time_offset;
unsigned sample_size = frag->size;
int sample_flags = i ? frag->flags : first_sample_flags;
unsigned sample_duration = frag->duration;
+ unsigned ctts_duration = 0;
int keyframe = 0;
+ int ctts_index = 0;
+ int old_nb_index_entries = st->nb_index_entries;
if (flags & MOV_TRUN_SAMPLE_DURATION) sample_duration = avio_rb32(pb);
if (flags & MOV_TRUN_SAMPLE_SIZE) sample_size = avio_rb32(pb);
if (flags & MOV_TRUN_SAMPLE_FLAGS) sample_flags = avio_rb32(pb);
- sc->ctts_data[sc->ctts_count].count = 1;
- sc->ctts_data[sc->ctts_count].duration = (flags & MOV_TRUN_SAMPLE_CTS) ?
- avio_rb32(pb) : 0;
- mov_update_dts_shift(sc, sc->ctts_data[sc->ctts_count].duration);
+ if (flags & MOV_TRUN_SAMPLE_CTS) ctts_duration = avio_rb32(pb);
+
+ mov_update_dts_shift(sc, ctts_duration);
if (frag->time != AV_NOPTS_VALUE) {
if (c->use_mfra_for == FF_MOV_FLAG_MFRA_PTS) {
int64_t pts = frag->time;
av_log(c->fc, AV_LOG_DEBUG, "found frag time %"PRId64
" sc->dts_shift %d ctts.duration %d"
" sc->time_offset %"PRId64" flags & MOV_TRUN_SAMPLE_CTS %d\n", pts,
- sc->dts_shift, sc->ctts_data[sc->ctts_count].duration,
+ sc->dts_shift, ctts_duration,
sc->time_offset, flags & MOV_TRUN_SAMPLE_CTS);
dts = pts - sc->dts_shift;
if (flags & MOV_TRUN_SAMPLE_CTS) {
- dts -= sc->ctts_data[sc->ctts_count].duration;
+ dts -= ctts_duration;
} else {
dts -= sc->time_offset;
}
}
frag->time = AV_NOPTS_VALUE;
}
- sc->ctts_count++;
+
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
keyframe = 1;
else
MOV_FRAG_SAMPLE_FLAG_DEPENDS_YES));
if (keyframe)
distance = 0;
- err = av_add_index_entry(st, offset, dts, sample_size, distance,
- keyframe ? AVINDEX_KEYFRAME : 0);
- if (err < 0) {
+ ctts_index = av_add_index_entry(st, offset, dts, sample_size, distance,
+ keyframe ? AVINDEX_KEYFRAME : 0);
+ if (ctts_index >= 0 && old_nb_index_entries < st->nb_index_entries) {
+ unsigned int size_needed = st->nb_index_entries * sizeof(*sc->ctts_data);
+ unsigned int request_size = size_needed > sc->ctts_allocated_size ?
+ FFMAX(size_needed, 2 * sc->ctts_allocated_size) : size_needed;
+ unsigned int old_ctts_size = sc->ctts_allocated_size;
+ ctts_data = av_fast_realloc(sc->ctts_data, &sc->ctts_allocated_size, request_size);
+ if (!ctts_data) {
+ av_freep(&sc->ctts_data);
+ return AVERROR(ENOMEM);
+ }
+ sc->ctts_data = ctts_data;
+
+ // In case there were samples without ctts entries, ensure they get
+ // zero valued entries. This ensures clips which mix boxes with and
+ // without ctts entries don't pickup uninitialized data.
+ memset((uint8_t*)(sc->ctts_data) + old_ctts_size, 0, sc->ctts_allocated_size - old_ctts_size);
+
+ if (ctts_index != old_nb_index_entries) {
+ memmove(sc->ctts_data + ctts_index + 1, sc->ctts_data + ctts_index,
+ sizeof(*sc->ctts_data) * (sc->ctts_count - ctts_index));
+ if (ctts_index <= sc->current_sample) {
+ // if we inserted a new item before the current sample, move the
+ // counter ahead so it is still pointing to the same sample.
+ sc->current_sample++;
+ }
+ }
+
+ sc->ctts_data[ctts_index].count = 1;
+ sc->ctts_data[ctts_index].duration = ctts_duration;
+ sc->ctts_count++;
+ } else {
av_log(c->fc, AV_LOG_ERROR, "Failed to add index entry\n");
}
- av_log(c->fc, AV_LOG_TRACE, "AVIndex stream %d, sample %u, offset %"PRIx64", dts %"PRId64", "
- "size %u, distance %d, keyframe %d\n", st->index, sc->sample_count+i,
+
+ av_log(c->fc, AV_LOG_TRACE, "AVIndex stream %d, sample %d, offset %"PRIx64", dts %"PRId64", "
+ "size %u, distance %d, keyframe %d\n", st->index, ctts_index,
offset, dts, sample_size, distance, keyframe);
distance++;
dts += sample_duration;
return 0;
}
+static int mov_read_vpcc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+ int version, color_range, color_primaries, color_trc, color_space;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+ st = c->fc->streams[c->fc->nb_streams - 1];
+
+ if (atom.size < 5) {
+ av_log(c->fc, AV_LOG_ERROR, "Empty VP Codec Configuration box\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ version = avio_r8(pb);
+ if (version != 1) {
+ av_log(c->fc, AV_LOG_WARNING, "Unsupported VP Codec Configuration box version %d\n", version);
+ return 0;
+ }
+ avio_skip(pb, 3); /* flags */
+
+ avio_skip(pb, 2); /* profile + level */
+ color_range = avio_r8(pb); /* bitDepth, chromaSubsampling, videoFullRangeFlag */
+ color_primaries = avio_r8(pb);
+ color_trc = avio_r8(pb);
+ color_space = avio_r8(pb);
+ if (avio_rb16(pb)) /* codecIntializationDataSize */
+ return AVERROR_INVALIDDATA;
+
+ if (!av_color_primaries_name(color_primaries))
+ color_primaries = AVCOL_PRI_UNSPECIFIED;
+ if (!av_color_transfer_name(color_trc))
+ color_trc = AVCOL_TRC_UNSPECIFIED;
+ if (!av_color_space_name(color_space))
+ color_space = AVCOL_SPC_UNSPECIFIED;
+
+ st->codecpar->color_range = (color_range & 1) ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG;
+ st->codecpar->color_primaries = color_primaries;
+ st->codecpar->color_trc = color_trc;
+ st->codecpar->color_space = color_space;
+
+ return 0;
+}
+
+static int mov_read_smdm(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ MOVStreamContext *sc;
+ const int chroma_den = 50000;
+ const int luma_den = 10000;
+ int i, j, version;
+
+ if (c->fc->nb_streams < 1)
+ return AVERROR_INVALIDDATA;
+
+ sc = c->fc->streams[c->fc->nb_streams - 1]->priv_data;
+
+ if (atom.size < 5) {
+ av_log(c->fc, AV_LOG_ERROR, "Empty Mastering Display Metadata box\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ version = avio_r8(pb);
+ if (version) {
+ av_log(c->fc, AV_LOG_WARNING, "Unsupported Mastering Display Metadata box version %d\n", version);
+ return 0;
+ }
+ avio_skip(pb, 3); /* flags */
+
+ sc->mastering = av_mastering_display_metadata_alloc();
+ if (!sc->mastering)
+ return AVERROR(ENOMEM);
+
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < 2; j++)
+ sc->mastering->display_primaries[i][j] =
+ av_make_q(lrint(((double)avio_rb16(pb) / (1 << 16)) * chroma_den), chroma_den);
+ for (i = 0; i < 2; i++)
+ sc->mastering->white_point[i] =
+ av_make_q(lrint(((double)avio_rb16(pb) / (1 << 16)) * chroma_den), chroma_den);
+ sc->mastering->max_luminance =
+ av_make_q(lrint(((double)avio_rb32(pb) / (1 << 8)) * luma_den), luma_den);
+ sc->mastering->min_luminance =
+ av_make_q(lrint(((double)avio_rb32(pb) / (1 << 14)) * luma_den), luma_den);
+
+ sc->mastering->has_primaries = 1;
+ sc->mastering->has_luminance = 1;
+
+ return 0;
+}
+
+static int mov_read_coll(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ MOVStreamContext *sc;
+ int version;
+
+ if (c->fc->nb_streams < 1)
+ return AVERROR_INVALIDDATA;
+
+ sc = c->fc->streams[c->fc->nb_streams - 1]->priv_data;
+
+ if (atom.size < 5) {
+ av_log(c->fc, AV_LOG_ERROR, "Empty Content Light Level box\n");
+ return AVERROR_INVALIDDATA;
+ }
+
+ version = avio_r8(pb);
+ if (version) {
+ av_log(c->fc, AV_LOG_WARNING, "Unsupported Content Light Level box version %d\n", version);
+ return 0;
+ }
+ avio_skip(pb, 3); /* flags */
+
+ sc->coll = av_content_light_metadata_alloc(&sc->coll_size);
+ if (!sc->coll)
+ return AVERROR(ENOMEM);
+
+ sc->coll->MaxCLL = avio_rb16(pb);
+ sc->coll->MaxFALL = avio_rb16(pb);
+
+ return 0;
+}
+
static int mov_read_st3d(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
AVStream *st;
{ MKTAG('s','t','3','d'), mov_read_st3d }, /* stereoscopic 3D video box */
{ MKTAG('s','v','3','d'), mov_read_sv3d }, /* spherical video box */
{ MKTAG('d','O','p','s'), mov_read_dops },
+{ MKTAG('S','m','D','m'), mov_read_smdm },
+{ MKTAG('C','o','L','L'), mov_read_coll },
+{ MKTAG('v','p','c','C'), mov_read_vpcc },
{ 0, NULL }
};
if (atom.size < 0)
atom.size = INT64_MAX;
- while (total_size + 8 <= atom.size && !avio_feof(pb)) {
+ while (total_size <= atom.size - 8 && !avio_feof(pb)) {
int (*parse)(MOVContext*, AVIOContext*, MOVAtom) = NULL;
a.size = atom.size;
a.type=0;
av_freep(&sc->stereo3d);
av_freep(&sc->spherical);
+ av_freep(&sc->mastering);
+ av_freep(&sc->coll);
}
if (mov->dv_demux) {
}
for (i = 0; i < index->item_count; i++) {
int64_t time, offset;
+
+ if (avio_feof(f)) {
+ index->item_count = 0;
+ av_freep(&index->items);
+ return AVERROR_INVALIDDATA;
+ }
+
if (version == 1) {
time = avio_rb64(f);
offset = avio_rb64(f);
sc->spherical = NULL;
}
+ if (sc->mastering) {
+ err = av_stream_add_side_data(st, AV_PKT_DATA_MASTERING_DISPLAY_METADATA,
+ (uint8_t *)sc->mastering,
+ sizeof(*sc->mastering));
+ if (err < 0)
+ return err;
+
+ sc->mastering = NULL;
+ }
+ if (sc->coll) {
+ err = av_stream_add_side_data(st, AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
+ (uint8_t *)sc->coll,
+ sc->coll_size);
+ if (err < 0)
+ return err;
+
+ sc->coll = NULL;
+ }
break;
}
}
ff_configure_buffers_for_index(s, AV_TIME_BASE);
+ for (i = 0; i < mov->fragment_index_count; i++) {
+ MOVFragmentIndex *idx = mov->fragment_index_data[i];
+ for (j = 0; j < idx->item_count; j++)
+ if (idx->items[j].moof_offset <= mov->fragment.moof_offset)
+ idx->items[j].headers_read = 1;
+ }
+
return 0;
}