diff --git a/libraries/common/src/main/java/androidx/media3/common/util/ParsableByteArray.java b/libraries/common/src/main/java/androidx/media3/common/util/ParsableByteArray.java index ecb7f4cf8e..cf357a73de 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/ParsableByteArray.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/ParsableByteArray.java @@ -25,6 +25,7 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.UUID; /** * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are @@ -661,6 +662,26 @@ public final class ParsableByteArray { return null; } + /** + * Reads microsoft GUID as java UUID + * + * @throws IllegalStateException if there is not enough data decoding + * @return Decoded UUID + * */ + public UUID readMSGUID() { + if (bytesLeft() < 16) { + throw new IllegalStateException("Not enough data"); + } + // flip little ending to big ending + // https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid + long d1 = readLittleEndianUnsignedInt(); + long d2 = readLittleEndianUnsignedShort(); + long d3 = readLittleEndianUnsignedShort(); + long mostSigBits = d1 << 32 | d2 << 16 | d3; + long leastSigBits = readLong(); + return new UUID(mostSigBits, leastSigBits); + } + /** * Returns the index of the next occurrence of '\n' or '\r', or {@link #limit} if none is found. */ diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/WavUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/WavUtil.java index 169a20f234..7d4c9e47b9 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/WavUtil.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/WavUtil.java @@ -20,6 +20,8 @@ import androidx.media3.common.Format; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import java.util.UUID; + /** Utilities for handling WAVE files. */ @UnstableApi public final class WavUtil { @@ -60,6 +62,14 @@ public final class WavUtil { /** WAVE type value for extended WAVE format. */ public static final int TYPE_WAVE_FORMAT_EXTENSIBLE = 0xFFFE; + public static final UUID KSDATAFORMAT_SUBTYPE_PCM = UUID.fromString("00000001-0000-0010-8000-00aa00389b71"); + + public static final UUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = UUID.fromString("00000003-0000-0010-8000-00aa00389b71"); + + public static final UUID KSDATAFORMAT_SUBTYPE_ALAW = UUID.fromString("00000006-0000-0010-8000-00aa00389b71"); + + public static final UUID KSDATAFORMAT_SUBTYPE_MULAW = UUID.fromString("00000007-0000-0010-8000-00aa00389b71"); + /** * Returns the WAVE format type value for the given {@link C.PcmEncoding}. * diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavExtractor.java index 7195777653..92dcc838e1 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavExtractor.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavExtractor.java @@ -175,9 +175,19 @@ public final class WavExtractor implements Extractor { @RequiresNonNull({"extractorOutput", "trackOutput"}) private void readFormat(ExtractorInput input) throws IOException { WavFormat wavFormat = WavHeaderReader.readFormat(input); + // WAVE_FORMAT_EXTENSIBLE Determined by SubFormat + // available SubFormats (GUID , Decoder) + // ("00000001-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_PCM) + // ("00000003-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) + // ("00000006-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_ALAW) + // ("00000007-0000-0010-8000-00aa00389b71", KSDATAFORMAT_SUBTYPE_MULAW) + // full subformat GUIDs for compressed audio formats + // https://learn.microsoft.com/en-us/windows-hardware/drivers/audio/subformat-guids-for-compressed-audio-formats if (wavFormat.formatType == WavUtil.TYPE_IMA_ADPCM) { outputWriter = new ImaAdPcmOutputWriter(extractorOutput, trackOutput, wavFormat); - } else if (wavFormat.formatType == WavUtil.TYPE_ALAW) { + } else if (wavFormat.formatType == WavUtil.TYPE_ALAW || + wavFormat.formatType == WavUtil.TYPE_WAVE_FORMAT_EXTENSIBLE && + WavUtil.KSDATAFORMAT_SUBTYPE_ALAW.equals(wavFormat.uuid)) { outputWriter = new PassthroughOutputWriter( extractorOutput, @@ -185,7 +195,9 @@ public final class WavExtractor implements Extractor { wavFormat, MimeTypes.AUDIO_ALAW, /* pcmEncoding= */ Format.NO_VALUE); - } else if (wavFormat.formatType == WavUtil.TYPE_MLAW) { + } else if (wavFormat.formatType == WavUtil.TYPE_MLAW || + wavFormat.formatType == WavUtil.TYPE_WAVE_FORMAT_EXTENSIBLE && + WavUtil.KSDATAFORMAT_SUBTYPE_MULAW.equals(wavFormat.uuid)) { outputWriter = new PassthroughOutputWriter( extractorOutput, @@ -197,6 +209,10 @@ public final class WavExtractor implements Extractor { @C.PcmEncoding int pcmEncoding = WavUtil.getPcmEncodingForType(wavFormat.formatType, wavFormat.bitsPerSample); + if (wavFormat.formatType == WavUtil.TYPE_WAVE_FORMAT_EXTENSIBLE && + WavUtil.KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.equals(wavFormat.uuid)) { + pcmEncoding = C.ENCODING_PCM_FLOAT; + } if (pcmEncoding == C.ENCODING_INVALID) { throw ParserException.createForUnsupportedContainerFeature( "Unsupported WAV format type: " + wavFormat.formatType); diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavFormat.java b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavFormat.java index ac315e0828..359385cc4f 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavFormat.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavFormat.java @@ -15,6 +15,9 @@ */ package androidx.media3.extractor.wav; +import java.util.UUID; +import org.jetbrains.annotations.Nullable; + /** Format information for a WAV file. */ /* package */ final class WavFormat { @@ -42,6 +45,16 @@ package androidx.media3.extractor.wav; /** Extra data appended to the format chunk. */ public final byte[] extraData; + /** Number of valid bits */ + public final int validBitsPerSample; + + /** Speaker position mask */ + public final int channelMask; + + /** GUID, including the data format code */ + @Nullable + public final UUID uuid; + public WavFormat( int formatType, int numChannels, @@ -57,5 +70,32 @@ package androidx.media3.extractor.wav; this.blockSize = blockSize; this.bitsPerSample = bitsPerSample; this.extraData = extraData; + this.validBitsPerSample = 0; + this.channelMask = 0; + this.uuid = null; } + + public WavFormat( + int formatType, + int numChannels, + int frameRateHz, + int averageBytesPerSecond, + int blockSize, + int bitsPerSample, + byte[] extraData, + int validBitsPerSample, + int channelMask, + @Nullable UUID uuid) { + this.formatType = formatType; + this.numChannels = numChannels; + this.frameRateHz = frameRateHz; + this.averageBytesPerSecond = averageBytesPerSecond; + this.blockSize = blockSize; + this.bitsPerSample = bitsPerSample; + this.extraData = extraData; + this.validBitsPerSample = validBitsPerSample; + this.channelMask = channelMask; + this.uuid = uuid; + } + } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavHeaderReader.java b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavHeaderReader.java index 4db508135f..942838c232 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavHeaderReader.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/wav/WavHeaderReader.java @@ -25,6 +25,7 @@ import androidx.media3.common.util.Util; import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.WavUtil; import java.io.IOException; +import java.util.UUID; /** Reads a WAV header from an input stream; supports resuming from input failures. */ /* package */ final class WavHeaderReader { @@ -109,9 +110,21 @@ import java.io.IOException; int bytesLeft = (int) chunkHeader.size - 16; byte[] extraData; + int validBitsPerSample = 0; + int channelMask = 0; + UUID uuid = null; + if (bytesLeft > 0) { extraData = new byte[bytesLeft]; input.peekFully(extraData, 0, bytesLeft); + if (bytesLeft == 24) { + ParsableByteArray extra = new ParsableByteArray(extraData); + // ignore useless extra data length ( 0 or 22 ) + int cbSize = extra.readLittleEndianUnsignedShort(); + validBitsPerSample = extra.readLittleEndianUnsignedShort(); + channelMask = extra.readLittleEndianUnsignedIntToInt(); + uuid = extra.readMSGUID(); + } } else { extraData = Util.EMPTY_BYTE_ARRAY; } @@ -124,7 +137,10 @@ import java.io.IOException; averageBytesPerSecond, blockSize, bitsPerSample, - extraData); + extraData, + validBitsPerSample, + channelMask, + uuid); } /**