diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9d5326b5de..93e2109ede 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,6 +17,8 @@ * Ogg: Fix bug when seeking in files with a long duration ([#391](https://github.com/androidx/media/issues/391)). * Audio: + * Add direct playback support for DTS Express and DTS:X + ([#335](https://github.com/androidx/media/pull/335)). * Audio Offload: * Add `AudioSink.getFormatOffloadSupport(Format)` that retrieves level of offload support the sink can provide for the format through a diff --git a/libraries/common/src/main/java/androidx/media3/common/C.java b/libraries/common/src/main/java/androidx/media3/common/C.java index 5f243a3b93..ed3fb91c0a 100644 --- a/libraries/common/src/main/java/androidx/media3/common/C.java +++ b/libraries/common/src/main/java/androidx/media3/common/C.java @@ -192,6 +192,7 @@ public final class C { ENCODING_DTS_HD, ENCODING_DOLBY_TRUEHD, ENCODING_OPUS, + ENCODING_DTS_UHD_P2, }) public @interface Encoding {} @@ -256,6 +257,8 @@ public final class C { @UnstableApi public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; /** See {@link AudioFormat#ENCODING_DTS_HD}. */ @UnstableApi public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; + // TODO(internal b/283949283): Use AudioFormat.ENCODING_DTS_UHD_P2 when Android 14 is released. + @UnstableApi public static final int ENCODING_DTS_UHD_P2 = 0x0000001e; /** See {@link AudioFormat#ENCODING_DOLBY_TRUEHD}. */ @UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD; /** See {@link AudioFormat#ENCODING_OPUS}. */ diff --git a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java index 5c9742e79d..a42e52966b 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java +++ b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java @@ -595,6 +595,10 @@ public final class MimeTypes { return C.ENCODING_DTS; case MimeTypes.AUDIO_DTS_HD: return C.ENCODING_DTS_HD; + case MimeTypes.AUDIO_DTS_EXPRESS: + return C.ENCODING_DTS_HD; + case MimeTypes.AUDIO_DTS_X: + return C.ENCODING_DTS_UHD_P2; case MimeTypes.AUDIO_TRUEHD: return C.ENCODING_DOLBY_TRUEHD; case MimeTypes.AUDIO_OPUS: diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index 377b8bf6d8..2d82e993bb 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -1884,6 +1884,14 @@ public final class Util { return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER; case 8: return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + case 10: + if (Util.SDK_INT >= 32) { + return AudioFormat.CHANNEL_OUT_5POINT1POINT4; + } else { + // Before API 32, height channel masks are not available. For those 10-channel streams + // supported on the audio output devices (e.g. DTS:X P2), we use 7.1-surround instead. + return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + } case 12: return AudioFormat.CHANNEL_OUT_7POINT1POINT4; default: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java index 499a2ae51c..2c26755157 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java @@ -48,21 +48,19 @@ import java.util.Arrays; @UnstableApi public final class AudioCapabilities { - private static final int DEFAULT_MAX_CHANNEL_COUNT = 8; + // TODO(internal b/283945513): Have separate default max channel counts in `AudioCapabilities` + // for PCM and compressed audio. + private static final int DEFAULT_MAX_CHANNEL_COUNT = 10; @VisibleForTesting /* package */ static final int DEFAULT_SAMPLE_RATE_HZ = 48_000; /** The minimum audio capabilities supported by all devices. */ public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES = new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, DEFAULT_MAX_CHANNEL_COUNT); - /** Audio capabilities when the device specifies external surround sound. */ - @SuppressWarnings("InlinedApi") - private static final AudioCapabilities EXTERNAL_SURROUND_SOUND_CAPABILITIES = - new AudioCapabilities( - new int[] { - AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_E_AC3 - }, - DEFAULT_MAX_CHANNEL_COUNT); + /** Encodings supported when the device specifies external surround sound. */ + private static final ImmutableList EXTERNAL_SURROUND_SOUND_ENCODINGS = + ImmutableList.of( + AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_E_AC3); /** * All surround sound encodings that a device may be capable of playing mapped to a maximum @@ -73,6 +71,7 @@ public final class AudioCapabilities { .put(C.ENCODING_AC3, 6) .put(C.ENCODING_AC4, 6) .put(C.ENCODING_DTS, 6) + .put(C.ENCODING_DTS_UHD_P2, 10) .put(C.ENCODING_E_AC3_JOC, 6) .put(C.ENCODING_E_AC3, 8) .put(C.ENCODING_DTS_HD, 8) @@ -103,25 +102,39 @@ public final class AudioCapabilities { if (Util.SDK_INT >= 23 && Api23.isBluetoothConnected(context)) { return DEFAULT_AUDIO_CAPABILITIES; } + + ImmutableSet.Builder supportedEncodings = new ImmutableSet.Builder<>(); if (deviceMaySetExternalSurroundSoundGlobalSetting() && Global.getInt(context.getContentResolver(), EXTERNAL_SURROUND_SOUND_KEY, 0) == 1) { - return EXTERNAL_SURROUND_SOUND_CAPABILITIES; + supportedEncodings.addAll(EXTERNAL_SURROUND_SOUND_ENCODINGS); } // AudioTrack.isDirectPlaybackSupported returns true for encodings that are supported for audio // offload, as well as for encodings we want to list for passthrough mode. Therefore we only use // it on TV and automotive devices, which generally shouldn't support audio offload for surround // encodings. if (Util.SDK_INT >= 29 && (Util.isTv(context) || Util.isAutomotive(context))) { + supportedEncodings.addAll(Api29.getDirectPlaybackSupportedEncodings()); return new AudioCapabilities( - Api29.getDirectPlaybackSupportedEncodings(), DEFAULT_MAX_CHANNEL_COUNT); + Ints.toArray(supportedEncodings.build()), DEFAULT_MAX_CHANNEL_COUNT); } - if (intent == null || intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 0) { - return DEFAULT_AUDIO_CAPABILITIES; + + if (intent != null && intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) == 1) { + @Nullable int[] encodingsFromExtra = intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS); + if (encodingsFromExtra != null) { + supportedEncodings.addAll(Ints.asList(encodingsFromExtra)); + } + return new AudioCapabilities( + Ints.toArray(supportedEncodings.build()), + intent.getIntExtra( + AudioManager.EXTRA_MAX_CHANNEL_COUNT, /* defaultValue= */ DEFAULT_MAX_CHANNEL_COUNT)); } - return new AudioCapabilities( - intent.getIntArrayExtra(AudioManager.EXTRA_ENCODINGS), - intent.getIntExtra( - AudioManager.EXTRA_MAX_CHANNEL_COUNT, /* defaultValue= */ DEFAULT_MAX_CHANNEL_COUNT)); + + ImmutableSet supportedEncodingsSet = supportedEncodings.build(); + if (!supportedEncodingsSet.isEmpty()) { + return new AudioCapabilities( + Ints.toArray(supportedEncodingsSet), /* maxChannelCount= */ DEFAULT_MAX_CHANNEL_COUNT); + } + return DEFAULT_AUDIO_CAPABILITIES; } /** @@ -203,7 +216,8 @@ public final class AudioCapabilities { if (encoding == C.ENCODING_E_AC3_JOC && !supportsEncoding(C.ENCODING_E_AC3_JOC)) { // E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer). encoding = C.ENCODING_E_AC3; - } else if (encoding == C.ENCODING_DTS_HD && !supportsEncoding(C.ENCODING_DTS_HD)) { + } else if ((encoding == C.ENCODING_DTS_HD && !supportsEncoding(C.ENCODING_DTS_HD)) + || (encoding == C.ENCODING_DTS_UHD_P2 && !supportsEncoding(C.ENCODING_DTS_UHD_P2))) { // DTS receivers support DTS-HD streams (but decode only the core layer). encoding = C.ENCODING_DTS; } @@ -220,7 +234,13 @@ public final class AudioCapabilities { channelCount = getMaxSupportedChannelCountForPassthrough(encoding, sampleRate); } else { channelCount = format.channelCount; - if (channelCount > maxChannelCount) { + // Some DTS:X TVs reports ACTION_HDMI_AUDIO_PLUG.EXTRA_MAX_CHANNEL_COUNT as 8 + // instead of 10. See https://github.com/androidx/media/issues/396 + if (format.sampleMimeType.equals(MimeTypes.AUDIO_DTS_X)) { + if (channelCount > 10) { + return null; + } + } else if (channelCount > maxChannelCount) { return null; } } @@ -355,9 +375,13 @@ public final class AudioCapabilities { private Api29() {} @DoNotInline - public static int[] getDirectPlaybackSupportedEncodings() { + public static ImmutableList getDirectPlaybackSupportedEncodings() { ImmutableList.Builder supportedEncodingsListBuilder = ImmutableList.builder(); for (int encoding : ALL_SURROUND_ENCODINGS_AND_MAX_CHANNELS.keySet()) { + // AudioFormat.ENCODING_DTS_UHD_P2 is supported from API 34. + if (Util.SDK_INT < 34 && encoding == C.ENCODING_DTS_UHD_P2) { + continue; + } if (AudioTrack.isDirectPlaybackSupported( new AudioFormat.Builder() .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) @@ -369,7 +393,7 @@ public final class AudioCapabilities { } } supportedEncodingsListBuilder.add(AudioFormat.ENCODING_PCM_16BIT); - return Ints.toArray(supportedEncodingsListBuilder.build()); + return supportedEncodingsListBuilder.build(); } /** diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java index 75e4004125..19f870d84f 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/DtsUtil.java @@ -41,6 +41,17 @@ public final class DtsUtil { private static final int SYNC_VALUE_14B_BE = 0x1FFFE800; private static final int SYNC_VALUE_LE = 0xFE7F0180; private static final int SYNC_VALUE_14B_LE = 0xFF1F00E8; + /** + * DTS Extension Substream Syncword (in different Endianness). See ETSI TS 102 114 (V1.6.1) + * Section 7.4.1. + */ + private static final int SYNC_EXT_SUB_LE = 0x25205864; + /** + * DTS FTOC Sync words (in different Endianness). See ETSI TS 103 491 (V1.2.1) Section 6.4.4.1. + */ + private static final int SYNC_FTOC_LE = 0xF21B4140; + + private static final int SYNC_FTOC_NON_SYNC_LE = 0xE842C471; private static final byte FIRST_BYTE_BE = (byte) (SYNC_VALUE_BE >>> 24); private static final byte FIRST_BYTE_14B_BE = (byte) (SYNC_VALUE_14B_BE >>> 24); private static final byte FIRST_BYTE_LE = (byte) (SYNC_VALUE_LE >>> 24); @@ -149,6 +160,16 @@ public final class DtsUtil { * @return The number of audio samples represented by the syncframe. */ public static int parseDtsAudioSampleCount(ByteBuffer buffer) { + if ((buffer.getInt(0) == SYNC_FTOC_LE) || (buffer.getInt(0) == SYNC_FTOC_NON_SYNC_LE)) { + // Check for DTS:X Profile 2 sync or non sync word and return 1024 if found. This is the only + // audio sample count that is used by DTS:X Streaming Encoder. + return 1024; + } else if (buffer.getInt(0) == SYNC_EXT_SUB_LE) { + // Check for DTS Express sync word and return 4096 if found. This is the only audio sample + // count that is used by DTS Streaming Encoder. + return 4096; + } + // See ETSI TS 102 114 subsection 5.4.1. int position = buffer.position(); int nblks;