Java tutorial
/* * 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.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import java.lang.ref.WeakReference; import java.nio.ByteOrder; import java.nio.ByteBuffer; 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> * </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"); /** * 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}. * </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() { } /** * @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}. * @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; } /** * 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} * or {@link AudioEffect#EFFECT_TYPE_DYNAMICS_PROCESSING}.<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; }; /** * 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 (AudioRecord). * @hide */ public static final String EFFECT_PRE_PROCESSING = "Pre 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 { int[] id = new int[1]; Descriptor[] desc = new Descriptor[1]; // native initialization int initResult = native_setup(new WeakReference<AudioEffect>(this), type.toString(), uuid.toString(), priority, audioSession, id, desc, ActivityThread.currentOpPackageName()); 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]; synchronized (mStateLock) { mState = STATE_INITIALIZED; } } /** * 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 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 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[] id, Object[] desc, String opPackageName); 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; } }