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:
olly 2017-03-15 06:32:47 -07:00 committed by Oliver Woodman
parent d077e23daa
commit db5f81ecfd
3 changed files with 75 additions and 13 deletions

View File

@ -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})");

View File

@ -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);
} }

View File

@ -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);
} }