Add support for MP4/QuickTime non-full meta atoms

#minor-release

PiperOrigin-RevId: 357160215
This commit is contained in:
kimvde 2021-02-12 11:45:08 +00:00 committed by Oliver Woodman
parent 158e6de25a
commit b303eceafd
3 changed files with 34 additions and 28 deletions

View File

@ -11,6 +11,8 @@
* DASH: * DASH:
* Fix playback issue for multi-period DASH live streams * Fix playback issue for multi-period DASH live streams
([#8537](https://github.com/google/ExoPlayer/issues/8537)). ([#8537](https://github.com/google/ExoPlayer/issues/8537)).
* Extractors:
* Add support for MP4 and QuickTime meta atoms that are not full atoms.
* UI: * UI:
* Add builder for `PlayerNotificationManager`. * Add builder for `PlayerNotificationManager`.
* IMA extension: * IMA extension:

View File

@ -145,12 +145,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* Parses a udta atom. * Parses a udta atom.
* *
* @param udtaAtom The udta (user data) atom to decode. * @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 * @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). * any), and the metadata from the smta child atom as second value (if any).
*/ */
public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta( public static Pair<@NullableType Metadata, @NullableType Metadata> parseUdta(
Atom.LeafAtom udtaAtom, boolean isQuickTime) { Atom.LeafAtom udtaAtom) {
ParsableByteArray udtaData = udtaAtom.data; ParsableByteArray udtaData = udtaAtom.data;
udtaData.setPosition(Atom.HEADER_SIZE); udtaData.setPosition(Atom.HEADER_SIZE);
@Nullable Metadata metaMetadata = null; @Nullable Metadata metaMetadata = null;
@ -159,8 +158,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int atomPosition = udtaData.getPosition(); int atomPosition = udtaData.getPosition();
int atomSize = udtaData.readInt(); int atomSize = udtaData.readInt();
int atomType = 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) {
if (atomType == Atom.TYPE_meta && !isQuickTime) {
udtaData.setPosition(atomPosition); udtaData.setPosition(atomPosition);
metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize); metaMetadata = parseUdtaMeta(udtaData, atomPosition + atomSize);
} else if (atomType == Atom.TYPE_smta) { } else if (atomType == Atom.TYPE_smta) {
@ -227,6 +225,28 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return entries.isEmpty() ? null : new Metadata(entries); return entries.isEmpty() ? null : new Metadata(entries);
} }
/**
* Possibly skips the version and flags fields (1+3 byte) of a full meta atom.
*
* <p>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). * Parses a trak atom (defined in ISO/IEC 14496-12).
* *
@ -677,7 +697,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
@Nullable @Nullable
private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { 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) { while (meta.getPosition() < limit) {
int atomPosition = meta.getPosition(); int atomPosition = meta.getPosition();
int atomSize = meta.readInt(); int atomSize = meta.readInt();

View File

@ -470,7 +470,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
if (udta != null) { if (udta != null) {
Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata = Pair<@NullableType Metadata, @NullableType Metadata> udtaMetadata =
AtomParsers.parseUdta(udta, isQuickTime); AtomParsers.parseUdta(udta);
udtaMetaMetadata = udtaMetadata.first; udtaMetaMetadata = udtaMetadata.first;
smtaMetadata = udtaMetadata.second; smtaMetadata = udtaMetadata.second;
if (udtaMetaMetadata != null) { 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}.
*
* <p>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 { private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException {
scratch.reset(8); scratch.reset(4);
// Peek the next 8 bytes which can be either input.peekFully(scratch.getData(), 0, 4);
// (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch);
// (qt) [4 byte size of next atom ][4 byte hdlr atom type ] input.skipFully(scratch.getPosition());
// 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(); input.resetPeekPosition();
} else {
input.skipFully(4);
}
} }
/** Processes an atom whose payload does not need to be parsed. */ /** Processes an atom whose payload does not need to be parsed. */