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;
|
package androidx.media3.exoplayer.audio;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
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.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
|
||||||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
@ -30,9 +29,7 @@ import android.media.MediaCrypto;
|
|||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.DoNotInline;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.AuxEffectInfo;
|
import androidx.media3.common.AuxEffectInfo;
|
||||||
import androidx.media3.common.C;
|
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;
|
||||||
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import androidx.media3.exoplayer.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes and renders audio using {@link MediaCodec} and an {@link AudioSink}.
|
* 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 Context context;
|
||||||
private final EventDispatcher eventDispatcher;
|
private final EventDispatcher eventDispatcher;
|
||||||
private final AudioSink audioSink;
|
private final AudioSink audioSink;
|
||||||
private final SpatializationHelper spatializationHelper;
|
|
||||||
|
|
||||||
private int codecMaxInputSize;
|
private int codecMaxInputSize;
|
||||||
private boolean codecNeedsDiscardChannelsWorkaround;
|
private boolean codecNeedsDiscardChannelsWorkaround;
|
||||||
@ -263,7 +257,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
this.audioSink = audioSink;
|
this.audioSink = audioSink;
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
spatializationHelper = new SpatializationHelper(context, audioSink.getAudioAttributes());
|
|
||||||
audioSink.setListener(new AudioSinkListener());
|
audioSink.setListener(new AudioSinkListener());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,11 +414,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
return audioSink.supportsFormat(format);
|
return audioSink.supportsFormat(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldReinitCodec() {
|
|
||||||
return spatializationHelper.shouldReinitCodec();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MediaCodecAdapter.Configuration getMediaCodecConfiguration(
|
protected MediaCodecAdapter.Configuration getMediaCodecConfiguration(
|
||||||
MediaCodecInfo codecInfo,
|
MediaCodecInfo codecInfo,
|
||||||
@ -490,7 +478,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
MediaCodecAdapter.Configuration configuration,
|
MediaCodecAdapter.Configuration configuration,
|
||||||
long initializedTimestampMs,
|
long initializedTimestampMs,
|
||||||
long initializationDurationMs) {
|
long initializationDurationMs) {
|
||||||
spatializationHelper.onCodecInitialized(configuration);
|
|
||||||
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
|
eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,7 +568,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
audioSink.disableTunneling();
|
audioSink.disableTunneling();
|
||||||
}
|
}
|
||||||
audioSink.setPlayerId(getPlayerId());
|
audioSink.setPlayerId(getPlayerId());
|
||||||
spatializationHelper.enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -634,7 +620,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
audioSinkNeedsReset = false;
|
audioSinkNeedsReset = false;
|
||||||
audioSink.reset();
|
audioSink.reset();
|
||||||
}
|
}
|
||||||
spatializationHelper.reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,7 +744,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
case MSG_SET_AUDIO_ATTRIBUTES:
|
case MSG_SET_AUDIO_ATTRIBUTES:
|
||||||
AudioAttributes audioAttributes = (AudioAttributes) message;
|
AudioAttributes audioAttributes = (AudioAttributes) message;
|
||||||
audioSink.setAudioAttributes(audioAttributes);
|
audioSink.setAudioAttributes(audioAttributes);
|
||||||
spatializationHelper.setAudioAttributes(audioSink.getAudioAttributes());
|
|
||||||
break;
|
break;
|
||||||
case MSG_SET_AUX_EFFECT_INFO:
|
case MSG_SET_AUX_EFFECT_INFO:
|
||||||
AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
|
AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
|
||||||
@ -871,7 +855,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
== AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY) {
|
== AudioSink.SINK_FORMAT_SUPPORTED_DIRECTLY) {
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
|
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;
|
return mediaFormat;
|
||||||
}
|
}
|
||||||
@ -956,163 +944,4 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
eventDispatcher.audioSinkError(audioSinkError);
|
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