Allow disabling ID3 metadata parsing if not required
Issue: #2553 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=150184824
This commit is contained in:
parent
d077e23daa
commit
db5f81ecfd
@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor;
|
|||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
||||||
|
import com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -26,6 +27,18 @@ import java.util.regex.Pattern;
|
|||||||
*/
|
*/
|
||||||
public final class GaplessInfoHolder {
|
public final class GaplessInfoHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link FramePredicate} suitable for use when decoding {@link Metadata} that will be passed
|
||||||
|
* to {@link #setFromMetadata(Metadata)}. Only frames that might contain gapless playback
|
||||||
|
* information are decoded.
|
||||||
|
*/
|
||||||
|
public static final FramePredicate GAPLESS_INFO_ID3_FRAME_PREDICATE = new FramePredicate() {
|
||||||
|
@Override
|
||||||
|
public boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3) {
|
||||||
|
return id0 == 'C' && id1 == 'O' && id2 == 'M' && (id3 == 'M' || majorVersion == 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private static final String GAPLESS_COMMENT_ID = "iTunSMPB";
|
private static final String GAPLESS_COMMENT_ID = "iTunSMPB";
|
||||||
private static final Pattern GAPLESS_COMMENT_PATTERN =
|
private static final Pattern GAPLESS_COMMENT_PATTERN =
|
||||||
Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})");
|
Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})");
|
||||||
|
@ -58,13 +58,18 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
* Flags controlling the behavior of the extractor.
|
* Flags controlling the behavior of the extractor.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING})
|
@IntDef(flag = true, value = {FLAG_ENABLE_CONSTANT_BITRATE_SEEKING, FLAG_DISABLE_ID3_METADATA})
|
||||||
public @interface Flags {}
|
public @interface Flags {}
|
||||||
/**
|
/**
|
||||||
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
|
* Flag to force enable seeking using a constant bitrate assumption in cases where seeking would
|
||||||
* otherwise not be possible.
|
* otherwise not be possible.
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
|
public static final int FLAG_ENABLE_CONSTANT_BITRATE_SEEKING = 1;
|
||||||
|
/**
|
||||||
|
* Flag to disable parsing of ID3 metadata. Can be set to save memory if ID3 metadata is not
|
||||||
|
* required.
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DISABLE_ID3_METADATA = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum number of bytes to search when synchronizing, before giving up.
|
* The maximum number of bytes to search when synchronizing, before giving up.
|
||||||
@ -178,7 +183,8 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null,
|
trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null,
|
||||||
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels,
|
||||||
synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay,
|
synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay,
|
||||||
gaplessInfoHolder.encoderPadding, null, null, 0, null, metadata));
|
gaplessInfoHolder.encoderPadding, null, null, 0, null,
|
||||||
|
(flags & FLAG_DISABLE_ID3_METADATA) != 0 ? null : metadata));
|
||||||
}
|
}
|
||||||
return readSample(input);
|
return readSample(input);
|
||||||
}
|
}
|
||||||
@ -311,7 +317,11 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
byte[] id3Data = new byte[tagLength];
|
byte[] id3Data = new byte[tagLength];
|
||||||
System.arraycopy(scratch.data, 0, id3Data, 0, Id3Decoder.ID3_HEADER_LENGTH);
|
System.arraycopy(scratch.data, 0, id3Data, 0, Id3Decoder.ID3_HEADER_LENGTH);
|
||||||
input.peekFully(id3Data, Id3Decoder.ID3_HEADER_LENGTH, framesLength);
|
input.peekFully(id3Data, Id3Decoder.ID3_HEADER_LENGTH, framesLength);
|
||||||
metadata = new Id3Decoder().decode(id3Data, tagLength);
|
// We need to parse enough ID3 metadata to retrieve any gapless playback information even
|
||||||
|
// if ID3 metadata parsing is disabled.
|
||||||
|
Id3Decoder.FramePredicate id3FramePredicate = (flags & FLAG_DISABLE_ID3_METADATA) != 0
|
||||||
|
? GaplessInfoHolder.GAPLESS_INFO_ID3_FRAME_PREDICATE : null;
|
||||||
|
metadata = new Id3Decoder(id3FramePredicate).decode(id3Data, tagLength);
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
gaplessInfoHolder.setFromMetadata(metadata);
|
gaplessInfoHolder.setFromMetadata(metadata);
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,25 @@ import java.util.Locale;
|
|||||||
*/
|
*/
|
||||||
public final class Id3Decoder implements MetadataDecoder {
|
public final class Id3Decoder implements MetadataDecoder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A predicate for determining whether individual frames should be decoded.
|
||||||
|
*/
|
||||||
|
public interface FramePredicate {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a frame with the specified parameters should be decoded.
|
||||||
|
*
|
||||||
|
* @param majorVersion The major version of the ID3 tag.
|
||||||
|
* @param id0 The first byte of the frame ID.
|
||||||
|
* @param id1 The second byte of the frame ID.
|
||||||
|
* @param id2 The third byte of the frame ID.
|
||||||
|
* @param id3 The fourth byte of the frame ID.
|
||||||
|
* @return Whether the frame should be decoded.
|
||||||
|
*/
|
||||||
|
boolean evaluate(int majorVersion, int id0, int id1, int id2, int id3);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = "Id3Decoder";
|
private static final String TAG = "Id3Decoder";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,6 +69,19 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
private static final int ID3_TEXT_ENCODING_UTF_16BE = 2;
|
private static final int ID3_TEXT_ENCODING_UTF_16BE = 2;
|
||||||
private static final int ID3_TEXT_ENCODING_UTF_8 = 3;
|
private static final int ID3_TEXT_ENCODING_UTF_8 = 3;
|
||||||
|
|
||||||
|
private final FramePredicate framePredicate;
|
||||||
|
|
||||||
|
public Id3Decoder() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param framePredicate Determines which frames are decoded. May be null to decode all frames.
|
||||||
|
*/
|
||||||
|
public Id3Decoder(FramePredicate framePredicate) {
|
||||||
|
this.framePredicate = framePredicate;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
public Metadata decode(MetadataInputBuffer inputBuffer) {
|
||||||
ByteBuffer buffer = inputBuffer.data;
|
ByteBuffer buffer = inputBuffer.data;
|
||||||
@ -94,7 +126,7 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10;
|
int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10;
|
||||||
while (id3Data.bytesLeft() >= frameHeaderSize) {
|
while (id3Data.bytesLeft() >= frameHeaderSize) {
|
||||||
Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack,
|
Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack,
|
||||||
frameHeaderSize);
|
frameHeaderSize, framePredicate);
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
id3Frames.add(frame);
|
id3Frames.add(frame);
|
||||||
}
|
}
|
||||||
@ -200,7 +232,7 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data,
|
private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data,
|
||||||
boolean unsignedIntFrameSizeHack, int frameHeaderSize) {
|
boolean unsignedIntFrameSizeHack, int frameHeaderSize, FramePredicate framePredicate) {
|
||||||
int frameId0 = id3Data.readUnsignedByte();
|
int frameId0 = id3Data.readUnsignedByte();
|
||||||
int frameId1 = id3Data.readUnsignedByte();
|
int frameId1 = id3Data.readUnsignedByte();
|
||||||
int frameId2 = id3Data.readUnsignedByte();
|
int frameId2 = id3Data.readUnsignedByte();
|
||||||
@ -234,6 +266,13 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (framePredicate != null
|
||||||
|
&& !framePredicate.evaluate(majorVersion, frameId0, frameId1, frameId2, frameId3)) {
|
||||||
|
// Filtered by the predicate.
|
||||||
|
id3Data.setPosition(nextFramePosition);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Frame flags.
|
// Frame flags.
|
||||||
boolean isCompressed = false;
|
boolean isCompressed = false;
|
||||||
boolean isEncrypted = false;
|
boolean isEncrypted = false;
|
||||||
@ -302,10 +341,10 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
frame = decodeCommentFrame(id3Data, frameSize);
|
frame = decodeCommentFrame(id3Data, frameSize);
|
||||||
} else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') {
|
} else if (frameId0 == 'C' && frameId1 == 'H' && frameId2 == 'A' && frameId3 == 'P') {
|
||||||
frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
|
frame = decodeChapterFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
|
||||||
frameHeaderSize);
|
frameHeaderSize, framePredicate);
|
||||||
} else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') {
|
} else if (frameId0 == 'C' && frameId1 == 'T' && frameId2 == 'O' && frameId3 == 'C') {
|
||||||
frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
|
frame = decodeChapterTOCFrame(id3Data, frameSize, majorVersion, unsignedIntFrameSizeHack,
|
||||||
frameHeaderSize);
|
frameHeaderSize, framePredicate);
|
||||||
} else {
|
} else {
|
||||||
String id = majorVersion == 2
|
String id = majorVersion == 2
|
||||||
? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2)
|
? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2)
|
||||||
@ -513,8 +552,8 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize,
|
private static ChapterFrame decodeChapterFrame(ParsableByteArray id3Data, int frameSize,
|
||||||
int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize)
|
int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize,
|
||||||
throws UnsupportedEncodingException {
|
FramePredicate framePredicate) throws UnsupportedEncodingException {
|
||||||
int framePosition = id3Data.getPosition();
|
int framePosition = id3Data.getPosition();
|
||||||
int chapterIdEndIndex = indexOfZeroByte(id3Data.data, framePosition);
|
int chapterIdEndIndex = indexOfZeroByte(id3Data.data, framePosition);
|
||||||
String chapterId = new String(id3Data.data, framePosition, chapterIdEndIndex - framePosition,
|
String chapterId = new String(id3Data.data, framePosition, chapterIdEndIndex - framePosition,
|
||||||
@ -536,7 +575,7 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
int limit = framePosition + frameSize;
|
int limit = framePosition + frameSize;
|
||||||
while (id3Data.getPosition() < limit) {
|
while (id3Data.getPosition() < limit) {
|
||||||
Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
|
Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
|
||||||
frameHeaderSize);
|
frameHeaderSize, framePredicate);
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
subFrames.add(frame);
|
subFrames.add(frame);
|
||||||
}
|
}
|
||||||
@ -548,8 +587,8 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static ChapterTocFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize,
|
private static ChapterTocFrame decodeChapterTOCFrame(ParsableByteArray id3Data, int frameSize,
|
||||||
int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize)
|
int majorVersion, boolean unsignedIntFrameSizeHack, int frameHeaderSize,
|
||||||
throws UnsupportedEncodingException {
|
FramePredicate framePredicate) throws UnsupportedEncodingException {
|
||||||
int framePosition = id3Data.getPosition();
|
int framePosition = id3Data.getPosition();
|
||||||
int elementIdEndIndex = indexOfZeroByte(id3Data.data, framePosition);
|
int elementIdEndIndex = indexOfZeroByte(id3Data.data, framePosition);
|
||||||
String elementId = new String(id3Data.data, framePosition, elementIdEndIndex - framePosition,
|
String elementId = new String(id3Data.data, framePosition, elementIdEndIndex - framePosition,
|
||||||
@ -573,7 +612,7 @@ public final class Id3Decoder implements MetadataDecoder {
|
|||||||
int limit = framePosition + frameSize;
|
int limit = framePosition + frameSize;
|
||||||
while (id3Data.getPosition() < limit) {
|
while (id3Data.getPosition() < limit) {
|
||||||
Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
|
Id3Frame frame = decodeFrame(majorVersion, id3Data, unsignedIntFrameSizeHack,
|
||||||
frameHeaderSize);
|
frameHeaderSize, framePredicate);
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
subFrames.add(frame);
|
subFrames.add(frame);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user