From 66550463460b4413bd03497c506e5bd428905aa3 Mon Sep 17 00:00:00 2001 From: Sampath Shetty Date: Thu, 22 Aug 2024 04:40:21 +1000 Subject: [PATCH] Add Player.setAudioPresentation API * Set an available AudioPresentation via Player interface on an AudioSink that plays audio data using AudioTrack. * Verify AudioManager.getParameters() using the key "isAc4PresentationSelectionByIndexSupported" before setting the audio presentation. Additionally, extend the Player command by incorporating Player.COMMAND_SET_AUDIO_PRESENTATION, allowing the app to check for this capability before configuring audio presentations Change-Id: I8a37e3dc66878934b67c24e137cba8c9db7c79ac --- .../java/androidx/media3/common/Player.java | 26 ++++++++++ .../media3/exoplayer/ExoPlayerImpl.java | 9 ++++ .../androidx/media3/exoplayer/Renderer.java | 16 +++++-- .../exoplayer/RendererCapabilities.java | 22 +++++---- .../exoplayer/audio/AudioOffloadSupport.java | 29 ++++++++++-- .../media3/exoplayer/audio/AudioSink.java | 10 ++++ .../DefaultAudioOffloadSupportProvider.java | 47 +++++++++++++++++-- .../exoplayer/audio/DefaultAudioSink.java | 18 +++++++ .../audio/MediaCodecAudioRenderer.java | 9 ++++ .../trackselection/MappingTrackSelector.java | 16 +++++++ .../trackselection/TrackSelector.java | 10 ++++ .../audio/AudioOffloadSupportTest.java | 3 +- 12 files changed, 195 insertions(+), 20 deletions(-) diff --git a/libraries/common/src/main/java/androidx/media3/common/Player.java b/libraries/common/src/main/java/androidx/media3/common/Player.java index f54dc86ca90..a0334b6513e 100644 --- a/libraries/common/src/main/java/androidx/media3/common/Player.java +++ b/libraries/common/src/main/java/androidx/media3/common/Player.java @@ -21,6 +21,7 @@ import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.ElementType.TYPE_USE; +import android.media.AudioPresentation; import android.os.Bundle; import android.os.Looper; import android.view.Surface; @@ -554,6 +555,7 @@ public static final class Builder { COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, COMMAND_SET_AUDIO_ATTRIBUTES, + COMMAND_SET_AUDIO_PRESENTATION, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_SET_TRACK_SELECTION_PARAMETERS, @@ -1685,6 +1687,7 @@ default void onMetadata(Metadata metadata) {} *
  • {@link #COMMAND_ADJUST_DEVICE_VOLUME} *
  • {@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} *
  • {@link #COMMAND_SET_AUDIO_ATTRIBUTES} + *
  • {@link #COMMAND_SET_AUDIO_PRESENTATION} *
  • {@link #COMMAND_SET_VIDEO_SURFACE} *
  • {@link #COMMAND_GET_TEXT} *
  • {@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS} @@ -1732,6 +1735,7 @@ default void onMetadata(Metadata metadata) {} COMMAND_ADJUST_DEVICE_VOLUME, COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, COMMAND_SET_AUDIO_ATTRIBUTES, + COMMAND_SET_AUDIO_PRESENTATION, COMMAND_SET_VIDEO_SURFACE, COMMAND_GET_TEXT, COMMAND_SET_TRACK_SELECTION_PARAMETERS, @@ -2092,6 +2096,14 @@ default void onMetadata(Metadata metadata) {} */ int COMMAND_SET_AUDIO_ATTRIBUTES = 35; + /** + * Command to set the player's audio presentation. + * + *

    The {@link #setAudioPresentation(AudioPresentation presentation)} method must only + * be called if this command is {@linkplain #isCommandAvailable(int) available}. + */ + int COMMAND_SET_AUDIO_PRESENTATION = 36; + /** * Command to set and clear the surface on which to render the video. * @@ -3508,4 +3520,18 @@ default void onMetadata(Metadata metadata) {} * @param handleAudioFocus True if the player should handle audio focus, false otherwise. */ void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus); + + /** + * Sets the {@link AudioPresentation} to be selected in the audio renderer on an active audio + * track. This audio presentation will be decoded by the supported audio decoders. + * + *

    This method is supported only for direct/offload playback modes and on devices running a + * build platform API version 29 onwards. + * + *

    This method must only be called if {@link #COMMAND_SET_AUDIO_PRESENTATION} is {@linkplain + * #getAvailableCommands() available}. + * + * @param presentation The audio presentation to select. + */ + default void setAudioPresentation(AudioPresentation presentation) {} } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java index fe2a62b2f0f..6f71744bace 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImpl.java @@ -26,6 +26,7 @@ import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_ATTRIBUTES; +import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_PRESENTATION; import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_SESSION_ID; import static androidx.media3.exoplayer.Renderer.MSG_SET_AUX_EFFECT_INFO; import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; @@ -45,6 +46,7 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.AudioDeviceInfo; +import android.media.AudioPresentation; import android.media.MediaFormat; import android.os.Handler; import android.os.Looper; @@ -53,6 +55,7 @@ import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.TextureView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.media3.common.AudioAttributes; @@ -1944,6 +1947,12 @@ public void setImageOutput(@Nullable ImageOutput imageOutput) { sendRendererMessage(TRACK_TYPE_IMAGE, MSG_SET_IMAGE_OUTPUT, imageOutput); } + @Override + public void setAudioPresentation(AudioPresentation audioPresentation) { + verifyApplicationThread(); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_PRESENTATION, audioPresentation); + } + @SuppressWarnings("deprecation") // Calling deprecated methods. /* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) { this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java index af18ed5c5fa..81847e3a8ea 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/Renderer.java @@ -17,6 +17,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; +import android.media.AudioPresentation; import android.media.MediaCodec; import android.view.Surface; import androidx.annotation.IntDef; @@ -186,8 +187,9 @@ interface WakeupListener { * #MSG_SET_AUX_EFFECT_INFO}, {@link #MSG_SET_VIDEO_FRAME_METADATA_LISTENER}, {@link * #MSG_SET_CAMERA_MOTION_LISTENER}, {@link #MSG_SET_SKIP_SILENCE_ENABLED}, {@link * #MSG_SET_AUDIO_SESSION_ID}, {@link #MSG_SET_WAKEUP_LISTENER}, {@link #MSG_SET_VIDEO_EFFECTS}, - * {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION} or {@link #MSG_SET_IMAGE_OUTPUT}. May also be an - * app-defined value (see {@link #MSG_CUSTOM_BASE}). + * {@link #MSG_SET_VIDEO_OUTPUT_RESOLUTION}, {@link #MSG_SET_IMAGE_OUTPUT} or + * {@link #MSG_SET_AUDIO_PRESENTATION}. May also be an app-defined value + * (see {@link #MSG_CUSTOM_BASE}). */ @Documented @Retention(RetentionPolicy.SOURCE) @@ -211,7 +213,8 @@ interface WakeupListener { MSG_SET_IMAGE_OUTPUT, MSG_SET_PRIORITY, MSG_TRANSFER_RESOURCES, - MSG_SET_SCRUBBING_MODE + MSG_SET_SCRUBBING_MODE, + MSG_SET_AUDIO_PRESENTATION }) public @interface MessageType {} @@ -363,6 +366,13 @@ interface WakeupListener { */ int MSG_SET_SCRUBBING_MODE = 18; + /** + * The type of message that can be passed to an audio renderer to set a desired audio + * presentation. The message object should be an {@link AudioPresentation} instance that will + * configure the underlying audio track. + */ + int MSG_SET_AUDIO_PRESENTATION = 19; + /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * renderers. These custom constants must be greater than or equal to this value. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java index d144a3d3e99..a2a848bcaa1 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/RendererCapabilities.java @@ -145,10 +145,11 @@ interface Listener { /** * Level of renderer support for audio offload. * - *

    Speed change and gapless transition support with audio offload is represented by the bit - * mask flags {@link #AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED} and {@link - * #AUDIO_OFFLOAD_GAPLESS_SUPPORTED} respectively. If neither feature is supported then the value - * will be either {@link #AUDIO_OFFLOAD_SUPPORTED} or {@link #AUDIO_OFFLOAD_NOT_SUPPORTED}. + *

    Set presentation, speed change and gapless transition support with audio offload is + * represented by the bit mask flags {@link #AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED}, + * {@link #AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED} and {@link #AUDIO_OFFLOAD_GAPLESS_SUPPORTED} + * respectively. If neither feature is supported then the value will be either + * {@link #AUDIO_OFFLOAD_SUPPORTED} or {@link #AUDIO_OFFLOAD_NOT_SUPPORTED}. * *

    For non-audio renderers, the level of support is always {@link * #AUDIO_OFFLOAD_NOT_SUPPORTED}. @@ -157,6 +158,7 @@ interface Listener { @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({ + AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED, AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED, AUDIO_OFFLOAD_GAPLESS_SUPPORTED, AUDIO_OFFLOAD_SUPPORTED, @@ -165,7 +167,10 @@ interface Listener { @interface AudioOffloadSupport {} /** A mask to apply to {@link Capabilities} to obtain {@link AudioOffloadSupport} only. */ - int AUDIO_OFFLOAD_SUPPORT_MASK = 0b111 << 9; + int AUDIO_OFFLOAD_SUPPORT_MASK = 0b1111 << 9; + + /** The renderer supports audio offload and set presentation with this format. */ + int AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED = 0b1000 << 9; /** The renderer supports audio offload and speed changes with this format. */ int AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED = 0b100 << 9; @@ -215,9 +220,10 @@ interface Listener { *

  • {@link AudioOffloadSupport}: The level of offload support. Value will have the flag * {@link #AUDIO_OFFLOAD_SUPPORTED} or be {@link #AUDIO_OFFLOAD_NOT_SUPPORTED}. In addition, * if it is {@link #AUDIO_OFFLOAD_SUPPORTED}, then one can check for {@link - * #AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED} and {@link #AUDIO_OFFLOAD_GAPLESS_SUPPORTED}. - * These represent speed change and gapless transition support with audio offload - * respectively. + * #AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED}, {@link #AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED} + * and {@link #AUDIO_OFFLOAD_GAPLESS_SUPPORTED}. + * These represent set presentation, speed change and gapless transition support with audio + * offload respectively. * */ @Documented diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioOffloadSupport.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioOffloadSupport.java index 91ffc6c8a7d..165202cc898 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioOffloadSupport.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioOffloadSupport.java @@ -38,12 +38,16 @@ public static final class Builder { /** Whether playback of the format is supported with speed changes. */ private boolean isSpeedChangeSupported; + /** Whether playback of the format is supported with audio presentation selection. */ + private boolean isSetPresentationSupported; + public Builder() {} public Builder(AudioOffloadSupport audioOffloadSupport) { isFormatSupported = audioOffloadSupport.isFormatSupported; isGaplessSupported = audioOffloadSupport.isGaplessSupported; isSpeedChangeSupported = audioOffloadSupport.isSpeedChangeSupported; + isSetPresentationSupported = audioOffloadSupport.isSetPresentationSupported; } /** @@ -79,6 +83,17 @@ public Builder setIsSpeedChangeSupported(boolean isSpeedChangeSupported) { return this; } + /** + * Sets whether the format allows selection of different audio presentations during playback. + * + *

    Default is {@code false}. + */ + @CanIgnoreReturnValue + public Builder setIsPresentationSelectionSupported(boolean isSetPresentationSupported) { + this.isSetPresentationSupported = isSetPresentationSupported; + return this; + } + /** * Builds the {@link AudioOffloadSupport}. * @@ -103,10 +118,14 @@ public AudioOffloadSupport build() { /** Whether playback of the format is supported with speed changes. */ public final boolean isSpeedChangeSupported; + /** Whether the format allows selection of different audio presentations during playback. */ + public final boolean isSetPresentationSupported; + private AudioOffloadSupport(AudioOffloadSupport.Builder builder) { this.isFormatSupported = builder.isFormatSupported; this.isGaplessSupported = builder.isGaplessSupported; this.isSpeedChangeSupported = builder.isSpeedChangeSupported; + this.isSetPresentationSupported = builder.isSetPresentationSupported; } /** Creates a new {@link Builder}, copying the initial values from this instance. */ @@ -125,14 +144,16 @@ public boolean equals(@Nullable Object obj) { AudioOffloadSupport other = (AudioOffloadSupport) obj; return isFormatSupported == other.isFormatSupported && isGaplessSupported == other.isGaplessSupported - && isSpeedChangeSupported == other.isSpeedChangeSupported; + && isSpeedChangeSupported == other.isSpeedChangeSupported + && isSetPresentationSupported == other.isSetPresentationSupported; } @Override public int hashCode() { - int hashCode = (isFormatSupported ? 1 : 0) << 2; - hashCode += (isGaplessSupported ? 1 : 0) << 1; - hashCode += (isSpeedChangeSupported ? 1 : 0); + int hashCode = (isFormatSupported ? 1 : 0) << 3; + hashCode += (isGaplessSupported ? 1 : 0) << 2; + hashCode += (isSpeedChangeSupported ? 1 : 0) << 1; + hashCode += (isSetPresentationSupported ? 1 : 0); return hashCode; } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java index 27ba59986c8..707950ad382 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioSink.java @@ -18,6 +18,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.media.AudioDeviceInfo; +import android.media.AudioPresentation; import android.media.AudioTrack; import androidx.annotation.IntDef; import androidx.annotation.Nullable; @@ -643,6 +644,15 @@ default void setOffloadMode(@OffloadMode int offloadMode) {} @RequiresApi(29) default void setOffloadDelayPadding(int delayInFrames, int paddingInFrames) {} + /** + * Sets the presentation of an audio program, delivered by Next Generation Audio streams. + * Also requires platform API version 29 onwards. + * + * @param presentation The audio presentation to set. + */ + @RequiresApi(29) + default void setPresentation(AudioPresentation presentation) {} + /** * Sets the playback volume. * diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java index 442688c0f63..4f2eeb48a55 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioOffloadSupportProvider.java @@ -43,6 +43,13 @@ public final class DefaultAudioOffloadSupportProvider /** AudioManager parameters key for retrieving support of variable speeds during offload. */ private static final String OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY = "offloadVariableRateSupported"; + /** + * AudioManager parameters key for retrieving support of setting audio presentation during + * offload. + */ + private static final String OFFLOAD_AC4_SET_PRESENTATION_SUPPORTED_KEY + = "isAc4PresentationSelectionByIndexSupported"; + @Nullable private final Context context; /** @@ -51,6 +58,12 @@ public final class DefaultAudioOffloadSupportProvider */ private @MonotonicNonNull Boolean isOffloadVariableRateSupported; + /** + * Whether setting of audio presentations is supported during offload. If {@code null} then it has + * not been attempted to retrieve value from {@link AudioManager}. + */ + private @MonotonicNonNull Boolean isOffloadSetPresentationSupported; + /** Creates an instance. */ public DefaultAudioOffloadSupportProvider() { this(/* context= */ null); @@ -80,6 +93,7 @@ public AudioOffloadSupport getAudioOffloadSupport( // the constructor so that the platform will be queried from the playback thread. boolean isOffloadVariableRateSupported = isOffloadVariableRateSupported(context); + boolean isOffloadSetPresentationSupported = isOffloadSetPresentationSupported(context); @C.Encoding int encoding = MimeTypes.getEncoding(checkNotNull(format.sampleMimeType), format.codecs); if (encoding == C.ENCODING_INVALID @@ -104,12 +118,14 @@ public AudioOffloadSupport getAudioOffloadSupport( return Api31.getOffloadedPlaybackSupport( audioFormat, audioAttributes.getAudioAttributesV21().audioAttributes, - isOffloadVariableRateSupported); + isOffloadVariableRateSupported, + isOffloadSetPresentationSupported); } return Api29.getOffloadedPlaybackSupport( audioFormat, audioAttributes.getAudioAttributesV21().audioAttributes, - isOffloadVariableRateSupported); + isOffloadVariableRateSupported, + isOffloadSetPresentationSupported); } private boolean isOffloadVariableRateSupported(@Nullable Context context) { @@ -131,6 +147,25 @@ private boolean isOffloadVariableRateSupported(@Nullable Context context) { return isOffloadVariableRateSupported; } + private boolean isOffloadSetPresentationSupported(@Nullable Context context) { + if (isOffloadSetPresentationSupported != null) { + return isOffloadSetPresentationSupported; + } + + if (context != null) { + AudioManager audioManager = AudioManagerCompat.getAudioManager(context); + final String offloadSetPresentationSupportedKeyValue = + audioManager.getParameters(/* keys= */ OFFLOAD_AC4_SET_PRESENTATION_SUPPORTED_KEY); + isOffloadSetPresentationSupported = + offloadSetPresentationSupportedKeyValue != null + && offloadSetPresentationSupportedKeyValue.equals( + OFFLOAD_AC4_SET_PRESENTATION_SUPPORTED_KEY + "=1"); + } else { + isOffloadSetPresentationSupported = false; + } + return isOffloadSetPresentationSupported; + } + @RequiresApi(29) private static final class Api29 { private Api29() {} @@ -138,13 +173,15 @@ private Api29() {} public static AudioOffloadSupport getOffloadedPlaybackSupport( AudioFormat audioFormat, android.media.AudioAttributes audioAttributes, - boolean isOffloadVariableRateSupported) { + boolean isOffloadVariableRateSupported, + boolean isOffloadSetPresentationSupported) { if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, audioAttributes)) { return AudioOffloadSupport.DEFAULT_UNSUPPORTED; } return new AudioOffloadSupport.Builder() .setIsFormatSupported(true) .setIsSpeedChangeSupported(isOffloadVariableRateSupported) + .setIsPresentationSelectionSupported(isOffloadSetPresentationSupported) .build(); } } @@ -156,7 +193,8 @@ private Api31() {} public static AudioOffloadSupport getOffloadedPlaybackSupport( AudioFormat audioFormat, android.media.AudioAttributes audioAttributes, - boolean isOffloadVariableRateSupported) { + boolean isOffloadVariableRateSupported, + boolean isOffloadSetPresentationSupported) { int playbackOffloadSupport = AudioManager.getPlaybackOffloadSupport(audioFormat, audioAttributes); if (playbackOffloadSupport == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED) { @@ -171,6 +209,7 @@ public static AudioOffloadSupport getOffloadedPlaybackSupport( .setIsFormatSupported(true) .setIsGaplessSupported(isGaplessSupported) .setIsSpeedChangeSupported(isOffloadVariableRateSupported) + .setIsPresentationSelectionSupported(isOffloadSetPresentationSupported) .build(); } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java index f355b50af7d..0613a51916f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/DefaultAudioSink.java @@ -30,6 +30,7 @@ import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; +import android.media.AudioPresentation; import android.media.AudioRouting; import android.media.AudioRouting.OnRoutingChangedListener; import android.media.AudioTrack; @@ -1575,6 +1576,23 @@ && isOffloadedPlayback(audioTrack) } } + @RequiresApi(29) + @Override + public void setPresentation(AudioPresentation presentation) { + if (audioTrack != null) { + try { + int status = audioTrack.setPresentation(presentation); + if (status != AudioTrack.SUCCESS) { + Log.e(TAG, "Failed to set audio presentation: " + status); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "audioTrack.setPresentation is not supported: ", e); + } catch (IllegalStateException e) { + Log.e(TAG, "Error applying audioTrack.setPresentation", e); + } + } + } + @Override public void setVolume(float volume) { if (this.volume != volume) { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java index dbba20ddcd5..6b040494106 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java @@ -27,6 +27,7 @@ import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioFormat; +import android.media.AudioPresentation; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; @@ -407,6 +408,9 @@ public String getName() { if (audioSinkOffloadSupport.isSpeedChangeSupported) { audioOffloadSupport |= AUDIO_OFFLOAD_SPEED_CHANGE_SUPPORTED; } + if (audioSinkOffloadSupport.isSetPresentationSupported) { + audioOffloadSupport |= AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED; + } return audioOffloadSupport; } @@ -909,6 +913,11 @@ public void handleMessage(@MessageType int messageType, @Nullable Object message case MSG_SET_SKIP_SILENCE_ENABLED: audioSink.setSkipSilenceEnabled((Boolean) checkNotNull(message)); break; + case MSG_SET_AUDIO_PRESENTATION: + if (SDK_INT >= 29) { + audioSink.setPresentation((AudioPresentation) checkNotNull(message)); + } + break; case MSG_SET_AUDIO_SESSION_ID: setAudioSessionId((int) checkNotNull(message)); break; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/MappingTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/MappingTrackSelector.java index 5335a3e8f86..3478ecb57e0 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/MappingTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/MappingTrackSelector.java @@ -332,6 +332,8 @@ public TrackGroupArray getUnmappedTrackGroups() { @Nullable private MappedTrackInfo currentMappedTrackInfo; + private boolean setAudioPresentationSupported; + /** * Returns the mapping information for the currently active track selection, or null if no * selection is currently active. @@ -391,6 +393,15 @@ public final TrackSelectorResult selectTracks( rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group; rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport; rendererTrackGroupCounts[rendererIndex]++; + if (group.type == C.TRACK_TYPE_AUDIO) { + for (int formatSupport : rendererFormatSupport) { + if ((formatSupport & + RendererCapabilities.AUDIO_OFFLOAD_SET_PRESENTATION_SUPPORTED) != 0) { + setAudioPresentationSupported = true; + break; + } + } + } } // Create a track group array for each renderer, and trim each rendererFormatSupports entry. @@ -438,6 +449,11 @@ public final TrackSelectorResult selectTracks( return new TrackSelectorResult(result.first, result.second, tracks, mappedTrackInfo); } + @Override + public boolean isSetAudioPresentationSupported() { + return setAudioPresentationSupported; + } + /** * Given mapped track information, returns a track selection and configuration for each renderer. * diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java index 94201f0fc44..87867eae896 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/TrackSelector.java @@ -209,6 +209,16 @@ public boolean isSetParametersSupported() { return false; } + /** + * Returns if this {@code TrackSelector} supports {@link + * #setAudioPresentation(AudioPresentation)}. + * + *

    The same value is always returned for a given {@code TrackSelector} instance. + */ + public boolean isSetAudioPresentationSupported() { + return false; + } + /** Called by the player to set the {@link AudioAttributes} that will be used for playback. */ public void setAudioAttributes(AudioAttributes audioAttributes) { // Default implementation is no-op. diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/AudioOffloadSupportTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/AudioOffloadSupportTest.java index 9e344f32186..f45c4de198a 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/AudioOffloadSupportTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/audio/AudioOffloadSupportTest.java @@ -42,9 +42,10 @@ public void hashCode_withAllFlagsTrue_reportedExpectedValue() { .setIsFormatSupported(true) .setIsGaplessSupported(true) .setIsSpeedChangeSupported(true) + .setIsPresentationSelectionSupported(true) .build(); - assertThat(audioOffloadSupport.hashCode()).isEqualTo(7); + assertThat(audioOffloadSupport.hashCode()).isEqualTo(15); } @Test