aboutsummaryrefslogtreecommitdiffstats
path: root/libavformat/mov.c
diff options
context:
space:
mode:
authorSasi Inguva <isasi-at-google.com@ffmpeg.org>2016-09-18 22:09:03 -0700
committerMichael Niedermayer <michael@niedermayer.cc>2016-09-19 19:52:05 +0200
commitca6cae73db207f17a0d5507609de12842d8f0ca3 (patch)
tree53f0b232498421ced1a9cd9a63386664f31a258e /libavformat/mov.c
parenta53201879ca36af6fcc8a0d4bcc1fa6d759b67ec (diff)
downloadffmpeg-ca6cae73db207f17a0d5507609de12842d8f0ca3.tar.gz
lavf/mov: Add support for edit list parsing.
Signed-off-by: Sasi Inguva <isasi@google.com> Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
Diffstat (limited to 'libavformat/mov.c')
-rw-r--r--libavformat/mov.c361
1 files changed, 351 insertions, 10 deletions
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 6e80b93271..b84d9c03fc 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -34,6 +34,7 @@
#include "libavutil/intfloat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time_internal.h"
+#include "libavutil/avassert.h"
#include "libavutil/avstring.h"
#include "libavutil/dict.h"
#include "libavutil/display.h"
@@ -2756,6 +2757,348 @@ static int mov_read_sbgp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
return pb->eof_reached ? AVERROR_EOF : 0;
}
+/**
+ * Get ith edit list entry (media time, duration).
+ */
+static int get_edit_list_entry(const MOVStreamContext *msc,
+ unsigned int edit_list_index,
+ int64_t *edit_list_media_time,
+ int64_t *edit_list_duration,
+ int64_t global_timescale)
+{
+ if (edit_list_index == msc->elst_count) {
+ return 0;
+ }
+ *edit_list_media_time = msc->elst_data[edit_list_index].time;
+ *edit_list_duration = msc->elst_data[edit_list_index].duration;
+
+ /* duration is in global timescale units;convert to msc timescale */
+ if (global_timescale == 0) {
+ avpriv_request_sample(msc, "Support for mvhd.timescale = 0 with editlists");
+ return 0;
+ }
+ *edit_list_duration = av_rescale(*edit_list_duration, msc->time_scale,
+ global_timescale);
+ return 1;
+}
+
+/**
+ * Find the closest previous keyframe to the timestamp, in e_old index
+ * entries.
+ * Returns the index of the entry in st->index_entries if successful,
+ * else returns -1.
+ */
+static int64_t find_prev_closest_keyframe_index(AVStream *st,
+ AVIndexEntry *e_old,
+ int nb_old,
+ int64_t timestamp,
+ int flag)
+{
+ AVIndexEntry *e_keep = st->index_entries;
+ int nb_keep = st->nb_index_entries;
+ int64_t found = -1;
+
+ st->index_entries = e_old;
+ st->nb_index_entries = nb_old;
+ found = av_index_search_timestamp(st, timestamp, flag | AVSEEK_FLAG_BACKWARD);
+
+ /* restore AVStream state*/
+ st->index_entries = e_keep;
+ st->nb_index_entries = nb_keep;
+ return found;
+}
+
+/**
+ * Add index entry with the given values, to the end of st->index_entries.
+ * Returns the new size st->index_entries if successful, else returns -1.
+ *
+ * This function is similar to ff_add_index_entry in libavformat/utils.c
+ * except that here we are always unconditionally adding an index entry to
+ * the end, instead of searching the entries list and skipping the add if
+ * there is an existing entry with the same timestamp.
+ * This is needed because the mov_fix_index calls this func with the same
+ * unincremented timestamp for successive discarded frames.
+ */
+static int64_t add_index_entry(AVStream *st, int64_t pos, int64_t timestamp,
+ int size, int distance, int flags)
+{
+ AVIndexEntry *entries, *ie;
+ int64_t index = -1;
+ const size_t min_size_needed = (st->nb_index_entries + 1) * sizeof(AVIndexEntry);
+
+ // Double the allocation each time, to lower memory fragmentation.
+ // Another difference from ff_add_index_entry function.
+ const size_t requested_size =
+ min_size_needed > st->index_entries_allocated_size ?
+ FFMAX(min_size_needed, 2 * st->index_entries_allocated_size) :
+ min_size_needed;
+
+ if((unsigned)st->nb_index_entries + 1 >= UINT_MAX / sizeof(AVIndexEntry))
+ return -1;
+
+ entries = av_fast_realloc(st->index_entries,
+ &st->index_entries_allocated_size,
+ requested_size);
+ if(!entries)
+ return -1;
+
+ st->index_entries= entries;
+
+ index= st->nb_index_entries++;
+ ie= &entries[index];
+
+ ie->pos = pos;
+ ie->timestamp = timestamp;
+ ie->min_distance= distance;
+ ie->size= size;
+ ie->flags = flags;
+ return index;
+}
+
+/**
+ * Append a new ctts entry to ctts_data.
+ * Returns the new ctts_count if successful, else returns -1.
+ */
+static int64_t add_ctts_entry(MOVStts** ctts_data, unsigned int* ctts_count, unsigned int* allocated_size,
+ int count, int duration)
+{
+ MOVStts *ctts_buf_new;
+ const size_t min_size_needed = (*ctts_count + 1) * sizeof(MOVStts);
+ const size_t requested_size =
+ min_size_needed > *allocated_size ?
+ FFMAX(min_size_needed, 2 * (*allocated_size)) :
+ min_size_needed;
+
+ if((unsigned)(*ctts_count) + 1 >= UINT_MAX / sizeof(MOVStts))
+ return -1;
+
+ ctts_buf_new = av_fast_realloc(*ctts_data, allocated_size, requested_size);
+
+ if(!ctts_buf_new)
+ return -1;
+
+ *ctts_data = ctts_buf_new;
+
+ ctts_buf_new[*ctts_count].count = count;
+ ctts_buf_new[*ctts_count].duration = duration;
+
+ *ctts_count = (*ctts_count) + 1;
+ return *ctts_count;
+}
+
+/**
+ * Fix st->index_entries, so that it contains only the entries (and the entries
+ * which are needed to decode them) that fall in the edit list time ranges.
+ * Also fixes the timestamps of the index entries to match the timeline
+ * specified the edit lists.
+ */
+static void mov_fix_index(MOVContext *mov, AVStream *st)
+{
+ MOVStreamContext *msc = st->priv_data;
+ AVIndexEntry *e_old = st->index_entries;
+ int nb_old = st->nb_index_entries;
+ const AVIndexEntry *e_old_end = e_old + nb_old;
+ const AVIndexEntry *current = NULL;
+ MOVStts *ctts_data_old = msc->ctts_data;
+ int64_t ctts_index_old = 0;
+ int64_t ctts_sample_old = 0;
+ int64_t ctts_count_old = msc->ctts_count;
+ int64_t edit_list_media_time = 0;
+ int64_t edit_list_duration = 0;
+ int64_t frame_duration = 0;
+ int64_t edit_list_dts_counter = 0;
+ int64_t edit_list_dts_entry_end = 0;
+ int64_t edit_list_start_ctts_sample = 0;
+ int64_t curr_cts;
+ 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;
+ int64_t search_timestamp = 0;
+
+
+ if (!msc->elst_data || msc->elst_count <= 0) {
+ return;
+ }
+ // Clean AVStream from traces of old index
+ st->index_entries = NULL;
+ st->index_entries_allocated_size = 0;
+ st->nb_index_entries = 0;
+
+ // Clean ctts fields of MOVStreamContext
+ msc->ctts_data = NULL;
+ msc->ctts_count = 0;
+ msc->ctts_index = 0;
+ msc->ctts_sample = 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)
+ 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);
+ }
+
+ start_dts = edit_list_dts_entry_end;
+
+ while (get_edit_list_entry(msc, edit_list_index, &edit_list_media_time,
+ &edit_list_duration, mov->time_scale)) {
+ av_log(mov->fc, AV_LOG_DEBUG, "Processing st: %d, edit list %"PRId64" - media time: %"PRId64", duration: %"PRId64"\n",
+ st->index, edit_list_index, edit_list_media_time, edit_list_duration);
+ edit_list_index++;
+ edit_list_dts_counter = edit_list_dts_entry_end;
+ edit_list_dts_entry_end += edit_list_duration;
+ if (edit_list_media_time == -1) {
+ continue;
+ }
+
+ // If we encounter a non-negative edit list reset the skip_samples/start_pad fields and set them
+ // according to the edit list below.
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+ st->skip_samples = msc->start_pad = 0;
+ }
+
+ //find closest previous key frame
+ edit_list_media_time_dts = edit_list_media_time;
+ if (msc->dts_shift > 0) {
+ edit_list_media_time_dts -= msc->dts_shift;
+ }
+
+ // While reordering frame index according to edit list we must handle properly
+ // the scenario when edit list entry starts from none key frame.
+ // We find closest previous key frame and preserve it and consequent frames in index.
+ // All frames which are outside edit list entry time boundaries will be dropped after decoding.
+ search_timestamp = edit_list_media_time_dts;
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
+ // Audio decoders like AAC need need a decoder delay samples previous to the current sample,
+ // to correctly decode this frame. Hence for audio we seek to a frame 1 sec. before the
+ // edit_list_media_time to cover the decoder delay.
+ search_timestamp = FFMAX(search_timestamp - mov->time_scale, e_old[0].timestamp);
+ }
+
+ index = find_prev_closest_keyframe_index(st, e_old, nb_old, search_timestamp, 0);
+ if (index == -1) {
+ av_log(mov->fc, AV_LOG_ERROR, "Missing key frame while reordering index according to edit list\n");
+ continue;
+ }
+ current = e_old + index;
+
+ ctts_index_old = 0;
+ ctts_sample_old = 0;
+
+ // set ctts_index properly for the found key frame
+ for (index_ctts_count = 0; index_ctts_count < index; index_ctts_count++) {
+ if (ctts_data_old && ctts_index_old < ctts_count_old) {
+ ctts_sample_old++;
+ if (ctts_data_old[ctts_index_old].count == ctts_sample_old) {
+ ctts_index_old++;
+ ctts_sample_old = 0;
+ }
+ }
+ }
+
+ edit_list_start_ctts_sample = ctts_sample_old;
+
+ // Iterate over index and arrange it according to edit list
+ edit_list_start_encountered = 0;
+ for (; current < e_old_end; current++, index++) {
+ // check if frame outside edit list mark it for discard
+ frame_duration = (current + 1 < e_old_end) ?
+ ((current + 1)->timestamp - current->timestamp) : edit_list_duration;
+
+ flags = current->flags;
+
+ // frames (pts) before or after edit list
+ curr_cts = current->timestamp + msc->dts_shift;
+
+ 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;
+ 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,
+ 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",
+ ctts_index_old,
+ ctts_data_old[ctts_index_old].count - edit_list_start_ctts_sample,
+ ctts_data_old[ctts_index_old].duration);
+ break;
+ }
+ ctts_index_old++;
+ ctts_sample_old = 0;
+ edit_list_start_ctts_sample = 0;
+ }
+ }
+
+ if (curr_cts < edit_list_media_time || curr_cts >= (edit_list_duration + edit_list_media_time)) {
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && curr_cts < edit_list_media_time &&
+ curr_cts + frame_duration > edit_list_media_time &&
+ st->skip_samples == 0 && msc->start_pad == 0) {
+ st->skip_samples = msc->start_pad = edit_list_media_time - curr_cts;
+
+ // Shift the index entry timestamp by skip_samples to be correct.
+ edit_list_dts_counter -= st->skip_samples;
+ if (edit_list_start_encountered == 0) {
+ edit_list_start_encountered = 1;
+ }
+
+ av_log(mov->fc, AV_LOG_DEBUG, "skip %d audio samples from curr_cts: %"PRId64"\n", st->skip_samples, curr_cts);
+ } else {
+ flags |= AVINDEX_DISCARD_FRAME;
+ av_log(mov->fc, AV_LOG_DEBUG, "drop a frame at curr_cts: %"PRId64" @ %"PRId64"\n", curr_cts, index);
+ }
+ } else if (edit_list_start_encountered == 0) {
+ edit_list_start_encountered = 1;
+ }
+
+ if (add_index_entry(st, current->pos, edit_list_dts_counter, current->size,
+ current->min_distance, flags) == -1) {
+ av_log(mov->fc, AV_LOG_ERROR, "Cannot add index entry\n");
+ break;
+ }
+
+ // Only start incrementing DTS in frame_duration amounts, when we encounter a frame in edit list.
+ if (edit_list_start_encountered > 0) {
+ edit_list_dts_counter = edit_list_dts_counter + frame_duration;
+ }
+
+ // Break when found first key frame after edit entry completion
+ if (((curr_cts + frame_duration) >= (edit_list_duration + edit_list_media_time)) &&
+ ((flags & AVINDEX_KEYFRAME) || ((st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)))) {
+
+ if (ctts_data_old && ctts_sample_old != 0) {
+ if (add_ctts_entry(&msc->ctts_data, &msc->ctts_count,
+ &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",
+ ctts_index_old, ctts_sample_old - edit_list_start_ctts_sample,
+ ctts_data_old[ctts_index_old].duration);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ // Update av stream length
+ st->duration = edit_list_dts_entry_end - start_dts;
+
+ // Free the old index and the old CTTS structures
+ av_free(e_old);
+ av_free(ctts_data_old);
+}
+
static void mov_build_index(MOVContext *mov, AVStream *st)
{
MOVStreamContext *sc = st->priv_data;
@@ -2769,7 +3112,7 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
uint64_t stream_size = 0;
if (sc->elst_count) {
- int i, edit_start_index = 0, unsupported = 0;
+ int i, edit_start_index = 0;
int64_t empty_duration = 0; // empty duration of the first edit list entry
int64_t start_time = 0; // start time of the media
@@ -2782,23 +3125,15 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
edit_start_index = 1;
} else if (i == edit_start_index && e->time >= 0) {
start_time = e->time;
- } else
- unsupported = 1;
+ }
}
- if (unsupported)
- av_log(mov->fc, AV_LOG_WARNING, "multiple edit list entries, "
- "a/v desync might occur, patch welcome\n");
/* adjust first dts according to edit list */
if ((empty_duration || start_time) && mov->time_scale > 0) {
if (empty_duration)
empty_duration = av_rescale(empty_duration, sc->time_scale, mov->time_scale);
sc->time_offset = start_time - empty_duration;
- current_dts = -sc->time_offset;
}
-
- if (!unsupported && st->codecpar->codec_id == AV_CODEC_ID_AAC && start_time > 0)
- sc->start_pad = start_time;
}
/* only use old uncompressed audio chunk demuxing when stts specifies it */
@@ -3031,6 +3366,9 @@ static void mov_build_index(MOVContext *mov, AVStream *st)
}
}
}
+
+ // Fix index according to edit lists.
+ mov_fix_index(mov, st);
}
static int test_same_origin(const char *src, const char *ref) {
@@ -5330,6 +5668,9 @@ static int mov_read_packet(AVFormatContext *s, AVPacket *pkt)
pkt->stream_index = sc->ffindex;
pkt->dts = sample->timestamp;
+ if (sample->flags & AVINDEX_DISCARD_FRAME) {
+ pkt->flags |= AV_PKT_FLAG_DISCARD;
+ }
if (sc->ctts_data && sc->ctts_index < sc->ctts_count) {
pkt->pts = pkt->dts + sc->dts_shift + sc->ctts_data[sc->ctts_index].duration;
/* update ctts context */