diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4932376eed..ae5a914dee 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -11,6 +11,8 @@ * DASH: * Fix playback issue for multi-period DASH live streams ([#8537](https://github.com/google/ExoPlayer/issues/8537)). +* Extractors: + * Add support for MP4 and QuickTime meta atoms that are not full atoms. * UI: * Add builder for `PlayerNotificationManager`. * IMA extension: diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 162294f1a0..b2db7a6df5 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -145,12 +145,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; * Parses a udta atom. * * @param udtaAtom The udta (user data) atom to decode. - * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Pair} containing the metadata from the meta child atom as first value (if * any), and the metadata from the smta child atom as second value (if any). */ public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta( - Atom.LeafAtom udtaAtom, boolean isQuickTime) { + Atom.LeafAtom udtaAtom) { ParsableByteArray udtaData = udtaAtom.data; udtaData.setPosition(Atom.HEADER_SIZE); @Nullable Metadata metaMetadata = null; @@ -159,8 +158,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int atomPosition = udtaData.getPosition(); int atomSize = udtaData.readInt(); int atomType = udtaData.readInt(); - // Meta boxes are regular boxes rather than full boxes in QuickTime. Ignore them for now. - if (atomType == Atom.TYPE_meta && !isQuickTime) { + if (atomType == Atom.TYPE_meta) { udtaData.setPosition(atomPosition); metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize); } else if (atomType == Atom.TYPE_smta) { @@ -227,6 +225,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return entries.isEmpty() ? null : new Metadata(entries); } + /** + * Possibly skips the version and flags fields (1+3 byte) of a full meta atom. + * + *

Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional + * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). + * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, + * we can't rely on the file type though. Instead we must check the 4 bytes after the common + * header bytes ourselves. + * + * @param meta The 4 or more bytes following the meta atom size and type. + */ + public static void maybeSkipRemainingMetaAtomHeaderBytes(ParsableByteArray meta) { + int startPosition = meta.getPosition(); + // The next 4 bytes can be either: + // (iso) 4 zero bytes (version + flags) + // (qt) 4 byte size of next atom + // In case of (iso) we need to skip the next 4 bytes. + if (meta.readInt() != 0) { + meta.setPosition(startPosition); + } + } + /** * Parses a trak atom (defined in ISO/IEC 14496-12). * @@ -677,7 +697,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; @Nullable private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { - meta.skipBytes(Atom.FULL_HEADER_SIZE); + meta.skipBytes(Atom.HEADER_SIZE); + maybeSkipRemainingMetaAtomHeaderBytes(meta); while (meta.getPosition() < limit) { int atomPosition = meta.getPosition(); int atomSize = meta.readInt(); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 823fd77fe0..f284eaff4f 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -470,7 +470,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata = - AtomParsers.parseUdta(udta, isQuickTime); + AtomParsers.parseUdta(udta); udtaMetaMetadata = udtaMetadata.first; smtaMetadata = udtaMetadata.second; if (udtaMetaMetadata != null) { @@ -727,29 +727,12 @@ public final class Mp4Extractor implements Extractor, SeekMap { } } - /** - * Possibly skips the version and flags fields (1+3 byte) of a full meta atom of the {@code - * input}. - * - *

Atoms of type {@link Atom#TYPE_meta} are defined to be full atoms which have four additional - * bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC 14496-12:2005). - * QuickTime do not have such a full box structure. Since some of these files are encoded wrongly, - * we can't rely on the file type though. Instead we must check the 8 bytes after the common - * header bytes ourselves. - */ private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { - scratch.reset(8); - // Peek the next 8 bytes which can be either - // (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] - // (qt) [4 byte size of next atom ][4 byte hdlr atom type ] - // In case of (iso) we need to skip the next 4 bytes. - input.peekFully(scratch.getData(), 0, 8); - scratch.skipBytes(4); - if (scratch.readInt() == Atom.TYPE_hdlr) { - input.resetPeekPosition(); - } else { - input.skipFully(4); - } + scratch.reset(4); + input.peekFully(scratch.getData(), 0, 4); + AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch); + input.skipFully(scratch.getPosition()); + input.resetPeekPosition(); } /** Processes an atom whose payload does not need to be parsed. */