From c111138ac2656a1bda1cc739df7e386145b847b0 Mon Sep 17 00:00:00 2001 From: krocard Date: Wed, 18 Dec 2019 09:20:28 +0000 Subject: [PATCH] Parse MP3 header to retrieve the nb of sample per frames Add support for MP3 as an encoding format for passthrough. This change does not change the observable behavior of Exoplayer. Also name the magics. #exo-offload PiperOrigin-RevId: 286146539 --- .../java/com/google/android/exoplayer2/C.java | 9 ++- .../exoplayer2/audio/DefaultAudioSink.java | 38 +++++++----- .../exoplayer2/extractor/MpegAudioHeader.java | 60 +++++++++++++++++-- .../android/exoplayer2/util/MimeTypes.java | 2 + 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/C.java b/library/core/src/main/java/com/google/android/exoplayer2/C.java index 9661b7d072..e431b2d899 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/C.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/C.java @@ -151,9 +151,9 @@ public final class C { * Represents an audio encoding, or an invalid or unset value. One of {@link Format#NO_VALUE}, * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT}, {@link #ENCODING_PCM_FLOAT}, {@link - * #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_AC3}, {@link - * #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS}, - * {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}. + * #ENCODING_PCM_MU_LAW}, {@link #ENCODING_PCM_A_LAW}, {@link #ENCODING_MP3}, {@link + * #ENCODING_AC3}, {@link #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, + * {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}. */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -167,6 +167,7 @@ public final class C { ENCODING_PCM_FLOAT, ENCODING_PCM_MU_LAW, ENCODING_PCM_A_LAW, + ENCODING_MP3, ENCODING_AC3, ENCODING_E_AC3, ENCODING_E_AC3_JOC, @@ -213,6 +214,8 @@ public final class C { public static final int ENCODING_PCM_MU_LAW = 0x10000000; /** Audio encoding for A-law. */ public static final int ENCODING_PCM_A_LAW = 0x20000000; + /** @see AudioFormat#ENCODING_MP3 */ + public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3; /** @see AudioFormat#ENCODING_AC3 */ public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; /** @see AudioFormat#ENCODING_E_AC3 */ 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 27823e3006..240a8554b7 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,6 +28,7 @@ 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; @@ -1158,22 +1159,27 @@ public final class DefaultAudioSink implements AudioSink { } private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { - if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) { - return DtsUtil.parseDtsAudioSampleCount(buffer); - } else if (encoding == C.ENCODING_AC3) { - return Ac3Util.getAc3SyncframeAudioSampleCount(); - } else if (encoding == C.ENCODING_E_AC3 || encoding == C.ENCODING_E_AC3_JOC) { - return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer); - } else if (encoding == C.ENCODING_AC4) { - return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer); - } else if (encoding == C.ENCODING_DOLBY_TRUEHD) { - int syncframeOffset = Ac3Util.findTrueHdSyncframeOffset(buffer); - return syncframeOffset == C.INDEX_UNSET - ? 0 - : (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset) - * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT); - } else { - throw new IllegalStateException("Unexpected audio encoding: " + encoding); + switch (encoding) { + case C.ENCODING_MP3: + return MpegAudioHeader.getFrameSampleCount(buffer.get(buffer.position())); + case C.ENCODING_DTS: + case C.ENCODING_DTS_HD: + return DtsUtil.parseDtsAudioSampleCount(buffer); + case C.ENCODING_AC3: + return Ac3Util.getAc3SyncframeAudioSampleCount(); + case C.ENCODING_E_AC3: + case C.ENCODING_E_AC3_JOC: + return Ac3Util.parseEAc3SyncframeAudioSampleCount(buffer); + case C.ENCODING_AC4: + return Ac4Util.parseAc4SyncframeAudioSampleCount(buffer); + case C.ENCODING_DOLBY_TRUEHD: + int syncframeOffset = Ac3Util.findTrueHdSyncframeOffset(buffer); + return syncframeOffset == C.INDEX_UNSET + ? 0 + : (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset) + * Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT); + default: + throw new IllegalStateException("Unexpected audio encoding: " + encoding); } } 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 04d85b8bc5..b3155233d0 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 @@ -56,12 +56,17 @@ public final class MpegAudioHeader { 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 ((header & 0xFFE00000) != 0xFFE00000) { + if (!isMagicPresent(header)) { return C.LENGTH_UNSET; } @@ -120,6 +125,36 @@ public final class MpegAudioHeader { } } + /** + * 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. * @@ -129,7 +164,7 @@ public final class MpegAudioHeader { * is not a valid MPEG audio header. */ public static boolean populateHeader(int headerData, MpegAudioHeader header) { - if ((headerData & 0xFFE00000) != 0xFFE00000) { + if (!isMagicPresent(headerData)) { return false; } @@ -166,23 +201,20 @@ public final class MpegAudioHeader { int padding = (headerData >>> 9) & 1; int bitrate; int frameSize; - int samplesPerFrame; + 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; - samplesPerFrame = 384; } 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]; - samplesPerFrame = 1152; frameSize = 144 * bitrate / sampleRate + padding; } else { // Version 2 or 2.5. bitrate = BITRATE_V2[bitrateIndex - 1]; - samplesPerFrame = layer == 1 ? 576 : 1152; frameSize = (layer == 1 ? 72 : 144) * bitrate / sampleRate + padding; } } @@ -193,6 +225,22 @@ public final class MpegAudioHeader { 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. */ diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index de30cfd214..803ef6f41d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -345,6 +345,8 @@ public final class MimeTypes { */ public static @C.Encoding int getEncoding(String mimeType) { switch (mimeType) { + case MimeTypes.AUDIO_MPEG: + return C.ENCODING_MP3; case MimeTypes.AUDIO_AC3: return C.ENCODING_AC3; case MimeTypes.AUDIO_E_AC3: