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
This commit is contained in:
olly 2019-11-15 04:29:30 +00:00 committed by Oliver Woodman
parent 1345010e27
commit 2e0719b555
3 changed files with 81 additions and 11 deletions

View File

@ -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<Integer, Integer> 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<Integer, Integer> audioSpecificConfig =
CodecSpecificDataUtil.parseAlacAudioSpecificConfig(initializationData);
sampleRate = audioSpecificConfig.first;
channelCount = audioSpecificConfig.second;
}
childPosition += childAtomSize;
}

View File

@ -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<Integer, Integer> parseAacAudioSpecificConfig(ParsableBitArray bitArray,
boolean forceReadToEnd) throws ParserException {
public static Pair<Integer, Integer> 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 <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
*
* @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<Integer, Integer> 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.
*

View File

@ -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<Integer, Integer> sampleRateAndChannelCount =
CodecSpecificDataUtil.parseAlacAudioSpecificConfig(alacSpecificConfig);
assertThat(sampleRateAndChannelCount.first).isEqualTo(96000);
assertThat(sampleRateAndChannelCount.second).isEqualTo(2);
}
}