Snap to frame boundary in ConstantBitrateSeeker

- This change snaps the seek position for constant bitrate MP3s
  to the nearest frame boundary, avoiding the need to skip one
  byte at a time to re-synchronize (this may still happen if the
  MP3 does not really have fixed size frames).
- Tweaked both ConstantBitrateSeeker and WavHeader to ensure the
  returned positions are valid.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=177441798
This commit is contained in:
olly 2017-11-30 05:34:51 -08:00 committed by Oliver Woodman
parent 21d55d4eba
commit cc54d4d3e6
6 changed files with 50 additions and 15 deletions

View File

@ -25,5 +25,9 @@ track 0:
language = null
drmInitData = -
initializationData:
sample count = 0
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true

View File

@ -25,5 +25,9 @@ track 0:
language = null
drmInitData = -
initializationData:
sample count = 0
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true

View File

@ -25,5 +25,9 @@ track 0:
language = null
drmInitData = -
initializationData:
sample count = 0
sample count = 1
sample 0:
time = 0
flags = 1
data = length 418, hash B819987
tracksEnded = true

View File

@ -26,27 +26,47 @@ import com.google.android.exoplayer2.util.Util;
private static final int BITS_PER_BYTE = 8;
private final long firstFramePosition;
private final long dataSize;
private final int frameSize;
private final int bitrate;
private final long durationUs;
public ConstantBitrateSeeker(long firstFramePosition, int bitrate, long inputLength) {
/**
* @param firstFramePosition The position (byte offset) of the first frame.
* @param inputLength The length of the stream.
* @param frameSize The size of a single frame in the stream.
* @param bitrate The stream's bitrate.
*/
public ConstantBitrateSeeker(long firstFramePosition, long inputLength, int frameSize,
int bitrate) {
this.firstFramePosition = firstFramePosition;
this.frameSize = frameSize;
this.bitrate = bitrate;
durationUs = inputLength == C.LENGTH_UNSET ? C.TIME_UNSET : getTimeUs(inputLength);
if (inputLength == C.LENGTH_UNSET) {
dataSize = C.LENGTH_UNSET;
durationUs = C.TIME_UNSET;
} else {
dataSize = inputLength - firstFramePosition;
durationUs = getTimeUs(inputLength);
}
}
@Override
public boolean isSeekable() {
return durationUs != C.TIME_UNSET;
return dataSize != C.LENGTH_UNSET;
}
@Override
public long getPosition(long timeUs) {
if (durationUs == C.TIME_UNSET) {
if (dataSize == C.LENGTH_UNSET) {
return firstFramePosition;
}
timeUs = Util.constrainValue(timeUs, 0, durationUs);
return firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE);
long positionOffset = (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE);
// Constrain to nearest preceding frame offset.
positionOffset = (positionOffset / frameSize) * frameSize;
positionOffset = Util.constrainValue(positionOffset, 0, dataSize - frameSize);
// Add data start position.
return firstFramePosition + positionOffset;
}
@Override

View File

@ -393,8 +393,8 @@ public final class Mp3Extractor implements Extractor {
input.peekFully(scratch.data, 0, 4);
scratch.setPosition(0);
MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader);
return new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate,
input.getLength());
return new ConstantBitrateSeeker(input.getPosition(), input.getLength(),
synchronizedHeader.frameSize, synchronizedHeader.bitrate);
}
/**

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.wav;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.util.Util;
/** Header for a WAV file. */
/* package */ final class WavHeader implements SeekMap {
@ -83,10 +84,12 @@ import com.google.android.exoplayer2.extractor.SeekMap;
@Override
public long getPosition(long timeUs) {
long unroundedPosition = (timeUs * averageBytesPerSecond) / C.MICROS_PER_SECOND;
// Round down to nearest frame.
long position = (unroundedPosition / blockAlignment) * blockAlignment;
return Math.min(position, dataSize - blockAlignment) + dataStartPosition;
long positionOffset = (timeUs * averageBytesPerSecond) / C.MICROS_PER_SECOND;
// Constrain to nearest preceding frame offset.
positionOffset = (positionOffset / blockAlignment) * blockAlignment;
positionOffset = Util.constrainValue(positionOffset, 0, dataSize - blockAlignment);
// Add data start position.
return dataStartPosition + positionOffset;
}
// Misc getters.