diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 97edd2b5aff..84bb49a6392 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,8 @@ ### dev-v2 (not yet released) ### +* Match codecs starting with "mp4a" to different Audio MimeTypes + ([#3779](https://github.com/google/ExoPlayer/issues/3779)). * Moved initial bitrate estimate from `AdaptiveTrackSelection` to `DefaultBandwidthMeter`. * Updated default max buffer length in `DefaultLoadControl`. 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 37cfce7c7cd..30358ff7c7f 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 @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import static com.google.android.exoplayer2.util.MimeTypes.getMimeTypeFromMp4ObjectType; + import android.util.Log; import android.util.Pair; import com.google.android.exoplayer2.C; @@ -1030,47 +1032,11 @@ private static Pair parseEsdsFromParent(ParsableByteArray parent // Set the MIME type based on the object type indication (14496-1 table 5). int objectTypeIndication = parent.readUnsignedByte(); - String mimeType; - switch (objectTypeIndication) { - case 0x60: - case 0x61: - mimeType = MimeTypes.VIDEO_MPEG2; - break; - case 0x20: - mimeType = MimeTypes.VIDEO_MP4V; - break; - case 0x21: - mimeType = MimeTypes.VIDEO_H264; - break; - case 0x23: - mimeType = MimeTypes.VIDEO_H265; - break; - case 0x6B: - mimeType = MimeTypes.AUDIO_MPEG; - return Pair.create(mimeType, null); - case 0x40: - case 0x66: - case 0x67: - case 0x68: - mimeType = MimeTypes.AUDIO_AAC; - break; - case 0xA5: - mimeType = MimeTypes.AUDIO_AC3; - break; - case 0xA6: - mimeType = MimeTypes.AUDIO_E_AC3; - break; - case 0xA9: - case 0xAC: - mimeType = MimeTypes.AUDIO_DTS; - return Pair.create(mimeType, null); - case 0xAA: - case 0xAB: - mimeType = MimeTypes.AUDIO_DTS_HD; - return Pair.create(mimeType, null); - default: - mimeType = null; - break; + String mimeType = getMimeTypeFromMp4ObjectType(objectTypeIndication); + if (MimeTypes.AUDIO_MPEG.equals(mimeType) + || MimeTypes.AUDIO_DTS.equals(mimeType) + || MimeTypes.AUDIO_DTS_HD.equals(mimeType)) { + return Pair.create(mimeType, null); } parent.skipBytes(12); 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 f39f897567a..778ea9487b0 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 @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.util; +import android.support.annotation.Nullable; import android.text.TextUtils; import com.google.android.exoplayer2.C; @@ -194,7 +195,20 @@ public static String getMediaMimeType(String codec) { } else if (codec.startsWith("vp8") || codec.startsWith("vp08")) { return MimeTypes.VIDEO_VP8; } else if (codec.startsWith("mp4a")) { - return MimeTypes.AUDIO_AAC; + String mimeType = null; + if (codec.startsWith("mp4a.")) { + String objectTypeString = codec.substring(5); // remove the 'mp4a.' prefix + if (objectTypeString.length() >= 2) { + try { + String objectTypeHexString = objectTypeString.toUpperCase().substring(0, 2); + int objectTypeInt = Integer.parseInt(objectTypeHexString, 16); + mimeType = getMimeTypeFromMp4ObjectType(objectTypeInt); + } catch (NumberFormatException ignored) { + // ignored + } + } + } + return mimeType == null ? MimeTypes.AUDIO_AAC : mimeType; } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { return MimeTypes.AUDIO_AC3; } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { @@ -213,6 +227,50 @@ public static String getMediaMimeType(String codec) { return null; } + /** + * Derives a mimeType from MP4 object type identifier, as defined in RFC 6381 and + * http://www.mp4ra.org/object.html. + * + * @param objectType The objectType identifier to derive. + * @return The mimeType, or null if it could not be derived. + */ + @Nullable + public static String getMimeTypeFromMp4ObjectType(int objectType) { + switch (objectType) { + case 0x60: + case 0x61: + return MimeTypes.VIDEO_MPEG2; + case 0x20: + return MimeTypes.VIDEO_MP4V; + case 0x21: + return MimeTypes.VIDEO_H264; + case 0x23: + return MimeTypes.VIDEO_H265; + case 0x69: + case 0x6B: + return MimeTypes.AUDIO_MPEG; + case 0x40: + case 0x66: + case 0x67: + case 0x68: + return MimeTypes.AUDIO_AAC; + case 0xA5: + return MimeTypes.AUDIO_AC3; + case 0xA6: + return MimeTypes.AUDIO_E_AC3; + case 0xA9: + case 0xAC: + return MimeTypes.AUDIO_DTS; + case 0xAA: + case 0xAB: + return MimeTypes.AUDIO_DTS_HD; + case 0xAD: + return MimeTypes.AUDIO_OPUS; + default: + return null; + } + } + /** * Returns the {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified MIME type. * {@link C#TRACK_TYPE_UNKNOWN} if the MIME type is not known or the mapping cannot be diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java new file mode 100644 index 00000000000..c607e920555 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/MimeTypesTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 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 org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Unit test for {@link MimeTypes}. */ +@RunWith(RobolectricTestRunner.class) +public final class MimeTypesTest { + + @Test + public void testGetMediaMimeType_fromValidCodecs_returnsCorrectMimeType() { + assertThat(MimeTypes.getMediaMimeType("avc1")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.42E01E")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.42E01F")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.4D401F")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.4D4028")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.640028")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc1.640029")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("avc3")).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMediaMimeType("hev1")).isEqualTo(MimeTypes.VIDEO_H265); + assertThat(MimeTypes.getMediaMimeType("hvc1")).isEqualTo(MimeTypes.VIDEO_H265); + assertThat(MimeTypes.getMediaMimeType("vp08")).isEqualTo(MimeTypes.VIDEO_VP8); + assertThat(MimeTypes.getMediaMimeType("vp8")).isEqualTo(MimeTypes.VIDEO_VP8); + assertThat(MimeTypes.getMediaMimeType("vp09")).isEqualTo(MimeTypes.VIDEO_VP9); + assertThat(MimeTypes.getMediaMimeType("vp9")).isEqualTo(MimeTypes.VIDEO_VP9); + + assertThat(MimeTypes.getMediaMimeType("ac-3")).isEqualTo(MimeTypes.AUDIO_AC3); + assertThat(MimeTypes.getMediaMimeType("dac3")).isEqualTo(MimeTypes.AUDIO_AC3); + assertThat(MimeTypes.getMediaMimeType("dec3")).isEqualTo(MimeTypes.AUDIO_E_AC3); + assertThat(MimeTypes.getMediaMimeType("ec-3")).isEqualTo(MimeTypes.AUDIO_E_AC3); + assertThat(MimeTypes.getMediaMimeType("ec+3")).isEqualTo(MimeTypes.AUDIO_E_AC3_JOC); + assertThat(MimeTypes.getMediaMimeType("dtsc")).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMediaMimeType("dtse")).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMediaMimeType("dtsh")).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMediaMimeType("dtsl")).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMediaMimeType("opus")).isEqualTo(MimeTypes.AUDIO_OPUS); + assertThat(MimeTypes.getMediaMimeType("vorbis")).isEqualTo(MimeTypes.AUDIO_VORBIS); + assertThat(MimeTypes.getMediaMimeType("mp4a")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.40.02")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.40.05")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.40.2")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.40.5")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.40.29")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.66")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.67")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.68")).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMediaMimeType("mp4a.69")).isEqualTo(MimeTypes.AUDIO_MPEG); + assertThat(MimeTypes.getMediaMimeType("mp4a.6B")).isEqualTo(MimeTypes.AUDIO_MPEG); + assertThat(MimeTypes.getMediaMimeType("mp4a.a5")).isEqualTo(MimeTypes.AUDIO_AC3); + assertThat(MimeTypes.getMediaMimeType("mp4a.A5")).isEqualTo(MimeTypes.AUDIO_AC3); + assertThat(MimeTypes.getMediaMimeType("mp4a.a6")).isEqualTo(MimeTypes.AUDIO_E_AC3); + assertThat(MimeTypes.getMediaMimeType("mp4a.A6")).isEqualTo(MimeTypes.AUDIO_E_AC3); + assertThat(MimeTypes.getMediaMimeType("mp4a.A9")).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMediaMimeType("mp4a.AC")).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMediaMimeType("mp4a.AA")).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMediaMimeType("mp4a.AB")).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMediaMimeType("mp4a.AD")).isEqualTo(MimeTypes.AUDIO_OPUS); + } + + @Test + public void testGetMimeTypeFromMp4ObjectType_forValidObjectType_returnsCorrectMimeType() { + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x60)).isEqualTo(MimeTypes.VIDEO_MPEG2); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x61)).isEqualTo(MimeTypes.VIDEO_MPEG2); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x20)).isEqualTo(MimeTypes.VIDEO_MP4V); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x21)).isEqualTo(MimeTypes.VIDEO_H264); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x23)).isEqualTo(MimeTypes.VIDEO_H265); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x6B)).isEqualTo(MimeTypes.AUDIO_MPEG); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x40)).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x66)).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x67)).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x68)).isEqualTo(MimeTypes.AUDIO_AAC); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xA5)).isEqualTo(MimeTypes.AUDIO_AC3); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xA6)).isEqualTo(MimeTypes.AUDIO_E_AC3); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xA9)).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xAC)).isEqualTo(MimeTypes.AUDIO_DTS); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xAA)).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xAB)).isEqualTo(MimeTypes.AUDIO_DTS_HD); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0xAD)).isEqualTo(MimeTypes.AUDIO_OPUS); + } + + @Test + public void testGetMimeTypeFromMp4ObjectType_forInvalidObjectType_returnsNull() { + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0)).isNull(); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x600)).isNull(); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(0x01)).isNull(); + assertThat(MimeTypes.getMimeTypeFromMp4ObjectType(-1)).isNull(); + } +}