Implement format to encoding for AAC

PiperOrigin-RevId: 310199693
This commit is contained in:
krocard 2020-05-06 20:02:30 +01:00 committed by Oliver Woodman
parent a2ce75d836
commit 535e14cb4d
3 changed files with 110 additions and 13 deletions

View file

@ -18,13 +18,29 @@ package com.google.android.exoplayer2.util;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.audio.AacUtil;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Defines common MIME types and helper methods.
*/
public final class MimeTypes {
/** An mp4a Object Type Indication (OTI) and its optional audio OTI is defined by RFC 6381. */
public static final class Mp4aObjectType {
/** The Object Type Indication of the mp4a codec. */
public final int objectTypeIndication;
/** The Audio Object Type Indication of the mp4a codec, or 0 if it is absent. */
@AacUtil.AacAudioObjectType public final int audioObjectTypeIndication;
private Mp4aObjectType(int objectTypeIndication, int audioObjectTypeIndication) {
this.objectTypeIndication = objectTypeIndication;
this.audioObjectTypeIndication = audioObjectTypeIndication;
}
}
public static final String BASE_TYPE_VIDEO = "video";
public static final String BASE_TYPE_AUDIO = "audio";
public static final String BASE_TYPE_TEXT = "text";
@ -106,6 +122,9 @@ public final class MimeTypes {
private static final ArrayList<CustomMimeType> customMimeTypes = new ArrayList<>();
private static final Pattern MP4A_RFC_6381_CODEC_PATTERN =
Pattern.compile("^mp4a\\.([a-zA-Z0-9]{2})(?:\\.([0-9]{1,2}))?$");
/**
* Registers a custom MIME type. Most applications do not need to call this method, as handling of
* standard MIME types is built in. These built-in MIME types take precedence over any registered
@ -275,15 +294,9 @@ public final class MimeTypes {
} else if (codec.startsWith("mp4a")) {
@Nullable String mimeType = null;
if (codec.startsWith("mp4a.")) {
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
if (objectTypeString.length() >= 2) {
try {
String objectTypeHexString = Util.toUpperInvariant(objectTypeString.substring(0, 2));
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
} catch (NumberFormatException ignored) {
// Ignored.
}
@Nullable Mp4aObjectType objectType = getObjectTypeFromMp4aRFC6381CodecString(codec);
if (objectType != null) {
mimeType = getMimeTypeFromMp4ObjectType(objectType.objectTypeIndication);
}
}
return mimeType == null ? MimeTypes.AUDIO_AAC : mimeType;
@ -407,13 +420,25 @@ public final class MimeTypes {
* it is an encoded (non-PCM) audio format, or {@link C#ENCODING_INVALID} otherwise.
*
* @param mimeType The MIME type.
* @return The {@link C}{@code .ENCODING_*} constant that corresponds to a specified MIME type, or
* @param codecs Codecs of the format as described in RFC 6381, or null if unknown or not
* applicable.
* @return One of {@link C.Encoding} constants that corresponds to a specified MIME type, or
* {@link C#ENCODING_INVALID}.
*/
public static @C.Encoding int getEncoding(String mimeType) {
@C.Encoding
public static int getEncoding(String mimeType, @Nullable String codecs) {
switch (mimeType) {
case MimeTypes.AUDIO_MPEG:
return C.ENCODING_MP3;
case MimeTypes.AUDIO_AAC:
if (codecs == null) {
return C.ENCODING_INVALID;
}
@Nullable Mp4aObjectType objectType = getObjectTypeFromMp4aRFC6381CodecString(codecs);
if (objectType == null) {
return C.ENCODING_INVALID;
}
return AacUtil.getEncodingForAudioObjectType(objectType.audioObjectTypeIndication);
case MimeTypes.AUDIO_AC3:
return C.ENCODING_AC3;
case MimeTypes.AUDIO_E_AC3:
@ -443,6 +468,40 @@ public final class MimeTypes {
return getTrackType(getMediaMimeType(codec));
}
/**
* Retrieves the object type of an mp4 audio codec from its string as defined in RFC 6381.
*
* <p>Per https://mp4ra.org/#/object_types and https://tools.ietf.org/html/rfc6381#section-3.3, an
* mp4 codec string has the form: <code>
* ~~~~~~~~~~~~~~ Object Type Indication (OTI) byte in hex
* mp4a.[a-zA-Z0-9]{2}(.[0-9]{1,2})?
* ~~~~~~~~~~ audio OTI, decimal. Only for certain OTI.
* </code> For example: mp4a.40.2, has an OTI of 0x40 and an audio OTI of 2.
*
* @param codec The string as defined in RFC 6381 describing an mp4 audio codec.
* @return The {@link Mp4aObjectType} or {@code null} if the input is invalid.
*/
@Nullable
public static Mp4aObjectType getObjectTypeFromMp4aRFC6381CodecString(String codec) {
Matcher matcher = MP4A_RFC_6381_CODEC_PATTERN.matcher(codec);
if (!matcher.matches()) {
return null;
}
String objectTypeIndicationHex = Assertions.checkNotNull(matcher.group(1));
@Nullable String audioObjectTypeIndicationDec = matcher.group(2);
int objectTypeIndication;
int audioObjectTypeIndication = 0;
try {
objectTypeIndication = Integer.parseInt(objectTypeIndicationHex, 16);
if (audioObjectTypeIndicationDec != null) {
audioObjectTypeIndication = Integer.parseInt(audioObjectTypeIndicationDec);
}
} catch (NumberFormatException e) {
return null;
}
return new Mp4aObjectType(objectTypeIndication, audioObjectTypeIndication);
}
/**
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
* contain a forward slash character ({@code '/'}).

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.util;
import static com.google.common.truth.Truth.assertThat;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -133,4 +134,41 @@ public final class MimeTypesTest {
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x01)).isNull();
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(-1)).isNull();
}
@Test
public void getObjectTypeFromMp4aRFC6381CodecString_onInvalidInput_returnsNull() {
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("abc")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.1")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.a")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.1g")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4v.20.9")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.100.1")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.10.")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.a.1")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.10,01")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.1f.f1")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.1a.a")).isNull();
assertThat(MimeTypes.getObjectTypeFromMp4aRFC6381CodecString("mp4a.01.110")).isNull();
}
@Test
public void getObjectTypeFromMp4aRFC6381CodecString_onValidInput_returnsCorrectObjectType() {
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.00.0", 0x00, 0);
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.01.01", 0x01, 1);
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.10.10", 0x10, 10);
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.a0.90", 0xa0, 90);
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.Ff.99", 0xff, 99);
assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns("mp4a.D0.9", 0xd0, 9);
}
private static void assert_getObjectTypeFromMp4aRFC6381CodecString_for_returns(
String codec, int expectedObjectTypeIndicator, int expectedAudioObjectTypeIndicator) {
@Nullable
MimeTypes.Mp4aObjectType objectType = MimeTypes.getObjectTypeFromMp4aRFC6381CodecString(codec);
assertThat(objectType).isNotNull();
assertThat(objectType.objectTypeIndication).isEqualTo(expectedObjectTypeIndicator);
assertThat(objectType.audioObjectTypeIndication).isEqualTo(expectedAudioObjectTypeIndicator);
}
}

View file

@ -463,13 +463,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
// E-AC3 JOC is object-based so the output channel count is arbitrary.
if (audioSink.supportsOutput(
/* channelCount= */ Format.NO_VALUE, format.sampleRate, C.ENCODING_E_AC3_JOC)) {
return MimeTypes.getEncoding(MimeTypes.AUDIO_E_AC3_JOC);
return MimeTypes.getEncoding(MimeTypes.AUDIO_E_AC3_JOC, format.codecs);
}
// E-AC3 receivers can decode JOC streams, but in 2-D rather than 3-D, so try to fall back.
mimeType = MimeTypes.AUDIO_E_AC3;
}
@C.Encoding int encoding = MimeTypes.getEncoding(mimeType);
@C.Encoding int encoding = MimeTypes.getEncoding(mimeType, format.codecs);
if (audioSink.supportsOutput(format.channelCount, format.sampleRate, encoding)) {
return encoding;
} else {