1572 lines
57 KiB
Java
1572 lines
57 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2010 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 android.media.audiofx;
|
||
|
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.RequiresPermission;
|
||
|
import android.annotation.SdkConstant;
|
||
|
import android.annotation.SdkConstant.SdkConstantType;
|
||
|
import android.annotation.SystemApi;
|
||
|
import android.annotation.TestApi;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.content.AttributionSource;
|
||
|
import android.content.AttributionSource.ScopedParcelState;
|
||
|
import android.media.AudioDeviceAttributes;
|
||
|
import android.media.AudioDeviceInfo;
|
||
|
import android.media.AudioSystem;
|
||
|
import android.os.Build;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Looper;
|
||
|
import android.os.Message;
|
||
|
import android.os.Parcel;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.nio.ByteOrder;
|
||
|
import java.util.Objects;
|
||
|
import java.util.UUID;
|
||
|
|
||
|
/**
|
||
|
* AudioEffect is the base class for controlling audio effects provided by the android audio
|
||
|
* framework.
|
||
|
* <p>Applications should not use the AudioEffect class directly but one of its derived classes to
|
||
|
* control specific effects:
|
||
|
* <ul>
|
||
|
* <li> {@link android.media.audiofx.Equalizer}</li>
|
||
|
* <li> {@link android.media.audiofx.Virtualizer}</li>
|
||
|
* <li> {@link android.media.audiofx.BassBoost}</li>
|
||
|
* <li> {@link android.media.audiofx.PresetReverb}</li>
|
||
|
* <li> {@link android.media.audiofx.EnvironmentalReverb}</li>
|
||
|
* <li> {@link android.media.audiofx.DynamicsProcessing}</li>
|
||
|
* <li> {@link android.media.audiofx.HapticGenerator}</li>
|
||
|
* </ul>
|
||
|
* <p>To apply the audio effect to a specific AudioTrack or MediaPlayer instance,
|
||
|
* the application must specify the audio session ID of that instance when creating the AudioEffect.
|
||
|
* (see {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions).
|
||
|
* <p>NOTE: attaching insert effects (equalizer, bass boost, virtualizer) to the global audio output
|
||
|
* mix by use of session 0 is deprecated.
|
||
|
* <p>Creating an AudioEffect object will create the corresponding effect engine in the audio
|
||
|
* framework if no instance of the same effect type exists in the specified audio session.
|
||
|
* If one exists, this instance will be used.
|
||
|
* <p>The application creating the AudioEffect object (or a derived class) will either receive
|
||
|
* control of the effect engine or not depending on the priority parameter. If priority is higher
|
||
|
* than the priority used by the current effect engine owner, the control will be transfered to the
|
||
|
* new object. Otherwise control will remain with the previous object. In this case, the new
|
||
|
* application will be notified of changes in effect engine state or control ownership by the
|
||
|
* appropriate listener.
|
||
|
*/
|
||
|
|
||
|
public class AudioEffect {
|
||
|
static {
|
||
|
System.loadLibrary("audioeffect_jni");
|
||
|
native_init();
|
||
|
}
|
||
|
|
||
|
private final static String TAG = "AudioEffect-JAVA";
|
||
|
|
||
|
// effect type UUIDs are taken from hardware/libhardware/include/hardware/audio_effect.h
|
||
|
|
||
|
/**
|
||
|
* The following UUIDs define effect types corresponding to standard audio
|
||
|
* effects whose implementation and interface conform to the OpenSL ES
|
||
|
* specification. The definitions match the corresponding interface IDs in
|
||
|
* OpenSLES_IID.h
|
||
|
*/
|
||
|
/**
|
||
|
* UUID for environmental reverberation effect
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_ENV_REVERB = UUID
|
||
|
.fromString("c2e5d5f0-94bd-4763-9cac-4e234d06839e");
|
||
|
/**
|
||
|
* UUID for preset reverberation effect
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_PRESET_REVERB = UUID
|
||
|
.fromString("47382d60-ddd8-11db-bf3a-0002a5d5c51b");
|
||
|
/**
|
||
|
* UUID for equalizer effect
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_EQUALIZER = UUID
|
||
|
.fromString("0bed4300-ddd6-11db-8f34-0002a5d5c51b");
|
||
|
/**
|
||
|
* UUID for bass boost effect
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_BASS_BOOST = UUID
|
||
|
.fromString("0634f220-ddd4-11db-a0fc-0002a5d5c51b");
|
||
|
/**
|
||
|
* UUID for virtualizer effect
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_VIRTUALIZER = UUID
|
||
|
.fromString("37cc2c00-dddd-11db-8577-0002a5d5c51b");
|
||
|
|
||
|
/**
|
||
|
* UUIDs for effect types not covered by OpenSL ES.
|
||
|
*/
|
||
|
/**
|
||
|
* UUID for Automatic Gain Control (AGC)
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_AGC = UUID
|
||
|
.fromString("0a8abfe0-654c-11e0-ba26-0002a5d5c51b");
|
||
|
|
||
|
/**
|
||
|
* UUID for Acoustic Echo Canceler (AEC)
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_AEC = UUID
|
||
|
.fromString("7b491460-8d4d-11e0-bd61-0002a5d5c51b");
|
||
|
|
||
|
/**
|
||
|
* UUID for Noise Suppressor (NS)
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_NS = UUID
|
||
|
.fromString("58b4b260-8e06-11e0-aa8e-0002a5d5c51b");
|
||
|
|
||
|
/**
|
||
|
* UUID for Loudness Enhancer
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_LOUDNESS_ENHANCER = UUID
|
||
|
.fromString("fe3199be-aed0-413f-87bb-11260eb63cf1");
|
||
|
|
||
|
/**
|
||
|
* UUID for Dynamics Processing
|
||
|
*/
|
||
|
public static final UUID EFFECT_TYPE_DYNAMICS_PROCESSING = UUID
|
||
|
.fromString("7261676f-6d75-7369-6364-28e2fd3ac39e");
|
||
|
|
||
|
/**
|
||
|
* UUID for Haptic Generator.
|
||
|
*/
|
||
|
// This is taken from system/media/audio/include/system/audio_effects/effect_hapticgenerator.h
|
||
|
@NonNull
|
||
|
public static final UUID EFFECT_TYPE_HAPTIC_GENERATOR = UUID
|
||
|
.fromString("1411e6d6-aecd-4021-a1cf-a6aceb0d71e5");
|
||
|
|
||
|
/**
|
||
|
* Null effect UUID. See {@link AudioEffect(UUID, UUID, int, int)} for use.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static final UUID EFFECT_TYPE_NULL = UUID
|
||
|
.fromString("ec7178ec-e5e1-4432-a3f4-4657e6795210");
|
||
|
|
||
|
/**
|
||
|
* State of an AudioEffect object that was not successfully initialized upon
|
||
|
* creation
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final int STATE_UNINITIALIZED = 0;
|
||
|
/**
|
||
|
* State of an AudioEffect object that is ready to be used.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final int STATE_INITIALIZED = 1;
|
||
|
|
||
|
// to keep in sync with
|
||
|
// frameworks/base/include/media/AudioEffect.h
|
||
|
/**
|
||
|
* Event id for engine control ownership change notification.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final int NATIVE_EVENT_CONTROL_STATUS = 0;
|
||
|
/**
|
||
|
* Event id for engine state change notification.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final int NATIVE_EVENT_ENABLED_STATUS = 1;
|
||
|
/**
|
||
|
* Event id for engine parameter change notification.
|
||
|
* @hide
|
||
|
*/
|
||
|
public static final int NATIVE_EVENT_PARAMETER_CHANGED = 2;
|
||
|
|
||
|
/**
|
||
|
* Successful operation.
|
||
|
*/
|
||
|
public static final int SUCCESS = 0;
|
||
|
/**
|
||
|
* Unspecified error.
|
||
|
*/
|
||
|
public static final int ERROR = -1;
|
||
|
/**
|
||
|
* Internal operation status. Not returned by any method.
|
||
|
*/
|
||
|
public static final int ALREADY_EXISTS = -2;
|
||
|
/**
|
||
|
* Operation failed due to bad object initialization.
|
||
|
*/
|
||
|
public static final int ERROR_NO_INIT = -3;
|
||
|
/**
|
||
|
* Operation failed due to bad parameter value.
|
||
|
*/
|
||
|
public static final int ERROR_BAD_VALUE = -4;
|
||
|
/**
|
||
|
* Operation failed because it was requested in wrong state.
|
||
|
*/
|
||
|
public static final int ERROR_INVALID_OPERATION = -5;
|
||
|
/**
|
||
|
* Operation failed due to lack of memory.
|
||
|
*/
|
||
|
public static final int ERROR_NO_MEMORY = -6;
|
||
|
/**
|
||
|
* Operation failed due to dead remote object.
|
||
|
*/
|
||
|
public static final int ERROR_DEAD_OBJECT = -7;
|
||
|
|
||
|
/**
|
||
|
* The effect descriptor contains information on a particular effect implemented in the
|
||
|
* audio framework:<br>
|
||
|
* <ul>
|
||
|
* <li>type: UUID identifying the effect type. May be one of:
|
||
|
* {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}, {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_HAPTIC_GENERATOR}.
|
||
|
* </li>
|
||
|
* <li>uuid: UUID for this particular implementation</li>
|
||
|
* <li>connectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li>
|
||
|
* <li>name: human readable effect name</li>
|
||
|
* <li>implementor: human readable effect implementor name</li>
|
||
|
* </ul>
|
||
|
* The method {@link #queryEffects()} returns an array of Descriptors to facilitate effects
|
||
|
* enumeration.
|
||
|
*/
|
||
|
public static class Descriptor {
|
||
|
|
||
|
public Descriptor() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Indicates the generic type of the effect (Equalizer, Bass boost ...).
|
||
|
* One of {@link AudioEffect#EFFECT_TYPE_AEC},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_AGC}, {@link AudioEffect#EFFECT_TYPE_BASS_BOOST},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_ENV_REVERB}, {@link AudioEffect#EFFECT_TYPE_EQUALIZER},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_NS}, {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB}
|
||
|
* {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING},
|
||
|
* or {@link AudioEffect#EFFECT_TYPE_HAPTIC_GENERATOR}.<br>
|
||
|
* For reverberation, bass boost, EQ and virtualizer, the UUID
|
||
|
* corresponds to the OpenSL ES Interface ID.
|
||
|
*/
|
||
|
public UUID type;
|
||
|
/**
|
||
|
* Indicates the particular implementation of the effect in that type. Several effects
|
||
|
* can have the same type but this uuid is unique to a given implementation.
|
||
|
*/
|
||
|
public UUID uuid;
|
||
|
/**
|
||
|
* Indicates if the effect is of insert category {@link #EFFECT_INSERT} or auxiliary
|
||
|
* category {@link #EFFECT_AUXILIARY}.
|
||
|
* Insert effects (typically an {@link Equalizer}) are applied
|
||
|
* to the entire audio source and usually not shared by several sources. Auxiliary effects
|
||
|
* (typically a reverberator) are applied to part of the signal (wet) and the effect output
|
||
|
* is added to the original signal (dry).
|
||
|
* Audio pre processing are applied to audio captured on a particular
|
||
|
* {@link android.media.AudioRecord}.
|
||
|
*/
|
||
|
public String connectMode;
|
||
|
/**
|
||
|
* Human readable effect name
|
||
|
*/
|
||
|
public String name;
|
||
|
/**
|
||
|
* Human readable effect implementor name
|
||
|
*/
|
||
|
public String implementor;
|
||
|
|
||
|
/**
|
||
|
* @param type UUID identifying the effect type. May be one of:
|
||
|
* {@link AudioEffect#EFFECT_TYPE_AEC}, {@link AudioEffect#EFFECT_TYPE_AGC},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_BASS_BOOST}, {@link AudioEffect#EFFECT_TYPE_ENV_REVERB},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_EQUALIZER}, {@link AudioEffect#EFFECT_TYPE_NS},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_PRESET_REVERB},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING},
|
||
|
* {@link AudioEffect#EFFECT_TYPE_HAPTIC_GENERATOR}.
|
||
|
* @param uuid UUID for this particular implementation
|
||
|
* @param connectMode {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}
|
||
|
* @param name human readable effect name
|
||
|
* @param implementor human readable effect implementor name
|
||
|
*
|
||
|
*/
|
||
|
public Descriptor(String type, String uuid, String connectMode,
|
||
|
String name, String implementor) {
|
||
|
this.type = UUID.fromString(type);
|
||
|
this.uuid = UUID.fromString(uuid);
|
||
|
this.connectMode = connectMode;
|
||
|
this.name = name;
|
||
|
this.implementor = implementor;
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@TestApi
|
||
|
public Descriptor(Parcel in) {
|
||
|
type = UUID.fromString(in.readString());
|
||
|
uuid = UUID.fromString(in.readString());
|
||
|
connectMode = in.readString();
|
||
|
name = in.readString();
|
||
|
implementor = in.readString();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
return Objects.hash(type, uuid, connectMode, name, implementor);
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
@TestApi
|
||
|
public void writeToParcel(Parcel dest) {
|
||
|
dest.writeString(type.toString());
|
||
|
dest.writeString(uuid.toString());
|
||
|
dest.writeString(connectMode);
|
||
|
dest.writeString(name);
|
||
|
dest.writeString(implementor);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (this == o) return true;
|
||
|
if (o == null || !(o instanceof Descriptor)) return false;
|
||
|
|
||
|
Descriptor that = (Descriptor) o;
|
||
|
|
||
|
return (type.equals(that.type)
|
||
|
&& uuid.equals(that.uuid)
|
||
|
&& connectMode.equals(that.connectMode)
|
||
|
&& name.equals(that.name)
|
||
|
&& implementor.equals(that.implementor));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Effect connection mode is insert. Specifying an audio session ID when creating the effect
|
||
|
* will insert this effect after all players in the same audio session.
|
||
|
*/
|
||
|
public static final String EFFECT_INSERT = "Insert";
|
||
|
/**
|
||
|
* Effect connection mode is auxiliary.
|
||
|
* <p>Auxiliary effects must be created on session 0 (global output mix). In order for a
|
||
|
* MediaPlayer or AudioTrack to be fed into this effect, they must be explicitely attached to
|
||
|
* this effect and a send level must be specified.
|
||
|
* <p>Use the effect ID returned by {@link #getId()} to designate this particular effect when
|
||
|
* attaching it to the MediaPlayer or AudioTrack.
|
||
|
*/
|
||
|
public static final String EFFECT_AUXILIARY = "Auxiliary";
|
||
|
/**
|
||
|
* Effect connection mode is pre processing.
|
||
|
* The audio pre processing effects are attached to an audio input stream or device
|
||
|
*/
|
||
|
public static final String EFFECT_PRE_PROCESSING = "Pre Processing";
|
||
|
/**
|
||
|
* Effect connection mode is post processing.
|
||
|
* The audio post processing effects are attached to an audio output stream or device
|
||
|
*/
|
||
|
public static final String EFFECT_POST_PROCESSING = "Post Processing";
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Member variables
|
||
|
// --------------------
|
||
|
/**
|
||
|
* Indicates the state of the AudioEffect instance
|
||
|
*/
|
||
|
private int mState = STATE_UNINITIALIZED;
|
||
|
/**
|
||
|
* Lock to synchronize access to mState
|
||
|
*/
|
||
|
private final Object mStateLock = new Object();
|
||
|
/**
|
||
|
* System wide unique effect ID
|
||
|
*/
|
||
|
private int mId;
|
||
|
|
||
|
// accessed by native methods
|
||
|
private long mNativeAudioEffect;
|
||
|
private long mJniData;
|
||
|
|
||
|
/**
|
||
|
* Effect descriptor
|
||
|
*/
|
||
|
private Descriptor mDescriptor;
|
||
|
|
||
|
/**
|
||
|
* Listener for effect engine state change notifications.
|
||
|
*
|
||
|
* @see #setEnableStatusListener(OnEnableStatusChangeListener)
|
||
|
*/
|
||
|
private OnEnableStatusChangeListener mEnableStatusChangeListener = null;
|
||
|
/**
|
||
|
* Listener for effect engine control ownership change notifications.
|
||
|
*
|
||
|
* @see #setControlStatusListener(OnControlStatusChangeListener)
|
||
|
*/
|
||
|
private OnControlStatusChangeListener mControlChangeStatusListener = null;
|
||
|
/**
|
||
|
* Listener for effect engine control ownership change notifications.
|
||
|
*
|
||
|
* @see #setParameterListener(OnParameterChangeListener)
|
||
|
*/
|
||
|
private OnParameterChangeListener mParameterChangeListener = null;
|
||
|
/**
|
||
|
* Lock to protect listeners updates against event notifications
|
||
|
* @hide
|
||
|
*/
|
||
|
public final Object mListenerLock = new Object();
|
||
|
/**
|
||
|
* Handler for events coming from the native code
|
||
|
* @hide
|
||
|
*/
|
||
|
public NativeEventHandler mNativeEventHandler = null;
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Constructor, Finalize
|
||
|
// --------------------
|
||
|
/**
|
||
|
* Class constructor.
|
||
|
*
|
||
|
* @param type type of effect engine created. See {@link #EFFECT_TYPE_ENV_REVERB},
|
||
|
* {@link #EFFECT_TYPE_EQUALIZER} ... Types corresponding to
|
||
|
* built-in effects are defined by AudioEffect class. Other types
|
||
|
* can be specified provided they correspond an existing OpenSL
|
||
|
* ES interface ID and the corresponsing effect is available on
|
||
|
* the platform. If an unspecified effect type is requested, the
|
||
|
* constructor with throw the IllegalArgumentException. This
|
||
|
* parameter can be set to {@link #EFFECT_TYPE_NULL} in which
|
||
|
* case only the uuid will be used to select the effect.
|
||
|
* @param uuid unique identifier of a particular effect implementation.
|
||
|
* Must be specified if the caller wants to use a particular
|
||
|
* implementation of an effect type. This parameter can be set to
|
||
|
* {@link #EFFECT_TYPE_NULL} in which case only the type will
|
||
|
* be used to select the effect.
|
||
|
* @param priority the priority level requested by the application for
|
||
|
* controlling the effect engine. As the same effect engine can
|
||
|
* be shared by several applications, this parameter indicates
|
||
|
* how much the requesting application needs control of effect
|
||
|
* parameters. The normal priority is 0, above normal is a
|
||
|
* positive number, below normal a negative number.
|
||
|
* @param audioSession system wide unique audio session identifier.
|
||
|
* The effect will be attached to the MediaPlayer or AudioTrack in
|
||
|
* the same audio session.
|
||
|
*
|
||
|
* @throws java.lang.IllegalArgumentException
|
||
|
* @throws java.lang.UnsupportedOperationException
|
||
|
* @throws java.lang.RuntimeException
|
||
|
* @hide
|
||
|
*/
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
public AudioEffect(UUID type, UUID uuid, int priority, int audioSession)
|
||
|
throws IllegalArgumentException, UnsupportedOperationException,
|
||
|
RuntimeException {
|
||
|
this(type, uuid, priority, audioSession, null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructs an AudioEffect attached to a particular audio device.
|
||
|
* The device does not have to be attached when the effect is created. The effect will only
|
||
|
* be applied when the device is actually selected for playback or capture.
|
||
|
* @param uuid unique identifier of a particular effect implementation.
|
||
|
* @param device the device the effect must be attached to.
|
||
|
*
|
||
|
* @throws java.lang.IllegalArgumentException
|
||
|
* @throws java.lang.UnsupportedOperationException
|
||
|
* @throws java.lang.RuntimeException
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
|
public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAttributes device) {
|
||
|
this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid),
|
||
|
0, -2, Objects.requireNonNull(device));
|
||
|
}
|
||
|
|
||
|
private AudioEffect(UUID type, UUID uuid, int priority,
|
||
|
int audioSession, @Nullable AudioDeviceAttributes device)
|
||
|
throws IllegalArgumentException, UnsupportedOperationException,
|
||
|
RuntimeException {
|
||
|
this(type, uuid, priority, audioSession, device, false);
|
||
|
}
|
||
|
|
||
|
private AudioEffect(UUID type, UUID uuid, int priority,
|
||
|
int audioSession, @Nullable AudioDeviceAttributes device, boolean probe)
|
||
|
throws IllegalArgumentException, UnsupportedOperationException,
|
||
|
RuntimeException {
|
||
|
int[] id = new int[1];
|
||
|
Descriptor[] desc = new Descriptor[1];
|
||
|
|
||
|
int deviceType = AudioSystem.DEVICE_NONE;
|
||
|
String deviceAddress = "";
|
||
|
if (device != null) {
|
||
|
if (device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT) {
|
||
|
deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
|
||
|
} else {
|
||
|
deviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(
|
||
|
device.getType(), device.getAddress());
|
||
|
}
|
||
|
deviceAddress = device.getAddress();
|
||
|
}
|
||
|
|
||
|
// native initialization
|
||
|
// TODO b/182469354: Make consistent with AudioRecord
|
||
|
int initResult;
|
||
|
try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource()
|
||
|
.asScopedParcelState()) {
|
||
|
initResult = native_setup(new WeakReference<>(this), type.toString(), uuid.toString(),
|
||
|
priority, audioSession, deviceType, deviceAddress, id, desc,
|
||
|
attributionSourceState.getParcel(), probe);
|
||
|
}
|
||
|
if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
|
||
|
Log.e(TAG, "Error code " + initResult
|
||
|
+ " when initializing AudioEffect.");
|
||
|
switch (initResult) {
|
||
|
case ERROR_BAD_VALUE:
|
||
|
throw (new IllegalArgumentException("Effect type: " + type
|
||
|
+ " not supported."));
|
||
|
case ERROR_INVALID_OPERATION:
|
||
|
throw (new UnsupportedOperationException(
|
||
|
"Effect library not loaded"));
|
||
|
default:
|
||
|
throw (new RuntimeException(
|
||
|
"Cannot initialize effect engine for type: " + type
|
||
|
+ " Error: " + initResult));
|
||
|
}
|
||
|
}
|
||
|
mId = id[0];
|
||
|
mDescriptor = desc[0];
|
||
|
if (!probe) {
|
||
|
synchronized (mStateLock) {
|
||
|
mState = STATE_INITIALIZED;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if an AudioEffect identified by the supplied uuid can be attached
|
||
|
* to an audio device described by the supplied AudioDeviceAttributes.
|
||
|
* @param uuid unique identifier of a particular effect implementation.
|
||
|
* @param device the device the effect would be attached to.
|
||
|
* @return true if possible, false otherwise.
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemApi
|
||
|
@RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
|
||
|
public static boolean isEffectSupportedForDevice(
|
||
|
@NonNull UUID uuid, @NonNull AudioDeviceAttributes device) {
|
||
|
try {
|
||
|
AudioEffect fx = new AudioEffect(
|
||
|
EFFECT_TYPE_NULL, Objects.requireNonNull(uuid),
|
||
|
0, -2, Objects.requireNonNull(device), true);
|
||
|
fx.release();
|
||
|
return true;
|
||
|
} catch (Exception e) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Releases the native AudioEffect resources. It is a good practice to
|
||
|
* release the effect engine when not in use as control can be returned to
|
||
|
* other applications or the native resources released.
|
||
|
*/
|
||
|
public void release() {
|
||
|
synchronized (mStateLock) {
|
||
|
native_release();
|
||
|
mState = STATE_UNINITIALIZED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() {
|
||
|
native_finalize();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the effect descriptor.
|
||
|
*
|
||
|
* @see android.media.audiofx.AudioEffect.Descriptor
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
public Descriptor getDescriptor() throws IllegalStateException {
|
||
|
checkState("getDescriptor()");
|
||
|
return mDescriptor;
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Effects Enumeration
|
||
|
// --------------------
|
||
|
|
||
|
/**
|
||
|
* Query all effects available on the platform. Returns an array of
|
||
|
* {@link android.media.audiofx.AudioEffect.Descriptor} objects
|
||
|
*
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
|
||
|
static public Descriptor[] queryEffects() {
|
||
|
return (Descriptor[]) native_query_effects();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Query all audio pre-processing effects applied to the AudioRecord with the supplied
|
||
|
* audio session ID. Returns an array of {@link android.media.audiofx.AudioEffect.Descriptor}
|
||
|
* objects.
|
||
|
* @param audioSession system wide unique audio session identifier.
|
||
|
* @throws IllegalStateException
|
||
|
* @hide
|
||
|
*/
|
||
|
|
||
|
static public Descriptor[] queryPreProcessings(int audioSession) {
|
||
|
return (Descriptor[]) native_query_pre_processing(audioSession);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if the device implements the specified effect type.
|
||
|
* @param type the requested effect type.
|
||
|
* @return true if the device implements the specified effect type, false otherwise.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static boolean isEffectTypeAvailable(UUID type) {
|
||
|
AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
|
||
|
if (desc == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < desc.length; i++) {
|
||
|
if (desc[i].type.equals(type)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Control methods
|
||
|
// --------------------
|
||
|
|
||
|
/**
|
||
|
* Enable or disable the effect.
|
||
|
* Creating an audio effect does not automatically apply this effect on the audio source. It
|
||
|
* creates the resources necessary to process this effect but the audio signal is still bypassed
|
||
|
* through the effect engine. Calling this method will make that the effect is actually applied
|
||
|
* or not to the audio content being played in the corresponding audio session.
|
||
|
*
|
||
|
* @param enabled the requested enable state
|
||
|
* @return {@link #SUCCESS} in case of success, {@link #ERROR_INVALID_OPERATION}
|
||
|
* or {@link #ERROR_DEAD_OBJECT} in case of failure.
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
public int setEnabled(boolean enabled) throws IllegalStateException {
|
||
|
checkState("setEnabled()");
|
||
|
return native_setEnabled(enabled);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The setParameter method is provided in several
|
||
|
* forms addressing most common parameter formats. This form is the most
|
||
|
* generic one where the parameter and its value are both specified as an
|
||
|
* array of bytes. The parameter and value type and length are therefore
|
||
|
* totally free. For standard effect defined by OpenSL ES, the parameter
|
||
|
* format and values must match the definitions in the corresponding OpenSL
|
||
|
* ES interface.
|
||
|
*
|
||
|
* @param param the identifier of the parameter to set
|
||
|
* @param value the new value for the specified parameter
|
||
|
* @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE},
|
||
|
* {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or
|
||
|
* {@link #ERROR_DEAD_OBJECT} in case of failure
|
||
|
* @throws IllegalStateException
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(byte[] param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
checkState("setParameter()");
|
||
|
return native_setParameter(param.length, param, value.length, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter and its value are integers.
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(int param, int value) throws IllegalStateException {
|
||
|
byte[] p = intToByteArray(param);
|
||
|
byte[] v = intToByteArray(value);
|
||
|
return setParameter(p, v);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter is an integer and the value is a
|
||
|
* short integer.
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(int param, short value)
|
||
|
throws IllegalStateException {
|
||
|
byte[] p = intToByteArray(param);
|
||
|
byte[] v = shortToByteArray(value);
|
||
|
return setParameter(p, v);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter is an integer and the value is an
|
||
|
* array of bytes.
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(int param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
byte[] p = intToByteArray(param);
|
||
|
return setParameter(p, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is also an array of 1 or 2 integers
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(int[] param, int[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2 || value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
byte[] v = intToByteArray(value[0]);
|
||
|
if (value.length > 1) {
|
||
|
byte[] v2 = intToByteArray(value[1]);
|
||
|
v = concatArrays(v, v2);
|
||
|
}
|
||
|
return setParameter(p, v);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is an array of 1 or 2 short integers
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
public int setParameter(int[] param, short[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2 || value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
|
||
|
byte[] v = shortToByteArray(value[0]);
|
||
|
if (value.length > 1) {
|
||
|
byte[] v2 = shortToByteArray(value[1]);
|
||
|
v = concatArrays(v, v2);
|
||
|
}
|
||
|
return setParameter(p, v);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is an array of bytes
|
||
|
*
|
||
|
* @see #setParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int setParameter(int[] param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
return setParameter(p, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The getParameter method is provided in several
|
||
|
* forms addressing most common parameter formats. This form is the most
|
||
|
* generic one where the parameter and its value are both specified as an
|
||
|
* array of bytes. The parameter and value type and length are therefore
|
||
|
* totally free.
|
||
|
*
|
||
|
* @param param the identifier of the parameter to set
|
||
|
* @param value the new value for the specified parameter
|
||
|
* @return the number of meaningful bytes in value array in case of success or
|
||
|
* {@link #ERROR_BAD_VALUE}, {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION}
|
||
|
* or {@link #ERROR_DEAD_OBJECT} in case of failure.
|
||
|
* @throws IllegalStateException
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int getParameter(byte[] param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
checkState("getParameter()");
|
||
|
return native_getParameter(param.length, param, value.length, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an integer and the value is an
|
||
|
* array of bytes.
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int getParameter(int param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
byte[] p = intToByteArray(param);
|
||
|
|
||
|
return getParameter(p, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an integer and the value is an
|
||
|
* array of 1 or 2 integers
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* In case of success, returns the number of meaningful integers in value array.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int getParameter(int param, int[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param);
|
||
|
|
||
|
byte[] v = new byte[value.length * 4];
|
||
|
|
||
|
int status = getParameter(p, v);
|
||
|
|
||
|
if (status == 4 || status == 8) {
|
||
|
value[0] = byteArrayToInt(v);
|
||
|
if (status == 8) {
|
||
|
value[1] = byteArrayToInt(v, 4);
|
||
|
}
|
||
|
status /= 4;
|
||
|
} else {
|
||
|
status = ERROR;
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an integer and the value is an
|
||
|
* array of 1 or 2 short integers
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* In case of success, returns the number of meaningful short integers in value array.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int getParameter(int param, short[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param);
|
||
|
|
||
|
byte[] v = new byte[value.length * 2];
|
||
|
|
||
|
int status = getParameter(p, v);
|
||
|
|
||
|
if (status == 2 || status == 4) {
|
||
|
value[0] = byteArrayToShort(v);
|
||
|
if (status == 4) {
|
||
|
value[1] = byteArrayToShort(v, 2);
|
||
|
}
|
||
|
status /= 2;
|
||
|
} else {
|
||
|
status = ERROR;
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is also an array of 1 or 2 integers
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* In case of success, the returns the number of meaningful integers in value array.
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public int getParameter(int[] param, int[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2 || value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
byte[] v = new byte[value.length * 4];
|
||
|
|
||
|
int status = getParameter(p, v);
|
||
|
|
||
|
if (status == 4 || status == 8) {
|
||
|
value[0] = byteArrayToInt(v);
|
||
|
if (status == 8) {
|
||
|
value[1] = byteArrayToInt(v, 4);
|
||
|
}
|
||
|
status /= 4;
|
||
|
} else {
|
||
|
status = ERROR;
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is an array of 1 or 2 short integers
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* In case of success, returns the number of meaningful short integers in value array.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public int getParameter(int[] param, short[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2 || value.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
byte[] v = new byte[value.length * 2];
|
||
|
|
||
|
int status = getParameter(p, v);
|
||
|
|
||
|
if (status == 2 || status == 4) {
|
||
|
value[0] = byteArrayToShort(v);
|
||
|
if (status == 4) {
|
||
|
value[1] = byteArrayToShort(v, 2);
|
||
|
}
|
||
|
status /= 2;
|
||
|
} else {
|
||
|
status = ERROR;
|
||
|
}
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get effect parameter. The parameter is an array of 1 or 2 integers and
|
||
|
* the value is an array of bytes
|
||
|
*
|
||
|
* @see #getParameter(byte[], byte[])
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
public int getParameter(int[] param, byte[] value)
|
||
|
throws IllegalStateException {
|
||
|
if (param.length > 2) {
|
||
|
return ERROR_BAD_VALUE;
|
||
|
}
|
||
|
byte[] p = intToByteArray(param[0]);
|
||
|
if (param.length > 1) {
|
||
|
byte[] p2 = intToByteArray(param[1]);
|
||
|
p = concatArrays(p, p2);
|
||
|
}
|
||
|
|
||
|
return getParameter(p, value);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a command to the effect engine. This method is intended to send
|
||
|
* proprietary commands to a particular effect implementation.
|
||
|
* In case of success, returns the number of meaningful bytes in reply array.
|
||
|
* In case of failure, the returned value is negative and implementation specific.
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
|
||
|
public int command(int cmdCode, byte[] command, byte[] reply)
|
||
|
throws IllegalStateException {
|
||
|
checkState("command()");
|
||
|
return native_command(cmdCode, command.length, command, reply.length, reply);
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Getters
|
||
|
// --------------------
|
||
|
|
||
|
/**
|
||
|
* Returns effect unique identifier. This system wide unique identifier can
|
||
|
* be used to attach this effect to a MediaPlayer or an AudioTrack when the
|
||
|
* effect is an auxiliary effect (Reverb)
|
||
|
*
|
||
|
* @return the effect identifier.
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
public int getId() throws IllegalStateException {
|
||
|
checkState("getId()");
|
||
|
return mId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns effect enabled state
|
||
|
*
|
||
|
* @return true if the effect is enabled, false otherwise.
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
public boolean getEnabled() throws IllegalStateException {
|
||
|
checkState("getEnabled()");
|
||
|
return native_getEnabled();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if this AudioEffect object is controlling the effect engine.
|
||
|
*
|
||
|
* @return true if this instance has control of effect engine, false
|
||
|
* otherwise.
|
||
|
* @throws IllegalStateException
|
||
|
*/
|
||
|
public boolean hasControl() throws IllegalStateException {
|
||
|
checkState("hasControl()");
|
||
|
return native_hasControl();
|
||
|
}
|
||
|
|
||
|
// --------------------------------------------------------------------------
|
||
|
// Initialization / configuration
|
||
|
// --------------------
|
||
|
/**
|
||
|
* Sets the listener AudioEffect notifies when the effect engine is enabled
|
||
|
* or disabled.
|
||
|
*
|
||
|
* @param listener
|
||
|
*/
|
||
|
public void setEnableStatusListener(OnEnableStatusChangeListener listener) {
|
||
|
synchronized (mListenerLock) {
|
||
|
mEnableStatusChangeListener = listener;
|
||
|
}
|
||
|
if ((listener != null) && (mNativeEventHandler == null)) {
|
||
|
createNativeEventHandler();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the listener AudioEffect notifies when the effect engine control is
|
||
|
* taken or returned.
|
||
|
*
|
||
|
* @param listener
|
||
|
*/
|
||
|
public void setControlStatusListener(OnControlStatusChangeListener listener) {
|
||
|
synchronized (mListenerLock) {
|
||
|
mControlChangeStatusListener = listener;
|
||
|
}
|
||
|
if ((listener != null) && (mNativeEventHandler == null)) {
|
||
|
createNativeEventHandler();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the listener AudioEffect notifies when a parameter is changed.
|
||
|
*
|
||
|
* @param listener
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public void setParameterListener(OnParameterChangeListener listener) {
|
||
|
synchronized (mListenerLock) {
|
||
|
mParameterChangeListener = listener;
|
||
|
}
|
||
|
if ((listener != null) && (mNativeEventHandler == null)) {
|
||
|
createNativeEventHandler();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Convenience method for the creation of the native event handler
|
||
|
// It is called only when a non-null event listener is set.
|
||
|
// precondition:
|
||
|
// mNativeEventHandler is null
|
||
|
private void createNativeEventHandler() {
|
||
|
Looper looper;
|
||
|
if ((looper = Looper.myLooper()) != null) {
|
||
|
mNativeEventHandler = new NativeEventHandler(this, looper);
|
||
|
} else if ((looper = Looper.getMainLooper()) != null) {
|
||
|
mNativeEventHandler = new NativeEventHandler(this, looper);
|
||
|
} else {
|
||
|
mNativeEventHandler = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
// Interface definitions
|
||
|
// --------------------
|
||
|
/**
|
||
|
* The OnEnableStatusChangeListener interface defines a method called by the AudioEffect
|
||
|
* when the enabled state of the effect engine was changed by the controlling application.
|
||
|
*/
|
||
|
public interface OnEnableStatusChangeListener {
|
||
|
/**
|
||
|
* Called on the listener to notify it that the effect engine has been
|
||
|
* enabled or disabled.
|
||
|
* @param effect the effect on which the interface is registered.
|
||
|
* @param enabled new effect state.
|
||
|
*/
|
||
|
void onEnableStatusChange(AudioEffect effect, boolean enabled);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The OnControlStatusChangeListener interface defines a method called by the AudioEffect
|
||
|
* when control of the effect engine is gained or lost by the application
|
||
|
*/
|
||
|
public interface OnControlStatusChangeListener {
|
||
|
/**
|
||
|
* Called on the listener to notify it that the effect engine control
|
||
|
* has been taken or returned.
|
||
|
* @param effect the effect on which the interface is registered.
|
||
|
* @param controlGranted true if the application has been granted control of the effect
|
||
|
* engine, false otherwise.
|
||
|
*/
|
||
|
void onControlStatusChange(AudioEffect effect, boolean controlGranted);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The OnParameterChangeListener interface defines a method called by the AudioEffect
|
||
|
* when a parameter is changed in the effect engine by the controlling application.
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public interface OnParameterChangeListener {
|
||
|
/**
|
||
|
* Called on the listener to notify it that a parameter value has changed.
|
||
|
* @param effect the effect on which the interface is registered.
|
||
|
* @param status status of the set parameter operation.
|
||
|
* @param param ID of the modified parameter.
|
||
|
* @param value the new parameter value.
|
||
|
*/
|
||
|
void onParameterChange(AudioEffect effect, int status, byte[] param,
|
||
|
byte[] value);
|
||
|
}
|
||
|
|
||
|
|
||
|
// -------------------------------------------------------------------------
|
||
|
// Audio Effect Control panel intents
|
||
|
// -------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Intent to launch an audio effect control panel UI.
|
||
|
* <p>The goal of this intent is to enable separate implementations of music/media player
|
||
|
* applications and audio effect control application or services.
|
||
|
* This will allow platform vendors to offer more advanced control options for standard effects
|
||
|
* or control for platform specific effects.
|
||
|
* <p>The intent carries a number of extras used by the player application to communicate
|
||
|
* necessary pieces of information to the control panel application.
|
||
|
* <p>The calling application must use the
|
||
|
* {@link android.app.Activity#startActivityForResult(Intent, int)} method to launch the
|
||
|
* control panel so that its package name is indicated and used by the control panel
|
||
|
* application to keep track of changes for this particular application.
|
||
|
* <p>The {@link #EXTRA_AUDIO_SESSION} extra will indicate an audio session to which the
|
||
|
* audio effects should be applied. If no audio session is specified, either one of the
|
||
|
* follownig will happen:
|
||
|
* <p>- If an audio session was previously opened by the calling application with
|
||
|
* {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intent, the effect changes will
|
||
|
* be applied to that session.
|
||
|
* <p>- If no audio session is opened, the changes will be stored in the package specific
|
||
|
* storage area and applied whenever a new audio session is opened by this application.
|
||
|
* <p>The {@link #EXTRA_CONTENT_TYPE} extra will help the control panel application
|
||
|
* customize both the UI layout and the default audio effect settings if none are already
|
||
|
* stored for the calling application.
|
||
|
*/
|
||
|
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||
|
public static final String ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL =
|
||
|
"android.media.action.DISPLAY_AUDIO_EFFECT_CONTROL_PANEL";
|
||
|
|
||
|
/**
|
||
|
* Intent to signal to the effect control application or service that a new audio session
|
||
|
* is opened and requires audio effects to be applied.
|
||
|
* <p>This is different from {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} in that no
|
||
|
* UI should be displayed in this case. Music player applications can broadcast this intent
|
||
|
* before starting playback to make sure that any audio effect settings previously selected
|
||
|
* by the user are applied.
|
||
|
* <p>The effect control application receiving this intent will look for previously stored
|
||
|
* settings for the calling application, create all required audio effects and apply the
|
||
|
* effect settings to the specified audio session.
|
||
|
* <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
|
||
|
* audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
|
||
|
* <p>If no stored settings are found for the calling application, default settings for the
|
||
|
* content type indicated by {@link #EXTRA_CONTENT_TYPE} will be applied. The default settings
|
||
|
* for a given content type are platform specific.
|
||
|
*/
|
||
|
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||
|
public static final String ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION =
|
||
|
"android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION";
|
||
|
|
||
|
/**
|
||
|
* Intent to signal to the effect control application or service that an audio session
|
||
|
* is closed and that effects should not be applied anymore.
|
||
|
* <p>The effect control application receiving this intent will delete all effects on
|
||
|
* this session and store current settings in package specific storage.
|
||
|
* <p>The calling package name is indicated by the {@link #EXTRA_PACKAGE_NAME} extra and the
|
||
|
* audio session ID by the {@link #EXTRA_AUDIO_SESSION} extra. Both extras are mandatory.
|
||
|
* <p>It is good practice for applications to broadcast this intent when music playback stops
|
||
|
* and/or when exiting to free system resources consumed by audio effect engines.
|
||
|
*/
|
||
|
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||
|
public static final String ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION =
|
||
|
"android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION";
|
||
|
|
||
|
/**
|
||
|
* Contains the ID of the audio session the effects should be applied to.
|
||
|
* <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL},
|
||
|
* {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
|
||
|
* {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
|
||
|
* <p>The extra value is of type int and is the audio session ID.
|
||
|
* @see android.media.MediaPlayer#getAudioSessionId() for details on audio sessions.
|
||
|
*/
|
||
|
public static final String EXTRA_AUDIO_SESSION = "android.media.extra.AUDIO_SESSION";
|
||
|
|
||
|
/**
|
||
|
* Contains the package name of the calling application.
|
||
|
* <p>This extra is for use with {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
|
||
|
* {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
|
||
|
* <p>The extra value is a string containing the full package name.
|
||
|
*/
|
||
|
public static final String EXTRA_PACKAGE_NAME = "android.media.extra.PACKAGE_NAME";
|
||
|
|
||
|
/**
|
||
|
* Indicates which type of content is played by the application.
|
||
|
* <p>This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} and
|
||
|
* {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intents.
|
||
|
* <p>This information is used by the effect control application to customize UI and select
|
||
|
* appropriate default effect settings. The content type is one of the following:
|
||
|
* <ul>
|
||
|
* <li>{@link #CONTENT_TYPE_MUSIC}</li>
|
||
|
* <li>{@link #CONTENT_TYPE_MOVIE}</li>
|
||
|
* <li>{@link #CONTENT_TYPE_GAME}</li>
|
||
|
* <li>{@link #CONTENT_TYPE_VOICE}</li>
|
||
|
* </ul>
|
||
|
* If omitted, the content type defaults to {@link #CONTENT_TYPE_MUSIC}.
|
||
|
*/
|
||
|
public static final String EXTRA_CONTENT_TYPE = "android.media.extra.CONTENT_TYPE";
|
||
|
|
||
|
/**
|
||
|
* Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is music
|
||
|
*/
|
||
|
public static final int CONTENT_TYPE_MUSIC = 0;
|
||
|
/**
|
||
|
* Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is video or movie
|
||
|
*/
|
||
|
public static final int CONTENT_TYPE_MOVIE = 1;
|
||
|
/**
|
||
|
* Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is game audio
|
||
|
*/
|
||
|
public static final int CONTENT_TYPE_GAME = 2;
|
||
|
/**
|
||
|
* Value for {@link #EXTRA_CONTENT_TYPE} when the type of content played is voice audio
|
||
|
*/
|
||
|
public static final int CONTENT_TYPE_VOICE = 3;
|
||
|
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
// Inner classes
|
||
|
// --------------------
|
||
|
/**
|
||
|
* Helper class to handle the forwarding of native events to the appropriate
|
||
|
* listeners
|
||
|
*/
|
||
|
private class NativeEventHandler extends Handler {
|
||
|
private AudioEffect mAudioEffect;
|
||
|
|
||
|
public NativeEventHandler(AudioEffect ae, Looper looper) {
|
||
|
super(looper);
|
||
|
mAudioEffect = ae;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
if (mAudioEffect == null) {
|
||
|
return;
|
||
|
}
|
||
|
switch (msg.what) {
|
||
|
case NATIVE_EVENT_ENABLED_STATUS:
|
||
|
OnEnableStatusChangeListener enableStatusChangeListener = null;
|
||
|
synchronized (mListenerLock) {
|
||
|
enableStatusChangeListener = mAudioEffect.mEnableStatusChangeListener;
|
||
|
}
|
||
|
if (enableStatusChangeListener != null) {
|
||
|
enableStatusChangeListener.onEnableStatusChange(
|
||
|
mAudioEffect, (boolean) (msg.arg1 != 0));
|
||
|
}
|
||
|
break;
|
||
|
case NATIVE_EVENT_CONTROL_STATUS:
|
||
|
OnControlStatusChangeListener controlStatusChangeListener = null;
|
||
|
synchronized (mListenerLock) {
|
||
|
controlStatusChangeListener = mAudioEffect.mControlChangeStatusListener;
|
||
|
}
|
||
|
if (controlStatusChangeListener != null) {
|
||
|
controlStatusChangeListener.onControlStatusChange(
|
||
|
mAudioEffect, (boolean) (msg.arg1 != 0));
|
||
|
}
|
||
|
break;
|
||
|
case NATIVE_EVENT_PARAMETER_CHANGED:
|
||
|
OnParameterChangeListener parameterChangeListener = null;
|
||
|
synchronized (mListenerLock) {
|
||
|
parameterChangeListener = mAudioEffect.mParameterChangeListener;
|
||
|
}
|
||
|
if (parameterChangeListener != null) {
|
||
|
// arg1 contains offset of parameter value from start of
|
||
|
// byte array
|
||
|
int vOffset = msg.arg1;
|
||
|
byte[] p = (byte[]) msg.obj;
|
||
|
// See effect_param_t in EffectApi.h for psize and vsize
|
||
|
// fields offsets
|
||
|
int status = byteArrayToInt(p, 0);
|
||
|
int psize = byteArrayToInt(p, 4);
|
||
|
int vsize = byteArrayToInt(p, 8);
|
||
|
byte[] param = new byte[psize];
|
||
|
byte[] value = new byte[vsize];
|
||
|
System.arraycopy(p, 12, param, 0, psize);
|
||
|
System.arraycopy(p, vOffset, value, 0, vsize);
|
||
|
|
||
|
parameterChangeListener.onParameterChange(mAudioEffect,
|
||
|
status, param, value);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
Log.e(TAG, "handleMessage() Unknown event type: " + msg.what);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
// Java methods called from the native side
|
||
|
// --------------------
|
||
|
@SuppressWarnings("unused")
|
||
|
private static void postEventFromNative(Object effect_ref, int what,
|
||
|
int arg1, int arg2, Object obj) {
|
||
|
AudioEffect effect = (AudioEffect) ((WeakReference) effect_ref).get();
|
||
|
if (effect == null) {
|
||
|
return;
|
||
|
}
|
||
|
if (effect.mNativeEventHandler != null) {
|
||
|
Message m = effect.mNativeEventHandler.obtainMessage(what, arg1,
|
||
|
arg2, obj);
|
||
|
effect.mNativeEventHandler.sendMessage(m);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
// Native methods called from the Java side
|
||
|
// --------------------
|
||
|
|
||
|
private static native final void native_init();
|
||
|
|
||
|
private native final int native_setup(Object audioeffect_this, String type,
|
||
|
String uuid, int priority, int audioSession,
|
||
|
int deviceType, String deviceAddress, int[] id, Object[] desc,
|
||
|
@NonNull Parcel attributionSource, boolean probe);
|
||
|
|
||
|
private native final void native_finalize();
|
||
|
|
||
|
private native final void native_release();
|
||
|
|
||
|
private native final int native_setEnabled(boolean enabled);
|
||
|
|
||
|
private native final boolean native_getEnabled();
|
||
|
|
||
|
private native final boolean native_hasControl();
|
||
|
|
||
|
private native final int native_setParameter(int psize, byte[] param,
|
||
|
int vsize, byte[] value);
|
||
|
|
||
|
private native final int native_getParameter(int psize, byte[] param,
|
||
|
int vsize, byte[] value);
|
||
|
|
||
|
private native final int native_command(int cmdCode, int cmdSize,
|
||
|
byte[] cmdData, int repSize, byte[] repData);
|
||
|
|
||
|
private static native Object[] native_query_effects();
|
||
|
|
||
|
private static native Object[] native_query_pre_processing(int audioSession);
|
||
|
|
||
|
// ---------------------------------------------------------
|
||
|
// Utility methods
|
||
|
// ------------------
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
public void checkState(String methodName) throws IllegalStateException {
|
||
|
synchronized (mStateLock) {
|
||
|
if (mState != STATE_INITIALIZED) {
|
||
|
throw (new IllegalStateException(methodName
|
||
|
+ " called on uninitialized AudioEffect."));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public void checkStatus(int status) {
|
||
|
if (isError(status)) {
|
||
|
switch (status) {
|
||
|
case AudioEffect.ERROR_BAD_VALUE:
|
||
|
throw (new IllegalArgumentException(
|
||
|
"AudioEffect: bad parameter value"));
|
||
|
case AudioEffect.ERROR_INVALID_OPERATION:
|
||
|
throw (new UnsupportedOperationException(
|
||
|
"AudioEffect: invalid parameter operation"));
|
||
|
default:
|
||
|
throw (new RuntimeException("AudioEffect: set/get parameter error"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static boolean isError(int status) {
|
||
|
return (status < 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static int byteArrayToInt(byte[] valueBuf) {
|
||
|
return byteArrayToInt(valueBuf, 0);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static int byteArrayToInt(byte[] valueBuf, int offset) {
|
||
|
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
return converter.getInt(offset);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static byte[] intToByteArray(int value) {
|
||
|
ByteBuffer converter = ByteBuffer.allocate(4);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
converter.putInt(value);
|
||
|
return converter.array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static short byteArrayToShort(byte[] valueBuf) {
|
||
|
return byteArrayToShort(valueBuf, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static short byteArrayToShort(byte[] valueBuf, int offset) {
|
||
|
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
return converter.getShort(offset);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
public static byte[] shortToByteArray(short value) {
|
||
|
ByteBuffer converter = ByteBuffer.allocate(2);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
short sValue = (short) value;
|
||
|
converter.putShort(sValue);
|
||
|
return converter.array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static float byteArrayToFloat(byte[] valueBuf) {
|
||
|
return byteArrayToFloat(valueBuf, 0);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static float byteArrayToFloat(byte[] valueBuf, int offset) {
|
||
|
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
return converter.getFloat(offset);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static byte[] floatToByteArray(float value) {
|
||
|
ByteBuffer converter = ByteBuffer.allocate(4);
|
||
|
converter.order(ByteOrder.nativeOrder());
|
||
|
converter.putFloat(value);
|
||
|
return converter.array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public static byte[] concatArrays(byte[]... arrays) {
|
||
|
int len = 0;
|
||
|
for (byte[] a : arrays) {
|
||
|
len += a.length;
|
||
|
}
|
||
|
byte[] b = new byte[len];
|
||
|
|
||
|
int offs = 0;
|
||
|
for (byte[] a : arrays) {
|
||
|
System.arraycopy(a, 0, b, offs, a.length);
|
||
|
offs += a.length;
|
||
|
}
|
||
|
return b;
|
||
|
}
|
||
|
}
|