From 2e0719b5553519a6d6b1445f5e89c1f6ea23759d Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 15 Nov 2019 04:29:30 +0000 Subject: [PATCH] Parse channel count and sample rate from ALAC initialization data Also remove the "do we really need to do this" comment for AAC. Parsing from codec specific data is likely to be more robust, so I think we should continue to do it for formats where we've seen this problem. Issue: #6648 PiperOrigin-RevId: 280575466 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 10 +++- .../util/CodecSpecificDataUtil.java | 34 +++++++++---- .../util/CodecSpecificDataUtilTest.java | 48 +++++++++++++++++++ 3 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/util/CodecSpecificDataUtilTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 58bd59a448..694ad22e29 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -1114,8 +1114,8 @@ import java.util.List; mimeType = mimeTypeAndInitializationData.first; initializationData = mimeTypeAndInitializationData.second; if (MimeTypes.AUDIO_AAC.equals(mimeType)) { - // TODO: Do we really need to do this? See [Internal: b/10903778] - // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. + // Update sampleRate and channelCount from the AudioSpecificConfig initialization data, + // which is more reliable. See [Internal: b/10903778]. Pair audioSpecificConfig = CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); sampleRate = audioSpecificConfig.first; @@ -1160,6 +1160,12 @@ import java.util.List; initializationData = new byte[childAtomBodySize]; parent.setPosition(childPosition + Atom.FULL_HEADER_SIZE); parent.readBytes(initializationData, /* offset= */ 0, childAtomBodySize); + // Update sampleRate and channelCount from the AudioSpecificConfig initialization data, + // which is more reliable. See https://github.com/google/ExoPlayer/pull/6629. + Pair audioSpecificConfig = + CodecSpecificDataUtil.parseAlacAudioSpecificConfig(initializationData); + sampleRate = audioSpecificConfig.first; + channelCount = audioSpecificConfig.second; } childPosition += childAtomSize; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java index 16a891dbc6..8d62a65fcf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java @@ -83,7 +83,7 @@ public final class CodecSpecificDataUtil { private CodecSpecificDataUtil() {} /** - * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse. * @return A pair consisting of the sample rate in Hz and the channel count. @@ -95,7 +95,7 @@ public final class CodecSpecificDataUtil { } /** - * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * * @param bitArray A {@link ParsableBitArray} containing the AudioSpecificConfig to parse. The * position is advanced to the end of the AudioSpecificConfig. @@ -104,8 +104,8 @@ public final class CodecSpecificDataUtil { * @return A pair consisting of the sample rate in Hz and the channel count. * @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported. */ - public static Pair parseAacAudioSpecificConfig(ParsableBitArray bitArray, - boolean forceReadToEnd) throws ParserException { + public static Pair parseAacAudioSpecificConfig( + ParsableBitArray bitArray, boolean forceReadToEnd) throws ParserException { int audioObjectType = getAacAudioObjectType(bitArray); int sampleRate = getAacSamplingFrequency(bitArray); int channelConfiguration = bitArray.readBits(4); @@ -166,10 +166,10 @@ public final class CodecSpecificDataUtil { * Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * * @param sampleRate The sample rate in Hz. - * @param numChannels The number of channels. + * @param channelCount The channel count. * @return The AudioSpecificConfig. */ - public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int numChannels) { + public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int channelCount) { int sampleRateIndex = C.INDEX_UNSET; for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) { if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) { @@ -178,13 +178,13 @@ public final class CodecSpecificDataUtil { } int channelConfig = C.INDEX_UNSET; for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) { - if (numChannels == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) { + if (channelCount == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) { channelConfig = i; } } if (sampleRate == C.INDEX_UNSET || channelConfig == C.INDEX_UNSET) { - throw new IllegalArgumentException("Invalid sample rate or number of channels: " - + sampleRate + ", " + numChannels); + throw new IllegalArgumentException( + "Invalid sample rate or number of channels: " + sampleRate + ", " + channelCount); } return buildAacAudioSpecificConfig(AUDIO_OBJECT_TYPE_AAC_LC, sampleRateIndex, channelConfig); } @@ -205,6 +205,22 @@ public final class CodecSpecificDataUtil { return specificConfig; } + /** + * Parses an ALAC AudioSpecificConfig (i.e. an ALACSpecificConfig). + * + * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse. + * @return A pair consisting of the sample rate in Hz and the channel count. + */ + public static Pair parseAlacAudioSpecificConfig(byte[] audioSpecificConfig) { + ParsableByteArray byteArray = new ParsableByteArray(audioSpecificConfig); + byteArray.setPosition(9); + int channelCount = byteArray.readUnsignedByte(); + byteArray.setPosition(20); + int sampleRate = byteArray.readUnsignedIntToInt(); + return Pair.create(sampleRate, channelCount); + } + /** * Builds an RFC 6381 AVC codec string using the provided parameters. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/CodecSpecificDataUtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/CodecSpecificDataUtilTest.java new file mode 100644 index 0000000000..a880e82250 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/CodecSpecificDataUtilTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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 com.google.android.exoplayer2.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.Pair; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit test for {@link CodecSpecificDataUtil}. */ +@RunWith(AndroidJUnit4.class) +public class CodecSpecificDataUtilTest { + + @Test + public void parseAlacAudioSpecificConfig() { + byte[] alacSpecificConfig = + new byte[] { + 0, 0, 16, 0, // frameLength + 0, // compatibleVersion + 16, // bitDepth + 40, 10, 14, // tuning parameters + 2, // numChannels = 2 + 0, 0, // maxRun + 0, 0, 64, 4, // maxFrameBytes + 0, 46, -32, 0, // avgBitRate + 0, 1, 119, 0, // sampleRate = 96000 + }; + Pair sampleRateAndChannelCount = + CodecSpecificDataUtil.parseAlacAudioSpecificConfig(alacSpecificConfig); + assertThat(sampleRateAndChannelCount.first).isEqualTo(96000); + assertThat(sampleRateAndChannelCount.second).isEqualTo(2); + } +}