#include "libavutil/mathematics.h"
#include "libavcodec/bytestream.h"
#include "libavutil/intreadwrite.h"
+#include "libavutil/parseutils.h"
#include "libavutil/timecode.h"
#include "avformat.h"
#include "internal.h"
typedef struct MXFStructuralComponent {
UID uid;
enum MXFMetadataSetType type;
+ UID source_package_ul;
UID source_package_uid;
UID data_definition_ul;
int64_t duration;
MXFSequence *sequence; /* mandatory, and only one */
UID sequence_ref;
int track_id;
+ char *name;
uint8_t track_number[4];
AVRational edit_rate;
int intra_only;
int width;
int height; /* Field height, not frame height */
int frame_layout; /* See MXFFrameLayout enum */
-#define MXF_TFF 1
-#define MXF_BFF 2
+ int video_line_map[2];
+#define MXF_FIELD_DOMINANCE_DEFAULT 0
+#define MXF_FIELD_DOMINANCE_FF 1 /* coded first, displayed first */
+#define MXF_FIELD_DOMINANCE_FL 2 /* coded first, displayed last */
int field_dominance;
int channels;
int bits_per_sample;
av_freep(&((MXFTaggedValue *)*ctx)->name);
av_freep(&((MXFTaggedValue *)*ctx)->value);
break;
+ case Track:
+ av_freep(&((MXFTrack *)*ctx)->name);
+ break;
case IndexTableSegment:
seg = (MXFIndexTableSegment *)*ctx;
av_freep(&seg->temporal_offset_entries);
for (i = 0; i < s->nb_streams; i++) {
MXFTrack *track = s->streams[i]->priv_data;
/* SMPTE 379M 7.3 */
- if (!memcmp(klv->key + sizeof(mxf_essence_element_key), track->track_number, sizeof(track->track_number)))
+ if (track && !memcmp(klv->key + sizeof(mxf_essence_element_key), track->track_number, sizeof(track->track_number)))
return i;
}
/* return 0 if only one stream, for OP Atom files with 0 as track number */
return 0;
}
+static inline int mxf_read_utf16_string(AVIOContext *pb, int size, char** str, int be)
+{
+ int ret;
+ size_t buf_size;
+
+ if (size < 0 || size > INT_MAX/2)
+ return AVERROR(EINVAL);
+
+ buf_size = size + size / 2 + 1;
+ *str = av_malloc(buf_size);
+ if (!*str)
+ return AVERROR(ENOMEM);
+
+ if (be)
+ ret = avio_get_str16be(pb, size, *str, buf_size);
+ else
+ ret = avio_get_str16le(pb, size, *str, buf_size);
+
+ if (ret < 0) {
+ av_freep(str);
+ return ret;
+ }
+
+ return ret;
+}
+
+#define READ_STR16(type, big_endian) \
+static int mxf_read_utf16 ## type ##_string(AVIOContext *pb, int size, char** str) \
+{ \
+return mxf_read_utf16_string(pb, size, str, big_endian); \
+}
+READ_STR16(be, 1)
+READ_STR16(le, 0)
+#undef READ_STR16
+
static int mxf_read_content_storage(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
MXFContext *mxf = arg;
break;
case 0x1101:
/* UMID, only get last 16 bytes */
- avio_skip(pb, 16);
+ avio_read(pb, source_clip->source_package_ul, 16);
avio_read(pb, source_clip->source_package_uid, 16);
break;
case 0x1102:
case 0x4804:
avio_read(pb, track->track_number, 4);
break;
+ case 0x4802:
+ mxf_read_utf16be_string(pb, size, &track->name);
+ break;
case 0x4b01:
track->edit_rate.num = avio_rb32(pb);
track->edit_rate.den = avio_rb32(pb);
return 0;
}
-static inline int mxf_read_utf16_string(AVIOContext *pb, int size, char** str, int be)
-{
- int ret;
- size_t buf_size;
-
- if (size < 0)
- return AVERROR(EINVAL);
-
- buf_size = size + size / 2 + 1;
- *str = av_malloc(buf_size);
- if (!*str)
- return AVERROR(ENOMEM);
-
- if (be)
- ret = avio_get_str16be(pb, size, *str, buf_size);
- else
- ret = avio_get_str16le(pb, size, *str, buf_size);
-
- if (ret < 0) {
- av_freep(str);
- return ret;
- }
-
- return ret;
-}
-
-#define READ_STR16(type, big_endian) \
-static int mxf_read_utf16 ## type ##_string(AVIOContext *pb, int size, char** str) \
-{ \
-return mxf_read_utf16_string(pb, size, str, big_endian); \
-}
-READ_STR16(be, 1)
-READ_STR16(le, 0)
-#undef READ_STR16
-
static int mxf_read_package(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
MXFPackage *package = arg;
static int mxf_read_generic_descriptor(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
MXFDescriptor *descriptor = arg;
+ int entry_count, entry_size;
+
switch(tag) {
case 0x3F01:
return mxf_read_strong_ref_array(pb, &descriptor->sub_descriptors_refs,
case 0x320C:
descriptor->frame_layout = avio_r8(pb);
break;
+ case 0x320D:
+ entry_count = avio_rb32(pb);
+ entry_size = avio_rb32(pb);
+ if (entry_size == 4) {
+ if (entry_count > 0)
+ descriptor->video_line_map[0] = avio_rb32(pb);
+ else
+ descriptor->video_line_map[0] = 0;
+ if (entry_count > 1)
+ descriptor->video_line_map[1] = avio_rb32(pb);
+ else
+ descriptor->video_line_map[1] = 0;
+ } else
+ av_log(NULL, AV_LOG_WARNING, "VideoLineMap element size %d currently not supported\n", entry_size);
+ break;
case 0x320E:
descriptor->aspect_ratio.num = avio_rb32(pb);
descriptor->aspect_ratio.den = avio_rb32(pb);
{
int i, j, k, ret, nb_sorted_segments;
MXFIndexTableSegment **sorted_segments = NULL;
+ AVStream *st = NULL;
+
+ for (i = 0; i < mxf->fc->nb_streams; i++) {
+ if (mxf->fc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_DATA)
+ continue;
+ st = mxf->fc->streams[i];
+ break;
+ }
if ((ret = mxf_get_sorted_table_segments(mxf, &nb_sorted_segments, &sorted_segments)) ||
nb_sorted_segments <= 0) {
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) {
+ if (!st) {
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;
+ t->segments[k]->index_duration = st->duration;
break;
}
}
return 0;
}
+static int mxf_add_metadata_stream(MXFContext *mxf, MXFTrack *track)
+{
+ MXFStructuralComponent *component = NULL;
+ const MXFCodecUL *codec_ul = NULL;
+ MXFPackage tmp_package;
+ AVStream *st;
+ int j;
+
+ for (j = 0; j < track->sequence->structural_components_count; j++) {
+ component = mxf_resolve_sourceclip(mxf, &track->sequence->structural_components_refs[j]);
+ if (!component)
+ continue;
+ break;
+ }
+ if (!component)
+ return 0;
+
+ st = avformat_new_stream(mxf->fc, NULL);
+ if (!st) {
+ av_log(mxf->fc, AV_LOG_ERROR, "could not allocate metadata stream\n");
+ return AVERROR(ENOMEM);
+ }
+
+ st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
+ st->codecpar->codec_id = AV_CODEC_ID_NONE;
+ st->id = track->track_id;
+
+ memcpy(&tmp_package.package_ul, component->source_package_ul, 16);
+ memcpy(&tmp_package.package_uid, component->source_package_uid, 16);
+ mxf_add_umid_metadata(&st->metadata, "file_package_umid", &tmp_package);
+ if (track->name && track->name[0])
+ av_dict_set(&st->metadata, "track_name", track->name, 0);
+
+ codec_ul = mxf_get_codec_ul(ff_mxf_data_definition_uls, &track->sequence->data_definition_ul);
+ av_dict_set(&st->metadata, "data_type", av_get_media_type_string(codec_ul->id), 0);
+ return 0;
+}
+
static int mxf_parse_structural_metadata(MXFContext *mxf)
{
MXFPackage *material_package = NULL;
}
}
- /* TODO: handle multiple source clips */
+ /* TODO: handle multiple source clips, only finds first valid source clip */
+ if(material_track->sequence->structural_components_count > 1)
+ av_log(mxf->fc, AV_LOG_WARNING, "material track %d: has %d components\n",
+ material_track->track_id, material_track->sequence->structural_components_count);
+
for (j = 0; j < material_track->sequence->structural_components_count; j++) {
component = mxf_resolve_sourceclip(mxf, &material_track->sequence->structural_components_refs[j]);
if (!component)
av_log(mxf->fc, AV_LOG_ERROR, "material track %d: no corresponding source track found\n", material_track->track_id);
break;
}
+ if(source_track && component)
+ break;
}
- if (!source_track || !component || !source_package)
+ if (!source_track || !component || !source_package) {
+ if((ret = mxf_add_metadata_stream(mxf, material_track)))
+ goto fail_and_free;
continue;
+ }
if (!(source_track->sequence = mxf_resolve_strong_ref(mxf, &source_track->sequence_ref, Sequence))) {
av_log(mxf->fc, AV_LOG_ERROR, "could not resolve source track sequence strong ref\n");
ret = AVERROR(ENOMEM);
goto fail_and_free;
}
- st->id = source_track->track_id;
+ st->id = material_track->track_id;
st->priv_data = source_track;
source_package->descriptor = mxf_resolve_strong_ref(mxf, &source_package->descriptor_ref, AnyType);
mxf_add_umid_metadata(&st->metadata, "file_package_umid", source_package);
if (source_package->name && source_package->name[0])
av_dict_set(&st->metadata, "file_package_name", source_package->name, 0);
+ if (material_track->name && material_track->name[0])
+ av_dict_set(&st->metadata, "track_name", material_track->name, 0);
mxf_parse_physical_source_package(mxf, source_track, st);
case SegmentedFrame:
st->codecpar->field_order = AV_FIELD_PROGRESSIVE;
case SeparateFields:
- switch (descriptor->field_dominance) {
- case MXF_TFF:
- st->codecpar->field_order = AV_FIELD_TT;
- break;
- case MXF_BFF:
- st->codecpar->field_order = AV_FIELD_BB;
- break;
- default:
- avpriv_request_sample(mxf->fc,
- "Field dominance %d support",
- descriptor->field_dominance);
- case 0: // we already have many samples with field_dominance == unknown
- break;
+ av_log(mxf->fc, AV_LOG_DEBUG, "video_line_map: (%d, %d), field_dominance: %d\n",
+ descriptor->video_line_map[0], descriptor->video_line_map[1],
+ descriptor->field_dominance);
+ if ((descriptor->video_line_map[0] > 0) && (descriptor->video_line_map[1] > 0)) {
+ /* Detect coded field order from VideoLineMap:
+ * (even, even) => bottom field coded first
+ * (even, odd) => top field coded first
+ * (odd, even) => top field coded first
+ * (odd, odd) => bottom field coded first
+ */
+ if ((descriptor->video_line_map[0] + descriptor->video_line_map[1]) % 2) {
+ switch (descriptor->field_dominance) {
+ case MXF_FIELD_DOMINANCE_DEFAULT:
+ case MXF_FIELD_DOMINANCE_FF:
+ st->codecpar->field_order = AV_FIELD_TT;
+ break;
+ case MXF_FIELD_DOMINANCE_FL:
+ st->codecpar->field_order = AV_FIELD_TB;
+ break;
+ default:
+ avpriv_request_sample(mxf->fc,
+ "Field dominance %d support",
+ descriptor->field_dominance);
+ }
+ } else {
+ switch (descriptor->field_dominance) {
+ case MXF_FIELD_DOMINANCE_DEFAULT:
+ case MXF_FIELD_DOMINANCE_FF:
+ st->codecpar->field_order = AV_FIELD_BB;
+ break;
+ case MXF_FIELD_DOMINANCE_FL:
+ st->codecpar->field_order = AV_FIELD_BT;
+ break;
+ default:
+ avpriv_request_sample(mxf->fc,
+ "Field dominance %d support",
+ descriptor->field_dominance);
+ }
+ }
}
/* Turn field height into frame height. */
st->codecpar->height *= 2;
return ret;
}
-static int mxf_timestamp_to_str(uint64_t timestamp, char **str)
+static int64_t mxf_timestamp_to_int64(uint64_t timestamp)
{
struct tm time = { 0 };
time.tm_year = (timestamp >> 48) - 1900;
time.tm_min = av_clip(time.tm_min, 0, 59);
time.tm_sec = av_clip(time.tm_sec, 0, 59);
- *str = av_mallocz(32);
- if (!*str)
- return AVERROR(ENOMEM);
- if (!strftime(*str, 32, "%Y-%m-%d %H:%M:%S", &time))
- (*str)[0] = '\0';
-
- return 0;
+ return (int64_t)av_timegm(&time) * 1000000;
}
#define SET_STR_METADATA(pb, name, str) do { \
#define SET_TS_METADATA(pb, name, var, str) do { \
var = avio_rb64(pb); \
- if ((ret = mxf_timestamp_to_str(var, &str)) < 0) \
+ if ((ret = avpriv_dict_set_timestamp(&s->metadata, name, mxf_timestamp_to_int64(var)) < 0)) \
return ret; \
- av_dict_set(&s->metadata, name, str, AV_DICT_DONT_STRDUP_VAL); \
} while (0)
static int mxf_read_identification_metadata(void *arg, AVIOContext *pb, int tag, int size, UID _uid, int64_t klv_offset)
return codec_id >= AV_CODEC_ID_PCM_S16LE && codec_id < AV_CODEC_ID_PCM_S24DAUD;
}
+static AVStream* mxf_get_opatom_stream(MXFContext *mxf)
+{
+ int i;
+
+ if (mxf->op != OPAtom)
+ return NULL;
+
+ for (i = 0; i < mxf->fc->nb_streams; i++) {
+ if (mxf->fc->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_DATA)
+ continue;
+ return mxf->fc->streams[i];
+ }
+ return NULL;
+}
+
/**
* Deal with the case where for some audio atoms EditUnitByteCount is
* very small (2, 4..). In those cases we should read more than one
/* assuming non-OPAtom == frame wrapped
* no sane writer would wrap 2 byte PCM packets with 20 byte headers.. */
- if (mxf->op != OPAtom)
+ AVStream *st = mxf_get_opatom_stream(mxf);
+ if (!st)
return;
/* expect PCM with exactly one index table segment and a small (< 32) EUBC */
- if (s->nb_streams != 1 ||
- s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO ||
- !is_pcm(s->streams[0]->codecpar->codec_id) ||
+ if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO ||
+ !is_pcm(st->codecpar->codec_id) ||
mxf->nb_index_tables != 1 ||
mxf->index_tables[0].nb_segments != 1 ||
mxf->index_tables[0].segments[0]->edit_unit_byte_count >= 32)
int essence_partition_count = 0;
int i, ret;
- if (mxf->op != OPAtom)
+ st = mxf_get_opatom_stream(mxf);
+ if (!st)
return 0;
/* TODO: support raw video without an index if they exist */
- if (s->nb_streams != 1 || s->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || !is_pcm(s->streams[0]->codecpar->codec_id))
+ if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || !is_pcm(st->codecpar->codec_id))
return 0;
/* check if file already has a IndexTableSegment */
return ret;
}
- st = s->streams[0];
segment->type = IndexTableSegment;
/* stream will be treated as small EditUnitByteCount */
segment->edit_unit_byte_count = (av_get_bits_per_sample(st->codecpar->codec_id) * st->codecpar->channels) >> 3;
return mxf_read_packet_old(s, pkt);
// If we have no streams then we basically are at EOF
- if (s->nb_streams < 1)
+ st = mxf_get_opatom_stream(mxf);
+ if (!st)
return AVERROR_EOF;
/* OPAtom - clip wrapped demuxing */
/* NOTE: mxf_read_header() makes sure nb_index_tables > 0 for OPAtom */
- st = s->streams[0];
t = &mxf->index_tables[0];
if (mxf->current_edit_unit >= st->duration)
if ((size = av_get_packet(s->pb, pkt, size)) < 0)
return size;
- pkt->stream_index = 0;
+ pkt->stream_index = st->index;
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && t->ptses &&
mxf->current_edit_unit >= 0 && mxf->current_edit_unit < t->nb_ptses) {
MXFIndexTable *t;
MXFTrack *source_track = st->priv_data;
+ if(st->codecpar->codec_type == AVMEDIA_TYPE_DATA)
+ return 0;
+
/* if audio then truncate sample_time to EditRate */
if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
sample_time = av_rescale_q(sample_time, st->time_base,