diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0edff22658..9871277717 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -7,6 +7,7 @@ * Support for playing spherical videos on Daydream. * Improve decoder re-use between playbacks. TODO: Write and link a blog post here ([#2826](https://github.com/google/ExoPlayer/issues/2826)). +* Use the true bitrate for constant-bitrate MP3 seeking. * Track selection: * Add options for controlling audio track selections to `DefaultTrackSelector` ([#3314](https://github.com/google/ExoPlayer/issues/3314)). diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java index ab49ca5454..87bb992082 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java @@ -34,16 +34,26 @@ public final class MpegAudioHeader { private static final String[] MIME_TYPE_BY_LAYER = new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; - private static final int[] BITRATE_V1_L1 = - {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}; - private static final int[] BITRATE_V2_L1 = - {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}; - private static final int[] BITRATE_V1_L2 = - {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}; - private static final int[] BITRATE_V1_L3 = - {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}; - private static final int[] BITRATE_V2 = - {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; + private static final int[] BITRATE_V1_L1 = { + 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, + 416000, 448000 + }; + private static final int[] BITRATE_V2_L1 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, + 224000, 256000 + }; + private static final int[] BITRATE_V1_L2 = { + 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000, 384000 + }; + private static final int[] BITRATE_V1_L3 = { + 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, + 320000 + }; + private static final int[] BITRATE_V2 = { + 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, + 160000 + }; /** * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it @@ -89,7 +99,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - return (12000 * bitrate / samplingRate + padding) * 4; + return (12 * bitrate / samplingRate + padding) * 4; } else { // Layer II (layer == 2) or III (layer == 1) if (version == 3) { @@ -102,10 +112,10 @@ public final class MpegAudioHeader { if (version == 3) { // Version 1 - return 144000 * bitrate / samplingRate + padding; + return 144 * bitrate / samplingRate + padding; } else { // Version 2 or 2.5 - return (layer == 1 ? 72000 : 144000) * bitrate / samplingRate + padding; + return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding; } } @@ -159,7 +169,7 @@ public final class MpegAudioHeader { if (layer == 3) { // Layer I (layer == 3) bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - frameSize = (12000 * bitrate / sampleRate + padding) * 4; + frameSize = (12 * bitrate / sampleRate + padding) * 4; samplesPerFrame = 384; } else { // Layer II (layer == 2) or III (layer == 1) @@ -167,19 +177,22 @@ public final class MpegAudioHeader { // Version 1 bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; samplesPerFrame = 1152; - frameSize = 144000 * bitrate / sampleRate + padding; + frameSize = 144 * bitrate / sampleRate + padding; } else { // Version 2 or 2.5. bitrate = BITRATE_V2[bitrateIndex - 1]; samplesPerFrame = layer == 1 ? 576 : 1152; - frameSize = (layer == 1 ? 72000 : 144000) * bitrate / sampleRate + padding; + frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; } } + // Calculate the bitrate in the same way Mp3Extractor calculates sample timestamps so that + // seeking to a given timestamp and playing from the start up to that timestamp give the same + // results for CBR streams. See also [internal: b/120390268]. + bitrate = 8 * frameSize * sampleRate / samplesPerFrame; String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; - header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate * 1000, - samplesPerFrame); + header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame); return true; } @@ -198,8 +211,14 @@ public final class MpegAudioHeader { /** Number of samples stored in the frame. */ public int samplesPerFrame; - private void setValues(int version, String mimeType, int frameSize, int sampleRate, int channels, - int bitrate, int samplesPerFrame) { + private void setValues( + int version, + String mimeType, + int frameSize, + int sampleRate, + int channels, + int bitrate, + int samplesPerFrame) { this.version = version; this.mimeType = mimeType; this.frameSize = frameSize; diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.0.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.1.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.2.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: diff --git a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump index 96b0cd259c..d4df3ffeba 100644 --- a/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump +++ b/library/core/src/test/assets/mp3/play-trimmed.mp3.3.dump @@ -1,6 +1,6 @@ seekMap: isSeekable = true - duration = 26125 + duration = 26122 getPosition(0) = [[timeUs=0, position=0]] numberOfTracks = 1 track 0: