Skip to content

Commit 884388e

Browse files
Merge pull request #3090 from nift4:pcmfdouble
PiperOrigin-RevId: 879061595
2 parents 081338a + 79a78e8 commit 884388e

35 files changed

Lines changed: 2516 additions & 20 deletions

‎RELEASENOTES.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@
2020
* Transformer:
2121
* Track selection:
2222
* Extractors:
23+
* WAV, Matroska, and MP4: Add support for 64-bit floating point PCM audio
24+
([#3090](https://github.com/androidx/media/pull/3090)).
2325
* Inspector:
2426
* Audio:
2527
* Convert parameters of `AudioSink.configure` to data class. Custom
2628
overrides of `ForwardingAudioSink.configure` need to be migrated to the
2729
new method signature.
2830
* Add support for maintaining pitch when time-stretching an audio stream
2931
with `EditedMediaItem.Builder#setSpeed(SpeedParameters)`.
32+
* Enhance `ToFloatPcmAudioProcessor` to support converting 8-bit PCM,
33+
16-bit big-endian PCM, and 64-bit floating point PCM to 32-bit floating
34+
point PCM ([#3090](https://github.com/androidx/media/pull/3090)).
3035
* Video:
3136
* Add support for skipping frames that are late during join rather than
3237
dropping in DecoderVideoRenderer.

‎libraries/common/src/main/java/androidx/media3/common/util/Util.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2421,6 +2421,25 @@ public static Format getPcmFormat(AudioProcessor.AudioFormat audioFormat) {
24212421
}
24222422
}
24232423

2424+
/**
2425+
* Converts a sample bit depth to a corresponding little-endian float PCM encoding constant.
2426+
*
2427+
* @param bitDepth The bit depth. Supported values are 32 and 64.
2428+
* @return The corresponding float PCM encoding. If the bit depth is unsupported then {@link
2429+
* C#ENCODING_INVALID} is returned.
2430+
*/
2431+
@UnstableApi
2432+
public static @C.PcmEncoding int getFloatPcmEncoding(int bitDepth) {
2433+
switch (bitDepth) {
2434+
case 32:
2435+
return C.ENCODING_PCM_FLOAT;
2436+
case 64:
2437+
return C.ENCODING_PCM_DOUBLE;
2438+
default:
2439+
return C.ENCODING_INVALID;
2440+
}
2441+
}
2442+
24242443
/**
24252444
* Returns whether {@code encoding} is one of the linear PCM encodings.
24262445
*

‎libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessor.java‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@
2424
import java.nio.ByteBuffer;
2525

2626
/**
27-
* An {@link AudioProcessor} that converts high resolution PCM audio to 32-bit float. The following
28-
* encodings are supported as input:
27+
* An {@link AudioProcessor} that converts different PCM audio encodings to 32-bit float. The
28+
* following encodings are supported as input:
2929
*
3030
* <ul>
31+
* <li>{@link C#ENCODING_PCM_8BIT}
3132
* <li>{@link C#ENCODING_PCM_16BIT}
33+
* <li>{@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}
3234
* <li>{@link C#ENCODING_PCM_24BIT}
3335
* <li>{@link C#ENCODING_PCM_24BIT_BIG_ENDIAN}
3436
* <li>{@link C#ENCODING_PCM_32BIT}
@@ -47,7 +49,7 @@ public final class ToFloatPcmAudioProcessor extends BaseAudioProcessor {
4749
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
4850
throws UnhandledAudioFormatException {
4951
@C.PcmEncoding int encoding = inputAudioFormat.encoding;
50-
if (!Util.isEncodingHighResolutionPcm(encoding) && encoding != C.ENCODING_PCM_16BIT) {
52+
if (!Util.isEncodingLinearPcm(encoding)) {
5153
throw new UnhandledAudioFormatException(inputAudioFormat);
5254
}
5355
return encoding != C.ENCODING_PCM_FLOAT
@@ -64,6 +66,13 @@ public void queueInput(ByteBuffer inputBuffer) {
6466

6567
ByteBuffer buffer;
6668
switch (inputAudioFormat.encoding) {
69+
case C.ENCODING_PCM_8BIT:
70+
buffer = replaceOutputBuffer(size * 4);
71+
for (int i = position; i < limit; i++) {
72+
int pcm32BitInteger = (((inputBuffer.get(i) & 0xFF) - 128) << 24);
73+
writePcm32BitFloat(pcm32BitInteger, buffer);
74+
}
75+
break;
6776
case C.ENCODING_PCM_16BIT:
6877
buffer = replaceOutputBuffer(size * 2);
6978
for (int i = position; i < limit; i += 2) {
@@ -72,6 +81,14 @@ public void queueInput(ByteBuffer inputBuffer) {
7281
writePcm32BitFloat(pcm32BitInteger, buffer);
7382
}
7483
break;
84+
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
85+
buffer = replaceOutputBuffer(size * 2);
86+
for (int i = position; i < limit; i += 2) {
87+
int pcm32BitInteger =
88+
((inputBuffer.get(i + 1) & 0xFF) << 16) | ((inputBuffer.get(i) & 0xFF) << 24);
89+
writePcm32BitFloat(pcm32BitInteger, buffer);
90+
}
91+
break;
7592
case C.ENCODING_PCM_24BIT:
7693
buffer = replaceOutputBuffer((size / 3) * 4);
7794
for (int i = position; i < limit; i += 3) {
@@ -120,8 +137,6 @@ public void queueInput(ByteBuffer inputBuffer) {
120137
buffer.putFloat((float) inputBuffer.getDouble(i));
121138
}
122139
break;
123-
case C.ENCODING_PCM_8BIT:
124-
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
125140
case C.ENCODING_PCM_FLOAT:
126141
case C.ENCODING_INVALID:
127142
case Format.NO_VALUE:

‎libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/ToFloatPcmAudioProcessorTest.java‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package androidx.media3.exoplayer.audio;
1717

1818
import static androidx.media3.common.util.Util.getByteDepth;
19+
import static androidx.media3.test.utils.TestUtil.createByteArray;
1920
import static androidx.media3.test.utils.TestUtil.createByteBuffer;
2021
import static androidx.media3.test.utils.TestUtil.createFloatArray;
2122
import static com.google.common.truth.Truth.assertThat;
@@ -39,15 +40,17 @@ public class ToFloatPcmAudioProcessorTest {
3940
* <p>Can be one of:
4041
*
4142
* <ul>
43+
* <li>{@link C#ENCODING_PCM_8BIT}
4244
* <li>{@link C#ENCODING_PCM_16BIT}
43-
* <li>{@link C#ENCODING_PCM_32BIT}
45+
* <li>{@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}
4446
* <li>{@link C#ENCODING_PCM_24BIT}
45-
* <li>{@link C#ENCODING_PCM_32BIT_BIG_ENDIAN}
4647
* <li>{@link C#ENCODING_PCM_24BIT_BIG_ENDIAN}
48+
* <li>{@link C#ENCODING_PCM_32BIT}
49+
* <li>{@link C#ENCODING_PCM_32BIT_BIG_ENDIAN}
4750
* <li>{@link C#ENCODING_PCM_DOUBLE}
4851
* </ul>
4952
*/
50-
@TestParameter({"2", "22", "21", "1610612736", "1342177280", "1879048192"})
53+
@TestParameter({"3", "2", "268435456", "21", "1342177280", "22", "1610612736", "1879048192"})
5154
private int pcmEncoding;
5255

5356
@Test
@@ -64,13 +67,18 @@ public void queueInput_withNonZeroValues_returnsCorrectConvertedValues() throws
6467
}
6568

6669
@Test
67-
public void queueInput_withZero_returnsZero() throws Exception {
70+
public void queueInput_withSilence_returnsZero() throws Exception {
6871
ToFloatPcmAudioProcessor processor = new ToFloatPcmAudioProcessor();
6972
processor.configure(
7073
new AudioFormat(/* sampleRate= */ 44100, /* channelCount= */ 1, pcmEncoding));
7174
processor.flush(StreamMetadata.DEFAULT);
7275

7376
ByteBuffer buffer = ByteBuffer.allocateDirect(getByteDepth(pcmEncoding));
77+
if (pcmEncoding == C.ENCODING_PCM_8BIT) {
78+
// 8-bit PCM is unsigned with a neutral midpoint of 128 (representing silence).
79+
buffer.put((byte) 128);
80+
buffer.flip();
81+
}
7482

7583
processor.queueInput(buffer);
7684
assertThat(createFloatArray(processor.getOutput())).isEqualTo(new float[] {0f});
@@ -87,7 +95,10 @@ public void configure_returnsFloatPcmEncoding() throws Exception {
8795

8896
private static float getToleranceForEncoding(int pcmEncoding) {
8997
switch (pcmEncoding) {
98+
case C.ENCODING_PCM_8BIT:
99+
return 1f / 0x80;
90100
case C.ENCODING_PCM_16BIT:
101+
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
91102
return 1f / 0x8000;
92103
case C.ENCODING_PCM_32BIT:
93104
case C.ENCODING_PCM_32BIT_BIG_ENDIAN:
@@ -107,9 +118,19 @@ private static float getToleranceForEncoding(int pcmEncoding) {
107118
*/
108119
private static ByteBuffer getTestSamplesForEncoding(int pcmEncoding) {
109120
switch (pcmEncoding) {
121+
case C.ENCODING_PCM_8BIT:
122+
return createByteBuffer(createByteArray(0xFF, 0, 0xC0, 0x40));
110123
case C.ENCODING_PCM_16BIT:
111124
return createByteBuffer(
112125
new short[] {Short.MAX_VALUE, Short.MIN_VALUE, 0x4000, (short) 0xC000});
126+
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
127+
return createByteBuffer(
128+
new short[] {
129+
Short.reverseBytes(Short.MAX_VALUE),
130+
Short.reverseBytes(Short.MIN_VALUE),
131+
Short.reverseBytes((short) 0x4000),
132+
Short.reverseBytes((short) 0xC000)
133+
});
113134
case C.ENCODING_PCM_32BIT:
114135
return createByteBuffer(
115136
new int[] {

‎libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/Mp4PlaybackTest.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static ImmutableList<Sample> mediaSamples() {
7070
Sample.forFile("sample_opus_fragmented.mp4"),
7171
Sample.forFile("sample_opus.mp4"),
7272
Sample.forFile("sample_alac.mp4"),
73+
Sample.forFile("sample_fpcm_64le.mp4"),
7374
Sample.forFile("sample_partially_fragmented.mp4"),
7475
Sample.withSubtitles("sample_with_vobsub.mp4", "eng"),
7576
Sample.forFile("testvid_1022ms.mp4"),

‎libraries/extractor/src/main/java/androidx/media3/extractor/WavUtil.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public static int getTypeForPcmEncoding(@C.PcmEncoding int pcmEncoding) {
7676
case C.ENCODING_PCM_32BIT:
7777
return TYPE_PCM;
7878
case C.ENCODING_PCM_FLOAT:
79+
case C.ENCODING_PCM_DOUBLE:
7980
return TYPE_FLOAT;
8081
// TYPE_PCM is little endian so big endian formats don't match.
8182
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
@@ -98,7 +99,7 @@ public static int getTypeForPcmEncoding(@C.PcmEncoding int pcmEncoding) {
9899
case TYPE_WAVE_FORMAT_EXTENSIBLE:
99100
return Util.getPcmEncoding(bitsPerSample);
100101
case TYPE_FLOAT:
101-
return bitsPerSample == 32 ? C.ENCODING_PCM_FLOAT : C.ENCODING_INVALID;
102+
return Util.getFloatPcmEncoding(bitsPerSample);
102103
default:
103104
return C.ENCODING_INVALID;
104105
}

‎libraries/extractor/src/main/java/androidx/media3/extractor/mkv/MatroskaExtractor.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,9 +2451,8 @@ public void initializeFormat(int trackId) throws ParserException {
24512451
break;
24522452
case CODEC_ID_PCM_FLOAT:
24532453
mimeType = MimeTypes.AUDIO_RAW;
2454-
if (audioBitDepth == 32) {
2455-
pcmEncoding = C.ENCODING_PCM_FLOAT;
2456-
} else {
2454+
pcmEncoding = Util.getFloatPcmEncoding(audioBitDepth);
2455+
if (pcmEncoding == C.ENCODING_INVALID) {
24572456
pcmEncoding = Format.NO_VALUE;
24582457
mimeType = MimeTypes.AUDIO_UNKNOWN;
24592458
Log.w(

‎libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2131,8 +2131,8 @@ private static void parseAudioSampleEntry(
21312131
boolean isBigEndian = (formatSpecificFlags & (1 << 1)) != 0;
21322132
if (!isFloat) {
21332133
pcmEncoding = Util.getPcmEncoding(bitsPerSample, isBigEndian ? BIG_ENDIAN : LITTLE_ENDIAN);
2134-
} else if (!isBigEndian && bitsPerSample == 32) {
2135-
pcmEncoding = C.ENCODING_PCM_FLOAT;
2134+
} else if (!isBigEndian) {
2135+
pcmEncoding = Util.getFloatPcmEncoding(bitsPerSample);
21362136
}
21372137
if (pcmEncoding == C.ENCODING_INVALID) {
21382138
pcmEncoding = Format.NO_VALUE;
@@ -2389,11 +2389,12 @@ private static void parseAudioSampleEntry(
23892389
int sampleSize = parent.readUnsignedByte();
23902390
if (atomType == Mp4Box.TYPE_ipcm) {
23912391
pcmEncoding = Util.getPcmEncoding(sampleSize, byteOrder);
2392-
} else if (atomType == Mp4Box.TYPE_fpcm
2393-
&& sampleSize == 32
2394-
&& byteOrder.equals(LITTLE_ENDIAN)) {
2395-
// Only single-width little-endian floating point PCM is supported.
2396-
pcmEncoding = C.ENCODING_PCM_FLOAT;
2392+
} else if (atomType == Mp4Box.TYPE_fpcm && byteOrder.equals(LITTLE_ENDIAN)) {
2393+
// Only little-endian floating point PCM is supported.
2394+
pcmEncoding = Util.getFloatPcmEncoding(sampleSize);
2395+
}
2396+
if (pcmEncoding == C.ENCODING_INVALID) {
2397+
pcmEncoding = Format.NO_VALUE;
23972398
}
23982399
if (pcmEncoding != Format.NO_VALUE) {
23992400
mimeType = MimeTypes.AUDIO_RAW;

‎libraries/extractor/src/test/java/androidx/media3/extractor/mkv/MatroskaExtractorTest.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,14 @@ public void mkvSample_withDtsX() throws Exception {
243243
simulationConfig);
244244
}
245245

246+
@Test
247+
public void mkaSample_withFpcm64le() throws Exception {
248+
ExtractorAsserts.assertBehavior(
249+
getExtractorFactory(subtitlesParsedDuringExtraction),
250+
"media/mka/bear-pcm-f64le.mka",
251+
simulationConfig);
252+
}
253+
246254
private static ExtractorAsserts.ExtractorFactory getExtractorFactory(
247255
boolean subtitlesParsedDuringExtraction) {
248256
SubtitleParser.Factory subtitleParserFactory;

‎libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorParameterizedTest.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ public void mp4SampleWith32leFpcm() throws Exception {
296296
assertExtractorBehavior("media/mp4/sample_fpcm_32le.mp4", /* peekLimit= */ 50);
297297
}
298298

299+
@Test
300+
public void mp4SampleWith64leFpcm() throws Exception {
301+
assertExtractorBehavior("media/mp4/sample_fpcm_64le.mp4", /* peekLimit= */ 50);
302+
}
303+
299304
// Only the rotation part of the transformation matrix is resolved (b/390422593 tracks supporting
300305
// reflection too).
301306
@Test

0 commit comments

Comments
 (0)