From 258925d5f34af4622cdcc1ed3271352ec0e8e6fe Mon Sep 17 00:00:00 2001 From: christosts Date: Tue, 8 Feb 2022 16:02:04 +0000 Subject: [PATCH] Android 12L: Always set codec max output channels With this change, MediaCodecAudioRenderer always configures MediaCodec with max output channels set to 99 on API 32+. #minor-release PiperOrigin-RevId: 427192801 --- .../audio/MediaCodecAudioRenderer.java | 181 +-------------- .../exoplayer/audio/SpatializerDelegate.java | 212 ------------------ 2 files changed, 5 insertions(+), 388 deletions(-) delete mode 100644 libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/SpatializerDelegate.java 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 6c929efada..a07750ce25 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 @@ -16,7 +16,6 @@ package androidx.media3.exoplayer.audio; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Assertions.checkStateNotNull; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO; import static com.google.common.base.MoreObjects.firstNonNull; @@ -30,9 +29,7 @@ import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Handler; import androidx.annotation.CallSuper; -import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.media3.common.AudioAttributes; import androidx.media3.common.AuxEffectInfo; import androidx.media3.common.C; @@ -63,10 +60,8 @@ import androidx.media3.exoplayer.mediacodec.MediaCodecSelector; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.common.collect.ImmutableList; -import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.util.List; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Decodes and renders audio using {@link MediaCodec} and an {@link AudioSink}. @@ -103,7 +98,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media private final Context context; private final EventDispatcher eventDispatcher; private final AudioSink audioSink; - private final SpatializationHelper spatializationHelper; private int codecMaxInputSize; private boolean codecNeedsDiscardChannelsWorkaround; @@ -263,7 +257,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media this.context = context; this.audioSink = audioSink; eventDispatcher = new EventDispatcher(eventHandler, eventListener); - spatializationHelper = new SpatializationHelper(context, audioSink.getAudioAttributes()); audioSink.setListener(new AudioSinkListener()); } @@ -421,11 +414,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return audioSink.supportsFormat(format); } - @Override - protected boolean shouldReinitCodec() { - return spatializationHelper.shouldReinitCodec(); - } - @Override protected MediaCodecAdapter.Configuration getMediaCodecConfiguration( MediaCodecInfo codecInfo, @@ -490,7 +478,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media MediaCodecAdapter.Configuration configuration, long initializedTimestampMs, long initializationDurationMs) { - spatializationHelper.onCodecInitialized(configuration); eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); } @@ -581,7 +568,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media audioSink.disableTunneling(); } audioSink.setPlayerId(getPlayerId()); - spatializationHelper.enable(); } @Override @@ -634,7 +620,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media audioSinkNeedsReset = false; audioSink.reset(); } - spatializationHelper.reset(); } } @@ -759,7 +744,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media case MSG_SET_AUDIO_ATTRIBUTES: AudioAttributes audioAttributes = (AudioAttributes) message; audioSink.setAudioAttributes(audioAttributes); - spatializationHelper.setAudioAttributes(audioSink.getAudioAttributes()); break; case MSG_SET_AUX_EFFECT_INFO: AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message; @@ -871,7 +855,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media == AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY) { mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT); } - spatializationHelper.configureForSpatialization(mediaFormat, format); + if (Util.SDK_INT >= 32) { + // TODO[b/190759307] Use MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT once the + // compile SDK target is set to 32. + mediaFormat.setInteger("max-output-channel-count", 99); + } return mediaFormat; } @@ -956,163 +944,4 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media eventDispatcher.audioSinkError(audioSinkError); } } - - /** - * A helper class that signals whether the codec needs to be re-initialized because spatialization - * properties changed. - */ - private static final class SpatializationHelper implements SpatializerDelegate.Listener { - // TODO[b/190759307] Remove and use MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT once the - // compile SDK target is set to 32. - private static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count"; - private static final int SPATIALIZATION_CHANNEL_COUNT = 99; - - @Nullable private final SpatializerDelegate spatializerDelegate; - - private @MonotonicNonNull Handler handler; - @Nullable private AudioAttributes audioAttributes; - @Nullable private Format inputFormat; - private boolean codecConfiguredForSpatialization; - private boolean codecNeedsReinit; - private boolean listenerAdded; - - /** Creates a new instance. */ - public SpatializationHelper(Context context, @Nullable AudioAttributes audioAttributes) { - this.spatializerDelegate = maybeCreateSpatializer(context); - this.audioAttributes = audioAttributes; - } - - /** Enables this helper. Call this method when the renderer is enabled. */ - public void enable() { - maybeAddSpatalizationListener(); - } - - /** Resets the helper and releases any resources. Call this method when renderer is reset. */ - public void reset() { - maybeRemoveSpatalizationListener(); - } - - /** Sets the audio attributes set by the player. */ - public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) { - if (Util.areEqual(this.audioAttributes, audioAttributes)) { - return; - } - - this.audioAttributes = audioAttributes; - updateCodecNeedsReinit(); - } - - /** - * Sets keys for audio spatialization on the {@code mediaFormat} if the platform can apply - * spatialization to this {@code format}. - */ - public void configureForSpatialization(MediaFormat mediaFormat, Format format) { - if (canBeSpatialized(format)) { - mediaFormat.setInteger(KEY_MAX_OUTPUT_CHANNEL_COUNT, SPATIALIZATION_CHANNEL_COUNT); - } - } - - /** Informs the helper that a codec was initialized. */ - public void onCodecInitialized(MediaCodecAdapter.Configuration configuration) { - codecNeedsReinit = false; - inputFormat = configuration.format; - codecConfiguredForSpatialization = - configuration.mediaFormat.containsKey(KEY_MAX_OUTPUT_CHANNEL_COUNT) - && configuration.mediaFormat.getInteger(KEY_MAX_OUTPUT_CHANNEL_COUNT) - == SPATIALIZATION_CHANNEL_COUNT; - } - - /** - * Returns whether the codec should be re-initialized, caused by a change in the spatialization - * properties. - */ - public boolean shouldReinitCodec() { - return codecNeedsReinit; - } - - // SpatializerDelegate.Listener - - @Override - public void onSpatializerEnabledChanged(SpatializerDelegate spatializer, boolean enabled) { - updateCodecNeedsReinit(); - } - - @Override - public void onSpatializerAvailableChanged(SpatializerDelegate spatializer, boolean available) { - updateCodecNeedsReinit(); - } - - // Other internal methods - - /** Returns whether this format can be spatialized by the platform. */ - private boolean canBeSpatialized(@Nullable Format format) { - if (Util.SDK_INT < 32 - || format == null - || audioAttributes == null - || spatializerDelegate == null - || spatializerDelegate.getImmersiveAudioLevel() - != SpatializerDelegate.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL - || !spatializerDelegate.isAvailable() - || !spatializerDelegate.isEnabled()) { - return false; - } - AudioFormat.Builder audioFormatBuilder = - new AudioFormat.Builder() - .setEncoding(AudioFormat.ENCODING_PCM_16BIT) - .setChannelMask(Util.getAudioTrackChannelConfig(format.channelCount)); - if (format.sampleRate != Format.NO_VALUE) { - audioFormatBuilder.setSampleRate(format.sampleRate); - } - return spatializerDelegate.canBeSpatialized( - audioAttributes.getAudioAttributesV21(), audioFormatBuilder.build()); - } - - private void maybeAddSpatalizationListener() { - if (!listenerAdded && spatializerDelegate != null && Util.SDK_INT >= 32) { - if (handler == null) { - // Route callbacks to the playback thread. - handler = Util.createHandlerForCurrentLooper(); - } - spatializerDelegate.addOnSpatializerStateChangedListener(handler::post, this); - listenerAdded = true; - } - } - - private void maybeRemoveSpatalizationListener() { - if (listenerAdded && spatializerDelegate != null && Util.SDK_INT >= 32) { - spatializerDelegate.removeOnSpatializerStateChangedListener(this); - checkStateNotNull(handler).removeCallbacksAndMessages(null); - } - } - - private void updateCodecNeedsReinit() { - codecNeedsReinit = codecConfiguredForSpatialization != canBeSpatialized(inputFormat); - } - - @Nullable - private static SpatializerDelegate maybeCreateSpatializer(Context context) { - if (Util.SDK_INT >= 32) { - return Api32.createSpatializer(context); - } - return null; - } - } - - @RequiresApi(32) - private static final class Api32 { - private Api32() {} - - @DoNotInline - @Nullable - public static SpatializerDelegate createSpatializer(Context context) { - try { - return new SpatializerDelegate(context); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) { - // Do nothing for these cases. - } catch (InvocationTargetException e) { - Log.w(TAG, "Failed to load Spatializer with reflection", e); - } - return null; - } - } } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/SpatializerDelegate.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/SpatializerDelegate.java deleted file mode 100644 index 0c1556012c..0000000000 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/SpatializerDelegate.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.media3.exoplayer.audio; - -import static androidx.media3.common.util.Assertions.checkStateNotNull; -import static java.lang.annotation.ElementType.TYPE_USE; - -import android.content.Context; -import android.media.AudioAttributes; -import android.media.AudioFormat; -import android.media.AudioManager; -import androidx.annotation.IntDef; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.media3.common.util.Assertions; -import androidx.media3.common.util.Util; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; - -/** - * Exposes the android.media.Spatializer API via reflection. This is so that we can use the - * Spatializer while the compile SDK target is set to 31. - */ -@RequiresApi(31) -/* package */ final class SpatializerDelegate { - /** Level of support for audio spatialization. */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL, - SPATIALIZER_IMMERSIVE_LEVEL_NONE, - SPATIALIZER_IMMERSIVE_LEVEL_OTHER - }) - @interface ImmersiveAudioLevel {} - - /** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL */ - public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; - /** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_NONE */ - public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; - /** See Spatializer#SPATIALIZER_IMMERSIVE_LEVEL_OTHER */ - public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; - - /** Wrapper for Spatializer.OnSpatializerStateChangedListener */ - public interface Listener { - /** See Spatializer.OnSpatializerStateChangedListener.onSpatializerEnabledChanged */ - void onSpatializerEnabledChanged(SpatializerDelegate spatializer, boolean enabled); - - /** See Spatializer.OnSpatializerStateChangedListener.onSpatializerAvailableChanged */ - void onSpatializerAvailableChanged(SpatializerDelegate spatializer, boolean available); - } - - private final Object spatializer; - private final Class spatializerClass; - private final Class spatializerListenerClass; - private final Method isEnabled; - private final Method isAvailable; - private final Method getImmersiveAudioLevel; - private final Method canBeSpatialized; - private final Method addListener; - private final Method removeListener; - private final Map listeners; - - /** Creates an instance. */ - public SpatializerDelegate(Context context) - throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, - IllegalAccessException { - Method getSpatializerMethod = AudioManager.class.getMethod("getSpatializer"); - AudioManager manager = - Assertions.checkNotNull( - (AudioManager) context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)); - spatializer = checkStateNotNull(getSpatializerMethod.invoke(manager)); - spatializerClass = Class.forName("android.media.Spatializer"); - spatializerListenerClass = - Class.forName("android.media.Spatializer$OnSpatializerStateChangedListener"); - isEnabled = spatializerClass.getMethod("isEnabled"); - isAvailable = spatializerClass.getMethod("isAvailable"); - getImmersiveAudioLevel = spatializerClass.getMethod("getImmersiveAudioLevel"); - canBeSpatialized = - spatializerClass.getMethod( - "canBeSpatialized", android.media.AudioAttributes.class, AudioFormat.class); - addListener = - spatializerClass.getMethod( - "addOnSpatializerStateChangedListener", Executor.class, spatializerListenerClass); - removeListener = - spatializerClass.getMethod( - "removeOnSpatializerStateChangedListener", spatializerListenerClass); - listeners = new HashMap<>(); - } - - /** Delegates to Spatializer.isEnabled() */ - public boolean isEnabled() { - try { - return (boolean) Util.castNonNull(isEnabled.invoke(spatializer)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - /** Delegates to Spatializer.isAvailable() */ - public boolean isAvailable() { - try { - return (boolean) Util.castNonNull(isAvailable.invoke(spatializer)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - /** Delegates to Spatializer.getImmersiveAudioLevel() */ - public @ImmersiveAudioLevel int getImmersiveAudioLevel() { - try { - return (int) Util.castNonNull(getImmersiveAudioLevel.invoke(spatializer)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - /** Delegates to Spatializer.canBeSpatialized() */ - public boolean canBeSpatialized(AudioAttributes attributes, AudioFormat format) { - try { - return (boolean) Util.castNonNull(canBeSpatialized.invoke(spatializer, attributes, format)); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - /** Delegates to Spatializer.addOnSpatializerStateChangedListener() */ - public void addOnSpatializerStateChangedListener(Executor executor, Listener listener) { - if (listeners.containsKey(listener)) { - return; - } - Object listenerProxy = createSpatializerListenerProxy(listener); - try { - addListener.invoke(spatializer, executor, listenerProxy); - listeners.put(listener, listenerProxy); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - /** Delegates to Spatializer.removeOnSpatializerStateChangedListener() */ - public void removeOnSpatializerStateChangedListener(Listener listener) { - @Nullable Object proxy = listeners.get(listener); - if (proxy == null) { - return; - } - try { - removeListener.invoke(spatializer, proxy); - listeners.remove(listener); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - - private Object createSpatializerListenerProxy(Listener listener) { - return Proxy.newProxyInstance( - spatializerListenerClass.getClassLoader(), - new Class[] {spatializerListenerClass}, - new ProxySpatializerListener(this, listener)); - } - - /** Proxy-based implementation of Spatializer.OnSpatializerStateChangedListener. */ - private static final class ProxySpatializerListener implements InvocationHandler { - private final SpatializerDelegate spatializerDelegate; - private final Listener listener; - - private ProxySpatializerListener(SpatializerDelegate spatializerDelegate, Listener listener) { - this.spatializerDelegate = spatializerDelegate; - this.listener = listener; - } - - @Override - public Object invoke(Object o, Method method, Object[] objects) { - String methodName = method.getName(); - Class[] parameterTypes = method.getParameterTypes(); - if (methodName.equals("onSpatializerAvailableChanged") - && parameterTypes.length == 2 - && spatializerDelegate.spatializerClass.isAssignableFrom(parameterTypes[0]) - && parameterTypes[1].equals(Boolean.TYPE)) { - listener.onSpatializerAvailableChanged(spatializerDelegate, (boolean) objects[1]); - } else if (methodName.equals("onSpatializerEnabledChanged") - && parameterTypes.length == 2 - && spatializerDelegate.spatializerClass.isAssignableFrom(parameterTypes[0]) - && parameterTypes[1].equals(Boolean.TYPE)) { - listener.onSpatializerEnabledChanged(spatializerDelegate, (boolean) objects[1]); - } - return this; - } - } -}