summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java275
1 files changed, 275 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java
new file mode 100644
index 0000000000..66c3411094
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/MpegAudioHeader.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2016 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor;
+
+import androidx.annotation.Nullable;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MimeTypes;
+
+/**
+ * An MPEG audio frame header.
+ */
+public final class MpegAudioHeader {
+
+ /**
+ * 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.
+ */
+ public static final int MAX_FRAME_SIZE_BYTES = 4096;
+
+ 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 = {
+ 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
+ };
+
+ private static final int SAMPLES_PER_FRAME_L1 = 384;
+ private static final int SAMPLES_PER_FRAME_L2 = 1152;
+ 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;
+ }
+
+ 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 int getFrameSizeInSamples(int version, int layer) {
+ switch (layer) {
+ case 1:
+ return version == 3 ? SAMPLES_PER_FRAME_L3_V1 : SAMPLES_PER_FRAME_L3_V2; // Layer III
+ case 2:
+ return SAMPLES_PER_FRAME_L2; // Layer II
+ case 3:
+ return SAMPLES_PER_FRAME_L1; // Layer I
+ }
+ 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;
+ }
+}