@@ -223,7 +223,9 @@ typedef struct MOVContext {
int found_moov; ///< 'moov' atom has been found
int found_mdat; ///< 'mdat' atom has been found
int found_hdlr_mdta; ///< 'hdlr' atom with type 'mdta' has been found
+ int found_iloc; ///< 'iloc' atom has been found
int trak_index; ///< Index of the current 'trak'
+ int cur_stream_index; ///< Stream currently being populated
char **meta_keys;
unsigned meta_keys_count;
DVDemuxContext *dv_demux;
@@ -1678,9 +1678,9 @@ static int mov_read_glbl(MOVContext *c, AVIOContext *pb, MOVAtom atom)
AVStream *st;
int ret;
- if (c->fc->nb_streams < 1)
+ if (c->cur_stream_index < 0)
return 0;
- st = c->fc->streams[c->fc->nb_streams-1];
+ st = c->fc->streams[c->cur_stream_index];
if ((uint64_t)atom.size > (1<<30))
return AVERROR_INVALIDDATA;
@@ -3742,6 +3742,8 @@ static int mov_read_trak(MOVContext *c, AVIOContext *pb, MOVAtom atom)
sc = av_mallocz(sizeof(MOVStreamContext));
if (!sc) return AVERROR(ENOMEM);
+ c->cur_stream_index = st->index;
+
st->priv_data = sc;
st->codecpar->codec_type = AVMEDIA_TYPE_DATA;
sc->ffindex = st->index;
@@ -3976,6 +3978,10 @@ static int mov_read_custom(MOVContext *c, AVIOContext *pb, MOVAtom atom)
static int mov_read_meta(MOVContext *c, AVIOContext *pb, MOVAtom atom)
{
+ // Skip version and flags
+ avio_skip(pb, 4);
+ atom.size -= 4;
+
while (atom.size > 8) {
uint32_t tag = avio_rl32(pb);
atom.size -= 4;
@@ -5470,6 +5476,310 @@ static int mov_read_dops(MOVContext *c, AVIOContext *pb, MOVAtom atom)
return 0;
}
+static uint64_t read_length(AVIOContext *pb, unsigned len)
+{
+ uint64_t ret = 0, i = 0;
+ for (i = 0; i < len; i++)
+ ret = (ret << 8) | avio_r8(pb);
+ return ret;
+}
+
+enum HEIFOffsetType {
+ HEIF_OFFSET_FILE = 0,
+ HEIF_OFFSET_MDAT = 1,
+ HEIF_OFFSET_ITEM = 2
+};
+
+static int mov_read_iloc(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ int version = avio_r8(pb);
+ avio_rb24(pb); // flags
+ int ret = 0;
+
+ unsigned item_count, i, j;
+
+ int offset_size = avio_r8(pb);
+ int length_size = offset_size & 0xf;
+ int base_offset_size = avio_r8(pb);
+ int index_size = base_offset_size & 0xf;
+ offset_size >>= 4;
+ base_offset_size >>= 4;
+
+ if (version > 2) {
+ avpriv_request_sample(c->fc, "iloc atom Version %d", version);
+ return AVERROR_PATCHWELCOME;
+ }
+
+ if (offset_size > 8 || length_size > 8 || base_offset_size > 8 || index_size > 8) {
+ avpriv_request_sample(c->fc, ">8-byte sizes in iloc");
+ return AVERROR_PATCHWELCOME;
+ }
+
+ item_count = (version < 2) ? avio_rb16(pb) : avio_rb32(pb);
+
+ for (i = 0; i < item_count; i++) {
+ int64_t base_offset;
+ int data_ref_index, nb_extents;
+ enum HEIFOffsetType offset_type = 0;
+
+ MOVStreamContext *sc;
+ AVStream *st = avformat_new_stream(c->fc, NULL);
+ if (!st)
+ return AVERROR(ENOMEM);
+ sc = av_mallocz(sizeof(*sc));
+ if (!sc)
+ return AVERROR(ENOMEM);
+ st->priv_data = sc;
+
+ st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->disposition |= AV_DISPOSITION_ATTACHED_PIC;
+
+ st->id = (version < 2) ? avio_rb16(pb) : (int)avio_rb32(pb);
+
+ if (version > 0) {
+ avio_r8(pb); // reserved
+ offset_type = avio_r8(pb) & 0xf;
+ }
+
+ if (offset_type != HEIF_OFFSET_FILE) {
+ avpriv_request_sample(c->fc, "iloc offset type %d", offset_type);
+ return AVERROR_PATCHWELCOME;
+ }
+
+ data_ref_index = avio_rb16(pb);
+ base_offset = read_length(pb, base_offset_size);
+ nb_extents = avio_rb16(pb);
+
+ for (j = 0; j < nb_extents; j++) {
+ int64_t pos = 0;
+ int64_t old_pos;
+ int64_t ret64;
+ int64_t ext_index = 0, ext_offset, ext_len;
+ if (version > 0)
+ ext_index = read_length(pb, index_size);
+ ext_offset = read_length(pb, offset_size);
+ ext_len = read_length(pb, length_size);
+
+ if (offset_type == HEIF_OFFSET_FILE) {
+ pos = base_offset + ext_offset;
+ }
+
+ //FIXME: this will thrash badly if there are lots of extents or lots of items
+ old_pos = avio_tell(pb);
+ if ((ret64 = avio_seek(pb, pos, SEEK_SET)) < 0) {
+ ret = (int)ret64;
+ goto err;
+ }
+
+ if ((ret = av_append_packet(pb, &st->attached_pic, ext_len)) < 0)
+ goto err;
+
+ if ((ret64 = avio_seek(pb, old_pos, SEEK_SET)) < 0) {
+ ret = (int)ret64;
+ goto err;
+ }
+ }
+
+ st->attached_pic.stream_index = st->index;
+ st->attached_pic.flags |= AV_PKT_FLAG_KEY;
+
+err:
+ if (ret < 0) {
+ av_packet_unref(&st->attached_pic);
+ break;
+ }
+ }
+
+ if (ret >= 0)
+ c->found_iloc = 1;
+
+ return ret;
+}
+
+static int mov_read_iinf(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ int version = avio_r8(pb);
+ avio_rb24(pb); // flags
+
+ atom.size -= 4;
+
+ // Completely useless entry count
+ if (version > 0) {
+ avio_rb32(pb);
+ atom.size -= 4;
+ } else {
+ avio_rb16(pb);
+ atom.size -= 2;
+ }
+
+ return mov_read_default(c, pb, atom);
+}
+
+static int mov_read_infe(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st = NULL;
+ int codec_tag;
+ int stream_id, i;
+ int version = avio_r8(pb);
+ avio_rb24(pb); // flags
+
+ stream_id = (version >= 3) ? avio_rb32(pb) : avio_rb16(pb);
+
+ for (i = 0; i < c->fc->nb_streams; i++) {
+ if (c->fc->streams[i]->id == stream_id) {
+ st = c->fc->streams[i];
+ break;
+ }
+ }
+
+ if (!st)
+ return AVERROR_INVALIDDATA;
+
+ if (avio_rb16(pb) != 0) {
+ avpriv_request_sample(c->fc, "protected HEIF");
+ return AVERROR_PATCHWELCOME;
+ }
+
+ codec_tag = avio_rl32(pb);
+ st->codecpar->codec_id = mov_codec_id(st, codec_tag);
+
+ //FIXME: there may be more information here; no samples I've seen use it
+ return 0;
+}
+
+static int mov_read_iprp(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ typedef struct AtomPos {
+ int64_t pos;
+ int64_t size;
+ } AtomPos;
+ int version, flags;
+ unsigned count, i, j;
+ AtomPos *atoms = NULL;
+ int nb_atoms = 0;
+ int ret = 0;
+ int64_t ret64;
+ int64_t old_pos;
+
+ MOVAtom a;
+ a.size = avio_rb32(pb);
+ a.type = avio_rl32(pb);
+
+ if (a.size < 8 || a.type != MKTAG('i','p','c','o'))
+ return AVERROR_INVALIDDATA;
+
+ a.size -= 8;
+
+ while (a.size >= 8) {
+ AtomPos *ref = av_dynarray2_add((void**)&atoms, &nb_atoms, sizeof(AtomPos), NULL);
+ if (!ref) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ ref->pos = avio_tell(pb);
+ ref->size = avio_rb32(pb);
+ if (ref->size > a.size || ref->size < 8)
+ break;
+ if ((ret64 = avio_seek(pb, ref->pos + ref->size, SEEK_SET)) < 0) {
+ ret = ret64;
+ goto fail;
+ }
+ a.size -= ref->size;
+ }
+
+ if (a.size) {
+ ret = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+
+ a.size = avio_rb32(pb);
+ a.type = avio_rl32(pb);
+
+ if (a.size < 8 || a.type != MKTAG('i','p','m','a')) {
+ ret = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+
+ version = avio_r8(pb);
+ flags = avio_rb24(pb);
+
+ count = avio_rb32(pb);
+
+ for (i = 0; i < count; i++) {
+ int stream_id = (version >= 1) ? avio_rb32(pb) : avio_rb16(pb);
+ int acount = avio_r8(pb);
+
+ for (j = 0; j < c->fc->nb_streams; j++) {
+ if (c->fc->streams[j]->id == stream_id)
+ break;
+ }
+
+ if (j == c->fc->nb_streams) {
+ ret = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+
+ c->cur_stream_index = j;
+
+ for (j = 0; j < acount; j++) {
+ MOVAtom parentAtom;
+ AtomPos *atom;
+ int index = avio_r8(pb) & 0x7f;
+ if (flags & 1) {
+ index <<= 8;
+ index |= avio_r8(pb);
+ }
+ if (index > nb_atoms || index == 0) {
+ ret = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+
+ index--;
+
+ atom = &atoms[index];
+
+ old_pos = avio_tell(pb);
+
+ if ((ret64 = avio_seek(pb, atom->pos, SEEK_SET)) < 0) {
+ ret = (int)ret64;
+ goto fail;
+ }
+
+ parentAtom = (MOVAtom){ .size = atom->size, .type = MKTAG('i','p','c','o') };
+ if ((ret = mov_read_default(c, pb, parentAtom)) < 0)
+ goto fail;
+
+ if ((ret64 = avio_seek(pb, old_pos, SEEK_SET)) < 0) {
+ ret = (int)ret64;
+ goto fail;
+ }
+ }
+ }
+
+ ret = 0;
+
+fail:
+ av_free(atoms);
+ return ret;
+}
+
+static int mov_read_ispe(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+{
+ AVStream *st;
+
+ if (c->cur_stream_index < 0)
+ return 0;
+ st = c->fc->streams[c->cur_stream_index];
+
+ avio_r8(pb); // version
+ avio_rb24(pb); // flags
+
+ st->codecpar->width = avio_rb32(pb);
+ st->codecpar->height = avio_rb32(pb);
+
+ return 0;
+}
+
static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('A','C','L','R'), mov_read_aclr },
{ MKTAG('A','P','R','G'), mov_read_avid },
@@ -5555,6 +5865,11 @@ static const MOVParseTableEntry mov_default_parse_table[] = {
{ MKTAG('S','m','D','m'), mov_read_smdm },
{ MKTAG('C','o','L','L'), mov_read_coll },
{ MKTAG('v','p','c','C'), mov_read_vpcc },
+{ MKTAG('i','l','o','c'), mov_read_iloc },
+{ MKTAG('i','i','n','f'), mov_read_iinf },
+{ MKTAG('i','n','f','e'), mov_read_infe },
+{ MKTAG('i','p','r','p'), mov_read_iprp },
+{ MKTAG('i','s','p','e'), mov_read_ispe },
{ 0, NULL }
};
@@ -5701,6 +6016,7 @@ static int mov_probe(AVProbeData *p)
case MKTAG('p','n','o','t'): /* detect movs with preview pics like ew.mov and april.mov */
case MKTAG('u','d','t','a'): /* Packet Video PVAuthor adds this and a lot of more junk */
case MKTAG('f','t','y','p'):
+ case MKTAG('i','l','o','c'):
if (AV_RB32(p->buf+offset) < 8 &&
(AV_RB32(p->buf+offset) != 1 ||
offset + 12 > (unsigned int)p->buf_size ||
@@ -6167,6 +6483,7 @@ static int mov_read_header(AVFormatContext *s)
mov->fc = s;
mov->trak_index = -1;
+ mov->cur_stream_index = -1;
/* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
if (pb->seekable & AVIO_SEEKABLE_NORMAL)
atom.size = avio_size(pb);
@@ -6182,8 +6499,8 @@ static int mov_read_header(AVFormatContext *s)
mov_read_close(s);
return err;
}
- } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->moov_retry++);
- if (!mov->found_moov) {
+ } while ((pb->seekable & AVIO_SEEKABLE_NORMAL) && !mov->found_moov && !mov->found_iloc && !mov->moov_retry++);
+ if (!mov->found_moov && !mov->found_iloc) {
av_log(s, AV_LOG_ERROR, "moov atom not found\n");
mov_read_close(s);
return AVERROR_INVALIDDATA;
@@ -6767,18 +7084,18 @@ static const AVOption mov_options[] = {
};
static const AVClass mov_class = {
- .class_name = "mov,mp4,m4a,3gp,3g2,mj2",
+ .class_name = "mov,mp4,m4a,3gp,3g2,mj2,heif,heic",
.item_name = av_default_item_name,
.option = mov_options,
.version = LIBAVUTIL_VERSION_INT,
};
AVInputFormat ff_mov_demuxer = {
- .name = "mov,mp4,m4a,3gp,3g2,mj2",
+ .name = "mov,mp4,m4a,3gp,3g2,mj2,heif,heic",
.long_name = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
.priv_class = &mov_class,
.priv_data_size = sizeof(MOVContext),
- .extensions = "mov,mp4,m4a,3gp,3g2,mj2",
+ .extensions = "mov,mp4,m4a,3gp,3g2,mj2,heif,heic",
.read_probe = mov_probe,
.read_header = mov_read_header,
.read_packet = mov_read_packet,