#include "libavutil/intreadwrite.h"
#include "libavutil/parseutils.h"
#include "libavutil/timecode.h"
+#include "libavutil/opt.h"
#include "avformat.h"
#include "internal.h"
#include "mxf.h"
} MXFIndexTable;
typedef struct MXFContext {
+ const AVClass *class; /**< Class for private options. */
MXFPartition *partitions;
unsigned partitions_count;
MXFOP op;
int last_forward_partition;
int nb_index_tables;
MXFIndexTable *index_tables;
+ int eia608_extract;
} MXFContext;
/* NOTE: klv_offset is not set (-1) for local keys */
return s->nb_streams == 1 && s->streams[0]->priv_data ? 0 : -1;
}
-static int find_body_sid_by_offset(MXFContext *mxf, int64_t offset)
+static int find_body_sid_by_absolute_offset(MXFContext *mxf, int64_t offset)
{
// we look for partition where the offset is placed
int a, b, m;
- int64_t this_partition;
+ int64_t pack_ofs;
a = -1;
b = mxf->partitions_count;
while (b - a > 1) {
- m = (a + b) >> 1;
- this_partition = mxf->partitions[m].this_partition;
- if (this_partition <= offset)
+ m = (a + b) >> 1;
+ pack_ofs = mxf->partitions[m].pack_ofs;
+ if (pack_ofs <= offset)
a = m;
else
b = m;
return mxf->partitions[a].body_sid;
}
+static int mxf_get_eia608_packet(AVFormatContext *s, AVStream *st, AVPacket *pkt, int64_t length)
+{
+ int count = avio_rb16(s->pb);
+ int cdp_identifier, cdp_length, cdp_footer_id, ccdata_id, cc_count;
+ int line_num, sample_coding, sample_count;
+ int did, sdid, data_length;
+ int i, ret;
+
+ if (count != 1)
+ av_log(s, AV_LOG_WARNING, "unsupported multiple ANC packets (%d) per KLV packet\n", count);
+
+ for (i = 0; i < count; i++) {
+ if (length < 6) {
+ av_log(s, AV_LOG_ERROR, "error reading s436m packet %"PRId64"\n", length);
+ return AVERROR_INVALIDDATA;
+ }
+ line_num = avio_rb16(s->pb);
+ avio_r8(s->pb); // wrapping type
+ sample_coding = avio_r8(s->pb);
+ sample_count = avio_rb16(s->pb);
+ length -= 6 + 8 + sample_count;
+ if (line_num != 9 && line_num != 11)
+ continue;
+ if (sample_coding == 7 || sample_coding == 8 || sample_coding == 9) {
+ av_log(s, AV_LOG_WARNING, "unsupported s436m 10 bit sample coding\n");
+ continue;
+ }
+ if (length < 0)
+ return AVERROR_INVALIDDATA;
+
+ avio_rb32(s->pb); // array count
+ avio_rb32(s->pb); // array elem size
+ did = avio_r8(s->pb);
+ sdid = avio_r8(s->pb);
+ data_length = avio_r8(s->pb);
+ if (did != 0x61 || sdid != 1) {
+ av_log(s, AV_LOG_WARNING, "unsupported did or sdid: %x %x\n", did, sdid);
+ continue;
+ }
+ cdp_identifier = avio_rb16(s->pb); // cdp id
+ if (cdp_identifier != 0x9669) {
+ av_log(s, AV_LOG_ERROR, "wrong cdp identifier %x\n", cdp_identifier);
+ return AVERROR_INVALIDDATA;
+ }
+ cdp_length = avio_r8(s->pb);
+ avio_r8(s->pb); // cdp_frame_rate
+ avio_r8(s->pb); // cdp_flags
+ avio_rb16(s->pb); // cdp_hdr_sequence_cntr
+ ccdata_id = avio_r8(s->pb); // ccdata_id
+ if (ccdata_id != 0x72) {
+ av_log(s, AV_LOG_ERROR, "wrong cdp data section %x\n", ccdata_id);
+ return AVERROR_INVALIDDATA;
+ }
+ cc_count = avio_r8(s->pb) & 0x1f;
+ ret = av_get_packet(s->pb, pkt, cc_count * 3);
+ if (ret < 0)
+ return ret;
+ if (cdp_length - 9 - 4 < cc_count * 3) {
+ av_log(s, AV_LOG_ERROR, "wrong cdp size %d cc count %d\n", cdp_length, cc_count);
+ return AVERROR_INVALIDDATA;
+ }
+ avio_skip(s->pb, data_length - 9 - 4 - cc_count * 3);
+ cdp_footer_id = avio_r8(s->pb);
+ if (cdp_footer_id != 0x74) {
+ av_log(s, AV_LOG_ERROR, "wrong cdp footer section %x\n", cdp_footer_id);
+ return AVERROR_INVALIDDATA;
+ }
+ avio_rb16(s->pb); // cdp_ftr_sequence_cntr
+ avio_r8(s->pb); // packet_checksum
+ break;
+ }
+
+ return 0;
+}
+
/* XXX: use AVBitStreamFilter */
static int mxf_get_d10_aes3_packet(AVIOContext *pb, AVStream *st, AVPacket *pkt, int64_t length)
{
if (!IS_KLV_KEY(klv, mxf_essence_element_key))
return AVERROR_INVALIDDATA;
- body_sid = find_body_sid_by_offset(mxf, klv->offset);
+ body_sid = find_body_sid_by_absolute_offset(mxf, klv->offset);
index = mxf_get_stream_index(s, klv, body_sid);
if (index < 0)
return AVERROR_INVALIDDATA;
static int mxf_read_partition_pack(void *arg, AVIOContext *pb, int tag, int size, UID uid, int64_t klv_offset)
{
MXFContext *mxf = arg;
+ AVFormatContext *s = mxf->fc;
MXFPartition *partition, *tmp_part;
UID op;
uint64_t footer_partition;
}
nb_essence_containers = avio_rb32(pb);
+ if (partition->type == Header) {
+ char str[36];
+ snprintf(str, sizeof(str), "%08x.%08x.%08x.%08x", AV_RB32(&op[0]), AV_RB32(&op[4]), AV_RB32(&op[8]), AV_RB32(&op[12]));
+ av_dict_set(&s->metadata, "operational_pattern_ul", str, 0);
+ }
+
if (partition->this_partition &&
partition->previous_partition == partition->this_partition) {
av_log(mxf->fc, AV_LOG_ERROR,
{ { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x01,0x0d,0x01,0x03,0x01,0x02,0x05,0x00,0x00 }, 14, AV_CODEC_ID_RAWVIDEO, NULL, 15, RawVWrap }, /* uncompressed picture */
{ { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0a,0x0e,0x0f,0x03,0x01,0x02,0x20,0x01,0x01 }, 15, AV_CODEC_ID_HQ_HQA },
{ { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0a,0x0e,0x0f,0x03,0x01,0x02,0x20,0x02,0x01 }, 15, AV_CODEC_ID_HQX },
+ { { 0x06,0x0e,0x2b,0x34,0x04,0x01,0x01,0x0a,0x0e,0x15,0x00,0x04,0x02,0x10,0x00,0x01 }, 16, AV_CODEC_ID_HEVC, NULL, 15 }, /* Canon XF-HEVC */
{ { 0x06,0x0e,0x2b,0x34,0x01,0x01,0x01,0xff,0x4b,0x46,0x41,0x41,0x00,0x0d,0x4d,0x4f }, 14, AV_CODEC_ID_RAWVIDEO }, /* Legacy ?? Uncompressed Picture */
{ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, 0, AV_CODEC_ID_NONE },
};
*/
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++) {
+ for (int x = mxf->partitions_count - 1; x >= 0; x--) {
MXFPartition *p = &mxf->partitions[x];
if (p->body_sid != body_sid)
if (!p->essence_length)
return 0;
- ret = p->essence_offset + p->essence_length;
+ return p->essence_offset + p->essence_length;
}
- return ret;
+ return 0;
}
/* EditUnit -> absolute offset */
continue;
}
- if (physical_track->edit_rate.num <= 0 ||
- physical_track->edit_rate.den <= 0) {
- av_log(mxf->fc, AV_LOG_WARNING,
- "Invalid edit rate (%d/%d) found on structural"
- " component #%d, defaulting to 25/1\n",
- physical_track->edit_rate.num,
- physical_track->edit_rate.den, i);
- physical_track->edit_rate = (AVRational){25, 1};
- }
+ if (physical_track->edit_rate.num <= 0 ||
+ physical_track->edit_rate.den <= 0) {
+ av_log(mxf->fc, AV_LOG_WARNING,
+ "Invalid edit rate (%d/%d) found on structural"
+ " component #%d, defaulting to 25/1\n",
+ physical_track->edit_rate.num,
+ physical_track->edit_rate.den, i);
+ physical_track->edit_rate = (AVRational){25, 1};
+ }
for (k = 0; k < physical_track->sequence->structural_components_count; k++) {
if (!(mxf_tc = mxf_resolve_timecode_component(mxf, &physical_track->sequence->structural_components_refs[k])))
default:
av_log(mxf->fc, AV_LOG_INFO, "Unknown frame layout type: %d\n", descriptor->frame_layout);
}
+
+ if (st->codecpar->codec_id == AV_CODEC_ID_PRORES) {
+ switch (descriptor->essence_codec_ul[14]) {
+ case 1: st->codecpar->codec_tag = MKTAG('a','p','c','o'); break;
+ case 2: st->codecpar->codec_tag = MKTAG('a','p','c','s'); break;
+ case 3: st->codecpar->codec_tag = MKTAG('a','p','c','n'); break;
+ case 4: st->codecpar->codec_tag = MKTAG('a','p','c','h'); break;
+ case 5: st->codecpar->codec_tag = MKTAG('a','p','4','h'); break;
+ case 6: st->codecpar->codec_tag = MKTAG('a','p','4','x'); break;
+ }
+ }
+
if (st->codecpar->codec_id == AV_CODEC_ID_RAWVIDEO) {
st->codecpar->format = descriptor->pix_fmt;
if (st->codecpar->format == AV_PIX_FMT_NONE) {
st->codecpar->codec_type = type;
if (container_ul->desc)
av_dict_set(&st->metadata, "data_type", container_ul->desc, 0);
+ if (mxf->eia608_extract &&
+ !strcmp(container_ul->desc, "vbi_vanc_smpte_436M")) {
+ st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE;
+ st->codecpar->codec_id = AV_CODEC_ID_EIA_608;
+ }
}
if (descriptor->extradata) {
if (!ff_alloc_extradata(st->codecpar, descriptor->extradata_size)) {
}
}
+ for (int i = 0; i < mxf->fc->nb_streams; i++) {
+ MXFTrack *track1 = mxf->fc->streams[i]->priv_data;
+ if (track1 && track1->body_sid) {
+ for (int j = i + 1; j < mxf->fc->nb_streams; j++) {
+ MXFTrack *track2 = mxf->fc->streams[j]->priv_data;
+ if (track2 && track1->body_sid == track2->body_sid && track1->wrapping != track2->wrapping) {
+ if (track1->wrapping == UnknownWrapped)
+ track1->wrapping = track2->wrapping;
+ else if (track2->wrapping == UnknownWrapped)
+ track2->wrapping = track1->wrapping;
+ else
+ av_log(mxf->fc, AV_LOG_ERROR, "stream %d and stream %d have the same BodySID (%d) "
+ "with different wrapping\n", i, j, track1->body_sid);
+ }
+ }
+ }
+ }
+
ret = 0;
fail_and_free:
return ret;
static int64_t mxf_timestamp_to_int64(uint64_t timestamp)
{
struct tm time = { 0 };
+ int msecs;
time.tm_year = (timestamp >> 48) - 1900;
time.tm_mon = (timestamp >> 40 & 0xFF) - 1;
time.tm_mday = (timestamp >> 32 & 0xFF);
time.tm_hour = (timestamp >> 24 & 0xFF);
time.tm_min = (timestamp >> 16 & 0xFF);
time.tm_sec = (timestamp >> 8 & 0xFF);
+ msecs = (timestamp & 0xFF) * 4;
- /* msvcrt versions of strftime calls the invalid parameter handler
- * (aborting the process if one isn't set) if the parameters are out
- * of range. */
+ /* Clip values for legacy reasons. Maybe we should return error instead? */
time.tm_mon = av_clip(time.tm_mon, 0, 11);
time.tm_mday = av_clip(time.tm_mday, 1, 31);
time.tm_hour = av_clip(time.tm_hour, 0, 23);
time.tm_min = av_clip(time.tm_min, 0, 59);
time.tm_sec = av_clip(time.tm_sec, 0, 59);
+ msecs = av_clip(msecs, 0, 999);
- return (int64_t)av_timegm(&time) * 1000000;
+ return (int64_t)av_timegm(&time) * 1000000 + msecs * 1000;
}
#define SET_STR_METADATA(pb, name, str) do { \
#define SET_TS_METADATA(pb, name, var, str) do { \
var = avio_rb64(pb); \
- if ((ret = avpriv_dict_set_timestamp(&s->metadata, name, mxf_timestamp_to_int64(var)) < 0)) \
+ if (var && (ret = avpriv_dict_set_timestamp(&s->metadata, name, mxf_timestamp_to_int64(var))) < 0) \
return ret; \
} while (0)
goto end;
avio_seek(s->pb, file_size - length, SEEK_SET);
if (klv_read_packet(&klv, s->pb) < 0 ||
- !IS_KLV_KEY(klv.key, mxf_random_index_pack_key) ||
- klv.length != length - 20)
+ !IS_KLV_KEY(klv.key, mxf_random_index_pack_key))
+ goto end;
+ if (klv.next_klv != file_size || klv.length <= 4 || (klv.length - 4) % 12) {
+ av_log(s, AV_LOG_WARNING, "Invalid RIP KLV length\n");
goto end;
+ }
avio_skip(s->pb, klv.length - 12);
mxf->footer_partition = avio_rb64(s->pb);
if (!mxf_read_sync(s->pb, mxf_header_partition_pack_key, 14)) {
av_log(s, AV_LOG_ERROR, "could not find header partition pack key\n");
+ //goto fail should not be needed as no metadata sets will have been parsed yet
return AVERROR_INVALIDDATA;
}
avio_seek(s->pb, -14, SEEK_CUR);
if (!mxf->current_partition) {
av_log(mxf->fc, AV_LOG_ERROR, "found essence prior to first PartitionPack\n");
- return AVERROR_INVALIDDATA;
+ ret = AVERROR_INVALIDDATA;
+ goto fail;
}
if (!mxf->current_partition->first_essence_klv.offset)
if (ret < 0)
return ret;
} else if (track) {
+ pkt->dts = pkt->pts = track->sample_count;
+ pkt->duration = 1;
track->sample_count++;
}
return 0;
if (IS_KLV_KEY(klv.key, mxf_essence_element_key) ||
IS_KLV_KEY(klv.key, mxf_canopus_essence_element_key) ||
IS_KLV_KEY(klv.key, mxf_avid_essence_element_key)) {
- int body_sid = find_body_sid_by_offset(mxf, klv.offset);
+ int body_sid = find_body_sid_by_absolute_offset(mxf, klv.offset);
int index = mxf_get_stream_index(s, &klv, body_sid);
int64_t next_ofs;
AVStream *st;
} else {
if ((size = next_ofs - pos) <= 0) {
av_log(s, AV_LOG_ERROR, "bad size: %"PRId64"\n", size);
- ret = AVERROR_INVALIDDATA;
- goto skip;
+ mxf->current_klv_data = (KLVPacket){{0}};
+ return AVERROR_INVALIDDATA;
}
// We must not overread, because the next edit unit might be in another KLV
if (size > max_data_size)
mxf->current_klv_data = (KLVPacket){{0}};
return ret;
}
+ } else if (mxf->eia608_extract &&
+ s->streams[index]->codecpar->codec_id == AV_CODEC_ID_EIA_608) {
+ ret = mxf_get_eia608_packet(s, s->streams[index], pkt, klv.length);
+ if (ret < 0) {
+ mxf->current_klv_data = (KLVPacket){{0}};
+ return ret;
+ }
} else {
ret = av_get_packet(s->pb, pkt, klv.length);
if (ret < 0) {
for (i = 0; i < mxf->metadata_sets_count; i++) {
mxf_free_metadataset(mxf->metadata_sets + i, 1);
}
+ mxf->metadata_sets_count = 0;
av_freep(&mxf->partitions);
av_freep(&mxf->metadata_sets);
av_freep(&mxf->aesc);
return 0;
}
-static int mxf_probe(AVProbeData *p) {
+static int mxf_probe(const AVProbeData *p) {
const uint8_t *bufp = p->buf;
const uint8_t *end = p->buf + p->buf_size;
return 0;
}
+static const AVOption options[] = {
+ { "eia608_extract", "extract eia 608 captions from s436m track",
+ offsetof(MXFContext, eia608_extract), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1,
+ AV_OPT_FLAG_DECODING_PARAM },
+ { NULL },
+};
+
+static const AVClass demuxer_class = {
+ .class_name = "mxf",
+ .item_name = av_default_item_name,
+ .option = options,
+ .version = LIBAVUTIL_VERSION_INT,
+ .category = AV_CLASS_CATEGORY_DEMUXER,
+};
+
AVInputFormat ff_mxf_demuxer = {
.name = "mxf",
.long_name = NULL_IF_CONFIG_SMALL("MXF (Material eXchange Format)"),
.read_packet = mxf_read_packet,
.read_close = mxf_read_close,
.read_seek = mxf_read_seek,
+ .priv_class = &demuxer_class,
};