mirror of
https://github.com/samsonjs/media.git
synced 2026-03-29 10:05:48 +00:00
Parse frame rate from 'mdta' metadata
PiperOrigin-RevId: 227813461
This commit is contained in:
parent
d834eeab6f
commit
ed1f41db1b
7 changed files with 405 additions and 58 deletions
|
|
@ -1181,6 +1181,37 @@ public final class Format implements Parcelable {
|
|||
metadata);
|
||||
}
|
||||
|
||||
public Format copyWithFrameRate(float frameRate) {
|
||||
return new Format(
|
||||
id,
|
||||
label,
|
||||
containerMimeType,
|
||||
sampleMimeType,
|
||||
codecs,
|
||||
bitrate,
|
||||
maxInputSize,
|
||||
width,
|
||||
height,
|
||||
frameRate,
|
||||
rotationDegrees,
|
||||
pixelWidthHeightRatio,
|
||||
projectionData,
|
||||
stereoMode,
|
||||
colorInfo,
|
||||
channelCount,
|
||||
sampleRate,
|
||||
pcmEncoding,
|
||||
encoderDelay,
|
||||
encoderPadding,
|
||||
selectionFlags,
|
||||
language,
|
||||
accessibilityChannel,
|
||||
subsampleOffsetUs,
|
||||
initializationData,
|
||||
drmInitData,
|
||||
metadata);
|
||||
}
|
||||
|
||||
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||
return new Format(
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("ConstantField")
|
||||
@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"})
|
||||
/* package */ abstract class Atom {
|
||||
|
||||
/**
|
||||
|
|
@ -130,6 +130,7 @@ import java.util.List;
|
|||
public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb");
|
||||
public static final int TYPE_udta = Util.getIntegerCodeForString("udta");
|
||||
public static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
||||
public static final int TYPE_keys = Util.getIntegerCodeForString("keys");
|
||||
public static final int TYPE_ilst = Util.getIntegerCodeForString("ilst");
|
||||
public static final int TYPE_mean = Util.getIntegerCodeForString("mean");
|
||||
public static final int TYPE_name = Util.getIntegerCodeForString("name");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.mp4;
|
|||
|
||||
import static com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
|
|
@ -39,7 +40,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
|
||||
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
|
||||
@SuppressWarnings("ConstantField")
|
||||
@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"})
|
||||
/* package */ final class AtomParsers {
|
||||
|
||||
private static final String TAG = "AtomParsers";
|
||||
|
|
@ -51,6 +52,7 @@ import java.util.List;
|
|||
private static final int TYPE_subt = Util.getIntegerCodeForString("subt");
|
||||
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
|
||||
private static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
||||
private static final int TYPE_mdta = Util.getIntegerCodeForString("mdta");
|
||||
|
||||
/**
|
||||
* The threshold number of samples to trim from the start/end of an audio track when applying an
|
||||
|
|
@ -77,7 +79,7 @@ import java.util.List;
|
|||
DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime)
|
||||
throws ParserException {
|
||||
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
||||
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
|
||||
int trackType = getTrackTypeForHdlr(parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data));
|
||||
if (trackType == C.TRACK_TYPE_UNKNOWN) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -485,6 +487,7 @@ import java.util.List;
|
|||
* @param isQuickTime True for QuickTime media. False otherwise.
|
||||
* @return Parsed metadata, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) {
|
||||
if (isQuickTime) {
|
||||
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
|
||||
|
|
@ -499,14 +502,69 @@ import java.util.List;
|
|||
int atomType = udtaData.readInt();
|
||||
if (atomType == Atom.TYPE_meta) {
|
||||
udtaData.setPosition(atomPosition);
|
||||
return parseMetaAtom(udtaData, atomPosition + atomSize);
|
||||
return parseUdtaMeta(udtaData, atomPosition + atomSize);
|
||||
}
|
||||
udtaData.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||
udtaData.setPosition(atomPosition + atomSize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) {
|
||||
/**
|
||||
* Parses a metadata meta atom if it contains metadata with handler 'mdta'.
|
||||
*
|
||||
* @param meta The metadata atom to decode.
|
||||
* @return Parsed metadata, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) {
|
||||
Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr);
|
||||
Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys);
|
||||
Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst);
|
||||
if (hdlrAtom == null
|
||||
|| keysAtom == null
|
||||
|| ilstAtom == null
|
||||
|| AtomParsers.parseHdlr(hdlrAtom.data) != TYPE_mdta) {
|
||||
// There isn't enough information to parse the metadata, or the handler type is unexpected.
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse metadata keys.
|
||||
ParsableByteArray keys = keysAtom.data;
|
||||
keys.setPosition(Atom.FULL_HEADER_SIZE);
|
||||
int entryCount = keys.readInt();
|
||||
String[] keyNames = new String[entryCount];
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
int entrySize = keys.readInt();
|
||||
keys.skipBytes(4); // keyNamespace
|
||||
int keySize = entrySize - 8;
|
||||
keyNames[i] = keys.readString(keySize);
|
||||
}
|
||||
|
||||
// Parse metadata items.
|
||||
ParsableByteArray ilst = ilstAtom.data;
|
||||
ilst.setPosition(Atom.HEADER_SIZE);
|
||||
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
||||
while (ilst.bytesLeft() > Atom.HEADER_SIZE) {
|
||||
int atomPosition = ilst.getPosition();
|
||||
int atomSize = ilst.readInt();
|
||||
int keyIndex = ilst.readInt() - 1;
|
||||
if (keyIndex >= 0 && keyIndex < keyNames.length) {
|
||||
String key = keyNames[keyIndex];
|
||||
Metadata.Entry entry =
|
||||
MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key);
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Skipped metadata with unknown key index: " + keyIndex);
|
||||
}
|
||||
ilst.setPosition(atomPosition + atomSize);
|
||||
}
|
||||
return entries.isEmpty() ? null : new Metadata(entries);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) {
|
||||
meta.skipBytes(Atom.FULL_HEADER_SIZE);
|
||||
while (meta.getPosition() < limit) {
|
||||
int atomPosition = meta.getPosition();
|
||||
|
|
@ -516,11 +574,12 @@ import java.util.List;
|
|||
meta.setPosition(atomPosition);
|
||||
return parseIlst(meta, atomPosition + atomSize);
|
||||
}
|
||||
meta.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||
meta.setPosition(atomPosition + atomSize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
|
||||
ilst.skipBytes(Atom.HEADER_SIZE);
|
||||
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
||||
|
|
@ -610,19 +669,22 @@ import java.util.List;
|
|||
* Parses an hdlr atom.
|
||||
*
|
||||
* @param hdlr The hdlr atom to decode.
|
||||
* @return The track type.
|
||||
* @return The handler value.
|
||||
*/
|
||||
private static int parseHdlr(ParsableByteArray hdlr) {
|
||||
hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4);
|
||||
int trackType = hdlr.readInt();
|
||||
if (trackType == TYPE_soun) {
|
||||
return hdlr.readInt();
|
||||
}
|
||||
|
||||
/** Returns the track type for a given handler value. */
|
||||
private static int getTrackTypeForHdlr(int hdlr) {
|
||||
if (hdlr == TYPE_soun) {
|
||||
return C.TRACK_TYPE_AUDIO;
|
||||
} else if (trackType == TYPE_vide) {
|
||||
} else if (hdlr == TYPE_vide) {
|
||||
return C.TRACK_TYPE_VIDEO;
|
||||
} else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt
|
||||
|| trackType == TYPE_clcp) {
|
||||
} else if (hdlr == TYPE_text || hdlr == TYPE_sbtl || hdlr == TYPE_subt || hdlr == TYPE_clcp) {
|
||||
return C.TRACK_TYPE_TEXT;
|
||||
} else if (trackType == TYPE_meta) {
|
||||
} else if (hdlr == TYPE_meta) {
|
||||
return C.TRACK_TYPE_METADATA;
|
||||
} else {
|
||||
return C.TRACK_TYPE_UNKNOWN;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Stores extensible metadata with handler type 'mdta'. See also the QuickTime File Format
|
||||
* Specification.
|
||||
*/
|
||||
public final class MdtaMetadataEntry implements Metadata.Entry {
|
||||
|
||||
/** The metadata key name. */
|
||||
public final String key;
|
||||
/** The payload. The interpretation of the value depends on {@link #typeIndicator}. */
|
||||
public final byte[] value;
|
||||
/** The four byte locale indicator. */
|
||||
public final int localeIndicator;
|
||||
/** The four byte type indicator. */
|
||||
public final int typeIndicator;
|
||||
|
||||
/** Creates a new metadata entry for the specified metadata key/value. */
|
||||
public MdtaMetadataEntry(String key, byte[] value, int localeIndicator, int typeIndicator) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.localeIndicator = localeIndicator;
|
||||
this.typeIndicator = typeIndicator;
|
||||
}
|
||||
|
||||
private MdtaMetadataEntry(Parcel in) {
|
||||
key = in.readString();
|
||||
value = new byte[in.readInt()];
|
||||
in.readByteArray(value);
|
||||
localeIndicator = in.readInt();
|
||||
typeIndicator = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MdtaMetadataEntry other = (MdtaMetadataEntry) obj;
|
||||
return key.equals(other.key)
|
||||
&& Arrays.equals(value, other.value)
|
||||
&& localeIndicator == other.localeIndicator
|
||||
&& typeIndicator == other.typeIndicator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + key.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(value);
|
||||
result = 31 * result + localeIndicator;
|
||||
result = 31 * result + typeIndicator;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "mdta: key=" + key;
|
||||
}
|
||||
|
||||
// Parcelable implementation.
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(key);
|
||||
dest.writeInt(value.length);
|
||||
dest.writeByteArray(value);
|
||||
dest.writeInt(localeIndicator);
|
||||
dest.writeInt(typeIndicator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MdtaMetadataEntry> CREATOR =
|
||||
new Parcelable.Creator<MdtaMetadataEntry>() {
|
||||
|
||||
@Override
|
||||
public MdtaMetadataEntry createFromParcel(Parcel in) {
|
||||
return new MdtaMetadataEntry(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MdtaMetadataEntry[] newArray(int size) {
|
||||
return new MdtaMetadataEntry[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -16,6 +16,9 @@
|
|||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
||||
|
|
@ -25,10 +28,9 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
|||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Parses metadata items stored in ilst atoms.
|
||||
*/
|
||||
/** Utilities for handling metadata in MP4. */
|
||||
/* package */ final class MetadataUtil {
|
||||
|
||||
private static final String TAG = "MetadataUtil";
|
||||
|
|
@ -106,17 +108,64 @@ import com.google.android.exoplayer2.util.Util;
|
|||
private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9;
|
||||
private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD.
|
||||
|
||||
private static final String MDTA_KEY_ANDROID_CAPTURE_FPS = "com.android.capture.fps";
|
||||
private static final int MDTA_TYPE_INDICATOR_FLOAT = 23;
|
||||
|
||||
private MetadataUtil() {}
|
||||
|
||||
/**
|
||||
* Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting
|
||||
* from the current position of the {@link ParsableByteArray}, and the position is advanced by the
|
||||
* size of the element. The position is advanced even if the element's type is unrecognized.
|
||||
* Returns a {@link Format} that is the same as the input format but includes information from the
|
||||
* specified sources of metadata.
|
||||
*/
|
||||
public static Format getFormatWithMetadata(
|
||||
int trackType,
|
||||
Format format,
|
||||
@Nullable Metadata udtaMetadata,
|
||||
@Nullable Metadata mdtaMetadata,
|
||||
GaplessInfoHolder gaplessInfoHolder) {
|
||||
if (trackType == C.TRACK_TYPE_AUDIO) {
|
||||
if (gaplessInfoHolder.hasGaplessInfo()) {
|
||||
format =
|
||||
format.copyWithGaplessInfo(
|
||||
gaplessInfoHolder.encoderDelay, gaplessInfoHolder.encoderPadding);
|
||||
}
|
||||
// We assume all udta metadata is associated with the audio track.
|
||||
if (udtaMetadata != null) {
|
||||
format = format.copyWithMetadata(udtaMetadata);
|
||||
}
|
||||
} else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) {
|
||||
// Populate only metadata keys that are known to be specific to video.
|
||||
for (int i = 0; i < mdtaMetadata.length(); i++) {
|
||||
Metadata.Entry entry = mdtaMetadata.get(i);
|
||||
if (entry instanceof MdtaMetadataEntry) {
|
||||
MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry;
|
||||
if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key)
|
||||
&& mdtaMetadataEntry.typeIndicator == MDTA_TYPE_INDICATOR_FLOAT) {
|
||||
try {
|
||||
float fps = ByteBuffer.wrap(mdtaMetadataEntry.value).asFloatBuffer().get();
|
||||
format = format.copyWithFrameRate(fps);
|
||||
format = format.copyWithMetadata(new Metadata(mdtaMetadataEntry));
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Ignoring invalid framerate");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a single userdata ilst element from a {@link ParsableByteArray}. The element is read
|
||||
* starting from the current position of the {@link ParsableByteArray}, and the position is
|
||||
* advanced by the size of the element. The position is advanced even if the element's type is
|
||||
* unrecognized.
|
||||
*
|
||||
* @param ilst Holds the data to be parsed.
|
||||
* @return The parsed element, or null if the element's type was not recognized.
|
||||
*/
|
||||
public static @Nullable Metadata.Entry parseIlstElement(ParsableByteArray ilst) {
|
||||
@Nullable
|
||||
public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) {
|
||||
int position = ilst.getPosition();
|
||||
int endPosition = position + ilst.readInt();
|
||||
int type = ilst.readInt();
|
||||
|
|
@ -187,7 +236,36 @@ import com.google.android.exoplayer2.util.Util;
|
|||
}
|
||||
}
|
||||
|
||||
private static @Nullable TextInformationFrame parseTextAttribute(
|
||||
/**
|
||||
* Parses an 'mdta' metadata entry starting at the current position in an ilst box.
|
||||
*
|
||||
* @param ilst The ilst box.
|
||||
* @param endPosition The end position of the entry in the ilst box.
|
||||
* @param key The mdta metadata entry key for the entry.
|
||||
* @return The parsed element, or null if the entry wasn't recognized.
|
||||
*/
|
||||
@Nullable
|
||||
public static MdtaMetadataEntry parseMdtaMetadataEntryFromIlst(
|
||||
ParsableByteArray ilst, int endPosition, String key) {
|
||||
int atomPosition;
|
||||
while ((atomPosition = ilst.getPosition()) < endPosition) {
|
||||
int atomSize = ilst.readInt();
|
||||
int atomType = ilst.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
int typeIndicator = ilst.readInt();
|
||||
int localeIndicator = ilst.readInt();
|
||||
int dataSize = atomSize - 16;
|
||||
byte[] value = new byte[dataSize];
|
||||
ilst.readBytes(value, 0, dataSize);
|
||||
return new MdtaMetadataEntry(key, value, localeIndicator, typeIndicator);
|
||||
}
|
||||
ilst.setPosition(atomPosition + atomSize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TextInformationFrame parseTextAttribute(
|
||||
int type, String id, ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
|
|
@ -200,7 +278,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable CommentFrame parseCommentAttribute(int type, ParsableByteArray data) {
|
||||
@Nullable
|
||||
private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
|
|
@ -212,7 +291,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Id3Frame parseUint8Attribute(
|
||||
@Nullable
|
||||
private static Id3Frame parseUint8Attribute(
|
||||
int type,
|
||||
String id,
|
||||
ParsableByteArray data,
|
||||
|
|
@ -231,7 +311,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable TextInformationFrame parseIndexAndCountAttribute(
|
||||
@Nullable
|
||||
private static TextInformationFrame parseIndexAndCountAttribute(
|
||||
int type, String attributeName, ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
|
|
@ -251,8 +332,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable TextInformationFrame parseStandardGenreAttribute(
|
||||
ParsableByteArray data) {
|
||||
@Nullable
|
||||
private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) {
|
||||
int genreCode = parseUint8AttributeValue(data);
|
||||
String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length)
|
||||
? STANDARD_GENRES[genreCode - 1] : null;
|
||||
|
|
@ -263,7 +344,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable ApicFrame parseCoverArt(ParsableByteArray data) {
|
||||
@Nullable
|
||||
private static ApicFrame parseCoverArt(ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
|
|
@ -287,8 +369,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||
return null;
|
||||
}
|
||||
|
||||
private static @Nullable Id3Frame parseInternalAttribute(
|
||||
ParsableByteArray data, int endPosition) {
|
||||
@Nullable
|
||||
private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) {
|
||||
String domain = null;
|
||||
String name = null;
|
||||
int dataAtomPosition = -1;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
private static final int STATE_READING_ATOM_PAYLOAD = 1;
|
||||
private static final int STATE_READING_SAMPLE = 2;
|
||||
|
||||
// Brand stored in the ftyp atom for QuickTime media.
|
||||
/** Brand stored in the ftyp atom for QuickTime media. */
|
||||
private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt ");
|
||||
|
||||
/**
|
||||
|
|
@ -377,15 +377,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
long durationUs = C.TIME_UNSET;
|
||||
List<Mp4Track> tracks = new ArrayList<>();
|
||||
|
||||
Metadata metadata = null;
|
||||
// Process metadata.
|
||||
Metadata udtaMetadata = null;
|
||||
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
|
||||
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
|
||||
if (udta != null) {
|
||||
metadata = AtomParsers.parseUdta(udta, isQuickTime);
|
||||
if (metadata != null) {
|
||||
gaplessInfoHolder.setFromMetadata(metadata);
|
||||
udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime);
|
||||
if (udtaMetadata != null) {
|
||||
gaplessInfoHolder.setFromMetadata(udtaMetadata);
|
||||
}
|
||||
}
|
||||
Metadata mdtaMetadata = null;
|
||||
Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta);
|
||||
if (meta != null) {
|
||||
mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta);
|
||||
}
|
||||
|
||||
boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
|
||||
ArrayList<TrackSampleTable> trackSampleTables =
|
||||
|
|
@ -401,15 +407,9 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
// Allow ten source samples per output sample, like the platform extractor.
|
||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||
Format format = track.format.copyWithMaxInputSize(maxInputSize);
|
||||
if (track.type == C.TRACK_TYPE_AUDIO) {
|
||||
if (gaplessInfoHolder.hasGaplessInfo()) {
|
||||
format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay,
|
||||
gaplessInfoHolder.encoderPadding);
|
||||
}
|
||||
if (metadata != null) {
|
||||
format = format.copyWithMetadata(metadata);
|
||||
}
|
||||
}
|
||||
format =
|
||||
MetadataUtil.getFormatWithMetadata(
|
||||
track.type, format, udtaMetadata, mdtaMetadata, gaplessInfoHolder);
|
||||
mp4Track.trackOutput.format(format);
|
||||
|
||||
durationUs =
|
||||
|
|
@ -716,24 +716,37 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extractor should decode a leaf atom with type {@code atom}.
|
||||
*/
|
||||
/** Returns whether the extractor should decode a leaf atom with type {@code atom}. */
|
||||
private static boolean shouldParseLeafAtom(int atom) {
|
||||
return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr
|
||||
|| atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss
|
||||
|| atom == Atom.TYPE_ctts || atom == Atom.TYPE_elst || atom == Atom.TYPE_stsc
|
||||
|| atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco
|
||||
|| atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp
|
||||
|| atom == Atom.TYPE_udta;
|
||||
return atom == Atom.TYPE_mdhd
|
||||
|| atom == Atom.TYPE_mvhd
|
||||
|| atom == Atom.TYPE_hdlr
|
||||
|| atom == Atom.TYPE_stsd
|
||||
|| atom == Atom.TYPE_stts
|
||||
|| atom == Atom.TYPE_stss
|
||||
|| atom == Atom.TYPE_ctts
|
||||
|| atom == Atom.TYPE_elst
|
||||
|| atom == Atom.TYPE_stsc
|
||||
|| atom == Atom.TYPE_stsz
|
||||
|| atom == Atom.TYPE_stz2
|
||||
|| atom == Atom.TYPE_stco
|
||||
|| atom == Atom.TYPE_co64
|
||||
|| atom == Atom.TYPE_tkhd
|
||||
|| atom == Atom.TYPE_ftyp
|
||||
|| atom == Atom.TYPE_udta
|
||||
|| atom == Atom.TYPE_keys
|
||||
|| atom == Atom.TYPE_ilst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the extractor should decode a container atom with type {@code atom}.
|
||||
*/
|
||||
/** Returns whether the extractor should decode a container atom with type {@code atom}. */
|
||||
private static boolean shouldParseContainerAtom(int atom) {
|
||||
return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia
|
||||
|| atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_edts;
|
||||
return atom == Atom.TYPE_moov
|
||||
|| atom == Atom.TYPE_trak
|
||||
|| atom == Atom.TYPE_mdia
|
||||
|| atom == Atom.TYPE_minf
|
||||
|| atom == Atom.TYPE_stbl
|
||||
|| atom == Atom.TYPE_edts
|
||||
|| atom == Atom.TYPE_meta;
|
||||
}
|
||||
|
||||
private static final class Mp4Track {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.os.Parcel;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Test for {@link MdtaMetadataEntry}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public final class MdtaMetadataEntryTest {
|
||||
|
||||
@Test
|
||||
public void testParcelable() {
|
||||
MdtaMetadataEntry mdtaMetadataEntryToParcel =
|
||||
new MdtaMetadataEntry("test", new byte[] {1, 2}, 3, 4);
|
||||
|
||||
Parcel parcel = Parcel.obtain();
|
||||
mdtaMetadataEntryToParcel.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
|
||||
MdtaMetadataEntry mdtaMetadataEntryFromParcel =
|
||||
MdtaMetadataEntry.CREATOR.createFromParcel(parcel);
|
||||
assertThat(mdtaMetadataEntryFromParcel).isEqualTo(mdtaMetadataEntryToParcel);
|
||||
|
||||
parcel.recycle();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue