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 @@
* Report unexpected discontinuities in
`AnalyticsListener.onAudioSinkError`
([#6384](https://github.com/google/ExoPlayer/issues/6384)).
* Allow forcing offload for gapless content even if gapless playback is
not supported.
* Analytics:
* Add `onAudioCodecError` and `onVideoCodecError` to `AnalyticsListener`.
* Downloads and caching:
......
......@@ -669,6 +669,8 @@ public class DefaultRenderersFactory implements RenderersFactory {
new DefaultAudioProcessorChain(),
enableFloatOutput,
enableAudioTrackPlaybackParams,
enableOffload);
enableOffload
? DefaultAudioSink.OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED
: DefaultAudioSink.OFFLOAD_MODE_DISABLED);
}
}
......@@ -678,7 +678,7 @@ public interface ExoPlayer extends Player {
* <li>Audio offload rendering is enabled in {@link
* DefaultRenderersFactory#setEnableAudioOffload} or the equivalent option passed to {@link
* 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,
* MP3 or AAC).
* <li>The {@link AudioSink} is playing with an offload {@link AudioTrack}.
......
......@@ -215,6 +215,35 @@ public final class DefaultAudioSink implements AudioSink {
/** The default skip silence flag. */
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
@Retention(RetentionPolicy.SOURCE)
@IntDef({OUTPUT_MODE_PCM, OUTPUT_MODE_OFFLOAD, OUTPUT_MODE_PASSTHROUGH})
......@@ -281,7 +310,7 @@ public final class DefaultAudioSink implements AudioSink {
private final AudioTrackPositionTracker audioTrackPositionTracker;
private final ArrayDeque<MediaPositionParameters> mediaPositionParametersCheckpoints;
private final boolean enableAudioTrackPlaybackParams;
private final boolean enableOffload;
@OffloadMode private final int offloadMode;
@MonotonicNonNull private StreamEventCallbackV29 offloadStreamEventCallbackV29;
private final PendingExceptionHolder<InitializationException>
initializationExceptionPendingExceptionHolder;
......@@ -364,7 +393,7 @@ public final class DefaultAudioSink implements AudioSink {
new DefaultAudioProcessorChain(audioProcessors),
enableFloatOutput,
/* enableAudioTrackPlaybackParams= */ false,
/* enableOffload= */ false);
OFFLOAD_MODE_DISABLED);
}
/**
......@@ -382,8 +411,8 @@ public final class DefaultAudioSink implements AudioSink {
* use.
* @param enableAudioTrackPlaybackParams Whether to enable setting playback speed using {@link
* android.media.AudioTrack#setPlaybackParams(PlaybackParams)}, if supported.
* @param enableOffload Whether to enable audio offload. If an audio format can be both played
* with offload and encoded audio passthrough, it will be played in offload. Audio offload is
* @param offloadMode Audio offload configuration. If an audio format can be both played with
* 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
* 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,
......@@ -394,12 +423,12 @@ public final class DefaultAudioSink implements AudioSink {
AudioProcessorChain audioProcessorChain,
boolean enableFloatOutput,
boolean enableAudioTrackPlaybackParams,
boolean enableOffload) {
@OffloadMode int offloadMode) {
this.audioCapabilities = audioCapabilities;
this.audioProcessorChain = Assertions.checkNotNull(audioProcessorChain);
this.enableFloatOutput = Util.SDK_INT >= 21 && enableFloatOutput;
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);
audioTrackPositionTracker = new AudioTrackPositionTracker(new PositionTrackerListener());
channelMappingAudioProcessor = new ChannelMappingAudioProcessor();
......@@ -462,9 +491,7 @@ public final class DefaultAudioSink implements AudioSink {
// guaranteed to support.
return SINK_FORMAT_SUPPORTED_WITH_TRANSCODING;
}
if (enableOffload
&& !offloadDisabledUntilNextConfiguration
&& isOffloadedPlaybackSupported(format, audioAttributes)) {
if (!offloadDisabledUntilNextConfiguration && useOffloadedPlayback(format, audioAttributes)) {
return SINK_FORMAT_SUPPORTED_DIRECTLY;
}
if (isPassthroughPlaybackSupported(format, audioCapabilities)) {
......@@ -541,7 +568,7 @@ public final class DefaultAudioSink implements AudioSink {
availableAudioProcessors = new AudioProcessor[0];
outputSampleRate = inputFormat.sampleRate;
outputPcmFrameSize = C.LENGTH_UNSET;
if (enableOffload && isOffloadedPlaybackSupported(inputFormat, audioAttributes)) {
if (useOffloadedPlayback(inputFormat, audioAttributes)) {
outputMode = OUTPUT_MODE_OFFLOAD;
outputEncoding =
MimeTypes.getEncoding(
......@@ -1561,9 +1588,8 @@ public final class DefaultAudioSink implements AudioSink {
return Util.getAudioTrackChannelConfig(channelCount);
}
private static boolean isOffloadedPlaybackSupported(
Format format, AudioAttributes audioAttributes) {
if (Util.SDK_INT < 29) {
private boolean useOffloadedPlayback(Format format, AudioAttributes audioAttributes) {
if (Util.SDK_INT < 29 || offloadMode == OFFLOAD_MODE_DISABLED) {
return false;
}
@C.Encoding
......@@ -1581,8 +1607,12 @@ public final class DefaultAudioSink implements AudioSink {
audioFormat, audioAttributes.getAudioAttributesV21())) {
return false;
}
boolean notGapless = format.encoderDelay == 0 && format.encoderPadding == 0;
return notGapless || isOffloadedGaplessPlaybackSupported();
boolean isGapless = format.encoderDelay != 0 || format.encoderPadding != 0;
boolean offloadRequiresGaplessSupport = offloadMode == OFFLOAD_MODE_ENABLED_GAPLESS_REQUIRED;
if (isGapless && offloadRequiresGaplessSupport && !isOffloadedGaplessPlaybackSupported()) {
return false;
}
return true;
}
private static boolean isOffloadedPlayback(AudioTrack audioTrack) {
......
......@@ -66,7 +66,7 @@ public final class DefaultAudioSinkTest {
new DefaultAudioSink.DefaultAudioProcessorChain(teeAudioProcessor),
/* enableFloatOutput= */ false,
/* enableAudioTrackPlaybackParams= */ false,
/* enableOffload= */ false);
DefaultAudioSink.OFFLOAD_MODE_DISABLED);
}
@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