From ea69a3dbb6db02ea00b535661f4c121c92019fa5 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Mon, 20 Jan 2020 12:18:13 +0000 Subject: [PATCH] Move MpegAudioHeader to MpegUtil This makes MPEG audio utilities similar to utilities we have for WAV, AC-3 etc., and moves them out of the extractor package so that an extractor module can be split out without needing to have a class in the extractor package in the common library. PiperOrigin-RevId: 290595089 --- .../exoplayer2/audio/DefaultAudioSink.java | 3 +- .../MpegAudioUtil.java} | 398 +++++++++--------- .../extractor/mkv/MatroskaExtractor.java | 6 +- .../extractor/mp3/ConstantBitrateSeeker.java | 4 +- .../extractor/mp3/Mp3Extractor.java | 20 +- .../exoplayer2/extractor/mp3/VbriSeeker.java | 7 +- .../exoplayer2/extractor/mp3/XingSeeker.java | 7 +- .../extractor/ts/MpegAudioReader.java | 24 +- .../extractor/mp3/XingSeekerTest.java | 6 +- 9 files changed, 237 insertions(+), 238 deletions(-) rename library/core/src/main/java/com/google/android/exoplayer2/{extractor/MpegAudioHeader.java => audio/MpegAudioUtil.java} (55%) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 27abf486fa..0093f7cf32 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -28,7 +28,6 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -1159,7 +1158,7 @@ public final class DefaultAudioSink implements AudioSink { private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { switch (encoding) { case C.ENCODING_MP3: - return MpegAudioHeader.getFrameSampleCount(buffer.get(buffer.position())); + return MpegAudioUtil.parseMpegAudioFrameSampleCount(buffer.get(buffer.position())); case C.ENCODING_DTS: case C.ENCODING_DTS_HD: return DtsUtil.parseDtsAudioSampleCount(buffer); 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/audio/MpegAudioUtil.java similarity index 55% rename from library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java rename to library/core/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java index b3155233d0..36a5a8d6df 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/MpegAudioHeader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MpegAudioUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,199 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer2.extractor; +package com.google.android.exoplayer2.audio; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.util.MimeTypes; -/** - * An MPEG audio frame header. - */ -public final class MpegAudioHeader { +/** Utility methods for handling MPEG audio streams. */ +public final class MpegAudioUtil { + + /** Stores the metadata for an MPEG audio frame. */ + public static final class Header { + + /** MPEG audio header version. */ + public int version; + /** The mime type. */ + @Nullable public String mimeType; + /** Size of the frame associated with this header, in bytes. */ + public int frameSize; + /** Sample rate in samples per second. */ + public int sampleRate; + /** Number of audio channels in the frame. */ + public int channels; + /** Bitrate of the frame in bit/s. */ + public int bitrate; + /** Number of samples stored in the frame. */ + public int samplesPerFrame; + + /** + * Populates the fields in this instance to reflect the the MPEG audio header in {@code + * headerData}, returning whether the header was valid. If false, the values of the fields in + * this instance will not be updated. + * + * @param headerData Header data to parse. + * @return True if the fields were populated. False otherwise, indicating that {@code + * headerData} is not a valid MPEG audio header. + */ + public boolean setForHeaderData(int headerData) { + if (!isMagicPresent(headerData)) { + return false; + } + + int version = (headerData >>> 19) & 3; + if (version == 1) { + return false; + } + + int layer = (headerData >>> 17) & 3; + if (layer == 0) { + return false; + } + + int bitrateIndex = (headerData >>> 12) & 15; + if (bitrateIndex == 0 || bitrateIndex == 0xF) { + // Disallow "free" bitrate. + return false; + } + + int samplingRateIndex = (headerData >>> 10) & 3; + if (samplingRateIndex == 3) { + return false; + } + + this.version = version; + mimeType = MIME_TYPE_BY_LAYER[3 - layer]; + sampleRate = SAMPLING_RATE_V1[samplingRateIndex]; + if (version == 2) { + // Version 2 + sampleRate /= 2; + } else if (version == 0) { + // Version 2.5 + sampleRate /= 4; + } + int padding = (headerData >>> 9) & 1; + samplesPerFrame = getFrameSizeInSamples(version, layer); + if (layer == 3) { + // Layer I (layer == 3) + bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; + frameSize = (12 * bitrate / sampleRate + padding) * 4; + } else { + // Layer II (layer == 2) or III (layer == 1) + if (version == 3) { + // Version 1 + bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; + frameSize = 144 * bitrate / sampleRate + padding; + } else { + // Version 2 or 2.5. + bitrate = BITRATE_V2[bitrateIndex - 1]; + frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; + } + } + channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; + return true; + } + } + + /** + * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it + * is invalid. + */ + public static int getFrameSize(int headerData) { + if (!isMagicPresent(headerData)) { + return C.LENGTH_UNSET; + } + + int version = (headerData >>> 19) & 3; + if (version == 1) { + return C.LENGTH_UNSET; + } + + int layer = (headerData >>> 17) & 3; + if (layer == 0) { + return C.LENGTH_UNSET; + } + + int bitrateIndex = (headerData >>> 12) & 15; + if (bitrateIndex == 0 || bitrateIndex == 0xF) { + // Disallow "free" bitrate. + return C.LENGTH_UNSET; + } + + int samplingRateIndex = (headerData >>> 10) & 3; + if (samplingRateIndex == 3) { + return C.LENGTH_UNSET; + } + + int samplingRate = SAMPLING_RATE_V1[samplingRateIndex]; + if (version == 2) { + // Version 2 + samplingRate /= 2; + } else if (version == 0) { + // Version 2.5 + samplingRate /= 4; + } + + int bitrate; + int padding = (headerData >>> 9) & 1; + if (layer == 3) { + // Layer I (layer == 3) + bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; + return (12 * bitrate / samplingRate + padding) * 4; + } else { + // Layer II (layer == 2) or III (layer == 1) + if (version == 3) { + bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; + } else { + // Version 2 or 2.5. + bitrate = BITRATE_V2[bitrateIndex - 1]; + } + } + + if (version == 3) { + // Version 1 + return 144 * bitrate / samplingRate + padding; + } else { + // Version 2 or 2.5 + return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding; + } + } + + /** + * Returns the number of samples per frame associated with {@code headerData}, or {@link + * C#LENGTH_UNSET} if it is invalid. + */ + public static int parseMpegAudioFrameSampleCount(int headerData) { + if (!isMagicPresent(headerData)) { + return C.LENGTH_UNSET; + } + + int version = (headerData >>> 19) & 3; + if (version == 1) { + return C.LENGTH_UNSET; + } + + int layer = (headerData >>> 17) & 3; + if (layer == 0) { + return C.LENGTH_UNSET; + } + + // Those header values are not used but are checked for consistency with the other methods + int bitrateIndex = (headerData >>> 12) & 15; + int samplingRateIndex = (headerData >>> 10) & 3; + if (bitrateIndex == 0 || bitrateIndex == 0xF || samplingRateIndex == 3) { + return C.LENGTH_UNSET; + } + + return getFrameSizeInSamples(version, layer); + } /** * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 - * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * - * 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. - * The next power of two size is 4 KiB. + * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * 160000 bit/s / + * (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. The next power of two + * size is 4 KiB. */ public static final int MAX_FRAME_SIZE_BYTES = 4096; @@ -61,172 +238,10 @@ public final class MpegAudioHeader { private static final int SAMPLES_PER_FRAME_L3_V1 = 1152; private static final int SAMPLES_PER_FRAME_L3_V2 = 576; - /** - * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it - * is invalid. - */ - public static int getFrameSize(int header) { - if (!isMagicPresent(header)) { - return C.LENGTH_UNSET; - } + private MpegAudioUtil() {} - int version = (header >>> 19) & 3; - if (version == 1) { - return C.LENGTH_UNSET; - } - - int layer = (header >>> 17) & 3; - if (layer == 0) { - return C.LENGTH_UNSET; - } - - int bitrateIndex = (header >>> 12) & 15; - if (bitrateIndex == 0 || bitrateIndex == 0xF) { - // Disallow "free" bitrate. - return C.LENGTH_UNSET; - } - - int samplingRateIndex = (header >>> 10) & 3; - if (samplingRateIndex == 3) { - return C.LENGTH_UNSET; - } - - int samplingRate = SAMPLING_RATE_V1[samplingRateIndex]; - if (version == 2) { - // Version 2 - samplingRate /= 2; - } else if (version == 0) { - // Version 2.5 - samplingRate /= 4; - } - - int bitrate; - int padding = (header >>> 9) & 1; - if (layer == 3) { - // Layer I (layer == 3) - bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - return (12 * bitrate / samplingRate + padding) * 4; - } else { - // Layer II (layer == 2) or III (layer == 1) - if (version == 3) { - bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; - } else { - // Version 2 or 2.5. - bitrate = BITRATE_V2[bitrateIndex - 1]; - } - } - - if (version == 3) { - // Version 1 - return 144 * bitrate / samplingRate + padding; - } else { - // Version 2 or 2.5 - return (layer == 1 ? 72 : 144) * bitrate / samplingRate + padding; - } - } - - /** - * Returns the number of samples per frame associated with {@code header}, or {@link - * C#LENGTH_UNSET} if it is invalid. - */ - public static int getFrameSampleCount(int header) { - - if (!isMagicPresent(header)) { - return C.LENGTH_UNSET; - } - - int version = (header >>> 19) & 3; - if (version == 1) { - return C.LENGTH_UNSET; - } - - int layer = (header >>> 17) & 3; - if (layer == 0) { - return C.LENGTH_UNSET; - } - - // Those header values are not used but are checked for consistency with the other methods - int bitrateIndex = (header >>> 12) & 15; - int samplingRateIndex = (header >>> 10) & 3; - if (bitrateIndex == 0 || bitrateIndex == 0xF || samplingRateIndex == 3) { - return C.LENGTH_UNSET; - } - - return getFrameSizeInSamples(version, layer); - } - - /** - * Parses {@code headerData}, populating {@code header} with the parsed data. - * - * @param headerData Header data to parse. - * @param header Header to populate with data from {@code headerData}. - * @return True if the header was populated. False otherwise, indicating that {@code headerData} - * is not a valid MPEG audio header. - */ - public static boolean populateHeader(int headerData, MpegAudioHeader header) { - if (!isMagicPresent(headerData)) { - return false; - } - - int version = (headerData >>> 19) & 3; - if (version == 1) { - return false; - } - - int layer = (headerData >>> 17) & 3; - if (layer == 0) { - return false; - } - - int bitrateIndex = (headerData >>> 12) & 15; - if (bitrateIndex == 0 || bitrateIndex == 0xF) { - // Disallow "free" bitrate. - return false; - } - - int samplingRateIndex = (headerData >>> 10) & 3; - if (samplingRateIndex == 3) { - return false; - } - - int sampleRate = SAMPLING_RATE_V1[samplingRateIndex]; - if (version == 2) { - // Version 2 - sampleRate /= 2; - } else if (version == 0) { - // Version 2.5 - sampleRate /= 4; - } - - int padding = (headerData >>> 9) & 1; - int bitrate; - int frameSize; - int samplesPerFrame = getFrameSizeInSamples(version, layer); - if (layer == 3) { - // Layer I (layer == 3) - bitrate = version == 3 ? BITRATE_V1_L1[bitrateIndex - 1] : BITRATE_V2_L1[bitrateIndex - 1]; - frameSize = (12 * bitrate / sampleRate + padding) * 4; - } else { - // Layer II (layer == 2) or III (layer == 1) - if (version == 3) { - // Version 1 - bitrate = layer == 2 ? BITRATE_V1_L2[bitrateIndex - 1] : BITRATE_V1_L3[bitrateIndex - 1]; - frameSize = 144 * bitrate / sampleRate + padding; - } else { - // Version 2 or 2.5. - bitrate = BITRATE_V2[bitrateIndex - 1]; - frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; - } - } - - String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; - int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; - header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame); - return true; - } - - private static boolean isMagicPresent(int header) { - return (header & 0xFFE00000) == 0xFFE00000; + private static boolean isMagicPresent(int headerData) { + return (headerData & 0xFFE00000) == 0xFFE00000; } private static int getFrameSizeInSamples(int version, int layer) { @@ -237,39 +252,8 @@ public final class MpegAudioHeader { return SAMPLES_PER_FRAME_L2; // Layer II case 3: return SAMPLES_PER_FRAME_L1; // Layer I + default: + throw new IllegalArgumentException(); } - throw new IllegalArgumentException(); - } - - /** MPEG audio header version. */ - public int version; - /** The mime type. */ - @Nullable public String mimeType; - /** Size of the frame associated with this header, in bytes. */ - public int frameSize; - /** Sample rate in samples per second. */ - public int sampleRate; - /** Number of audio channels in the frame. */ - public int channels; - /** Bitrate of the frame in bit/s. */ - public int bitrate; - /** 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) { - this.version = version; - this.mimeType = mimeType; - this.frameSize = frameSize; - this.sampleRate = sampleRate; - this.channels = channels; - this.bitrate = bitrate; - this.samplesPerFrame = samplesPerFrame; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index 7a64768956..e04dc89b7b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.audio.Ac3Util; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.extractor.ChunkIndex; @@ -31,7 +32,6 @@ import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TrackOutput; @@ -1989,11 +1989,11 @@ public class MatroskaExtractor implements Extractor { break; case CODEC_ID_MP2: mimeType = MimeTypes.AUDIO_MPEG_L2; - maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + maxInputSize = MpegAudioUtil.MAX_FRAME_SIZE_BYTES; break; case CODEC_ID_MP3: mimeType = MimeTypes.AUDIO_MPEG; - maxInputSize = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + maxInputSize = MpegAudioUtil.MAX_FRAME_SIZE_BYTES; break; case CODEC_ID_AC3: mimeType = MimeTypes.AUDIO_AC3; 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 4a5feb5096..73389a034b 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 @@ -16,8 +16,8 @@ package com.google.android.exoplayer2.extractor.mp3; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.ConstantBitrateSeekMap; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; /** * MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate. @@ -30,7 +30,7 @@ import com.google.android.exoplayer2.extractor.MpegAudioHeader; * @param mpegAudioHeader The MPEG audio header associated with the first frame. */ public ConstantBitrateSeeker( - long inputLength, long firstFramePosition, MpegAudioHeader mpegAudioHeader) { + long inputLength, long firstFramePosition, MpegAudioUtil.Header mpegAudioHeader) { super(inputLength, firstFramePosition, mpegAudioHeader.bitrate, mpegAudioHeader.frameSize); } 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 ae775f93e8..f5c48700e5 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 @@ -20,13 +20,13 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.GaplessInfoHolder; import com.google.android.exoplayer2.extractor.Id3Peeker; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.mp3.Seeker.UnseekableSeeker; @@ -107,7 +107,7 @@ public final class Mp3Extractor implements Extractor { @Flags private final int flags; private final long forcedFirstSampleTimestampUs; private final ParsableByteArray scratch; - private final MpegAudioHeader synchronizedHeader; + private final MpegAudioUtil.Header synchronizedHeader; private final GaplessInfoHolder gaplessInfoHolder; private final Id3Peeker id3Peeker; @@ -145,7 +145,7 @@ public final class Mp3Extractor implements Extractor { this.flags = flags; this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; scratch = new ParsableByteArray(SCRATCH_LENGTH); - synchronizedHeader = new MpegAudioHeader(); + synchronizedHeader = new MpegAudioUtil.Header(); gaplessInfoHolder = new GaplessInfoHolder(); basisTimeUs = C.TIME_UNSET; id3Peeker = new Id3Peeker(); @@ -215,7 +215,7 @@ public final class Mp3Extractor implements Extractor { synchronizedHeader.mimeType, /* codecs= */ null, /* bitrate= */ Format.NO_VALUE, - MpegAudioHeader.MAX_FRAME_SIZE_BYTES, + MpegAudioUtil.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, synchronizedHeader.sampleRate, /* pcmEncoding= */ Format.NO_VALUE, @@ -258,13 +258,13 @@ public final class Mp3Extractor implements Extractor { scratch.setPosition(0); int sampleHeaderData = scratch.readInt(); if (!headersMatch(sampleHeaderData, synchronizedHeaderData) - || MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { + || MpegAudioUtil.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { // We have lost synchronization, so attempt to resynchronize starting at the next byte. extractorInput.skipFully(1); synchronizedHeaderData = 0; return RESULT_CONTINUE; } - MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); + synchronizedHeader.setForHeaderData(sampleHeaderData); if (basisTimeUs == C.TIME_UNSET) { basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { @@ -325,8 +325,8 @@ public final class Mp3Extractor implements Extractor { int headerData = scratch.readInt(); int frameSize; if ((candidateSynchronizedHeaderData != 0 - && !headersMatch(headerData, candidateSynchronizedHeaderData)) - || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { + && !headersMatch(headerData, candidateSynchronizedHeaderData)) + || (frameSize = MpegAudioUtil.getFrameSize(headerData)) == C.LENGTH_UNSET) { // The header doesn't match the candidate header or is invalid. Try the next byte offset. if (searchedBytes++ == searchLimitBytes) { if (!sniffing) { @@ -346,7 +346,7 @@ public final class Mp3Extractor implements Extractor { // The header matches the candidate header and/or is valid. validFrameCount++; if (validFrameCount == 1) { - MpegAudioHeader.populateHeader(headerData, synchronizedHeader); + synchronizedHeader.setForHeaderData(headerData); candidateSynchronizedHeaderData = headerData; } else if (validFrameCount == 4) { break; @@ -439,7 +439,7 @@ public final class Mp3Extractor implements Extractor { throws IOException, InterruptedException { input.peekFully(scratch.data, 0, 4); scratch.setPosition(0); - MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); + synchronizedHeader.setForHeaderData(scratch.readInt()); return new ConstantBitrateSeeker(input.getLength(), input.getPosition(), synchronizedHeader); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java index dcccc675ed..29584e7be7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/VbriSeeker.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer2.extractor.mp3; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.SeekPoint; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -43,7 +43,10 @@ import com.google.android.exoplayer2.util.Util; */ @Nullable public static VbriSeeker create( - long inputLength, long position, MpegAudioHeader mpegAudioHeader, ParsableByteArray frame) { + long inputLength, + long position, + MpegAudioUtil.Header mpegAudioHeader, + ParsableByteArray frame) { frame.skipBytes(10); int numFrames = frame.readInt(); if (numFrames <= 0) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java index 816b647675..9f31fba25e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp3/XingSeeker.java @@ -17,7 +17,7 @@ package com.google.android.exoplayer2.extractor.mp3; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.SeekPoint; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; @@ -44,7 +44,10 @@ import com.google.android.exoplayer2.util.Util; */ @Nullable public static XingSeeker create( - long inputLength, long position, MpegAudioHeader mpegAudioHeader, ParsableByteArray frame) { + long inputLength, + long position, + MpegAudioUtil.Header mpegAudioHeader, + ParsableByteArray frame) { int samplesPerFrame = mpegAudioHeader.samplesPerFrame; int sampleRate = mpegAudioHeader.sampleRate; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index 4e4072e012..4edf93d7d5 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -17,8 +17,8 @@ package com.google.android.exoplayer2.extractor.ts; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.ExtractorOutput; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; @@ -39,7 +39,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { private static final int HEADER_SIZE = 4; private final ParsableByteArray headerScratch; - private final MpegAudioHeader header; + private final MpegAudioUtil.Header header; @Nullable private final String language; private @MonotonicNonNull TrackOutput output; @@ -68,7 +68,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { // The first byte of an MPEG Audio frame header is always 0xFF. headerScratch = new ParsableByteArray(4); headerScratch.data[0] = (byte) 0xFF; - header = new MpegAudioHeader(); + header = new MpegAudioUtil.Header(); this.language = language; } @@ -176,7 +176,7 @@ public final class MpegAudioReader implements ElementaryStreamReader { } headerScratch.setPosition(0); - boolean parsedHeader = MpegAudioHeader.populateHeader(headerScratch.readInt(), header); + boolean parsedHeader = header.setForHeaderData(headerScratch.readInt()); if (!parsedHeader) { // We thought we'd located a frame header, but we hadn't. frameBytesRead = 0; @@ -187,9 +187,19 @@ public final class MpegAudioReader implements ElementaryStreamReader { frameSize = header.frameSize; if (!hasOutputFormat) { frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; - Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null, - Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, - null, null, 0, language); + Format format = + Format.createAudioSampleFormat( + formatId, + header.mimeType, + null, + Format.NO_VALUE, + MpegAudioUtil.MAX_FRAME_SIZE_BYTES, + header.channels, + header.sampleRate, + null, + null, + 0, + language); output.format(format); hasOutputFormat = true; } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java index 96fee1d07a..5ebd1b2884 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/mp3/XingSeekerTest.java @@ -19,7 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.MpegAudioHeader; +import com.google.android.exoplayer2.audio.MpegAudioUtil; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; import com.google.android.exoplayer2.extractor.SeekPoint; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -59,8 +59,8 @@ public final class XingSeekerTest { @Before public void setUp() throws Exception { - MpegAudioHeader xingFrameHeader = new MpegAudioHeader(); - MpegAudioHeader.populateHeader(XING_FRAME_HEADER_DATA, xingFrameHeader); + MpegAudioUtil.Header xingFrameHeader = new MpegAudioUtil.Header(); + xingFrameHeader.setForHeaderData(XING_FRAME_HEADER_DATA); seeker = XingSeeker.create(C.LENGTH_UNSET, XING_FRAME_POSITION, xingFrameHeader, new ParsableByteArray(XING_FRAME_PAYLOAD)); seekerWithInputLength = XingSeeker.create(STREAM_LENGTH,