mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Implement format to encoding for AAC
PiperOrigin-RevId: 310199693
This commit is contained in:
parent
a2ce75d836
commit
535e14cb4d
3 changed files with 110 additions and 13 deletions
|
|
@ -18,13 +18,29 @@ package com.google.android.exoplayer2.util;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.audio.AacUtil;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines common MIME types and helper methods.
|
* Defines common MIME types and helper methods.
|
||||||
*/
|
*/
|
||||||
public final class MimeTypes {
|
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_VIDEO = "video";
|
||||||
public static final String BASE_TYPE_AUDIO = "audio";
|
public static final String BASE_TYPE_AUDIO = "audio";
|
||||||
public static final String BASE_TYPE_TEXT = "text";
|
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 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
|
* 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
|
* 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")) {
|
} else if (codec.startsWith("mp4a")) {
|
||||||
@Nullable String mimeType = null;
|
@Nullable String mimeType = null;
|
||||||
if (codec.startsWith("mp4a.")) {
|
if (codec.startsWith("mp4a.")) {
|
||||||
String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix
|
@Nullable Mp4aObjectType objectType = getObjectTypeFromMp4aRFC6381CodecString(codec);
|
||||||
if (objectTypeString.length() >= 2) {
|
if (objectType != null) {
|
||||||
try {
|
mimeType = getMimeTypeFromMp4ObjectType(objectType.objectTypeIndication);
|
||||||
String objectTypeHexString = Util.toUpperInvariant(objectTypeString.substring(0, 2));
|
|
||||||
int objectTypeInt = Integer.parseInt(objectTypeHexString, 16);
|
|
||||||
mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt);
|
|
||||||
} catch (NumberFormatException ignored) {
|
|
||||||
// Ignored.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mimeType == null ? MimeTypes.AUDIO_AAC : mimeType;
|
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.
|
* it is an encoded (non-PCM) audio format, or {@link C#ENCODING_INVALID} otherwise.
|
||||||
*
|
*
|
||||||
* @param mimeType The MIME type.
|
* @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}.
|
* {@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) {
|
switch (mimeType) {
|
||||||
case MimeTypes.AUDIO_MPEG:
|
case MimeTypes.AUDIO_MPEG:
|
||||||
return C.ENCODING_MP3;
|
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:
|
case MimeTypes.AUDIO_AC3:
|
||||||
return C.ENCODING_AC3;
|
return C.ENCODING_AC3;
|
||||||
case MimeTypes.AUDIO_E_AC3:
|
case MimeTypes.AUDIO_E_AC3:
|
||||||
|
|
@ -443,6 +468,40 @@ public final class MimeTypes {
|
||||||
return getTrackType(getMediaMimeType(codec));
|
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
|
* Returns the top-level type of {@code mimeType}, or null if {@code mimeType} is null or does not
|
||||||
* contain a forward slash character ({@code '/'}).
|
* contain a forward slash character ({@code '/'}).
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.util;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
@ -133,4 +134,41 @@ public final class MimeTypesTest {
|
||||||
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x01)).isNull();
|
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x01)).isNull();
|
||||||
assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(-1)).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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -463,13 +463,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
// E-AC3 JOC is object-based so the output channel count is arbitrary.
|
// E-AC3 JOC is object-based so the output channel count is arbitrary.
|
||||||
if (audioSink.supportsOutput(
|
if (audioSink.supportsOutput(
|
||||||
/* channelCount= */ Format.NO_VALUE, format.sampleRate, C.ENCODING_E_AC3_JOC)) {
|
/* 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.
|
// 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;
|
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)) {
|
if (audioSink.supportsOutput(format.channelCount, format.sampleRate, encoding)) {
|
||||||
return encoding;
|
return encoding;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue