Skip to content

Commit b492533

Browse files
committed
Better support for PCM audio formats around DefaultAudioSink
This change: - abstracts away the fact that SonicAudioProcessor does not support float PCM from the audio sink to the audio processor chain (easier to replace) - makes the audio sink allow custom audio processors with any output format - adds a new mode to DefaultAudioSink that allows any supported linear PCM format to be output into the AudioTrack Issue: google/ExoPlayer#10516 Issue: #1931
1 parent e2a7680 commit b492533

File tree

5 files changed

+205
-42
lines changed

5 files changed

+205
-42
lines changed

libraries/common/src/main/java/androidx/media3/common/audio/AudioProcessorChain.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package androidx.media3.common.audio;
1717

18+
import androidx.media3.common.Format;
1819
import androidx.media3.common.PlaybackParameters;
1920
import androidx.media3.common.util.UnstableApi;
2021

@@ -33,7 +34,7 @@ public interface AudioProcessorChain {
3334
* during initialization, but audio processors may change state to become active/inactive during
3435
* playback.
3536
*/
36-
AudioProcessor[] getAudioProcessors();
37+
AudioProcessor[] getAudioProcessors(Format inputFormat);
3738

3839
/**
3940
* Configures audio processors to apply the specified playback parameters immediately, returning
@@ -50,7 +51,7 @@ public interface AudioProcessorChain {
5051
* value. Only called when processors have no input pending.
5152
*
5253
* @param skipSilenceEnabled Whether silences should be skipped in the audio stream.
53-
* @return The new value.
54+
* @return The value that was actually applied.
5455
*/
5556
boolean applySkipSilenceEnabled(boolean skipSilenceEnabled);
5657

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,7 @@ public static int getApiLevelThatAudioFormatIntroducedAudioEncoding(int encoding
24012401
return 28;
24022402
case C.ENCODING_OPUS:
24032403
return 30;
2404+
case C.ENCODING_PCM_24BIT:
24042405
case C.ENCODING_PCM_32BIT:
24052406
return 31;
24062407
case C.ENCODING_DTS_UHD_P2:

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DefaultRenderersFactory.java

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public class DefaultRenderersFactory implements RenderersFactory {
110110
private long allowedVideoJoiningTimeMs;
111111
private boolean enableDecoderFallback;
112112
private MediaCodecSelector mediaCodecSelector;
113+
private boolean pcmEncodingRestrictionLifted;
113114
private boolean enableFloatOutput;
114115
private boolean enableAudioTrackPlaybackParams;
115116
private boolean enableMediaCodecVideoRendererPrewarming;
@@ -225,15 +226,38 @@ public final DefaultRenderersFactory setMediaCodecSelector(
225226
*
226227
* <p>The default value is {@code false}.
227228
*
229+
* @deprecated Use {@link #setPcmEncodingRestrictionLifted} instead to allow any encoding, not
230+
* just 32-bit float.
228231
* @param enableFloatOutput Whether to enable use of floating point audio output, if available.
229232
* @return This factory, for convenience.
230233
*/
231234
@CanIgnoreReturnValue
235+
@Deprecated
232236
public final DefaultRenderersFactory setEnableAudioFloatOutput(boolean enableFloatOutput) {
233237
this.enableFloatOutput = enableFloatOutput;
234238
return this;
235239
}
236240

241+
/**
242+
* Sets whether to enable outputting samples in any platform-supported format (such as 32-bit
243+
* float, 32-bit integer, 24-bit integer, 16-bit integer or 8-bit integer) instead of restricting
244+
* output to 16-bit integers. Where possible, the input sample format will be used, otherwise
245+
* high-resolution formats will be output as 32-bit float. Parts of the default audio processing
246+
* chain (for example, speed adjustment) will not be available when output formats other than
247+
* 16-bit integer are in use.
248+
*
249+
* <p>The default value is {@code false}.
250+
*
251+
* @param pcmEncodingRestrictionLifted Whether to lift any restriction of output sample format.
252+
* @return This factory, for convenience.
253+
*/
254+
@CanIgnoreReturnValue
255+
public final DefaultRenderersFactory setPcmEncodingRestrictionLifted(
256+
boolean pcmEncodingRestrictionLifted) {
257+
this.pcmEncodingRestrictionLifted = pcmEncodingRestrictionLifted;
258+
return this;
259+
}
260+
237261
/**
238262
* Sets whether to enable setting playback speed using {@link
239263
* android.media.AudioTrack#setPlaybackParams(PlaybackParams)}, which is supported from API level
@@ -377,7 +401,11 @@ public Renderer[] createRenderers(
377401
renderersList);
378402
@Nullable
379403
AudioSink audioSink =
380-
buildAudioSink(context, enableFloatOutput, enableAudioTrackPlaybackParams);
404+
buildAudioSink(
405+
context,
406+
pcmEncodingRestrictionLifted,
407+
enableFloatOutput,
408+
enableAudioTrackPlaybackParams);
381409
if (audioSink != null) {
382410
buildAudioRenderers(
383411
context,
@@ -854,11 +882,17 @@ protected void buildMiscellaneousRenderers(
854882
*/
855883
@Nullable
856884
protected AudioSink buildAudioSink(
857-
Context context, boolean enableFloatOutput, boolean enableAudioTrackPlaybackParams) {
858-
return new DefaultAudioSink.Builder(context)
859-
.setEnableFloatOutput(enableFloatOutput)
860-
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
861-
.build();
885+
Context context,
886+
boolean pcmEncodingRestrictionLifted,
887+
boolean enableFloatOutput,
888+
boolean enableAudioTrackPlaybackParams) {
889+
DefaultAudioSink.Builder builder = new DefaultAudioSink.Builder(context);
890+
if (pcmEncodingRestrictionLifted || !enableFloatOutput) {
891+
builder.setPcmEncodingRestrictionLifted(pcmEncodingRestrictionLifted);
892+
} else {
893+
builder.setEnableFloatOutput(true);
894+
}
895+
return builder.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams).build();
862896
}
863897

864898
@Override

libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,21 @@ default int getAudioTrackChannelConfig(int channelCount) {
142142
/** The time it takes to ramp AudioTrack's volume up or down when pausing or starting to play. */
143143
private static final int AUDIO_TRACK_VOLUME_RAMP_TIME_MS = 20;
144144

145+
/**
146+
* @see Builder#setPcmEncodingRestrictionLifted(boolean)
147+
*/
148+
private static final int PCM_ENCODING_ANY = 0;
149+
150+
/**
151+
* @see Builder#setPcmEncodingRestrictionLifted(boolean)
152+
*/
153+
private static final int PCM_ENCODING_INT16_ONLY = 1;
154+
155+
/**
156+
* @see Builder#setEnableFloatOutput(boolean)
157+
*/
158+
private static final int PCM_ENCODING_INT16_FLOAT32_ONLY = 2;
159+
145160
/**
146161
* Thrown when the audio track has provided a spurious timestamp, if {@link
147162
* #failOnSpuriousAudioTimestamp} is set.
@@ -167,10 +182,12 @@ public interface AudioProcessorChain extends androidx.media3.common.audio.AudioP
167182
/**
168183
* The default audio processor chain, which applies a (possibly empty) chain of user-defined audio
169184
* processors followed by {@link SilenceSkippingAudioProcessor} and {@link SonicAudioProcessor}.
185+
* No audio processors will be applied for PCM encodings other than 16-bit integer.
170186
*/
171187
@SuppressWarnings("deprecation")
172188
public static class DefaultAudioProcessorChain implements AudioProcessorChain {
173189

190+
private boolean formatSupported = false;
174191
private final AudioProcessor[] audioProcessors;
175192
private final SilenceSkippingAudioProcessor silenceSkippingAudioProcessor;
176193
private final SonicAudioProcessor sonicAudioProcessor;
@@ -207,33 +224,44 @@ public DefaultAudioProcessorChain(
207224
}
208225

209226
@Override
210-
public AudioProcessor[] getAudioProcessors() {
227+
public AudioProcessor[] getAudioProcessors(Format inputFormat) {
228+
if (inputFormat.pcmEncoding != C.ENCODING_PCM_16BIT) {
229+
formatSupported = false;
230+
return new AudioProcessor[0];
231+
}
232+
formatSupported = true;
211233
return audioProcessors;
212234
}
213235

214236
@Override
215237
public PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) {
238+
if (!formatSupported) {
239+
return PlaybackParameters.DEFAULT;
240+
}
216241
sonicAudioProcessor.setSpeed(playbackParameters.speed);
217242
sonicAudioProcessor.setPitch(playbackParameters.pitch);
218243
return playbackParameters;
219244
}
220245

221246
@Override
222247
public boolean applySkipSilenceEnabled(boolean skipSilenceEnabled) {
248+
if (!formatSupported) {
249+
return false;
250+
}
223251
silenceSkippingAudioProcessor.setEnabled(skipSilenceEnabled);
224252
return skipSilenceEnabled;
225253
}
226254

227255
@Override
228256
public long getMediaDuration(long playoutDuration) {
229-
return sonicAudioProcessor.isActive()
257+
return formatSupported && sonicAudioProcessor.isActive()
230258
? sonicAudioProcessor.getMediaDuration(playoutDuration)
231259
: playoutDuration;
232260
}
233261

234262
@Override
235263
public long getSkippedOutputFrameCount() {
236-
return silenceSkippingAudioProcessor.getSkippedFrames();
264+
return formatSupported ? silenceSkippingAudioProcessor.getSkippedFrames() : 0;
237265
}
238266
}
239267

@@ -297,7 +325,7 @@ public static final class Builder {
297325
@Nullable private final Context context;
298326
private AudioCapabilities audioCapabilities;
299327
@Nullable private androidx.media3.common.audio.AudioProcessorChain audioProcessorChain;
300-
private boolean enableFloatOutput;
328+
private int pcmEncodingRestrictionMode;
301329
private boolean enableAudioTrackPlaybackParams;
302330

303331
private boolean buildCalled;
@@ -316,6 +344,7 @@ public Builder() {
316344
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
317345
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
318346
audioTrackProvider = AudioTrackProvider.DEFAULT;
347+
pcmEncodingRestrictionMode = PCM_ENCODING_INT16_ONLY;
319348
}
320349

321350
/**
@@ -328,6 +357,7 @@ public Builder(Context context) {
328357
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
329358
audioTrackBufferSizeProvider = AudioTrackBufferSizeProvider.DEFAULT;
330359
audioTrackProvider = AudioTrackProvider.DEFAULT;
360+
pcmEncodingRestrictionMode = PCM_ENCODING_INT16_ONLY;
331361
}
332362

333363
/**
@@ -375,14 +405,37 @@ public Builder setAudioProcessorChain(
375405
/**
376406
* Sets whether to enable 32-bit float output or integer output. Where possible, 32-bit float
377407
* output will be used if the input is 32-bit float, and also if the input is high resolution
378-
* (24-bit or 32-bit) integer PCM. Audio processing (for example, speed adjustment) will not be
379-
* available when float output is in use.
408+
* (24-bit or 32-bit) integer PCM. Parts of the default audio processing chain (for example,
409+
* speed adjustment) will not be available when output formats other than 16-bit integer are in
410+
* use.
380411
*
381412
* <p>The default value is {@code false}.
413+
*
414+
* @deprecated Use {@link #setPcmEncodingRestrictionLifted} instead to allow any encoding, not
415+
* just 32-bit float.
382416
*/
417+
@Deprecated
383418
@CanIgnoreReturnValue
384419
public Builder setEnableFloatOutput(boolean enableFloatOutput) {
385-
this.enableFloatOutput = enableFloatOutput;
420+
this.pcmEncodingRestrictionMode =
421+
enableFloatOutput ? PCM_ENCODING_INT16_FLOAT32_ONLY : PCM_ENCODING_INT16_ONLY;
422+
return this;
423+
}
424+
425+
/**
426+
* Sets whether to enable outputting samples in any platform-supported format (such as 32-bit
427+
* float, 32-bit integer, 24-bit integer, 16-bit integer or 8-bit integer) instead of
428+
* restricting output to 16-bit integers. Where possible, the input sample format will be used,
429+
* otherwise high-resolution formats will be output as 32-bit float. Parts of the default audio
430+
* processing chain (for example, speed adjustment) will not be available when output formats
431+
* other than 16-bit integer are in use.
432+
*
433+
* <p>The default value is {@code false}.
434+
*/
435+
@CanIgnoreReturnValue
436+
public Builder setPcmEncodingRestrictionLifted(boolean pcmEncodingRestrictionLifted) {
437+
this.pcmEncodingRestrictionMode =
438+
pcmEncodingRestrictionLifted ? PCM_ENCODING_ANY : PCM_ENCODING_INT16_ONLY;
386439
return this;
387440
}
388441

@@ -549,7 +602,7 @@ public DefaultAudioSink build() {
549602

550603
@Nullable private final Context context;
551604
private final androidx.media3.common.audio.AudioProcessorChain audioProcessorChain;
552-
private final boolean enableFloatOutput;
605+
private final int pcmEncodingRestrictionMode;
553606
private final ChannelMappingAudioProcessor channelMappingAudioProcessor;
554607
private final TrimmingAudioProcessor trimmingAudioProcessor;
555608
private final ToInt16PcmAudioProcessor toInt16PcmAudioProcessor;
@@ -628,7 +681,7 @@ private DefaultAudioSink(Builder builder) {
628681
audioAttributes = AudioAttributes.DEFAULT;
629682
audioCapabilities = context != null ? null : builder.audioCapabilities;
630683
audioProcessorChain = builder.audioProcessorChain;
631-
enableFloatOutput = builder.enableFloatOutput;
684+
pcmEncodingRestrictionMode = builder.pcmEncodingRestrictionMode;
632685
preferAudioTrackPlaybackParams = SDK_INT >= 23 && builder.enableAudioTrackPlaybackParams;
633686
offloadMode = OFFLOAD_MODE_DISABLED;
634687
audioTrackBufferSizeProvider = builder.audioTrackBufferSizeProvider;
@@ -690,13 +743,24 @@ public boolean supportsFormat(Format format) {
690743
Log.w(TAG, "Invalid PCM encoding: " + format.pcmEncoding);
691744
return SINK_FORMAT_UNSUPPORTED;
692745
}
693-
if (format.pcmEncoding == C.ENCODING_PCM_16BIT
694-
|| (enableFloatOutput && format.pcmEncoding == C.ENCODING_PCM_FLOAT)) {
695-
return SINK_FORMAT_SUPPORTED_DIRECTLY;
746+
if (format.pcmEncoding != C.ENCODING_PCM_16BIT) {
747+
if (pcmEncodingRestrictionMode == PCM_ENCODING_INT16_FLOAT32_ONLY
748+
&& format.pcmEncoding != C.ENCODING_PCM_FLOAT) {
749+
// PCM_ENCODING_INT16_FLOAT32_ONLY is deprecated and kept for backwards compatibility.
750+
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
751+
}
752+
if (pcmEncodingRestrictionMode == PCM_ENCODING_INT16_ONLY) {
753+
// We will forcibly transcode to 16-bit PCM to allow the full audio processor chain to
754+
// operate.
755+
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
756+
}
757+
}
758+
if (SDK_INT < Util.getApiLevelThatAudioFormatIntroducedAudioEncoding(format.pcmEncoding)) {
759+
// We can resample all linear PCM encodings to 16-bit integer PCM, which AudioTrack is
760+
// guaranteed to support.
761+
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
696762
}
697-
// We can resample all linear PCM encodings to 16-bit integer PCM, which AudioTrack is
698-
// guaranteed to support.
699-
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
763+
return SINK_FORMAT_SUPPORTED_DIRECTLY;
700764
}
701765
if (audioCapabilities.isPassthroughPlaybackSupported(format, audioAttributes)) {
702766
return SINK_FORMAT_SUPPORTED_DIRECTLY;
@@ -743,12 +807,32 @@ public void configure(Format inputFormat, int specifiedBufferSize, @Nullable int
743807

744808
ImmutableList.Builder<AudioProcessor> pipelineProcessors = new ImmutableList.Builder<>();
745809
pipelineProcessors.addAll(availableAudioProcessors);
746-
if (shouldUseFloatOutput(inputFormat.pcmEncoding)) {
747-
pipelineProcessors.add(toFloatPcmAudioProcessor);
810+
// We need to convert sample formats either if we don't support it, or if:
811+
// - there is some pcm encoding restriction, and
812+
// - the format isn't 16-bit integer (which is always allowed), and
813+
// - the format isn't 32-bit float OR 32-bit float isn't allowed
814+
Format afterConversionFormat;
815+
if (SDK_INT < Util.getApiLevelThatAudioFormatIntroducedAudioEncoding(inputFormat.pcmEncoding)
816+
|| (pcmEncodingRestrictionMode != PCM_ENCODING_ANY
817+
&& inputFormat.pcmEncoding != C.ENCODING_PCM_16BIT
818+
&& (inputFormat.pcmEncoding != C.ENCODING_PCM_FLOAT
819+
|| pcmEncodingRestrictionMode == PCM_ENCODING_INT16_ONLY))) {
820+
if (Util.isEncodingHighResolutionPcm(inputFormat.pcmEncoding)
821+
&& pcmEncodingRestrictionMode != PCM_ENCODING_INT16_ONLY) {
822+
pipelineProcessors.add(toFloatPcmAudioProcessor);
823+
afterConversionFormat =
824+
Util.getPcmFormat(
825+
C.ENCODING_PCM_FLOAT, inputFormat.channelCount, inputFormat.sampleRate);
826+
} else {
827+
pipelineProcessors.add(toInt16PcmAudioProcessor);
828+
afterConversionFormat =
829+
Util.getPcmFormat(
830+
C.ENCODING_PCM_16BIT, inputFormat.channelCount, inputFormat.sampleRate);
831+
}
748832
} else {
749-
pipelineProcessors.add(toInt16PcmAudioProcessor);
750-
pipelineProcessors.add(audioProcessorChain.getAudioProcessors());
833+
afterConversionFormat = inputFormat;
751834
}
835+
pipelineProcessors.add(audioProcessorChain.getAudioProcessors(afterConversionFormat));
752836
audioProcessingPipeline = new AudioProcessingPipeline(pipelineProcessors.build());
753837

754838
// If the underlying processors of the new pipeline are the same as the existing pipeline,
@@ -1779,25 +1863,14 @@ private boolean shouldApplyAudioProcessorPlaybackParameters() {
17791863
// frame presentation times are currently not modified (see also
17801864
// https://github.com/google/ExoPlayer/issues/4803);
17811865
// - when playing encoded audio via passthrough/offload, because modifying the audio stream
1782-
// would require decoding/re-encoding; and
1783-
// - when outputting float PCM audio, because SonicAudioProcessor outputs 16-bit integer PCM.
1784-
return !tunneling
1785-
&& configuration.outputMode == OUTPUT_MODE_PCM
1786-
&& !shouldUseFloatOutput(configuration.inputFormat.pcmEncoding);
1866+
// would require decoding/re-encoding.
1867+
return !tunneling && configuration.outputMode == OUTPUT_MODE_PCM;
17871868
}
17881869

17891870
private boolean useAudioTrackPlaybackParams() {
17901871
return configuration != null && configuration.enableAudioTrackPlaybackParams && SDK_INT >= 23;
17911872
}
17921873

1793-
/**
1794-
* Returns whether audio in the specified PCM encoding should be written to the audio track as
1795-
* float PCM.
1796-
*/
1797-
private boolean shouldUseFloatOutput(@C.PcmEncoding int pcmEncoding) {
1798-
return enableFloatOutput && Util.isEncodingHighResolutionPcm(pcmEncoding);
1799-
}
1800-
18011874
/**
18021875
* Applies and updates media position parameters.
18031876
*

0 commit comments

Comments
 (0)