mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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);
|
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) {
|
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||||
return new Format(
|
return new Format(
|
||||||
id,
|
id,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantField")
|
@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"})
|
||||||
/* package */ abstract class Atom {
|
/* 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_sawb = Util.getIntegerCodeForString("sawb");
|
||||||
public static final int TYPE_udta = Util.getIntegerCodeForString("udta");
|
public static final int TYPE_udta = Util.getIntegerCodeForString("udta");
|
||||||
public static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
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_ilst = Util.getIntegerCodeForString("ilst");
|
||||||
public static final int TYPE_mean = Util.getIntegerCodeForString("mean");
|
public static final int TYPE_mean = Util.getIntegerCodeForString("mean");
|
||||||
public static final int TYPE_name = Util.getIntegerCodeForString("name");
|
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 static com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
|
@ -39,7 +40,7 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
|
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
|
||||||
@SuppressWarnings("ConstantField")
|
@SuppressWarnings({"ConstantField", "ConstantCaseForConstants"})
|
||||||
/* package */ final class AtomParsers {
|
/* package */ final class AtomParsers {
|
||||||
|
|
||||||
private static final String TAG = "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_subt = Util.getIntegerCodeForString("subt");
|
||||||
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
|
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
|
||||||
private static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
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
|
* 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)
|
DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime)
|
||||||
throws ParserException {
|
throws ParserException {
|
||||||
Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
|
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) {
|
if (trackType == C.TRACK_TYPE_UNKNOWN) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -485,6 +487,7 @@ import java.util.List;
|
||||||
* @param isQuickTime True for QuickTime media. False otherwise.
|
* @param isQuickTime True for QuickTime media. False otherwise.
|
||||||
* @return Parsed metadata, or null.
|
* @return Parsed metadata, or null.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) {
|
public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) {
|
||||||
if (isQuickTime) {
|
if (isQuickTime) {
|
||||||
// Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and
|
// 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();
|
int atomType = udtaData.readInt();
|
||||||
if (atomType == Atom.TYPE_meta) {
|
if (atomType == Atom.TYPE_meta) {
|
||||||
udtaData.setPosition(atomPosition);
|
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;
|
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);
|
meta.skipBytes(Atom.FULL_HEADER_SIZE);
|
||||||
while (meta.getPosition() < limit) {
|
while (meta.getPosition() < limit) {
|
||||||
int atomPosition = meta.getPosition();
|
int atomPosition = meta.getPosition();
|
||||||
|
|
@ -516,11 +574,12 @@ import java.util.List;
|
||||||
meta.setPosition(atomPosition);
|
meta.setPosition(atomPosition);
|
||||||
return parseIlst(meta, atomPosition + atomSize);
|
return parseIlst(meta, atomPosition + atomSize);
|
||||||
}
|
}
|
||||||
meta.skipBytes(atomSize - Atom.HEADER_SIZE);
|
meta.setPosition(atomPosition + atomSize);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
|
private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
|
||||||
ilst.skipBytes(Atom.HEADER_SIZE);
|
ilst.skipBytes(Atom.HEADER_SIZE);
|
||||||
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
||||||
|
|
@ -610,19 +669,22 @@ import java.util.List;
|
||||||
* Parses an hdlr atom.
|
* Parses an hdlr atom.
|
||||||
*
|
*
|
||||||
* @param hdlr The hdlr atom to decode.
|
* @param hdlr The hdlr atom to decode.
|
||||||
* @return The track type.
|
* @return The handler value.
|
||||||
*/
|
*/
|
||||||
private static int parseHdlr(ParsableByteArray hdlr) {
|
private static int parseHdlr(ParsableByteArray hdlr) {
|
||||||
hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4);
|
hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4);
|
||||||
int trackType = hdlr.readInt();
|
return hdlr.readInt();
|
||||||
if (trackType == TYPE_soun) {
|
}
|
||||||
|
|
||||||
|
/** Returns the track type for a given handler value. */
|
||||||
|
private static int getTrackTypeForHdlr(int hdlr) {
|
||||||
|
if (hdlr == TYPE_soun) {
|
||||||
return C.TRACK_TYPE_AUDIO;
|
return C.TRACK_TYPE_AUDIO;
|
||||||
} else if (trackType == TYPE_vide) {
|
} else if (hdlr == TYPE_vide) {
|
||||||
return C.TRACK_TYPE_VIDEO;
|
return C.TRACK_TYPE_VIDEO;
|
||||||
} else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt
|
} else if (hdlr == TYPE_text || hdlr == TYPE_sbtl || hdlr == TYPE_subt || hdlr == TYPE_clcp) {
|
||||||
|| trackType == TYPE_clcp) {
|
|
||||||
return C.TRACK_TYPE_TEXT;
|
return C.TRACK_TYPE_TEXT;
|
||||||
} else if (trackType == TYPE_meta) {
|
} else if (hdlr == TYPE_meta) {
|
||||||
return C.TRACK_TYPE_METADATA;
|
return C.TRACK_TYPE_METADATA;
|
||||||
} else {
|
} else {
|
||||||
return C.TRACK_TYPE_UNKNOWN;
|
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;
|
package com.google.android.exoplayer2.extractor.mp4;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
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.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||||
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
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.Log;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/** Utilities for handling metadata in MP4. */
|
||||||
* Parses metadata items stored in ilst atoms.
|
|
||||||
*/
|
|
||||||
/* package */ final class MetadataUtil {
|
/* package */ final class MetadataUtil {
|
||||||
|
|
||||||
private static final String TAG = "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_COPYRIGHT = 0xA9;
|
||||||
private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD.
|
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() {}
|
private MetadataUtil() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting
|
* Returns a {@link Format} that is the same as the input format but includes information from the
|
||||||
* from the current position of the {@link ParsableByteArray}, and the position is advanced by the
|
* specified sources of metadata.
|
||||||
* size of the element. The position is advanced even if the element's type is unrecognized.
|
*/
|
||||||
|
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.
|
* @param ilst Holds the data to be parsed.
|
||||||
* @return The parsed element, or null if the element's type was not recognized.
|
* @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 position = ilst.getPosition();
|
||||||
int endPosition = position + ilst.readInt();
|
int endPosition = position + ilst.readInt();
|
||||||
int type = 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 type, String id, ParsableByteArray data) {
|
||||||
int atomSize = data.readInt();
|
int atomSize = data.readInt();
|
||||||
int atomType = data.readInt();
|
int atomType = data.readInt();
|
||||||
|
|
@ -200,7 +278,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
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 atomSize = data.readInt();
|
||||||
int atomType = data.readInt();
|
int atomType = data.readInt();
|
||||||
if (atomType == Atom.TYPE_data) {
|
if (atomType == Atom.TYPE_data) {
|
||||||
|
|
@ -212,7 +291,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Id3Frame parseUint8Attribute(
|
@Nullable
|
||||||
|
private static Id3Frame parseUint8Attribute(
|
||||||
int type,
|
int type,
|
||||||
String id,
|
String id,
|
||||||
ParsableByteArray data,
|
ParsableByteArray data,
|
||||||
|
|
@ -231,7 +311,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable TextInformationFrame parseIndexAndCountAttribute(
|
@Nullable
|
||||||
|
private static TextInformationFrame parseIndexAndCountAttribute(
|
||||||
int type, String attributeName, ParsableByteArray data) {
|
int type, String attributeName, ParsableByteArray data) {
|
||||||
int atomSize = data.readInt();
|
int atomSize = data.readInt();
|
||||||
int atomType = data.readInt();
|
int atomType = data.readInt();
|
||||||
|
|
@ -251,8 +332,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable TextInformationFrame parseStandardGenreAttribute(
|
@Nullable
|
||||||
ParsableByteArray data) {
|
private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) {
|
||||||
int genreCode = parseUint8AttributeValue(data);
|
int genreCode = parseUint8AttributeValue(data);
|
||||||
String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length)
|
String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length)
|
||||||
? STANDARD_GENRES[genreCode - 1] : null;
|
? STANDARD_GENRES[genreCode - 1] : null;
|
||||||
|
|
@ -263,7 +344,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable ApicFrame parseCoverArt(ParsableByteArray data) {
|
@Nullable
|
||||||
|
private static ApicFrame parseCoverArt(ParsableByteArray data) {
|
||||||
int atomSize = data.readInt();
|
int atomSize = data.readInt();
|
||||||
int atomType = data.readInt();
|
int atomType = data.readInt();
|
||||||
if (atomType == Atom.TYPE_data) {
|
if (atomType == Atom.TYPE_data) {
|
||||||
|
|
@ -287,8 +369,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Id3Frame parseInternalAttribute(
|
@Nullable
|
||||||
ParsableByteArray data, int endPosition) {
|
private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) {
|
||||||
String domain = null;
|
String domain = null;
|
||||||
String name = null;
|
String name = null;
|
||||||
int dataAtomPosition = -1;
|
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_ATOM_PAYLOAD = 1;
|
||||||
private static final int STATE_READING_SAMPLE = 2;
|
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 ");
|
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;
|
long durationUs = C.TIME_UNSET;
|
||||||
List<Mp4Track> tracks = new ArrayList<>();
|
List<Mp4Track> tracks = new ArrayList<>();
|
||||||
|
|
||||||
Metadata metadata = null;
|
// Process metadata.
|
||||||
|
Metadata udtaMetadata = null;
|
||||||
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
|
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
|
||||||
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
|
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
|
||||||
if (udta != null) {
|
if (udta != null) {
|
||||||
metadata = AtomParsers.parseUdta(udta, isQuickTime);
|
udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime);
|
||||||
if (metadata != null) {
|
if (udtaMetadata != null) {
|
||||||
gaplessInfoHolder.setFromMetadata(metadata);
|
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;
|
boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
|
||||||
ArrayList<TrackSampleTable> trackSampleTables =
|
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.
|
// Allow ten source samples per output sample, like the platform extractor.
|
||||||
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
int maxInputSize = trackSampleTable.maximumSize + 3 * 10;
|
||||||
Format format = track.format.copyWithMaxInputSize(maxInputSize);
|
Format format = track.format.copyWithMaxInputSize(maxInputSize);
|
||||||
if (track.type == C.TRACK_TYPE_AUDIO) {
|
format =
|
||||||
if (gaplessInfoHolder.hasGaplessInfo()) {
|
MetadataUtil.getFormatWithMetadata(
|
||||||
format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay,
|
track.type, format, udtaMetadata, mdtaMetadata, gaplessInfoHolder);
|
||||||
gaplessInfoHolder.encoderPadding);
|
|
||||||
}
|
|
||||||
if (metadata != null) {
|
|
||||||
format = format.copyWithMetadata(metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mp4Track.trackOutput.format(format);
|
mp4Track.trackOutput.format(format);
|
||||||
|
|
||||||
durationUs =
|
durationUs =
|
||||||
|
|
@ -716,24 +716,37 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||||
return false;
|
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) {
|
private static boolean shouldParseLeafAtom(int atom) {
|
||||||
return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr
|
return atom == Atom.TYPE_mdhd
|
||||||
|| atom == Atom.TYPE_stsd || atom == Atom.TYPE_stts || atom == Atom.TYPE_stss
|
|| atom == Atom.TYPE_mvhd
|
||||||
|| atom == Atom.TYPE_ctts || atom == Atom.TYPE_elst || atom == Atom.TYPE_stsc
|
|| atom == Atom.TYPE_hdlr
|
||||||
|| atom == Atom.TYPE_stsz || atom == Atom.TYPE_stz2 || atom == Atom.TYPE_stco
|
|| atom == Atom.TYPE_stsd
|
||||||
|| atom == Atom.TYPE_co64 || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_ftyp
|
|| atom == Atom.TYPE_stts
|
||||||
|| atom == Atom.TYPE_udta;
|
|| 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) {
|
private static boolean shouldParseContainerAtom(int atom) {
|
||||||
return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia
|
return atom == Atom.TYPE_moov
|
||||||
|| atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_edts;
|
|| 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 {
|
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