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 411c0e3451e..594adc7ca0f 100644 --- a/libraries/common/src/main/java/androidx/media3/common/C.java +++ b/libraries/common/src/main/java/androidx/media3/common/C.java @@ -168,10 +168,10 @@ private C() {} * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link * #ENCODING_PCM_24BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_32BIT}, {@link - * #ENCODING_PCM_32BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link - * #ENCODING_AC3}, {@link #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, - * {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD}, {@link #ENCODING_DOLBY_TRUEHD} or {@link - * #ENCODING_OPUS}. + * #ENCODING_PCM_32BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_PCM_DOUBLE}, + * {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link #ENCODING_E_AC3}, {@link + * #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS}, {@link #ENCODING_DTS_HD}, + * {@link #ENCODING_DOLBY_TRUEHD}, {@link #ENCODING_OPUS} or {@link #ENCODING_DSD}. */ @UnstableApi @Documented @@ -188,6 +188,7 @@ private C() {} ENCODING_PCM_32BIT, ENCODING_PCM_32BIT_BIG_ENDIAN, ENCODING_PCM_FLOAT, + ENCODING_PCM_DOUBLE, ENCODING_MP3, ENCODING_AAC_LC, ENCODING_AAC_HE_V1, @@ -204,6 +205,7 @@ private C() {} ENCODING_DOLBY_TRUEHD, ENCODING_OPUS, ENCODING_DTS_UHD_P2, + ENCODING_DSD, }) public @interface Encoding {} @@ -212,7 +214,7 @@ private C() {} * {@link #ENCODING_INVALID}, {@link #ENCODING_PCM_8BIT}, {@link #ENCODING_PCM_16BIT}, {@link * #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link * #ENCODING_PCM_24BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_32BIT}, {@link - * #ENCODING_PCM_32BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_FLOAT}. + * #ENCODING_PCM_32BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_PCM_DOUBLE}. */ @UnstableApi @Documented @@ -228,7 +230,8 @@ private C() {} ENCODING_PCM_24BIT_BIG_ENDIAN, ENCODING_PCM_32BIT, ENCODING_PCM_32BIT_BIG_ENDIAN, - ENCODING_PCM_FLOAT + ENCODING_PCM_FLOAT, + ENCODING_PCM_DOUBLE }) public @interface PcmEncoding {} @@ -259,6 +262,9 @@ private C() {} /** See {@link AudioFormat#ENCODING_PCM_FLOAT}. */ @UnstableApi public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT; + /** PCM encoding with double-precision floating point samples. */ + @UnstableApi public static final int ENCODING_PCM_DOUBLE = 0x70000000; + /** See {@link AudioFormat#ENCODING_MP3}. */ @UnstableApi public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3; @@ -307,6 +313,9 @@ private C() {} /** See {@link AudioFormat#ENCODING_OPUS}. */ @UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS; + /** See {@link AudioFormat#ENCODING_DSD}. */ + @UnstableApi public static final int ENCODING_DSD = AudioFormat.ENCODING_DSD; + /** * Represents the behavior affecting whether spatialization will be used. One of {@link * #SPATIALIZATION_BEHAVIOR_AUTO} or {@link #SPATIALIZATION_BEHAVIOR_NEVER}. 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 1c1e0524d1a..120f314e866 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java +++ b/libraries/common/src/main/java/androidx/media3/common/MimeTypes.java @@ -86,6 +86,7 @@ public final class MimeTypes { public static final String AUDIO_E_AC3_JOC = BASE_TYPE_AUDIO + "/eac3-joc"; public static final String AUDIO_AC4 = BASE_TYPE_AUDIO + "/ac4"; public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; + public static final String AUDIO_DSD = BASE_TYPE_AUDIO + "/x-dsd"; public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; public static final String AUDIO_DTS_EXPRESS = BASE_TYPE_AUDIO + "/vnd.dts.hd;profile=lbr"; @@ -686,6 +687,8 @@ public static boolean isDolbyVisionCodec( return C.ENCODING_DOLBY_TRUEHD; case MimeTypes.AUDIO_OPUS: return C.ENCODING_OPUS; + case MimeTypes.AUDIO_DSD: + return C.ENCODING_DSD; default: return C.ENCODING_INVALID; } diff --git a/libraries/common/src/main/java/androidx/media3/common/audio/ToInt16PcmAudioProcessor.java b/libraries/common/src/main/java/androidx/media3/common/audio/ToInt16PcmAudioProcessor.java index 867805e7413..789956d9ae8 100644 --- a/libraries/common/src/main/java/androidx/media3/common/audio/ToInt16PcmAudioProcessor.java +++ b/libraries/common/src/main/java/androidx/media3/common/audio/ToInt16PcmAudioProcessor.java @@ -34,6 +34,7 @@ *
  • {@link C#ENCODING_PCM_32BIT} *
  • {@link C#ENCODING_PCM_32BIT_BIG_ENDIAN} *
  • {@link C#ENCODING_PCM_FLOAT} + *
  • {@link C#ENCODING_PCM_DOUBLE} * */ @UnstableApi @@ -50,7 +51,8 @@ public AudioFormat onConfigure(AudioFormat inputAudioFormat) && encoding != C.ENCODING_PCM_24BIT_BIG_ENDIAN && encoding != C.ENCODING_PCM_32BIT && encoding != C.ENCODING_PCM_32BIT_BIG_ENDIAN - && encoding != C.ENCODING_PCM_FLOAT) { + && encoding != C.ENCODING_PCM_FLOAT + && encoding != C.ENCODING_PCM_DOUBLE) { throw new UnhandledAudioFormatException(inputAudioFormat); } return encoding != C.ENCODING_PCM_16BIT @@ -82,6 +84,9 @@ public void queueInput(ByteBuffer inputBuffer) { case C.ENCODING_PCM_FLOAT: resampledSize = size / 2; break; + case C.ENCODING_PCM_DOUBLE: + resampledSize = size / 4; + break; case C.ENCODING_PCM_16BIT: case C.ENCODING_INVALID: case Format.NO_VALUE: @@ -147,6 +152,19 @@ public void queueInput(ByteBuffer inputBuffer) { buffer.put((byte) ((shortValue >> 8) & 0xFF)); } break; + case C.ENCODING_PCM_DOUBLE: + // 64 bit floating point -> 16 bit resampling. Floating point values are in the range + // [-1.0, 1.0], so need to be scaled by Short.MAX_VALUE. + for (int i = position; i < limit; i += 8) { + // Clamp to avoid integer overflow if the floating point values exceed their nominal range + // [Internal ref: b/161204847]. + double doubleValue = + Util.constrainValue(inputBuffer.getDouble(i), /* min= */ -1, /* max= */ 1); + short shortValue = (short) (doubleValue * Short.MAX_VALUE); + buffer.put((byte) (shortValue & 0xFF)); + buffer.put((byte) ((shortValue >> 8) & 0xFF)); + } + break; case C.ENCODING_PCM_16BIT: case C.ENCODING_INVALID: case Format.NO_VALUE: diff --git a/libraries/common/src/main/java/androidx/media3/common/util/MediaFormatUtil.java b/libraries/common/src/main/java/androidx/media3/common/util/MediaFormatUtil.java index 40289d9e2fa..8f4831f0bc1 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/MediaFormatUtil.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/MediaFormatUtil.java @@ -482,6 +482,7 @@ private static void maybeSetPcmEncoding( case C.ENCODING_PCM_16BIT_BIG_ENDIAN: case C.ENCODING_PCM_24BIT_BIG_ENDIAN: case C.ENCODING_PCM_32BIT_BIG_ENDIAN: + case C.ENCODING_PCM_DOUBLE: default: // No matching value. Do nothing. return; 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 3319921aee5..4c56141a94e 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 @@ -1188,6 +1188,19 @@ public static float constrainValue(float value, float min, float max) { return max(min, min(value, max)); } + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + @UnstableApi + public static double constrainValue(double value, double min, double max) { + return max(min, min(value, max)); + } + /** * Returns the sum of two arguments, or a third argument if the result overflows. * @@ -2258,7 +2271,8 @@ public static boolean isEncodingLinearPcm(@C.Encoding int encoding) { || encoding == C.ENCODING_PCM_24BIT_BIG_ENDIAN || encoding == C.ENCODING_PCM_32BIT || encoding == C.ENCODING_PCM_32BIT_BIG_ENDIAN - || encoding == C.ENCODING_PCM_FLOAT; + || encoding == C.ENCODING_PCM_FLOAT + || encoding == C.ENCODING_PCM_DOUBLE; } /** @@ -2273,7 +2287,8 @@ public static boolean isEncodingHighResolutionPcm(@C.PcmEncoding int encoding) { || encoding == C.ENCODING_PCM_24BIT_BIG_ENDIAN || encoding == C.ENCODING_PCM_32BIT || encoding == C.ENCODING_PCM_32BIT_BIG_ENDIAN - || encoding == C.ENCODING_PCM_FLOAT; + || encoding == C.ENCODING_PCM_FLOAT + || encoding == C.ENCODING_PCM_DOUBLE; } /** @@ -2383,6 +2398,7 @@ public static int getApiLevelThatAudioFormatIntroducedAudioEncoding(int encoding case C.ENCODING_PCM_32BIT: return 31; case C.ENCODING_DTS_UHD_P2: + case C.ENCODING_DSD: return 34; default: return Integer.MAX_VALUE; @@ -2422,6 +2438,8 @@ public static int getByteDepth(@C.PcmEncoding int pcmEncoding) { case C.ENCODING_PCM_32BIT_BIG_ENDIAN: case C.ENCODING_PCM_FLOAT: return 4; + case C.ENCODING_PCM_DOUBLE: + return 8; case C.ENCODING_INVALID: case Format.NO_VALUE: default: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ChannelMappingAudioProcessor.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ChannelMappingAudioProcessor.java index 10878714489..d34446a95c6 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ChannelMappingAudioProcessor.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ChannelMappingAudioProcessor.java @@ -113,6 +113,9 @@ public void queueInput(ByteBuffer inputBuffer) { case C.ENCODING_PCM_FLOAT: buffer.putFloat(inputBuffer.getFloat(inputIndex)); break; + case C.ENCODING_PCM_DOUBLE: + buffer.putDouble(inputBuffer.getDouble(inputIndex)); + break; default: throw new IllegalStateException("Unexpected encoding: " + inputAudioFormat.encoding); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index 8b77e0700e4..8dc295bebeb 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -1923,7 +1923,9 @@ private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffe case C.ENCODING_PCM_32BIT_BIG_ENDIAN: case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_FLOAT: + case C.ENCODING_PCM_DOUBLE: case C.ENCODING_AAC_ER_BSAC: + case C.ENCODING_DSD: case C.ENCODING_INVALID: case Format.NO_VALUE: default: diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/PcmAudioUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/PcmAudioUtil.java index 661be34de3c..7a597039f36 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/PcmAudioUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/PcmAudioUtil.java @@ -101,6 +101,13 @@ public static int readAs32BitIntPcm(ByteBuffer buffer, @C.Encoding int pcmEncodi } else { return (int) (floatValue * Integer.MAX_VALUE); } + case C.ENCODING_PCM_DOUBLE: + double doubleValue = Util.constrainValue(buffer.getDouble(), /* min= */ -1f, /* max= */ 1f); + if (doubleValue < 0) { + return (int) (-doubleValue * Integer.MIN_VALUE); + } else { + return (int) (doubleValue * Integer.MAX_VALUE); + } default: throw new IllegalStateException(); } @@ -156,6 +163,13 @@ public static void write32BitIntPcm( buffer.putFloat((float) pcm32bit / Integer.MAX_VALUE); } return; + case C.ENCODING_PCM_DOUBLE: + if (pcm32bit < 0) { + buffer.putDouble(-((double) pcm32bit) / Integer.MIN_VALUE); + } else { + buffer.putDouble((double) pcm32bit / Integer.MAX_VALUE); + } + return; default: throw new IllegalStateException(); } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessor.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessor.java index c5aaaeb562d..36bd9f453a0 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessor.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessor.java @@ -33,6 +33,7 @@ *
  • {@link C#ENCODING_PCM_32BIT} *
  • {@link C#ENCODING_PCM_32BIT_BIG_ENDIAN} *
  • {@link C#ENCODING_PCM_FLOAT} ({@link #isActive()} will return {@code false}) + *
  • {@link C#ENCODING_PCM_DOUBLE} * */ @UnstableApi @@ -104,6 +105,12 @@ public void queueInput(ByteBuffer inputBuffer) { writePcm32BitFloat(pcm32BitInteger, buffer); } break; + case C.ENCODING_PCM_DOUBLE: + buffer = replaceOutputBuffer(size / 2); + for (int i = position; i < limit; i += 8) { + buffer.putFloat((float) inputBuffer.getDouble(i)); + } + break; case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_16BIT: case C.ENCODING_PCM_16BIT_BIG_ENDIAN: diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ExtractorUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ExtractorUtil.java index 692adbc50d3..50e1becdff9 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ExtractorUtil.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ExtractorUtil.java @@ -168,7 +168,9 @@ public static int getMaximumEncodedRateBytesPerSecond(@C.Encoding int encoding) case C.ENCODING_PCM_32BIT_BIG_ENDIAN: case C.ENCODING_PCM_8BIT: case C.ENCODING_PCM_FLOAT: + case C.ENCODING_PCM_DOUBLE: case C.ENCODING_AAC_ER_BSAC: + case C.ENCODING_DSD: case C.ENCODING_INVALID: case Format.NO_VALUE: default: