From 0aef33a80f53a5cfdd71b227c08f56a6eec6240c Mon Sep 17 00:00:00 2001 From: nift4 Date: Sat, 8 Mar 2025 13:42:12 +0100 Subject: [PATCH] Try input format PCM encoding first in MediaCodecAudioRenderer ...and remove hard requirement for 16-bit PCM support in the audio sink even if that's not needed to playback. If the audio file is using 24-bit or 32-bit (int) PCM, MediaCodecAudioRenderer tries to output float, and if that is not supported, falls back to 16-bit PCM. This commit changes this behavior to first trying the media file format, then any close high-resolution audio formats (such as float) and then fall back to 16-bit PCM. The behaviour with DefaultAudioSink does not change, but if an audio sink supports any more optimal formats, they will now be used with MediaCodecAudioRenderer. Issue: #1931 --- .../androidx/media3/common/util/Util.java | 46 ++++++++++++++++ .../audio/MediaCodecAudioRenderer.java | 54 ++++++++++++++----- 2 files changed, 88 insertions(+), 12 deletions(-) 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 3da9718f5f..365ff7cafb 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 @@ -2268,6 +2268,52 @@ public static boolean isEncodingHighResolutionPcm(@C.PcmEncoding int encoding) { || encoding == C.ENCODING_PCM_FLOAT; } + /** + * Returns the closest platform PCM encodings supported on the current API level for + * {@code encoding}. The results never include 16-bit PCM, which should be used if none of the + * suggested formats can be used for playback. + * + * @param encoding The encoding of the audio data. + * @return The closest platform encodings, sorted by descending order of preference. May be empty. + */ + @UnstableApi + public static int[] getClosestPlatformPcmEncodings(@C.PcmEncoding int encoding) { + switch (encoding) { + case C.ENCODING_PCM_FLOAT: + return Build.VERSION.SDK_INT >= 31 + ? new int[] { + AudioFormat.ENCODING_PCM_FLOAT, + AudioFormat.ENCODING_PCM_32BIT, + AudioFormat.ENCODING_PCM_24BIT_PACKED + } + : new int[] {AudioFormat.ENCODING_PCM_FLOAT}; + case C.ENCODING_PCM_32BIT: + case C.ENCODING_PCM_32BIT_BIG_ENDIAN: + return Build.VERSION.SDK_INT >= 31 + ? new int[] { + AudioFormat.ENCODING_PCM_32BIT, + AudioFormat.ENCODING_PCM_FLOAT, + AudioFormat.ENCODING_PCM_24BIT_PACKED + } + : new int[] {AudioFormat.ENCODING_PCM_FLOAT}; + case C.ENCODING_PCM_24BIT: + case C.ENCODING_PCM_24BIT_BIG_ENDIAN: + return Build.VERSION.SDK_INT >= 31 + ? new int[] { + AudioFormat.ENCODING_PCM_24BIT_PACKED, + AudioFormat.ENCODING_PCM_32BIT, + AudioFormat.ENCODING_PCM_FLOAT + } + : new int[] {AudioFormat.ENCODING_PCM_FLOAT}; + case C.ENCODING_PCM_8BIT: + return new int[] {AudioFormat.ENCODING_PCM_8BIT}; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_PCM_16BIT_BIG_ENDIAN: + default: + return new int[] {}; + } + } + /** * Returns the audio track channel configuration for the given channel count, or {@link * AudioFormat#CHANNEL_INVALID} if output is not possible. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java index 6cf627db79..eefb5331bf 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java @@ -26,7 +26,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.media.AudioDeviceInfo; -import android.media.AudioFormat; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; @@ -338,14 +337,38 @@ public String getName() { } // If the input is PCM then it will be passed directly to the sink. Hence the sink must support // the input format directly. - if (MimeTypes.AUDIO_RAW.equals(format.sampleMimeType) && !audioSink.supportsFormat(format)) { - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); - } - // For all other input formats, we expect the decoder to output 16-bit PCM. - if (!audioSink.supportsFormat( - Util.getPcmFormat(C.ENCODING_PCM_16BIT, format.channelCount, format.sampleRate))) { - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); + if (MimeTypes.AUDIO_RAW.equals(format.sampleMimeType)) { + if (!audioSink.supportsFormat(format)) { + return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); + } + } else { + // For all other input formats, MediaCodec can do some conversions for us. Check if the sink + // supports any of the formats we can get out of MediaCodec. + boolean pcmEncodingSupported = false; + // On API levels before 24, any non-PCM input format is decoded to 16-bit PCM. + if (SDK_INT >= 24) { + int[] platformEncodings = Util.getClosestPlatformPcmEncodings(format.pcmEncoding); + for (int platformEncoding : platformEncodings) { + if (audioSink.getFormatSupport( + Util.getPcmFormat(platformEncoding, format.channelCount, format.sampleRate)) + == AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY) { + pcmEncodingSupported = true; + break; + } + } + } + // If none of the suggested encodings are supported, fall back to MediaCodec's default value + // of 16-bit PCM. + if (!pcmEncodingSupported + && audioSink.supportsFormat( + Util.getPcmFormat(C.ENCODING_PCM_16BIT, format.channelCount, format.sampleRate))) { + pcmEncodingSupported = true; + } + if (!pcmEncodingSupported) { + return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); + } } + List decoderInfos = getDecoderInfos(mediaCodecSelector, format, /* requiresSecureDecoder= */ false, audioSink); if (decoderInfos.isEmpty()) { @@ -1025,11 +1048,18 @@ protected MediaFormat getMediaFormat( mediaFormat.setInteger("ac4-is-sync", 1); } } - if (SDK_INT >= 24 - && audioSink.getFormatSupport( - Util.getPcmFormat(C.ENCODING_PCM_FLOAT, format.channelCount, format.sampleRate)) + if (SDK_INT >= 24) { + int[] platformEncodings = Util.getClosestPlatformPcmEncodings(format.pcmEncoding); + for (int platformEncoding : platformEncodings) { + if (audioSink.getFormatSupport( + Util.getPcmFormat(platformEncoding, format.channelCount, format.sampleRate)) == AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY) { - mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT); + mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, platformEncoding); + break; + } + } + // If none of the suggested encodings are supported, fall back to MediaCodec's default value + // of 16-bit PCM. } if (SDK_INT >= 32) { mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99);