+static int mxf_get_sorted_table_segments(MXFContext *mxf, int *nb_sorted_segments, MXFIndexTableSegment ***sorted_segments)
+{
+ int i, j, nb_segments = 0;
+ MXFIndexTableSegment **unsorted_segments;
+ int last_body_sid = -1, last_index_sid = -1, last_index_start = -1;
+
+ /* count number of segments, allocate arrays and copy unsorted segments */
+ for (i = 0; i < mxf->metadata_sets_count; i++)
+ if (mxf->metadata_sets[i]->type == IndexTableSegment)
+ nb_segments++;
+
+ if (!nb_segments)
+ return AVERROR_INVALIDDATA;
+
+ *sorted_segments = av_mallocz(nb_segments * sizeof(**sorted_segments));
+ unsorted_segments = av_mallocz(nb_segments * sizeof(*unsorted_segments));
+ if (!*sorted_segments || !unsorted_segments) {
+ av_freep(sorted_segments);
+ av_free(unsorted_segments);
+ return AVERROR(ENOMEM);
+ }
+
+ for (i = j = 0; i < mxf->metadata_sets_count; i++)
+ if (mxf->metadata_sets[i]->type == IndexTableSegment)
+ unsorted_segments[j++] = (MXFIndexTableSegment*)mxf->metadata_sets[i];
+
+ *nb_sorted_segments = 0;
+
+ /* sort segments by {BodySID, IndexSID, IndexStartPosition}, remove duplicates while we're at it */
+ for (i = 0; i < nb_segments; i++) {
+ int best = -1, best_body_sid = -1, best_index_sid = -1, best_index_start = -1;
+
+ for (j = 0; j < nb_segments; j++) {
+ MXFIndexTableSegment *s = unsorted_segments[j];
+
+ /* Require larger BosySID, IndexSID or IndexStartPosition then the previous entry. This removes duplicates.
+ * We want the smallest values for the keys than what we currently have, unless this is the first such entry this time around.
+ */
+ if ((i == 0 || s->body_sid > last_body_sid || s->index_sid > last_index_sid || s->index_start_position > last_index_start) &&
+ (best == -1 || s->body_sid < best_body_sid || s->index_sid < best_index_sid || s->index_start_position < best_index_start)) {
+ best = j;
+ best_body_sid = s->body_sid;
+ best_index_sid = s->index_sid;
+ best_index_start = s->index_start_position;
+ }
+ }
+
+ /* no suitable entry found -> we're done */
+ if (best == -1)
+ break;
+
+ (*sorted_segments)[(*nb_sorted_segments)++] = unsorted_segments[best];
+ last_body_sid = best_body_sid;
+ last_index_sid = best_index_sid;
+ last_index_start = best_index_start;
+ }
+
+ av_free(unsorted_segments);
+
+ return 0;
+}
+
+/**
+ * Computes the absolute file offset of the given essence container offset
+ */
+static int mxf_absolute_bodysid_offset(MXFContext *mxf, int body_sid, int64_t offset, int64_t *offset_out)
+{
+ int x;
+ int64_t offset_in = offset; /* for logging */
+
+ for (x = 0; x < mxf->partitions_count; x++) {
+ MXFPartition *p = &mxf->partitions[x];
+
+ if (p->body_sid != body_sid)
+ continue;
+
+ if (offset < p->essence_length || !p->essence_length) {
+ *offset_out = p->essence_offset + offset;
+ return 0;
+ }
+
+ offset -= p->essence_length;
+ }
+
+ av_log(mxf->fc, AV_LOG_ERROR,
+ "failed to find absolute offset of %"PRIX64" in BodySID %i - partial file?\n",
+ offset_in, body_sid);
+
+ return AVERROR_INVALIDDATA;
+}
+
+/**
+ * Returns the end position of the essence container with given BodySID, or zero if unknown
+ */
+static int64_t mxf_essence_container_end(MXFContext *mxf, int body_sid)
+{
+ int x;
+ int64_t ret = 0;
+
+ for (x = 0; x < mxf->partitions_count; x++) {
+ MXFPartition *p = &mxf->partitions[x];
+
+ if (p->body_sid != body_sid)
+ continue;
+
+ if (!p->essence_length)
+ return 0;
+
+ ret = p->essence_offset + p->essence_length;
+ }
+
+ return ret;
+}
+
+/* EditUnit -> absolute offset */
+static int mxf_edit_unit_absolute_offset(MXFContext *mxf, MXFIndexTable *index_table, int64_t edit_unit, int64_t *edit_unit_out, int64_t *offset_out, int nag)
+{
+ int i;
+ int64_t offset_temp = 0;
+
+ for (i = 0; i < index_table->nb_segments; i++) {
+ MXFIndexTableSegment *s = index_table->segments[i];
+
+ edit_unit = FFMAX(edit_unit, s->index_start_position); /* clamp if trying to seek before start */
+
+ if (edit_unit < s->index_start_position + s->index_duration) {
+ int64_t index = edit_unit - s->index_start_position;
+
+ if (s->edit_unit_byte_count)
+ offset_temp += s->edit_unit_byte_count * index;
+ else if (s->nb_index_entries) {
+ if (s->nb_index_entries == 2 * s->index_duration + 1)
+ index *= 2; /* Avid index */
+
+ if (index < 0 || index >= s->nb_index_entries) {
+ av_log(mxf->fc, AV_LOG_ERROR, "IndexSID %i segment at %"PRId64" IndexEntryArray too small\n",
+ index_table->index_sid, s->index_start_position);
+ return AVERROR_INVALIDDATA;
+ }
+
+ offset_temp = s->stream_offset_entries[index];
+ } else {
+ av_log(mxf->fc, AV_LOG_ERROR, "IndexSID %i segment at %"PRId64" missing EditUnitByteCount and IndexEntryArray\n",
+ index_table->index_sid, s->index_start_position);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (edit_unit_out)
+ *edit_unit_out = edit_unit;
+
+ return mxf_absolute_bodysid_offset(mxf, index_table->body_sid, offset_temp, offset_out);
+ } else {
+ /* EditUnitByteCount == 0 for VBR indexes, which is fine since they use explicit StreamOffsets */
+ offset_temp += s->edit_unit_byte_count * s->index_duration;
+ }
+ }
+
+ if (nag)
+ av_log(mxf->fc, AV_LOG_ERROR, "failed to map EditUnit %"PRId64" in IndexSID %i to an offset\n", edit_unit, index_table->index_sid);
+
+ return AVERROR_INVALIDDATA;
+}
+
+static int mxf_compute_ptses_fake_index(MXFContext *mxf, MXFIndexTable *index_table)
+{
+ int i, j, x;
+ int8_t max_temporal_offset = -128;
+
+ /* first compute how many entries we have */
+ for (i = 0; i < index_table->nb_segments; i++) {
+ MXFIndexTableSegment *s = index_table->segments[i];
+
+ if (!s->nb_index_entries) {
+ index_table->nb_ptses = 0;
+ return 0; /* no TemporalOffsets */
+ }
+
+ index_table->nb_ptses += s->index_duration;
+ }
+
+ /* paranoid check */
+ if (index_table->nb_ptses <= 0)
+ return 0;
+
+ if (index_table->nb_ptses > INT_MAX / sizeof(AVIndexEntry))
+ return AVERROR(ENOMEM);
+
+ index_table->ptses = av_mallocz(index_table->nb_ptses *
+ sizeof(int64_t));
+ index_table->fake_index = av_mallocz(index_table->nb_ptses *
+ sizeof(AVIndexEntry));
+ if (!index_table->ptses || !index_table->fake_index) {
+ av_freep(&index_table->ptses);
+ return AVERROR(ENOMEM);
+ }
+
+ /* we may have a few bad TemporalOffsets
+ * make sure the corresponding PTSes don't have the bogus value 0 */
+ for (x = 0; x < index_table->nb_ptses; x++)
+ index_table->ptses[x] = AV_NOPTS_VALUE;
+
+ /**
+ * We have this:
+ *
+ * x TemporalOffset
+ * 0: 0
+ * 1: 1
+ * 2: 1
+ * 3: -2
+ * 4: 1
+ * 5: 1
+ * 6: -2
+ *
+ * We want to transform it into this:
+ *
+ * x DTS PTS
+ * 0: -1 0
+ * 1: 0 3
+ * 2: 1 1
+ * 3: 2 2
+ * 4: 3 6
+ * 5: 4 4
+ * 6: 5 5
+ *
+ * We do this by bucket sorting x by x+TemporalOffset[x] into mxf->ptses,
+ * then settings mxf->first_dts = -max(TemporalOffset[x]).
+ * The latter makes DTS <= PTS.
+ */
+ for (i = x = 0; i < index_table->nb_segments; i++) {
+ MXFIndexTableSegment *s = index_table->segments[i];
+ int index_delta = 1;
+ int n = s->nb_index_entries;
+
+ if (s->nb_index_entries == 2 * s->index_duration + 1) {
+ index_delta = 2; /* Avid index */
+ /* ignore the last entry - it's the size of the essence container */
+ n--;
+ }
+
+ for (j = 0; j < n; j += index_delta, x++) {
+ int offset = s->temporal_offset_entries[j] / index_delta;
+ int index = x + offset;
+
+ if (x >= index_table->nb_ptses) {
+ av_log(mxf->fc, AV_LOG_ERROR,
+ "x >= nb_ptses - IndexEntryCount %i < IndexDuration %"PRId64"?\n",
+ s->nb_index_entries, s->index_duration);
+ break;
+ }
+
+ index_table->fake_index[x].timestamp = x;
+ index_table->fake_index[x].flags = !(s->flag_entries[j] & 0x30) ? AVINDEX_KEYFRAME : 0;
+
+ if (index < 0 || index >= index_table->nb_ptses) {
+ av_log(mxf->fc, AV_LOG_ERROR,
+ "index entry %i + TemporalOffset %i = %i, which is out of bounds\n",
+ x, offset, index);
+ continue;
+ }
+
+ index_table->ptses[index] = x;
+ max_temporal_offset = FFMAX(max_temporal_offset, offset);
+ }
+ }
+
+ index_table->first_dts = -max_temporal_offset;
+
+ return 0;
+}
+
+/**
+ * Sorts and collects index table segments into index tables.
+ * Also computes PTSes if possible.
+ */
+static int mxf_compute_index_tables(MXFContext *mxf)
+{
+ int i, j, k, ret, nb_sorted_segments;
+ MXFIndexTableSegment **sorted_segments = NULL;
+
+ if ((ret = mxf_get_sorted_table_segments(mxf, &nb_sorted_segments, &sorted_segments)) ||
+ nb_sorted_segments <= 0) {
+ av_log(mxf->fc, AV_LOG_WARNING, "broken or empty index\n");
+ return 0;
+ }
+
+ /* sanity check and count unique BodySIDs/IndexSIDs */
+ for (i = 0; i < nb_sorted_segments; i++) {
+ if (i == 0 || sorted_segments[i-1]->index_sid != sorted_segments[i]->index_sid)
+ mxf->nb_index_tables++;
+ else if (sorted_segments[i-1]->body_sid != sorted_segments[i]->body_sid) {
+ av_log(mxf->fc, AV_LOG_ERROR, "found inconsistent BodySID\n");
+ ret = AVERROR_INVALIDDATA;
+ goto finish_decoding_index;
+ }
+ }
+
+ if (mxf->nb_index_tables > INT_MAX / sizeof(MXFIndexTable) ||
+ !(mxf->index_tables = av_mallocz(mxf->nb_index_tables *
+ sizeof(MXFIndexTable)))) {
+ av_log(mxf->fc, AV_LOG_ERROR, "failed to allocate index tables\n");
+ ret = AVERROR(ENOMEM);
+ goto finish_decoding_index;
+ }
+
+ /* distribute sorted segments to index tables */
+ for (i = j = 0; i < nb_sorted_segments; i++) {
+ if (i != 0 && sorted_segments[i-1]->index_sid != sorted_segments[i]->index_sid) {
+ /* next IndexSID */
+ j++;
+ }
+
+ mxf->index_tables[j].nb_segments++;
+ }
+
+ for (i = j = 0; j < mxf->nb_index_tables; i += mxf->index_tables[j++].nb_segments) {
+ MXFIndexTable *t = &mxf->index_tables[j];
+
+ if (t->nb_segments >
+ (INT_MAX / sizeof(MXFIndexTableSegment *)) ||
+ !(t->segments = av_mallocz(t->nb_segments *
+ sizeof(MXFIndexTableSegment*)))) {
+ av_log(mxf->fc, AV_LOG_ERROR, "failed to allocate IndexTableSegment"
+ " pointer array\n");
+ ret = AVERROR(ENOMEM);
+ goto finish_decoding_index;
+ }
+
+ if (sorted_segments[i]->index_start_position)
+ av_log(mxf->fc, AV_LOG_WARNING, "IndexSID %i starts at EditUnit %"PRId64" - seeking may not work as expected\n",
+ sorted_segments[i]->index_sid, sorted_segments[i]->index_start_position);
+
+ memcpy(t->segments, &sorted_segments[i], t->nb_segments * sizeof(MXFIndexTableSegment*));
+ t->index_sid = sorted_segments[i]->index_sid;
+ t->body_sid = sorted_segments[i]->body_sid;
+
+ if ((ret = mxf_compute_ptses_fake_index(mxf, t)) < 0)
+ goto finish_decoding_index;
+
+ /* fix zero IndexDurations */
+ for (k = 0; k < t->nb_segments; k++) {
+ if (t->segments[k]->index_duration)
+ continue;
+
+ if (t->nb_segments > 1)
+ av_log(mxf->fc, AV_LOG_WARNING, "IndexSID %i segment %i has zero IndexDuration and there's more than one segment\n",
+ t->index_sid, k);
+
+ if (mxf->fc->nb_streams <= 0) {
+ av_log(mxf->fc, AV_LOG_WARNING, "no streams?\n");
+ break;
+ }
+
+ /* assume the first stream's duration is reasonable
+ * leave index_duration = 0 on further segments in case we have any (unlikely)
+ */
+ t->segments[k]->index_duration = mxf->fc->streams[0]->duration;
+ break;
+ }
+ }
+
+ ret = 0;
+finish_decoding_index:
+ av_free(sorted_segments);
+ return ret;
+}
+
+static int mxf_is_intra_only(MXFDescriptor *d)
+{
+ return mxf_get_codec_ul(mxf_intra_only_essence_container_uls,
+ &d->essence_container_ul)->id != AV_CODEC_ID_NONE ||
+ mxf_get_codec_ul(mxf_intra_only_picture_essence_coding_uls,
+ &d->essence_codec_ul)->id != AV_CODEC_ID_NONE;
+}
+