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
This commit is contained in:
parent
a41bdbad31
commit
258925d5f3
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<Listener, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user