From cc54d4d3e6902acf127a41ab88d724d024b95dab Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 30 Nov 2017 05:34:51 -0800 Subject: [PATCH] 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 --- .../assets/mp3/play-trimmed.mp3.1.dump | 6 +++- .../assets/mp3/play-trimmed.mp3.2.dump | 6 +++- .../assets/mp3/play-trimmed.mp3.3.dump | 6 +++- .../extractor/mp3/ConstantBitrateSeeker.java | 32 +++++++++++++++---- .../extractor/mp3/Mp3Extractor.java | 4 +-- .../exoplayer2/extractor/wav/WavHeader.java | 11 ++++--- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump index 0b6516ccdb..37a04215ee 100644 --- a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump +++ b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.1.dump @@ -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 diff --git a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump index 0b6516ccdb..37a04215ee 100644 --- a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump +++ b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.2.dump @@ -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 diff --git a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump index 0b6516ccdb..37a04215ee 100644 --- a/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump +++ b/library/core/src/androidTest/assets/mp3/play-trimmed.mp3.3.dump @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index 47e12161a8..e02e99e139 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index dc7d21851a..7c579504c3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -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); } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java index 1c1fc97a22..2cdd31cb6f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/wav/WavHeader.java @@ -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.