From d098effa699bed846f02575d3ae8b0aaddf4498d Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Wed, 24 Jan 2018 10:51:05 +0000 Subject: [PATCH] Cleanup merged pull requests --- RELEASENOTES.md | 8 +- .../exoplayer2/audio/DefaultAudioSink.java | 57 ++++---- .../audio/FloatResamplingAudioProcessor.java | 133 ++++++++---------- .../audio/ResamplingAudioProcessor.java | 3 +- .../upstream/DefaultDataSource.java | 30 ++-- .../upstream/RawResourceDataSource.java | 1 + .../google/android/exoplayer2/util/Util.java | 10 ++ 7 files changed, 120 insertions(+), 122 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 75845cf10c..c624cce996 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -66,8 +66,12 @@ * Fix ID3 context reuse across segment format changes ([#3622](https://github.com/google/ExoPlayer/issues/3622)). * New Cast extension: Simplifies toggling between local and Cast playbacks. -* Audio: Support TrueHD passthrough for rechunked samples in Matroska files - ([#2147](https://github.com/google/ExoPlayer/issues/2147)). +* Audio: + * Support TrueHD passthrough for rechunked samples in Matroska files + ([#2147](https://github.com/google/ExoPlayer/issues/2147)). + * Support resampling 24-bit and 32-bit integer to 32-bit float for high + resolution output in `DefaultAudioSink` + ([#3635](https://github.com/google/ExoPlayer/pull/3635)). * Captions: Initial support for PGS subtitles ([#3008](https://github.com/google/ExoPlayer/issues/3008)). * CacheDataSource: Check periodically if it's possible to read from/write to diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index 892fd428ff..bb9135edbf 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -164,7 +164,7 @@ public final class DefaultAudioSink implements AudioSink { public static boolean failOnSpuriousAudioTimestamp = false; @Nullable private final AudioCapabilities audioCapabilities; - private final boolean canConvertHiResPcmToFloat; + private final boolean enableConvertHighResIntPcmToFloat; private final ChannelMappingAudioProcessor channelMappingAudioProcessor; private final TrimmingAudioProcessor trimmingAudioProcessor; private final SonicAudioProcessor sonicAudioProcessor; @@ -182,14 +182,14 @@ public final class DefaultAudioSink implements AudioSink { private AudioTrack keepSessionIdAudioTrack; private AudioTrack audioTrack; private boolean isInputPcm; - private boolean shouldUpResPCMAudio; + private boolean shouldConvertHighResIntPcmToFloat; private int inputSampleRate; private int sampleRate; private int channelConfig; private @C.Encoding int outputEncoding; private AudioAttributes audioAttributes; private boolean processingEnabled; - private boolean canApplyPlaybackParams; + private boolean canApplyPlaybackParameters; private int bufferSize; private long bufferSizeUs; @@ -237,8 +237,6 @@ public final class DefaultAudioSink implements AudioSink { private boolean hasData; private long lastFeedElapsedRealtimeMs; - - /** * @param audioCapabilities The audio capabilities for playback on this device. May be null if the * default capabilities (no encoded audio passthrough support) should be assumed. @@ -247,7 +245,7 @@ public final class DefaultAudioSink implements AudioSink { */ public DefaultAudioSink(@Nullable AudioCapabilities audioCapabilities, AudioProcessor[] audioProcessors) { - this(audioCapabilities, audioProcessors, false); + this(audioCapabilities, audioProcessors, /* enableConvertHighResIntPcmToFloat= */ false); } /** @@ -255,15 +253,17 @@ public final class DefaultAudioSink implements AudioSink { * default capabilities (no encoded audio passthrough support) should be assumed. * @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio before * output. May be empty. - * @param canConvertHiResPcmToFloat Flag to convert > 16bit PCM Audio to 32bit Float PCM Audio to - * avoid dithering the input audio. If enabled other audio processors that expect 16bit PCM - * are disabled + * @param enableConvertHighResIntPcmToFloat Whether to enable conversion of high resolution + * integer PCM to 32-bit float for output, if possible. Functionality that uses 16-bit integer + * audio processing (for example, speed and pitch adjustment) will not be available when float + * output is in use. */ - public DefaultAudioSink(@Nullable AudioCapabilities audioCapabilities, - AudioProcessor[] audioProcessors, boolean canConvertHiResPcmToFloat) { - + public DefaultAudioSink( + @Nullable AudioCapabilities audioCapabilities, + AudioProcessor[] audioProcessors, + boolean enableConvertHighResIntPcmToFloat) { this.audioCapabilities = audioCapabilities; - this.canConvertHiResPcmToFloat = canConvertHiResPcmToFloat; + this.enableConvertHighResIntPcmToFloat = enableConvertHighResIntPcmToFloat; releasingConditionVariable = new ConditionVariable(true); if (Util.SDK_INT >= 18) { try { @@ -285,10 +285,10 @@ public final class DefaultAudioSink implements AudioSink { toIntPcmAvailableAudioProcessors[0] = new ResamplingAudioProcessor(); toIntPcmAvailableAudioProcessors[1] = channelMappingAudioProcessor; toIntPcmAvailableAudioProcessors[2] = trimmingAudioProcessor; - System.arraycopy(audioProcessors, 0, toIntPcmAvailableAudioProcessors, 3, audioProcessors.length); + System.arraycopy( + audioProcessors, 0, toIntPcmAvailableAudioProcessors, 3, audioProcessors.length); toIntPcmAvailableAudioProcessors[3 + audioProcessors.length] = sonicAudioProcessor; - toFloatPcmAvailableAudioProcessors = new AudioProcessor[1]; - toFloatPcmAvailableAudioProcessors[0] = new FloatResamplingAudioProcessor(); + toFloatPcmAvailableAudioProcessors = new AudioProcessor[] {new FloatResamplingAudioProcessor()}; playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; volume = 1.0f; startMediaTimeState = START_NOT_SET; @@ -366,20 +366,20 @@ public final class DefaultAudioSink implements AudioSink { int channelCount = inputChannelCount; int sampleRate = inputSampleRate; isInputPcm = isEncodingPcm(inputEncoding); - shouldUpResPCMAudio = canConvertHiResPcmToFloat && - (inputEncoding == C.ENCODING_PCM_24BIT || inputEncoding == C.ENCODING_PCM_32BIT); + shouldConvertHighResIntPcmToFloat = + enableConvertHighResIntPcmToFloat + && isEncodingSupported(C.ENCODING_PCM_32BIT) + && Util.isEncodingHighResolutionIntegerPcm(inputEncoding); if (isInputPcm) { pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount); } @C.Encoding int encoding = inputEncoding; boolean processingEnabled = isInputPcm && inputEncoding != C.ENCODING_PCM_FLOAT; - canApplyPlaybackParams = processingEnabled && !shouldUpResPCMAudio; + canApplyPlaybackParameters = processingEnabled && !shouldConvertHighResIntPcmToFloat; if (processingEnabled) { - AudioProcessor[] availableAudioProcessors = shouldUpResPCMAudio ? - toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors; trimmingAudioProcessor.setTrimSampleCount(trimStartSamples, trimEndSamples); channelMappingAudioProcessor.setChannelMap(outputChannels); - for (AudioProcessor audioProcessor : availableAudioProcessors) { + for (AudioProcessor audioProcessor : getAvailableAudioProcessors()) { try { flush |= audioProcessor.configure(sampleRate, channelCount, encoding); } catch (AudioProcessor.UnhandledFormatException e) { @@ -489,9 +489,7 @@ public final class DefaultAudioSink implements AudioSink { private void resetAudioProcessors() { ArrayList newAudioProcessors = new ArrayList<>(); - AudioProcessor[] availableAudioProcessors = shouldUpResPCMAudio ? - toFloatPcmAvailableAudioProcessors : toIntPcmAvailableAudioProcessors; - for (AudioProcessor audioProcessor : availableAudioProcessors) { + for (AudioProcessor audioProcessor : getAvailableAudioProcessors()) { if (audioProcessor.isActive()) { newAudioProcessors.add(audioProcessor); } else { @@ -839,8 +837,7 @@ public final class DefaultAudioSink implements AudioSink { @Override public PlaybackParameters setPlaybackParameters(PlaybackParameters playbackParameters) { - if (isInitialized() && !canApplyPlaybackParams) { - // The playback parameters are always the default if processing is disabled. + if (isInitialized() && !canApplyPlaybackParameters) { this.playbackParameters = PlaybackParameters.DEFAULT; return this.playbackParameters; } @@ -1256,6 +1253,12 @@ public final class DefaultAudioSink implements AudioSink { MODE_STATIC, audioSessionId); } + private AudioProcessor[] getAvailableAudioProcessors() { + return shouldConvertHighResIntPcmToFloat + ? toFloatPcmAvailableAudioProcessors + : toIntPcmAvailableAudioProcessors; + } + private static boolean isEncodingPcm(@C.Encoding int encoding) { return encoding == C.ENCODING_PCM_8BIT || encoding == C.ENCODING_PCM_16BIT || encoding == C.ENCODING_PCM_24BIT || encoding == C.ENCODING_PCM_32BIT diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java index f7073f1275..215b04821b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java @@ -15,31 +15,30 @@ */ package com.google.android.exoplayer2.audio; - import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; - +import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** - * An {@link AudioProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}. + * An {@link AudioProcessor} that converts 24-bit and 32-bit integer PCM audio to 32-bit float PCM + * audio. */ /* package */ final class FloatResamplingAudioProcessor implements AudioProcessor { - private int sampleRateHz; - private static final double PCM_INT32_FLOAT = 1.0 / 0x7fffffff; + private static final int FLOAT_NAN_AS_INT = Float.floatToIntBits(Float.NaN); + private static final double PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR = 1.0 / 0x7FFFFFFF; + private int sampleRateHz; private int channelCount; - @C.PcmEncoding - private int sourceEncoding; + private @C.PcmEncoding int sourceEncoding; private ByteBuffer buffer; private ByteBuffer outputBuffer; private boolean inputEnded; - /** - * Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_16BIT}. - */ + /** Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_FLOAT}. */ public FloatResamplingAudioProcessor() { sampleRateHz = Format.NO_VALUE; channelCount = Format.NO_VALUE; @@ -50,31 +49,35 @@ import java.nio.ByteOrder; @Override public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) - throws AudioProcessor.UnhandledFormatException { - if (encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { - throw new AudioProcessor.UnhandledFormatException(sampleRateHz, channelCount, encoding); + throws UnhandledFormatException { + if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) { + throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } - if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount - && this.sourceEncoding == encoding) { + if (this.sampleRateHz == sampleRateHz + && this.channelCount == channelCount + && sourceEncoding == encoding) { return false; } this.sampleRateHz = sampleRateHz; this.channelCount = channelCount; - this.sourceEncoding = encoding; - + sourceEncoding = encoding; return true; } @Override public boolean isActive() { - return sourceEncoding == C.ENCODING_PCM_24BIT || sourceEncoding == C.ENCODING_PCM_32BIT; + return Util.isEncodingHighResolutionIntegerPcm(sourceEncoding); } @Override - public int getOutputChannelCount() { return channelCount; } + public int getOutputChannelCount() { + return channelCount; + } @Override - public int getOutputEncoding() { return C.ENCODING_PCM_FLOAT; } + public int getOutputEncoding() { + return C.ENCODING_PCM_FLOAT; + } @Override public int getOutputSampleRateHz() { @@ -83,60 +86,36 @@ import java.nio.ByteOrder; @Override public void queueInput(ByteBuffer inputBuffer) { - int offset = inputBuffer.position(); + Assertions.checkState(isActive()); + + boolean isInput32Bit = sourceEncoding == C.ENCODING_PCM_32BIT; + int position = inputBuffer.position(); int limit = inputBuffer.limit(); - int size = limit - offset; - - int resampledSize; - switch (sourceEncoding) { - case C.ENCODING_PCM_24BIT: - resampledSize = (size / 3) * 4; - break; - case C.ENCODING_PCM_32BIT: - resampledSize = size; - break; - case C.ENCODING_PCM_8BIT: - case C.ENCODING_PCM_16BIT: - case C.ENCODING_PCM_FLOAT: - case C.ENCODING_INVALID: - case Format.NO_VALUE: - default: - // Never happens. - throw new IllegalStateException(); - } + int size = limit - position; + int resampledSize = isInput32Bit ? size : (size / 3) * 4; if (buffer.capacity() < resampledSize) { buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder()); } else { buffer.clear(); } - - // Samples are little endian. - switch (sourceEncoding) { - case C.ENCODING_PCM_24BIT: - // 24->32 bit resampling. - for (int i = offset; i < limit; i += 3) { - int val = (inputBuffer.get(i) << 8) & 0x0000ff00 | (inputBuffer.get(i + 1) << 16) & 0x00ff0000 | - (inputBuffer.get(i + 2) << 24) & 0xff000000; - writePcm32bitFloat(val, buffer); - } - break; - case C.ENCODING_PCM_32BIT: - // 32->32 bit conversion. - for (int i = offset; i < limit; i += 4) { - int val = inputBuffer.get(i) & 0x000000ff | (inputBuffer.get(i) << 8) & 0x0000ff00 | - (inputBuffer.get(i + 1) << 16) & 0x00ff0000 | (inputBuffer.get(i + 2) << 24) & 0xff000000; - writePcm32bitFloat(val, buffer); - } - break; - case C.ENCODING_PCM_8BIT: - case C.ENCODING_PCM_16BIT: - case C.ENCODING_PCM_FLOAT: - case C.ENCODING_INVALID: - case Format.NO_VALUE: - default: - // Never happens. - throw new IllegalStateException(); + if (isInput32Bit) { + for (int i = position; i < limit; i += 4) { + int pcm32BitInteger = + (inputBuffer.get(i) & 0xFF) + | ((inputBuffer.get(i + 1) & 0xFF) << 8) + | ((inputBuffer.get(i + 2) & 0xFF) << 16) + | ((inputBuffer.get(i + 3) & 0xFF) << 24); + writePcm32BitFloat(pcm32BitInteger, buffer); + } + } else { + for (int i = position; i < limit; i += 3) { + int pcm32BitInteger = + ((inputBuffer.get(i) & 0xFF) << 8) + | ((inputBuffer.get(i + 1) & 0xFF) << 16) + | ((inputBuffer.get(i + 2) & 0xFF) << 24); + writePcm32BitFloat(pcm32BitInteger, buffer); + } } inputBuffer.position(inputBuffer.limit()); @@ -178,17 +157,17 @@ import java.nio.ByteOrder; } /** - * Converts the provided value into 32-bit float PCM and writes to buffer. + * Converts the provided 32-bit integer to a 32-bit float value and writes it to {@code buffer}. * - * @param val 32-bit int value to convert to 32-bit float [-1.0, 1.0] + * @param pcm32BitInt The 32-bit integer value to convert to 32-bit float in [-1.0, 1.0]. * @param buffer The output buffer. */ - private static void writePcm32bitFloat(int val, ByteBuffer buffer) { - float convVal = (float) (PCM_INT32_FLOAT * val); - int bits = Float.floatToIntBits(convVal); - if (bits == 0x7fc00000) - bits = Float.floatToIntBits((float) 0.0); - buffer.putInt(bits); + private static void writePcm32BitFloat(int pcm32BitInt, ByteBuffer buffer) { + float pcm32BitFloat = (float) (PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR * pcm32BitInt); + int floatBits = Float.floatToIntBits(pcm32BitFloat); + if (floatBits == FLOAT_NAN_AS_INT) { + floatBits = Float.floatToIntBits((float) 0.0); + } + buffer.putInt(floatBits); } - -} \ No newline at end of file +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java index d5a18c5ebf..01123f3c59 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java @@ -21,7 +21,8 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; /** - * An {@link AudioProcessor} that converts audio data to {@link C#ENCODING_PCM_16BIT}. + * An {@link AudioProcessor} that converts 8-bit, 24-bit and 32-bit integer PCM audio to 16-bit + * integer PCM audio. */ /* package */ final class ResamplingAudioProcessor implements AudioProcessor { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java index 6d22f8b6c5..0542b6ce8c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/DefaultDataSource.java @@ -27,21 +27,21 @@ import java.lang.reflect.InvocationTargetException; * A {@link DataSource} that supports multiple URI schemes. The supported schemes are: * * */ public final class DefaultDataSource implements DataSource { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java index 941fa90e8f..1b58c7e095 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/RawResourceDataSource.java @@ -58,6 +58,7 @@ public final class RawResourceDataSource implements DataSource { return Uri.parse(RAW_RESOURCE_SCHEME + ":///" + rawResourceId); } + /** The scheme part of a raw resource URI. */ public static final String RAW_RESOURCE_SCHEME = "rawresource"; private final Resources resources; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 9a3567b9fc..ceefe758bb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -941,6 +941,16 @@ public final class Util { } } + /** + * Returns whether {@code encoding} is high resolution (> 16-bit) integer PCM. + * + * @param encoding The encoding of the audio data. + * @return Whether the encoding is high resolution integer PCM. + */ + public static boolean isEncodingHighResolutionIntegerPcm(@C.PcmEncoding int encoding) { + return encoding == C.ENCODING_PCM_24BIT || encoding == C.ENCODING_PCM_32BIT; + } + /** * Returns the frame size for audio with {@code channelCount} channels in the specified encoding. *