Fix incorrect position calculation in VBRI seeker.

Issue: #1197
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117125081
This commit is contained in:
olly 2016-03-14 05:37:52 -07:00 committed by Oliver Woodman
parent d3d63101d1
commit 8ef4af1358
2 changed files with 26 additions and 27 deletions

View File

@ -290,7 +290,7 @@ public final class Mp3Extractor implements Extractor {
frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes.
headerData = frame.readInt(); headerData = frame.readInt();
if (headerData == VBRI_HEADER) { if (headerData == VBRI_HEADER) {
seeker = VbriSeeker.create(synchronizedHeader, frame, position); seeker = VbriSeeker.create(synchronizedHeader, frame, position, length);
input.skipFully(synchronizedHeader.frameSize); input.skipFully(synchronizedHeader.frameSize);
} }
} }

View File

@ -34,11 +34,12 @@ import com.google.android.exoplayer.util.Util;
* @param frame The data in this audio frame, with its position set to immediately after the * @param frame The data in this audio frame, with its position set to immediately after the
* 'VBRI' tag. * 'VBRI' tag.
* @param position The position (byte offset) of the start of this frame in the stream. * @param position The position (byte offset) of the start of this frame in the stream.
* @param inputLength The length of the stream in bytes.
* @return A {@link VbriSeeker} for seeking in the stream, or {@code null} if the required * @return A {@link VbriSeeker} for seeking in the stream, or {@code null} if the required
* information is not present. * information is not present.
*/ */
public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame, public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArray frame,
long position) { long position, long inputLength) {
frame.skipBytes(10); frame.skipBytes(10);
int numFrames = frame.readInt(); int numFrames = frame.readInt();
if (numFrames <= 0) { if (numFrames <= 0) {
@ -47,53 +48,52 @@ import com.google.android.exoplayer.util.Util;
int sampleRate = mpegAudioHeader.sampleRate; int sampleRate = mpegAudioHeader.sampleRate;
long durationUs = Util.scaleLargeTimestamp(numFrames, long durationUs = Util.scaleLargeTimestamp(numFrames,
C.MICROS_PER_SECOND * (sampleRate >= 32000 ? 1152 : 576), sampleRate); C.MICROS_PER_SECOND * (sampleRate >= 32000 ? 1152 : 576), sampleRate);
int numEntries = frame.readUnsignedShort(); int entryCount = frame.readUnsignedShort();
int scale = frame.readUnsignedShort(); int scale = frame.readUnsignedShort();
int entrySize = frame.readUnsignedShort(); int entrySize = frame.readUnsignedShort();
frame.skipBytes(2);
// Read entries in the VBRI header. // Skip the frame containing the VBRI header.
long[] timesUs = new long[numEntries]; position += mpegAudioHeader.frameSize;
long[] offsets = new long[numEntries];
long segmentDurationUs = durationUs / numEntries; // Read table of contents entries.
long now = 0; long[] timesUs = new long[entryCount + 1];
int segmentIndex = 0; long[] positions = new long[entryCount + 1];
while (segmentIndex < numEntries) { timesUs[0] = 0L;
int numBytes; positions[0] = position;
for (int index = 1; index < timesUs.length; index++) {
int segmentSize;
switch (entrySize) { switch (entrySize) {
case 1: case 1:
numBytes = frame.readUnsignedByte(); segmentSize = frame.readUnsignedByte();
break; break;
case 2: case 2:
numBytes = frame.readUnsignedShort(); segmentSize = frame.readUnsignedShort();
break; break;
case 3: case 3:
numBytes = frame.readUnsignedInt24(); segmentSize = frame.readUnsignedInt24();
break; break;
case 4: case 4:
numBytes = frame.readUnsignedIntToInt(); segmentSize = frame.readUnsignedIntToInt();
break; break;
default: default:
return null; return null;
} }
now += segmentDurationUs; position += segmentSize * scale;
timesUs[segmentIndex] = now; timesUs[index] = index * durationUs / entryCount;
position += numBytes * scale; positions[index] =
offsets[segmentIndex] = position; inputLength == C.LENGTH_UNBOUNDED ? position : Math.min(inputLength, position);
segmentIndex++;
} }
return new VbriSeeker(timesUs, offsets, position + mpegAudioHeader.frameSize, durationUs); return new VbriSeeker(timesUs, positions, durationUs);
} }
private final long[] timesUs; private final long[] timesUs;
private final long[] positions; private final long[] positions;
private final long basePosition;
private final long durationUs; private final long durationUs;
private VbriSeeker(long[] timesUs, long[] positions, long basePosition, long durationUs) { private VbriSeeker(long[] timesUs, long[] positions, long durationUs) {
this.timesUs = timesUs; this.timesUs = timesUs;
this.positions = positions; this.positions = positions;
this.basePosition = basePosition;
this.durationUs = durationUs; this.durationUs = durationUs;
} }
@ -104,8 +104,7 @@ import com.google.android.exoplayer.util.Util;
@Override @Override
public long getPosition(long timeUs) { public long getPosition(long timeUs) {
int index = Util.binarySearchFloor(timesUs, timeUs, false, false); return positions[Util.binarySearchFloor(timesUs, timeUs, true, true)];
return basePosition + (index == -1 ? 0L : positions[index]);
} }
@Override @Override