Commit 36e480d8 by krocard Committed by kim-vde

Allow low level control of gapless offload

Allow offload of gapless content even if gapless offload is not known to be supported by the device.

This is not exposed in the high level DefaultRendererFactory as most
users are expected to prefer fidelity to power savings.

PiperOrigin-RevId: 359336407
parent 15c3c44e
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
* Report unexpected discontinuities in * Report unexpected discontinuities in
`AnalyticsListener.onAudioSinkError` `AnalyticsListener.onAudioSinkError`
([#6384](https://github.com/google/ExoPlayer/issues/6384)). ([#6384](https://github.com/google/ExoPlayer/issues/6384)).
* Allow forcing offload for gapless content even if gapless playback is
not supported.
* Analytics: * Analytics:
* Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`. * Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`.
* Downloads and caching: * Downloads and caching:
......
...@@ -669,6 +669,8 @@ public class DefaultRenderersFactory implements RenderersFactory { ...@@ -669,6 +669,8 @@ public class DefaultRenderersFactory implements RenderersFactory {
new DefaultAudioProcessorChain(), new DefaultAudioProcessorChain(),
enableFloatOutput, enableFloatOutput,
enableAudioTrackPlaybackParams, enableAudioTrackPlaybackParams,
enableOffload); enableOffload
? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
: DefaultAudioSink.OFFLOAD_MODE_DISABLED);
} }
} }
...@@ -678,7 +678,7 @@ public interface ExoPlayer extends Player { ...@@ -678,7 +678,7 @@ public interface ExoPlayer extends Player {
* <li>Audio offload rendering is enabled in {@link * <li>Audio offload rendering is enabled in {@link
* DefaultRenderersFactory#setEnableAudioOffload} or the equivalent option passed to {@link * DefaultRenderersFactory#setEnableAudioOffload} or the equivalent option passed to {@link
* DefaultAudioSink#DefaultAudioSink(AudioCapabilities, * DefaultAudioSink#DefaultAudioSink(AudioCapabilities,
* DefaultAudioSink.AudioProcessorChain, boolean, boolean, boolean)}. * DefaultAudioSink.AudioProcessorChain, boolean, boolean, int)}.
* <li>An audio track is playing in a format that the device supports offloading (for example, * <li>An audio track is playing in a format that the device supports offloading (for example,
* MP3 or AAC). * MP3 or AAC).
* <li>The {@link AudioSink} is playing with an offload {@link AudioTrack}. * <li>The {@link AudioSink} is playing with an offload {@link AudioTrack}.
......
...@@ -215,6 +215,35 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -215,6 +215,35 @@ public final class DefaultAudioSink implements AudioSink {
/** The default skip silence flag. */ /** The default skip silence flag. */
private static final boolean DEFAULT_SKIP_SILENCE = false; private static final boolean DEFAULT_SKIP_SILENCE = false;
/** Audio offload mode configuration. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
OFFLOAD_MODE_DISABLED,
OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED,
OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED
})
public @interface OffloadMode {}
/** The audio sink will never play in offload mode. */
public static final int OFFLOAD_MODE_DISABLED = 0;
/**
* The audio sink will prefer offload playback except if the track is gapless and the device does
* not advertise support for gapless playback in offload.
*
* <p>Use this option to prioritize seamless transitions between tracks of the same album to power
* savings.
*/
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED = 1;
/**
* The audio sink will prefer offload playback even if this might result in silence gaps between
* tracks.
*
* <p>Use this option to prioritize battery saving at the cost of a possible non seamless
* transitions between tracks of the same album.
*/
public static final int OFFLOAD_MODE_ENABLED_GAPLESS_NOT_REQUIRED = 2;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({OUTPUT_MODE_PCM, OUTPUT_MODE_OFFLOAD, OUTPUT_MODE_PASSTHROUGH}) @IntDef({OUTPUT_MODE_PCM, OUTPUT_MODE_OFFLOAD, OUTPUT_MODE_PASSTHROUGH})
...@@ -281,7 +310,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -281,7 +310,7 @@ public final class DefaultAudioSink implements AudioSink {
private final AudioTrackPositionTracker audioTrackPositionTracker; private final AudioTrackPositionTracker audioTrackPositionTracker;
private final ArrayDeque<MediaPositionParameters> mediaPositionParametersCheckpoints; private final ArrayDeque<MediaPositionParameters> mediaPositionParametersCheckpoints;
private final boolean enableAudioTrackPlaybackParams; private final boolean enableAudioTrackPlaybackParams;
private final boolean enableOffload; @OffloadMode private final int offloadMode;
@MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29; @MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29;
private final PendingExceptionHolder<InitializationException> private final PendingExceptionHolder<InitializationException>
initializationExceptionPendingExceptionHolder; initializationExceptionPendingExceptionHolder;
...@@ -364,7 +393,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -364,7 +393,7 @@ public final class DefaultAudioSink implements AudioSink {
new DefaultAudioProcessorChain(audioProcessors), new DefaultAudioProcessorChain(audioProcessors),
enableFloatOutput, enableFloatOutput,
/* enableAudioTrackPlaybackParams= */ false, /* enableAudioTrackPlaybackParams= */ false,
/* enableOffload= */ false); OFFLOAD_MODE_DISABLED);
} }
/** /**
...@@ -382,8 +411,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -382,8 +411,8 @@ public final class DefaultAudioSink implements AudioSink {
* use. * use.
* @param enableAudioTrackPlaybackParams Whether to enable setting playback speed using {@link * @param enableAudioTrackPlaybackParams Whether to enable setting playback speed using {@link
* android.media.AudioTrack#setPlaybackParams(PlaybackParams)}, if supported. * android.media.AudioTrack#setPlaybackParams(PlaybackParams)}, if supported.
* @param enableOffload Whether to enable audio offload. If an audio format can be both played * @param offloadMode Audio offload configuration. If an audio format can be both played with
* with offload and encoded audio passthrough, it will be played in offload. Audio offload is * offload and encoded audio passthrough, it will be played in offload. Audio offload is
* supported from API level 29. Most Android devices can only support one offload {@link * supported from API level 29. Most Android devices can only support one offload {@link
* android.media.AudioTrack} at a time and can invalidate it at any time. Thus an app can * android.media.AudioTrack} at a time and can invalidate it at any time. Thus an app can
* never be guaranteed that it will be able to play in offload. Audio processing (for example, * never be guaranteed that it will be able to play in offload. Audio processing (for example,
...@@ -394,12 +423,12 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -394,12 +423,12 @@ public final class DefaultAudioSink implements AudioSink {
AudioProcessorChain audioProcessorChain, AudioProcessorChain audioProcessorChain,
boolean enableFloatOutput, boolean enableFloatOutput,
boolean enableAudioTrackPlaybackParams, boolean enableAudioTrackPlaybackParams,
boolean enableOffload) { @OffloadMode int offloadMode) {
this.audioCapabilities = audioCapabilities; this.audioCapabilities = audioCapabilities;
this.audioProcessorChain = Assertions.checkNotNull(audioProcessorChain); this.audioProcessorChain = Assertions.checkNotNull(audioProcessorChain);
this.enableFloatOutput = Util.SDK_INT >= 21 && enableFloatOutput; this.enableFloatOutput = Util.SDK_INT >= 21 && enableFloatOutput;
this.enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && enableAudioTrackPlaybackParams; this.enableAudioTrackPlaybackParams = Util.SDK_INT >= 23 && enableAudioTrackPlaybackParams;
this.enableOffload = Util.SDK_INT >= 29 && enableOffload; this.offloadMode = Util.SDK_INT >= 29 ? offloadMode : OFFLOAD_MODE_DISABLED;
releasingConditionVariable = new ConditionVariable(true); releasingConditionVariable = new ConditionVariable(true);
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener()); audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor(); channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
...@@ -462,9 +491,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -462,9 +491,7 @@ public final class DefaultAudioSink implements AudioSink {
// guaranteed to support. // guaranteed to support.
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING; return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
} }
if (enableOffload if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) {
&& !offloadDisabledUntilNextConfiguration
&& isOffloadedPlaybackSupported(format, audioAttributes)) {
return SINK_FORMAT_SUPPORTED_DIRECTLY; return SINK_FORMAT_SUPPORTED_DIRECTLY;
} }
if (isPassthroughPlaybackSupported(format, audioCapabilities)) { if (isPassthroughPlaybackSupported(format, audioCapabilities)) {
...@@ -541,7 +568,7 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -541,7 +568,7 @@ public final class DefaultAudioSink implements AudioSink {
availableAudioProcessors = new AudioProcessor[0]; availableAudioProcessors = new AudioProcessor[0];
outputSampleRate = inputFormat.sampleRate; outputSampleRate = inputFormat.sampleRate;
outputPcmFrameSize = C.LENGTH_UNSET; outputPcmFrameSize = C.LENGTH_UNSET;
if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) { if (useOffloadedPlayback(inputFormat, audioAttributes)) {
outputMode = OUTPUT_MODE_OFFLOAD; outputMode = OUTPUT_MODE_OFFLOAD;
outputEncoding = outputEncoding =
MimeTypes.getEncoding( MimeTypes.getEncoding(
...@@ -1561,9 +1588,8 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1561,9 +1588,8 @@ public final class DefaultAudioSink implements AudioSink {
return Util.getAudioTrackChannelConfig(channelCount); return Util.getAudioTrackChannelConfig(channelCount);
} }
private static boolean isOffloadedPlaybackSupported( private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) {
Format format, AudioAttributes audioAttributes) { if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) {
if (Util.SDK_INT < 29) {
return false; return false;
} }
@C.Encoding @C.Encoding
...@@ -1581,8 +1607,12 @@ public final class DefaultAudioSink implements AudioSink { ...@@ -1581,8 +1607,12 @@ public final class DefaultAudioSink implements AudioSink {
audioFormat, audioAttributes.getAudioAttributesV21())) { audioFormat, audioAttributes.getAudioAttributesV21())) {
return false; return false;
} }
boolean notGapless = format.encoderDelay == 0 && format.encoderPadding == 0; boolean isGapless = format.encoderDelay != 0 || format.encoderPadding != 0;
return notGapless || isOffloadedGaplessPlaybackSupported(); boolean offloadRequiresGaplessSupport = offloadMode == OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED;
if (isGapless && offloadRequiresGaplessSupport && !isOffloadedGaplessPlaybackSupported()) {
return false;
}
return true;
} }
private static boolean isOffloadedPlayback(AudioTrack audioTrack) { private static boolean isOffloadedPlayback(AudioTrack audioTrack) {
......
...@@ -66,7 +66,7 @@ public final class DefaultAudioSinkTest { ...@@ -66,7 +66,7 @@ public final class DefaultAudioSinkTest {
new DefaultAudioSink.DefaultAudioProcessorChain(teeAudioProcessor), new DefaultAudioSink.DefaultAudioProcessorChain(teeAudioProcessor),
/* enableFloatOutput= */ false, /* enableFloatOutput= */ false,
/* enableAudioTrackPlaybackParams= */ false, /* enableAudioTrackPlaybackParams= */ false,
/* enableOffload= */ false); DefaultAudioSink.OFFLOAD_MODE_DISABLED);
} }
@Test @Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment