diff options
author | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
---|---|---|
committer | Justin Klaassen <justinklaassen@google.com> | 2017-09-15 17:58:39 -0400 |
commit | 10d07c88d69cc64f73a069163e7ea5ba2519a099 (patch) | |
tree | 8dbd149eb350320a29c3d10e7ad3201de1c5cbee /android/media/audiofx | |
parent | 677516fb6b6f207d373984757d3d9450474b6b00 (diff) | |
download | android-28-10d07c88d69cc64f73a069163e7ea5ba2519a099.tar.gz |
Import Android SDK Platform PI [4335822]
/google/data/ro/projects/android/fetch_artifact \
--bid 4335822 \
--target sdk_phone_armv7-win_sdk \
sdk-repo-linux-sources-4335822.zip
AndroidVersion.ApiLevel has been modified to appear as 28
Change-Id: Ic8f04be005a71c2b9abeaac754d8da8d6f9a2c32
Diffstat (limited to 'android/media/audiofx')
-rw-r--r-- | android/media/audiofx/AcousticEchoCanceler.java | 96 | ||||
-rw-r--r-- | android/media/audiofx/AudioEffect.java | 1361 | ||||
-rw-r--r-- | android/media/audiofx/AutomaticGainControl.java | 96 | ||||
-rw-r--r-- | android/media/audiofx/BassBoost.java | 287 | ||||
-rw-r--r-- | android/media/audiofx/EnvironmentalReverb.java | 661 | ||||
-rw-r--r-- | android/media/audiofx/Equalizer.java | 559 | ||||
-rw-r--r-- | android/media/audiofx/LoudnessEnhancer.java | 290 | ||||
-rw-r--r-- | android/media/audiofx/NoiseSuppressor.java | 98 | ||||
-rw-r--r-- | android/media/audiofx/PresetReverb.java | 303 | ||||
-rw-r--r-- | android/media/audiofx/Virtualizer.java | 629 | ||||
-rw-r--r-- | android/media/audiofx/Visualizer.java | 772 |
11 files changed, 5152 insertions, 0 deletions
diff --git a/android/media/audiofx/AcousticEchoCanceler.java b/android/media/audiofx/AcousticEchoCanceler.java new file mode 100644 index 00000000..3a44df4d --- /dev/null +++ b/android/media/audiofx/AcousticEchoCanceler.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 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.util.Log; + +/** + * Acoustic Echo Canceler (AEC). + * <p>Acoustic Echo Canceler (AEC) is an audio pre-processor which removes the contribution of the + * signal received from the remote party from the captured audio signal. + * <p>AEC is used by voice communication applications (voice chat, video conferencing, SIP calls) + * where the presence of echo with significant delay in the signal received from the remote party + * is highly disturbing. AEC is often used in conjunction with noise suppression (NS). + * <p>An application creates an AcousticEchoCanceler object to instantiate and control an AEC + * engine in the audio capture path. + * <p>To attach the AcousticEchoCanceler to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when creating the AcousticEchoCanceler. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, an AEC can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application should + * call AcousticEchoCanceler.getEnable() after creating the AEC to check the default AEC activation + * state on a particular AudioRecord session. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + */ + +public class AcousticEchoCanceler extends AudioEffect { + + private final static String TAG = "AcousticEchoCanceler"; + + /** + * Checks if the device implements acoustic echo cancellation. + * @return true if the device implements acoustic echo cancellation, false otherwise. + */ + public static boolean isAvailable() { + return AudioEffect.isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_AEC); + } + + /** + * Creates an AcousticEchoCanceler and attaches it to the AudioRecord on the audio + * session specified. + * @param audioSession system wide unique audio session identifier. The AcousticEchoCanceler + * will be applied to the AudioRecord with the same audio session. + * @return AcousticEchoCanceler created or null if the device does not implement AEC. + */ + public static AcousticEchoCanceler create(int audioSession) { + AcousticEchoCanceler aec = null; + try { + aec = new AcousticEchoCanceler(audioSession); + } catch (IllegalArgumentException e) { + Log.w(TAG, "not implemented on this device"+ aec); + } catch (UnsupportedOperationException e) { + Log.w(TAG, "not enough resources"); + } catch (RuntimeException e) { + Log.w(TAG, "not enough memory"); + } + return aec; + } + + /** + * Class constructor. + * <p> The constructor is not guarantied to succeed and throws the following exceptions: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an AEC</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * <li>RuntimeException is thrown if a memory allocation error occurs.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The AcousticEchoCanceler + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + private AcousticEchoCanceler(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_AEC, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/android/media/audiofx/AudioEffect.java b/android/media/audiofx/AudioEffect.java new file mode 100644 index 00000000..7dbca3b9 --- /dev/null +++ b/android/media/audiofx/AudioEffect.java @@ -0,0 +1,1361 @@ +/* + * 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.app.ActivityThread; +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> + * </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"); + + /** + * Null effect UUID. Used when the UUID for effect type of + * @hide + */ + 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}. + * </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}. + * @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} + * or {@link AudioEffect#EFFECT_TYPE_VIRTUALIZER}.<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 + */ + + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 a 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 a the 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 + */ + 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 + */ + 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 + */ + public static boolean isError(int status) { + return (status < 0); + } + + /** + * @hide + */ + 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 + */ + public static byte[] intToByteArray(int value) { + ByteBuffer converter = ByteBuffer.allocate(4); + converter.order(ByteOrder.nativeOrder()); + converter.putInt(value); + return converter.array(); + } + + /** + * @hide + */ + 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 + */ + 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 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; + } +} diff --git a/android/media/audiofx/AutomaticGainControl.java b/android/media/audiofx/AutomaticGainControl.java new file mode 100644 index 00000000..a76b4de7 --- /dev/null +++ b/android/media/audiofx/AutomaticGainControl.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011 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.util.Log; + +/** + * Automatic Gain Control (AGC). + * <p>Automatic Gain Control (AGC) is an audio pre-processor which automatically normalizes the + * output of the captured signal by boosting or lowering input from the microphone to match a preset + * level so that the output signal level is virtually constant. + * AGC can be used by applications where the input signal dynamic range is not important but where + * a constant strong capture level is desired. + * <p>An application creates a AutomaticGainControl object to instantiate and control an AGC + * engine in the audio framework. + * <p>To attach the AutomaticGainControl to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when creating the AutomaticGainControl. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, an AGC can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application should + * call AutomaticGainControl.getEnable() after creating the AGC to check the default AGC activation + * state on a particular AudioRecord session. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + */ + +public class AutomaticGainControl extends AudioEffect { + + private final static String TAG = "AutomaticGainControl"; + + /** + * Checks if the device implements automatic gain control. + * @return true if the device implements automatic gain control, false otherwise. + */ + public static boolean isAvailable() { + return AudioEffect.isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_AGC); + } + + /** + * Creates an AutomaticGainControl and attaches it to the AudioRecord on the audio + * session specified. + * @param audioSession system wide unique audio session identifier. The AutomaticGainControl + * will be applied to the AudioRecord with the same audio session. + * @return AutomaticGainControl created or null if the device does not implement AGC. + */ + public static AutomaticGainControl create(int audioSession) { + AutomaticGainControl agc = null; + try { + agc = new AutomaticGainControl(audioSession); + } catch (IllegalArgumentException e) { + Log.w(TAG, "not implemented on this device "+agc); + } catch (UnsupportedOperationException e) { + Log.w(TAG, "not enough resources"); + } catch (RuntimeException e) { + Log.w(TAG, "not enough memory"); + } + return agc; + } + + /** + * Class constructor. + * <p> The constructor is not guarantied to succeed and throws the following exceptions: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an AGC</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * <li>RuntimeException is thrown if a memory allocation error occurs.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The AutomaticGainControl + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + private AutomaticGainControl(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_AGC, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/android/media/audiofx/BassBoost.java b/android/media/audiofx/BassBoost.java new file mode 100644 index 00000000..a46cc223 --- /dev/null +++ b/android/media/audiofx/BassBoost.java @@ -0,0 +1,287 @@ +/* + * 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.media.audiofx.AudioEffect; +import android.util.Log; + +import java.util.StringTokenizer; + + +/** + * Bass boost is an audio effect to boost or amplify low frequencies of the sound. It is comparable + * to a simple equalizer but limited to one band amplification in the low frequency range. + * <p>An application creates a BassBoost object to instantiate and control a bass boost engine in + * the audio framework. + * <p>The methods, parameter types and units exposed by the BassBoost implementation are directly + * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/) + * for the SLBassBoostItf interface. Please refer to this specification for more details. + * <p>To attach the BassBoost to a particular AudioTrack or MediaPlayer, specify the audio session + * ID of this AudioTrack or MediaPlayer when constructing the BassBoost. + * <p>NOTE: attaching a BassBoost to the global audio output mix by use of session 0 is deprecated. + * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + */ + +public class BassBoost extends AudioEffect { + + private final static String TAG = "BassBoost"; + + // These constants must be synchronized with those in + // frameworks/base/include/media/EffectBassBoostApi.h + /** + * Is strength parameter supported by bass boost engine. Parameter ID for getParameter(). + */ + public static final int PARAM_STRENGTH_SUPPORTED = 0; + /** + * Bass boost effect strength. Parameter ID for + * {@link android.media.audiofx.BassBoost.OnParameterChangeListener} + */ + public static final int PARAM_STRENGTH = 1; + + /** + * Indicates if strength parameter is supported by the bass boost engine + */ + private boolean mStrengthSupported = false; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change event from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param priority the priority level requested by the application for controlling the BassBoost + * engine. As the same 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 BassBoost will be + * attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public BassBoost(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_BASS_BOOST, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a BassBoost to global output mix is deprecated!"); + } + + int[] value = new int[1]; + checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value)); + mStrengthSupported = (value[0] != 0); + } + + /** + * Indicates whether setting strength is supported. If this method returns false, only one + * strength is supported and the setStrength() method always rounds to that value. + * @return true is strength parameter is supported, false otherwise + */ + public boolean getStrengthSupported() { + return mStrengthSupported; + } + + /** + * Sets the strength of the bass boost effect. If the implementation does not support per mille + * accuracy for setting the strength, it is allowed to round the given strength to the nearest + * supported value. You can use the {@link #getRoundedStrength()} method to query the + * (possibly rounded) value that was actually set. + * @param strength strength of the effect. The valid range for strength strength is [0, 1000], + * where 0 per mille designates the mildest effect and 1000 per mille designates the strongest. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setStrength(short strength) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, strength)); + } + + /** + * Gets the current strength of the effect. + * @return the strength of the effect. The valid range for strength is [0, 1000], where 0 per + * mille designates the mildest effect and 1000 per mille the strongest + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getRoundedStrength() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + return value[0]; + } + + /** + * The OnParameterChangeListener interface defines a method called by the BassBoost when a + * parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * BassBoost engine. + * @param effect the BassBoost on which the interface is registered. + * @param status status of the set parameter operation. + * @param param ID of the modified parameter. See {@link #PARAM_STRENGTH} ... + * @param value the new parameter value. + */ + void onParameterChange(BassBoost effect, int status, int param, short value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + OnParameterChangeListener l = null; + + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + short v = -1; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 2) { + v = byteArrayToShort(value, 0); + } + if (p != -1 && v != -1) { + l.onParameterChange(BassBoost.this, status, p, v); + } + } + } + } + + /** + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mParamListener = listener; + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + } + } + + /** + * The Settings class regroups all bass boost parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short strength; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("BassBoost")) { + throw new IllegalArgumentException( + "invalid settings for BassBoost: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("strength")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + strength = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "BassBoost"+ + ";strength="+Short.toString(strength) + ); + return str; + } + }; + + + /** + * Gets the bass boost properties. This method is useful when a snapshot of current + * bass boost settings must be saved by the application. + * @return a BassBoost.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public BassBoost.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + settings.strength = value[0]; + return settings; + } + + /** + * Sets the bass boost properties. This method is useful when bass boost settings have to + * be applied from a previous backup. + * @param settings a BassBoost.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(BassBoost.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, settings.strength)); + } +} diff --git a/android/media/audiofx/EnvironmentalReverb.java b/android/media/audiofx/EnvironmentalReverb.java new file mode 100644 index 00000000..ef1c4c3e --- /dev/null +++ b/android/media/audiofx/EnvironmentalReverb.java @@ -0,0 +1,661 @@ +/* + * 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.media.audiofx.AudioEffect; +import java.util.StringTokenizer; + +/** + * A sound generated within a room travels in many directions. The listener first hears the direct + * sound from the source itself. Later, he or she hears discrete echoes caused by sound bouncing off + * nearby walls, the ceiling and the floor. As sound waves arrive after undergoing more and more + * reflections, individual reflections become indistinguishable and the listener hears continuous + * reverberation that decays over time. + * Reverb is vital for modeling a listener's environment. It can be used in music applications + * to simulate music being played back in various environments, or in games to immerse the + * listener within the game's environment. + * The EnvironmentalReverb class allows an application to control each reverb engine property in a + * global reverb environment and is more suitable for games. For basic control, more suitable for + * music applications, it is recommended to use the + * {@link android.media.audiofx.PresetReverb} class. + * <p>An application creates a EnvironmentalReverb object to instantiate and control a reverb engine + * in the audio framework. + * <p>The methods, parameter types and units exposed by the EnvironmentalReverb implementation are + * directly mapping those defined by the OpenSL ES 1.0.1 Specification + * (http://www.khronos.org/opensles/) for the SLEnvironmentalReverbItf interface. + * Please refer to this specification for more details. + * <p>The EnvironmentalReverb is an output mix auxiliary effect and should be created on + * Audio session 0. In order for a MediaPlayer or AudioTrack to be fed into this effect, + * they must be explicitely attached to it and a send level must be specified. Use the effect ID + * returned by getId() method to designate this particular effect when attaching it to the + * MediaPlayer or AudioTrack. + * <p>Creating a reverb on the output mix (audio session 0) requires permission + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling + * audio effects. + */ + +public class EnvironmentalReverb extends AudioEffect { + + private final static String TAG = "EnvironmentalReverb"; + + // These constants must be synchronized with those in + // frameworks/base/include/media/EffectEnvironmentalReverbApi.h + + /** + * Room level. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_ROOM_LEVEL = 0; + /** + * Room HF level. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_ROOM_HF_LEVEL = 1; + /** + * Decay time. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_DECAY_TIME = 2; + /** + * Decay HF ratio. Parameter ID for + * {@link android.media.audiofx.EnvironmentalReverb.OnParameterChangeListener} + */ + public static final int PARAM_DECAY_HF_RATIO = 3; + /** + * Early reflections level. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_REFLECTIONS_LEVEL = 4; + /** + * Early reflections delay. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_REFLECTIONS_DELAY = 5; + /** + * Reverb level. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_REVERB_LEVEL = 6; + /** + * Reverb delay. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_REVERB_DELAY = 7; + /** + * Diffusion. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_DIFFUSION = 8; + /** + * Density. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_DENSITY = 9; + + // used by setProperties()/getProperties + private static final int PARAM_PROPERTIES = 10; + + /** + * Registered listener for parameter changes + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change event from AudioEffect super + * class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param priority the priority level requested by the application for controlling the + * EnvironmentalReverb engine. As the same 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. If audioSession + * is not 0, the EnvironmentalReverb will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the EnvironmentalReverb will apply to the output mix. + * As the EnvironmentalReverb is an auxiliary effect it is recommended to instantiate it on + * audio session 0 and to attach it to the MediaPLayer auxiliary output. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public EnvironmentalReverb(int priority, int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_ENV_REVERB, EFFECT_TYPE_NULL, priority, audioSession); + } + + /** + * Sets the master volume level of the environmental reverb effect. + * @param room room level in millibels. The valid range is [-9000, 0]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setRoomLevel(short room) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(room); + checkStatus(setParameter(PARAM_ROOM_LEVEL, param)); + } + + /** + * Gets the master volume level of the environmental reverb effect. + * @return the room level in millibels. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getRoomLevel() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_ROOM_LEVEL, param)); + return byteArrayToShort(param); + } + + /** + * Sets the volume level at 5 kHz relative to the volume level at low frequencies of the + * overall reverb effect. + * <p>This controls a low-pass filter that will reduce the level of the high-frequency. + * @param roomHF high frequency attenuation level in millibels. The valid range is [-9000, 0]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setRoomHFLevel(short roomHF) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(roomHF); + checkStatus(setParameter(PARAM_ROOM_HF_LEVEL, param)); + } + + /** + * Gets the room HF level. + * @return the room HF level in millibels. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getRoomHFLevel() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_ROOM_HF_LEVEL, param)); + return byteArrayToShort(param); + } + + /** + * Sets the time taken for the level of reverberation to decay by 60 dB. + * @param decayTime decay time in milliseconds. The valid range is [100, 20000]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setDecayTime(int decayTime) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = intToByteArray(decayTime); + checkStatus(setParameter(PARAM_DECAY_TIME, param)); + } + + /** + * Gets the decay time. + * @return the decay time in milliseconds. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public int getDecayTime() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[4]; + checkStatus(getParameter(PARAM_DECAY_TIME, param)); + return byteArrayToInt(param); + } + + /** + * Sets the ratio of high frequency decay time (at 5 kHz) relative to the decay time at low + * frequencies. + * @param decayHFRatio high frequency decay ratio using a permille scale. The valid range is + * [100, 2000]. A ratio of 1000 indicates that all frequencies decay at the same rate. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setDecayHFRatio(short decayHFRatio) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(decayHFRatio); + checkStatus(setParameter(PARAM_DECAY_HF_RATIO, param)); + } + + /** + * Gets the ratio of high frequency decay time (at 5 kHz) relative to low frequencies. + * @return the decay HF ration. See {@link #setDecayHFRatio(short)} for units. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getDecayHFRatio() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_DECAY_HF_RATIO, param)); + return byteArrayToShort(param); + } + + /** + * Sets the volume level of the early reflections. + * <p>This level is combined with the overall room level + * (set using {@link #setRoomLevel(short)}). + * @param reflectionsLevel reflection level in millibels. The valid range is [-9000, 1000]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setReflectionsLevel(short reflectionsLevel) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(reflectionsLevel); + checkStatus(setParameter(PARAM_REFLECTIONS_LEVEL, param)); + } + + /** + * Gets the volume level of the early reflections. + * @return the early reflections level in millibels. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getReflectionsLevel() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_REFLECTIONS_LEVEL, param)); + return byteArrayToShort(param); + } + + /** + * Sets the delay time for the early reflections. + * <p>This method sets the time between when the direct path is heard and when the first + * reflection is heard. + * @param reflectionsDelay reflections delay in milliseconds. The valid range is [0, 300]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setReflectionsDelay(int reflectionsDelay) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = intToByteArray(reflectionsDelay); + checkStatus(setParameter(PARAM_REFLECTIONS_DELAY, param)); + } + + /** + * Gets the reflections delay. + * @return the early reflections delay in milliseconds. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public int getReflectionsDelay() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[4]; + checkStatus(getParameter(PARAM_REFLECTIONS_DELAY, param)); + return byteArrayToInt(param); + } + + /** + * Sets the volume level of the late reverberation. + * <p>This level is combined with the overall room level (set using {@link #setRoomLevel(short)}). + * @param reverbLevel reverb level in millibels. The valid range is [-9000, 2000]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setReverbLevel(short reverbLevel) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(reverbLevel); + checkStatus(setParameter(PARAM_REVERB_LEVEL, param)); + } + + /** + * Gets the reverb level. + * @return the reverb level in millibels. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getReverbLevel() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_REVERB_LEVEL, param)); + return byteArrayToShort(param); + } + + /** + * Sets the time between the first reflection and the reverberation. + * @param reverbDelay reverb delay in milliseconds. The valid range is [0, 100]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setReverbDelay(int reverbDelay) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = intToByteArray(reverbDelay); + checkStatus(setParameter(PARAM_REVERB_DELAY, param)); + } + + /** + * Gets the reverb delay. + * @return the reverb delay in milliseconds. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public int getReverbDelay() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[4]; + checkStatus(getParameter(PARAM_REVERB_DELAY, param)); + return byteArrayToInt(param); + } + + /** + * Sets the echo density in the late reverberation decay. + * <p>The scale should approximately map linearly to the perceived change in reverberation. + * @param diffusion diffusion specified using a permille scale. The diffusion valid range is + * [0, 1000]. A value of 1000 o/oo indicates a smooth reverberation decay. + * Values below this level give a more <i>grainy</i> character. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setDiffusion(short diffusion) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(diffusion); + checkStatus(setParameter(PARAM_DIFFUSION, param)); + } + + /** + * Gets diffusion level. + * @return the diffusion level. See {@link #setDiffusion(short)} for units. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getDiffusion() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_DIFFUSION, param)); + return byteArrayToShort(param); + } + + + /** + * Controls the modal density of the late reverberation decay. + * <p> The scale should approximately map linearly to the perceived change in reverberation. + * A lower density creates a hollow sound that is useful for simulating small reverberation + * spaces such as bathrooms. + * @param density density specified using a permille scale. The valid range is [0, 1000]. + * A value of 1000 o/oo indicates a natural sounding reverberation. Values below this level + * produce a more colored effect. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setDensity(short density) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = shortToByteArray(density); + checkStatus(setParameter(PARAM_DENSITY, param)); + } + + /** + * Gets the density level. + * @return the density level. See {@link #setDiffusion(short)} for units. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getDensity() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[2]; + checkStatus(getParameter(PARAM_DENSITY, param)); + return byteArrayToShort(param); + } + + + /** + * The OnParameterChangeListener interface defines a method called by the EnvironmentalReverb + * when a parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * EnvironmentalReverb engine. + * @param effect the EnvironmentalReverb on which the interface is registered. + * @param status status of the set parameter operation. + * @param param ID of the modified parameter. See {@link #PARAM_ROOM_LEVEL} ... + * @param value the new parameter value. + */ + void onParameterChange(EnvironmentalReverb effect, int status, int param, int value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + OnParameterChangeListener l = null; + + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + int v = -1; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 2) { + v = (int)byteArrayToShort(value, 0); + } else if (value.length == 4) { + v = byteArrayToInt(value, 0); + } + if (p != -1 && v != -1) { + l.onParameterChange(EnvironmentalReverb.this, status, p, v); + } + } + } + } + + /** + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mParamListener = listener; + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + } + } + + /** + * The Settings class regroups all environmental reverb parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short roomLevel; + public short roomHFLevel; + public int decayTime; + public short decayHFRatio; + public short reflectionsLevel; + public int reflectionsDelay; + public short reverbLevel; + public int reverbDelay; + public short diffusion; + public short density; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 21) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("EnvironmentalReverb")) { + throw new IllegalArgumentException( + "invalid settings for EnvironmentalReverb: " + key); + } + + try { + key = st.nextToken(); + if (!key.equals("roomLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + roomLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("roomHFLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + roomHFLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("decayTime")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + decayTime = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("decayHFRatio")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + decayHFRatio = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reflectionsLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reflectionsLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reflectionsDelay")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reflectionsDelay = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reverbLevel")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reverbLevel = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("reverbDelay")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + reverbDelay = Integer.parseInt(st.nextToken()); + key = st.nextToken(); + if (!key.equals("diffusion")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + diffusion = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("density")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + density = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + return new String ( + "EnvironmentalReverb"+ + ";roomLevel="+Short.toString(roomLevel)+ + ";roomHFLevel="+Short.toString(roomHFLevel)+ + ";decayTime="+Integer.toString(decayTime)+ + ";decayHFRatio="+Short.toString(decayHFRatio)+ + ";reflectionsLevel="+Short.toString(reflectionsLevel)+ + ";reflectionsDelay="+Integer.toString(reflectionsDelay)+ + ";reverbLevel="+Short.toString(reverbLevel)+ + ";reverbDelay="+Integer.toString(reverbDelay)+ + ";diffusion="+Short.toString(diffusion)+ + ";density="+Short.toString(density) + ); + } + }; + + // Keep this in sync with sizeof(s_reverb_settings) defined in + // frameworks/base/include/media/EffectEnvironmentalReverbApi.h + static private int PROPERTY_SIZE = 26; + + /** + * Gets the environmental reverb properties. This method is useful when a snapshot of current + * reverb settings must be saved by the application. + * @return an EnvironmentalReverb.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public EnvironmentalReverb.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[PROPERTY_SIZE]; + checkStatus(getParameter(PARAM_PROPERTIES, param)); + Settings settings = new Settings(); + settings.roomLevel = byteArrayToShort(param, 0); + settings.roomHFLevel = byteArrayToShort(param, 2); + settings.decayTime = byteArrayToInt(param, 4); + settings.decayHFRatio = byteArrayToShort(param, 8); + settings.reflectionsLevel = byteArrayToShort(param, 10); + settings.reflectionsDelay = byteArrayToInt(param, 12); + settings.reverbLevel = byteArrayToShort(param, 16); + settings.reverbDelay = byteArrayToInt(param, 18); + settings.diffusion = byteArrayToShort(param, 22); + settings.density = byteArrayToShort(param, 24); + return settings; + } + + /** + * Sets the environmental reverb properties. This method is useful when reverb settings have to + * be applied from a previous backup. + * @param settings a EnvironmentalReverb.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(EnvironmentalReverb.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + + byte[] param = concatArrays(shortToByteArray(settings.roomLevel), + shortToByteArray(settings.roomHFLevel), + intToByteArray(settings.decayTime), + shortToByteArray(settings.decayHFRatio), + shortToByteArray(settings.reflectionsLevel), + intToByteArray(settings.reflectionsDelay), + shortToByteArray(settings.reverbLevel), + intToByteArray(settings.reverbDelay), + shortToByteArray(settings.diffusion), + shortToByteArray(settings.density)); + + checkStatus(setParameter(PARAM_PROPERTIES, param)); + } +} diff --git a/android/media/audiofx/Equalizer.java b/android/media/audiofx/Equalizer.java new file mode 100644 index 00000000..7abada07 --- /dev/null +++ b/android/media/audiofx/Equalizer.java @@ -0,0 +1,559 @@ +/* + * 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.media.audiofx.AudioEffect; +import android.util.Log; + +import java.util.StringTokenizer; + + +/** + * An Equalizer is used to alter the frequency response of a particular music source or of the main + * output mix. + * <p>An application creates an Equalizer object to instantiate and control an Equalizer engine + * in the audio framework. The application can either simply use predefined presets or have a more + * precise control of the gain in each frequency band controlled by the equalizer. + * <p>The methods, parameter types and units exposed by the Equalizer implementation are directly + * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/) + * for the SLEqualizerItf interface. Please refer to this specification for more details. + * <p>To attach the Equalizer to a particular AudioTrack or MediaPlayer, specify the audio session + * ID of this AudioTrack or MediaPlayer when constructing the Equalizer. + * <p>NOTE: attaching an Equalizer to the global audio output mix by use of session 0 is deprecated. + * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio + * effects. + */ + +public class Equalizer extends AudioEffect { + + private final static String TAG = "Equalizer"; + + // These constants must be synchronized with those in + // frameworks/base/include/media/EffectEqualizerApi.h + /** + * Number of bands. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_NUM_BANDS = 0; + /** + * Band level range. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_LEVEL_RANGE = 1; + /** + * Band level. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_BAND_LEVEL = 2; + /** + * Band center frequency. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_CENTER_FREQ = 3; + /** + * Band frequency range. Parameter ID for + * {@link android.media.audiofx.Equalizer.OnParameterChangeListener} + */ + public static final int PARAM_BAND_FREQ_RANGE = 4; + /** + * Band for a given frequency. Parameter ID for OnParameterChangeListener + * + */ + public static final int PARAM_GET_BAND = 5; + /** + * Current preset. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_CURRENT_PRESET = 6; + /** + * Request number of presets. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_GET_NUM_OF_PRESETS = 7; + /** + * Request preset name. Parameter ID for OnParameterChangeListener + */ + public static final int PARAM_GET_PRESET_NAME = 8; + // used by setProperties()/getProperties + private static final int PARAM_PROPERTIES = 9; + /** + * Maximum size for preset name + */ + public static final int PARAM_STRING_SIZE_MAX = 32; + + /** + * Number of bands implemented by Equalizer engine + */ + private short mNumBands = 0; + + /** + * Number of presets implemented by Equalizer engine + */ + private int mNumPresets; + /** + * Names of presets implemented by Equalizer engine + */ + private String[] mPresetNames; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change event from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param priority the priority level requested by the application for controlling the Equalizer + * engine. As the same 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 Equalizer will be + * attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public Equalizer(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching an Equalizer to global output mix is deprecated!"); + } + + getNumberOfBands(); + + mNumPresets = (int)getNumberOfPresets(); + + if (mNumPresets != 0) { + mPresetNames = new String[mNumPresets]; + byte[] value = new byte[PARAM_STRING_SIZE_MAX]; + int[] param = new int[2]; + param[0] = PARAM_GET_PRESET_NAME; + for (int i = 0; i < mNumPresets; i++) { + param[1] = i; + checkStatus(getParameter(param, value)); + int length = 0; + while (value[length] != 0) length++; + try { + mPresetNames[i] = new String(value, 0, length, "ISO-8859-1"); + } catch (java.io.UnsupportedEncodingException e) { + Log.e(TAG, "preset name decode error"); + } + } + } + } + + /** + * Gets the number of frequency bands supported by the Equalizer engine. + * @return the number of bands + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getNumberOfBands() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + if (mNumBands != 0) { + return mNumBands; + } + int[] param = new int[1]; + param[0] = PARAM_NUM_BANDS; + short[] result = new short[1]; + checkStatus(getParameter(param, result)); + mNumBands = result[0]; + return mNumBands; + } + + /** + * Gets the level range for use by {@link #setBandLevel(short,short)}. The level is expressed in + * milliBel. + * @return the band level range in an array of short integers. The first element is the lower + * limit of the range, the second element the upper limit. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short[] getBandLevelRange() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] result = new short[2]; + checkStatus(getParameter(PARAM_LEVEL_RANGE, result)); + return result; + } + + /** + * Sets the given equalizer band to the given gain value. + * @param band frequency band that will have the new gain. The numbering of the bands starts + * from 0 and ends at (number of bands - 1). + * @param level new gain in millibels that will be set to the given band. getBandLevelRange() + * will define the maximum and minimum values. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + * @see #getNumberOfBands() + */ + public void setBandLevel(short band, short level) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] param = new int[2]; + short[] value = new short[1]; + + param[0] = PARAM_BAND_LEVEL; + param[1] = (int)band; + value[0] = level; + checkStatus(setParameter(param, value)); + } + + /** + * Gets the gain set for the given equalizer band. + * @param band frequency band whose gain is requested. The numbering of the bands starts + * from 0 and ends at (number of bands - 1). + * @return the gain in millibels of the given band. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getBandLevel(short band) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] param = new int[2]; + short[] result = new short[1]; + + param[0] = PARAM_BAND_LEVEL; + param[1] = (int)band; + checkStatus(getParameter(param, result)); + + return result[0]; + } + + + /** + * Gets the center frequency of the given band. + * @param band frequency band whose center frequency is requested. The numbering of the bands + * starts from 0 and ends at (number of bands - 1). + * @return the center frequency in milliHertz + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public int getCenterFreq(short band) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] param = new int[2]; + int[] result = new int[1]; + + param[0] = PARAM_CENTER_FREQ; + param[1] = (int)band; + checkStatus(getParameter(param, result)); + + return result[0]; + } + + /** + * Gets the frequency range of the given frequency band. + * @param band frequency band whose frequency range is requested. The numbering of the bands + * starts from 0 and ends at (number of bands - 1). + * @return the frequency range in millHertz in an array of integers. The first element is the + * lower limit of the range, the second element the upper limit. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public int[] getBandFreqRange(short band) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] param = new int[2]; + int[] result = new int[2]; + param[0] = PARAM_BAND_FREQ_RANGE; + param[1] = (int)band; + checkStatus(getParameter(param, result)); + + return result; + } + + /** + * Gets the band that has the most effect on the given frequency. + * @param frequency frequency in milliHertz which is to be equalized via the returned band. + * @return the frequency band that has most effect on the given frequency. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getBand(int frequency) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] param = new int[2]; + short[] result = new short[1]; + + param[0] = PARAM_GET_BAND; + param[1] = frequency; + checkStatus(getParameter(param, result)); + + return result[0]; + } + + /** + * Gets current preset. + * @return the preset that is set at the moment. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getCurrentPreset() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] result = new short[1]; + checkStatus(getParameter(PARAM_CURRENT_PRESET, result)); + return result[0]; + } + + /** + * Sets the equalizer according to the given preset. + * @param preset new preset that will be taken into use. The valid range is [0, + * number of presets-1]. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + * @see #getNumberOfPresets() + */ + public void usePreset(short preset) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_CURRENT_PRESET, preset)); + } + + /** + * Gets the total number of presets the equalizer supports. The presets will have indices + * [0, number of presets-1]. + * @return the number of presets the equalizer supports. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getNumberOfPresets() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] result = new short[1]; + checkStatus(getParameter(PARAM_GET_NUM_OF_PRESETS, result)); + return result[0]; + } + + /** + * Gets the preset name based on the index. + * @param preset index of the preset. The valid range is [0, number of presets-1]. + * @return a string containing the name of the given preset. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public String getPresetName(short preset) + { + if (preset >= 0 && preset < mNumPresets) { + return mPresetNames[preset]; + } else { + return ""; + } + } + + /** + * The OnParameterChangeListener interface defines a method called by the Equalizer when a + * parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * Equalizer engine. + * @param effect the Equalizer on which the interface is registered. + * @param status status of the set parameter operation. + * @param param1 ID of the modified parameter. See {@link #PARAM_BAND_LEVEL} ... + * @param param2 additional parameter qualifier (e.g the band for band level parameter). + * @param value the new parameter value. + */ + void onParameterChange(Equalizer effect, int status, int param1, int param2, int value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + OnParameterChangeListener l = null; + + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p1 = -1; + int p2 = -1; + int v = -1; + + if (param.length >= 4) { + p1 = byteArrayToInt(param, 0); + if (param.length >= 8) { + p2 = byteArrayToInt(param, 4); + } + } + if (value.length == 2) { + v = (int)byteArrayToShort(value, 0);; + } else if (value.length == 4) { + v = byteArrayToInt(value, 0); + } + + if (p1 != -1 && v != -1) { + l.onParameterChange(Equalizer.this, status, p1, p2, v); + } + } + } + } + + /** + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mParamListener = listener; + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + } + } + + /** + * The Settings class regroups all equalizer parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short curPreset; + public short numBands = 0; + public short[] bandLevels = null; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() < 5) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("Equalizer")) { + throw new IllegalArgumentException( + "invalid settings for Equalizer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("curPreset")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + curPreset = Short.parseShort(st.nextToken()); + key = st.nextToken(); + if (!key.equals("numBands")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + numBands = Short.parseShort(st.nextToken()); + if (st.countTokens() != numBands*2) { + throw new IllegalArgumentException("settings: " + settings); + } + bandLevels = new short[numBands]; + for (int i = 0; i < numBands; i++) { + key = st.nextToken(); + if (!key.equals("band"+(i+1)+"Level")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + bandLevels[i] = Short.parseShort(st.nextToken()); + } + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + + String str = new String ( + "Equalizer"+ + ";curPreset="+Short.toString(curPreset)+ + ";numBands="+Short.toString(numBands) + ); + for (int i = 0; i < numBands; i++) { + str = str.concat(";band"+(i+1)+"Level="+Short.toString(bandLevels[i])); + } + return str; + } + }; + + + /** + * Gets the equalizer properties. This method is useful when a snapshot of current + * equalizer settings must be saved by the application. + * @return an Equalizer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public Equalizer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + byte[] param = new byte[4 + mNumBands * 2]; + checkStatus(getParameter(PARAM_PROPERTIES, param)); + Settings settings = new Settings(); + settings.curPreset = byteArrayToShort(param, 0); + settings.numBands = byteArrayToShort(param, 2); + settings.bandLevels = new short[mNumBands]; + for (int i = 0; i < mNumBands; i++) { + settings.bandLevels[i] = byteArrayToShort(param, 4 + 2*i); + } + return settings; + } + + /** + * Sets the equalizer properties. This method is useful when equalizer settings have to + * be applied from a previous backup. + * @param settings an Equalizer.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(Equalizer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + if (settings.numBands != settings.bandLevels.length || + settings.numBands != mNumBands) { + throw new IllegalArgumentException("settings invalid band count: " +settings.numBands); + } + + byte[] param = concatArrays(shortToByteArray(settings.curPreset), + shortToByteArray(mNumBands)); + for (int i = 0; i < mNumBands; i++) { + param = concatArrays(param, + shortToByteArray(settings.bandLevels[i])); + } + checkStatus(setParameter(PARAM_PROPERTIES, param)); + } +} diff --git a/android/media/audiofx/LoudnessEnhancer.java b/android/media/audiofx/LoudnessEnhancer.java new file mode 100644 index 00000000..7dc41753 --- /dev/null +++ b/android/media/audiofx/LoudnessEnhancer.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2013 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.media.AudioTrack; +import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.util.Log; + +import java.util.StringTokenizer; + + +/** + * LoudnessEnhancer is an audio effect for increasing audio loudness. + * The processing is parametrized by a target gain value, which determines the maximum amount + * by which an audio signal will be amplified; signals amplified outside of the sample + * range supported by the platform are compressed. + * An application creates a LoudnessEnhancer object to instantiate and control a + * this audio effect in the audio framework. + * To attach the LoudnessEnhancer to a particular AudioTrack or MediaPlayer, + * specify the audio session ID of this AudioTrack or MediaPlayer when constructing the effect + * (see {@link AudioTrack#getAudioSessionId()} and {@link MediaPlayer#getAudioSessionId()}). + */ + +public class LoudnessEnhancer extends AudioEffect { + + private final static String TAG = "LoudnessEnhancer"; + + // These parameter constants must be synchronized with those in + // /system/media/audio_effects/include/audio_effects/effect_loudnessenhancer.h + /** + * The maximum gain applied applied to the signal to process. + * It is expressed in millibels (100mB = 1dB) where 0mB corresponds to no amplification. + */ + public static final int PARAM_TARGET_GAIN_MB = 0; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change events + * from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param audioSession system-wide unique audio session identifier. The LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, 0, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * @hide + * Class constructor for the LoudnessEnhancer audio effect. + * @param priority the priority level requested by the application for controlling the + * LoudnessEnhancer engine. As the same 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 LoudnessEnhancer + * will be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public LoudnessEnhancer(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_LOUDNESS_ENHANCER, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a LoudnessEnhancer to global output mix is deprecated!"); + } + } + + /** + * Set the target gain for the audio effect. + * The target gain is the maximum value by which a sample value will be amplified when the + * effect is enabled. + * @param gainmB the effect target gain expressed in mB. 0mB corresponds to no amplification. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setTargetGain(int gainmB) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, gainmB)); + } + + /** + * Return the target gain. + * @return the effect target gain expressed in mB. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public float getTargetGain() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + return value[0]; + } + + /** + * @hide + * The OnParameterChangeListener interface defines a method called by the LoudnessEnhancer + * when a parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * LoudnessEnhancer engine. + * @param effect the LoudnessEnhancer on which the interface is registered. + * @param param ID of the modified parameter. See {@link #PARAM_GENERIC_PARAM1} ... + * @param value the new parameter value. + */ + void onParameterChange(LoudnessEnhancer effect, int param, int value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + // only notify when the parameter was successfully change + if (status != AudioEffect.SUCCESS) { + return; + } + OnParameterChangeListener l = null; + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + int v = Integer.MIN_VALUE; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 4) { + v = byteArrayToInt(value, 0); + } + if (p != -1 && v != Integer.MIN_VALUE) { + l.onParameterChange(LoudnessEnhancer.this, p, v); + } + } + } + } + + /** + * @hide + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + mParamListener = listener; + } + } + + /** + * @hide + * The Settings class regroups the LoudnessEnhancer parameters. It is used in + * conjunction with the getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public int targetGainmB; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + //int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("LoudnessEnhancer")) { + throw new IllegalArgumentException( + "invalid settings for LoudnessEnhancer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("targetGainmB")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + targetGainmB = Integer.parseInt(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "LoudnessEnhancer"+ + ";targetGainmB="+Integer.toString(targetGainmB) + ); + return str; + } + }; + + + /** + * @hide + * Gets the LoudnessEnhancer properties. This method is useful when a snapshot of current + * effect settings must be saved by the application. + * @return a LoudnessEnhancer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public LoudnessEnhancer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + int[] value = new int[1]; + checkStatus(getParameter(PARAM_TARGET_GAIN_MB, value)); + settings.targetGainmB = value[0]; + return settings; + } + + /** + * @hide + * Sets the LoudnessEnhancer properties. This method is useful when bass boost settings + * have to be applied from a previous backup. + * @param settings a LoudnessEnhancer.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(LoudnessEnhancer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_TARGET_GAIN_MB, settings.targetGainmB)); + } +} diff --git a/android/media/audiofx/NoiseSuppressor.java b/android/media/audiofx/NoiseSuppressor.java new file mode 100644 index 00000000..70cc87cc --- /dev/null +++ b/android/media/audiofx/NoiseSuppressor.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011 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.util.Log; + +/** + * Noise Suppressor (NS). + * <p>Noise suppression (NS) is an audio pre-processor which removes background noise from the + * captured signal. The component of the signal considered as noise can be either stationary + * (car/airplane engine, AC system) or non-stationary (other peoples conversations, car horn) for + * more advanced implementations. + * <p>NS is mostly used by voice communication applications (voice chat, video conferencing, + * SIP calls). + * <p>An application creates a NoiseSuppressor object to instantiate and control an NS + * engine in the audio framework. + * <p>To attach the NoiseSuppressor to a particular {@link android.media.AudioRecord}, + * specify the audio session ID of this AudioRecord when creating the NoiseSuppressor. + * The audio session is retrieved by calling + * {@link android.media.AudioRecord#getAudioSessionId()} on the AudioRecord instance. + * <p>On some devices, NS can be inserted by default in the capture path by the platform + * according to the {@link android.media.MediaRecorder.AudioSource} used. The application should + * call NoiseSuppressor.getEnable() after creating the NS to check the default NS activation + * state on a particular AudioRecord session. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on + * controlling audio effects. + */ + +public class NoiseSuppressor extends AudioEffect { + + private final static String TAG = "NoiseSuppressor"; + + /** + * Checks if the device implements noise suppression. + * @return true if the device implements noise suppression, false otherwise. + */ + public static boolean isAvailable() { + return AudioEffect.isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_NS); + } + + /** + * Creates a NoiseSuppressor and attaches it to the AudioRecord on the audio + * session specified. + * @param audioSession system wide unique audio session identifier. The NoiseSuppressor + * will be applied to the AudioRecord with the same audio session. + * @return NoiseSuppressor created or null if the device does not implement noise + * suppression. + */ + public static NoiseSuppressor create(int audioSession) { + NoiseSuppressor ns = null; + try { + ns = new NoiseSuppressor(audioSession); + } catch (IllegalArgumentException e) { + Log.w(TAG, "not implemented on this device "+ns); + } catch (UnsupportedOperationException e) { + Log.w(TAG, "not enough resources"); + } catch (RuntimeException e) { + Log.w(TAG, "not enough memory"); + } + return ns; + } + + /** + * Class constructor. + * <p> The constructor is not guarantied to succeed and throws the following exceptions: + * <ul> + * <li>IllegalArgumentException is thrown if the device does not implement an NS</li> + * <li>UnsupportedOperationException is thrown is the resources allocated to audio + * pre-procesing are currently exceeded.</li> + * <li>RuntimeException is thrown if a memory allocation error occurs.</li> + * </ul> + * + * @param audioSession system wide unique audio session identifier. The NoiseSuppressor + * will be applied to the AudioRecord with the same audio session. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + private NoiseSuppressor(int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_NS, EFFECT_TYPE_NULL, 0, audioSession); + } +} diff --git a/android/media/audiofx/PresetReverb.java b/android/media/audiofx/PresetReverb.java new file mode 100644 index 00000000..ef916678 --- /dev/null +++ b/android/media/audiofx/PresetReverb.java @@ -0,0 +1,303 @@ +/* + * 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.media.audiofx.AudioEffect; +import java.util.StringTokenizer; + + +/** + * A sound generated within a room travels in many directions. The listener first hears the + * direct sound from the source itself. Later, he or she hears discrete echoes caused by sound + * bouncing off nearby walls, the ceiling and the floor. As sound waves arrive after + * undergoing more and more reflections, individual reflections become indistinguishable and + * the listener hears continuous reverberation that decays over time. + * Reverb is vital for modeling a listener's environment. It can be used in music applications + * to simulate music being played back in various environments, or in games to immerse the + * listener within the game's environment. + * The PresetReverb class allows an application to configure the global reverb using a reverb preset. + * This is primarily used for adding some reverb in a music playback context. Applications + * requiring control over a more advanced environmental reverb are advised to use the + * {@link android.media.audiofx.EnvironmentalReverb} class. + * <p>An application creates a PresetReverb object to instantiate and control a reverb engine in the + * audio framework. + * <p>The methods, parameter types and units exposed by the PresetReverb implementation are + * directly mapping those defined by the OpenSL ES 1.0.1 Specification + * (http://www.khronos.org/opensles/) for the SLPresetReverbItf interface. + * Please refer to this specification for more details. + * <p>The PresetReverb is an output mix auxiliary effect and should be created on + * Audio session 0. In order for a MediaPlayer or AudioTrack to be fed into this effect, + * they must be explicitely attached to it and a send level must be specified. Use the effect ID + * returned by getId() method to designate this particular effect when attaching it to the + * MediaPlayer or AudioTrack. + * <p>Creating a reverb on the output mix (audio session 0) requires permission + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling + * audio effects. + */ + +public class PresetReverb extends AudioEffect { + + private final static String TAG = "PresetReverb"; + + // These constants must be synchronized with those in + // frameworks/base/include/media/EffectPresetReverbApi.h + + /** + * Preset. Parameter ID for + * {@link android.media.audiofx.PresetReverb.OnParameterChangeListener} + */ + public static final int PARAM_PRESET = 0; + + /** + * No reverb or reflections + */ + public static final short PRESET_NONE = 0; + /** + * Reverb preset representing a small room less than five meters in length + */ + public static final short PRESET_SMALLROOM = 1; + /** + * Reverb preset representing a medium room with a length of ten meters or less + */ + public static final short PRESET_MEDIUMROOM = 2; + /** + * Reverb preset representing a large-sized room suitable for live performances + */ + public static final short PRESET_LARGEROOM = 3; + /** + * Reverb preset representing a medium-sized hall + */ + public static final short PRESET_MEDIUMHALL = 4; + /** + * Reverb preset representing a large-sized hall suitable for a full orchestra + */ + public static final short PRESET_LARGEHALL = 5; + /** + * Reverb preset representing a synthesis of the traditional plate reverb + */ + public static final short PRESET_PLATE = 6; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change event from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param priority the priority level requested by the application for controlling the + * PresetReverb engine. As the same 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. If audioSession + * is not 0, the PresetReverb will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the PresetReverb will apply to the output mix. + * As the PresetReverb is an auxiliary effect it is recommended to instantiate it on + * audio session 0 and to attach it to the MediaPLayer auxiliary output. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public PresetReverb(int priority, int audioSession) + throws IllegalArgumentException, UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_PRESET_REVERB, EFFECT_TYPE_NULL, priority, audioSession); + } + + /** + * Enables a preset on the reverb. + * <p>The reverb PRESET_NONE disables any reverb from the current output but does not free the + * resources associated with the reverb. For an application to signal to the implementation + * to free the resources, it must call the release() method. + * @param preset this must be one of the the preset constants defined in this class. + * e.g. {@link #PRESET_SMALLROOM} + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setPreset(short preset) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_PRESET, preset)); + } + + /** + * Gets current reverb preset. + * @return the preset that is set at the moment. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getPreset() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] value = new short[1]; + checkStatus(getParameter(PARAM_PRESET, value)); + return value[0]; + } + + /** + * The OnParameterChangeListener interface defines a method called by the PresetReverb + * when a parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * PresetReverb engine. + * @param effect the PresetReverb on which the interface is registered. + * @param status status of the set parameter operation. + * @param param ID of the modified parameter. See {@link #PARAM_PRESET} ... + * @param value the new parameter value. + */ + void onParameterChange(PresetReverb effect, int status, int param, short value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + OnParameterChangeListener l = null; + + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + short v = -1; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 2) { + v = byteArrayToShort(value, 0); + } + if (p != -1 && v != -1) { + l.onParameterChange(PresetReverb.this, status, p, v); + } + } + } + } + + /** + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mParamListener = listener; + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + } + } + + /** + * The Settings class regroups all preset reverb parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short preset; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("PresetReverb")) { + throw new IllegalArgumentException( + "invalid settings for PresetReverb: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("preset")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + preset = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "PresetReverb"+ + ";preset="+Short.toString(preset) + ); + return str; + } + }; + + + /** + * Gets the preset reverb properties. This method is useful when a snapshot of current + * preset reverb settings must be saved by the application. + * @return a PresetReverb.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public PresetReverb.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_PRESET, value)); + settings.preset = value[0]; + return settings; + } + + /** + * Sets the preset reverb properties. This method is useful when preset reverb settings have to + * be applied from a previous backup. + * @param settings a PresetReverb.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(PresetReverb.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_PRESET, settings.preset)); + } +} diff --git a/android/media/audiofx/Virtualizer.java b/android/media/audiofx/Virtualizer.java new file mode 100644 index 00000000..74b6fc13 --- /dev/null +++ b/android/media/audiofx/Virtualizer.java @@ -0,0 +1,629 @@ +/* + * 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.IntDef; +import android.media.AudioDeviceInfo; +import android.media.AudioFormat; +import android.media.audiofx.AudioEffect; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.StringTokenizer; + + +/** + * An audio virtualizer is a general name for an effect to spatialize audio channels. The exact + * behavior of this effect is dependent on the number of audio input channels and the types and + * number of audio output channels of the device. For example, in the case of a stereo input and + * stereo headphone output, a stereo widening effect is used when this effect is turned on. + * <p>An application creates a Virtualizer object to instantiate and control a virtualizer engine + * in the audio framework. + * <p>The methods, parameter types and units exposed by the Virtualizer implementation are directly + * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/) + * for the SLVirtualizerItf interface. Please refer to this specification for more details. + * <p>To attach the Virtualizer to a particular AudioTrack or MediaPlayer, specify the audio session + * ID of this AudioTrack or MediaPlayer when constructing the Virtualizer. + * <p>NOTE: attaching a Virtualizer to the global audio output mix by use of session 0 is + * deprecated. + * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions. + * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling + * audio effects. + */ + +public class Virtualizer extends AudioEffect { + + private final static String TAG = "Virtualizer"; + private final static boolean DEBUG = false; + + // These constants must be synchronized with those in + // system/media/audio_effects/include/audio_effects/effect_virtualizer.h + /** + * Is strength parameter supported by virtualizer engine. Parameter ID for getParameter(). + */ + public static final int PARAM_STRENGTH_SUPPORTED = 0; + /** + * Virtualizer effect strength. Parameter ID for + * {@link android.media.audiofx.Virtualizer.OnParameterChangeListener} + */ + public static final int PARAM_STRENGTH = 1; + /** + * @hide + * Parameter ID to query the virtual speaker angles for a channel mask / device configuration. + */ + public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2; + /** + * @hide + * Parameter ID to force the virtualization mode to be that of a specific device + */ + public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3; + /** + * @hide + * Parameter ID to query the current virtualization mode. + */ + public static final int PARAM_VIRTUALIZATION_MODE = 4; + + /** + * Indicates if strength parameter is supported by the virtualizer engine + */ + private boolean mStrengthSupported = false; + + /** + * Registered listener for parameter changes. + */ + private OnParameterChangeListener mParamListener = null; + + /** + * Listener used internally to to receive raw parameter change event from AudioEffect super class + */ + private BaseParameterListener mBaseParamListener = null; + + /** + * Lock for access to mParamListener + */ + private final Object mParamListenerLock = new Object(); + + /** + * Class constructor. + * @param priority the priority level requested by the application for controlling the Virtualizer + * engine. As the same 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 Virtualizer will + * be attached to the MediaPlayer or AudioTrack in the same audio session. + * + * @throws java.lang.IllegalStateException + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + public Virtualizer(int priority, int audioSession) + throws IllegalStateException, IllegalArgumentException, + UnsupportedOperationException, RuntimeException { + super(EFFECT_TYPE_VIRTUALIZER, EFFECT_TYPE_NULL, priority, audioSession); + + if (audioSession == 0) { + Log.w(TAG, "WARNING: attaching a Virtualizer to global output mix is deprecated!"); + } + + int[] value = new int[1]; + checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value)); + mStrengthSupported = (value[0] != 0); + } + + /** + * Indicates whether setting strength is supported. If this method returns false, only one + * strength is supported and the setStrength() method always rounds to that value. + * @return true is strength parameter is supported, false otherwise + */ + public boolean getStrengthSupported() { + return mStrengthSupported; + } + + /** + * Sets the strength of the virtualizer effect. If the implementation does not support per mille + * accuracy for setting the strength, it is allowed to round the given strength to the nearest + * supported value. You can use the {@link #getRoundedStrength()} method to query the + * (possibly rounded) value that was actually set. + * @param strength strength of the effect. The valid range for strength strength is [0, 1000], + * where 0 per mille designates the mildest effect and 1000 per mille designates the strongest. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setStrength(short strength) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, strength)); + } + + /** + * Gets the current strength of the effect. + * @return the strength of the effect. The valid range for strength is [0, 1000], where 0 per + * mille designates the mildest effect and 1000 per mille the strongest + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public short getRoundedStrength() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + return value[0]; + } + + /** + * Checks if a configuration is supported, and query the virtual speaker angles. + * @param inputChannelMask + * @param deviceType + * @param angles if non-null: array in which the angles will be written. If null, no angles + * are returned + * @return true if the combination of channel mask and output device type is supported, false + * otherwise + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + // parameter check + if (inputChannelMask == AudioFormat.CHANNEL_INVALID) { + throw (new IllegalArgumentException( + "Virtualizer: illegal CHANNEL_INVALID channel mask")); + } + int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ? + AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask; + int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask); + if ((angles != null) && (angles.length < (nbChannels * 3))) { + Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask (" + + nbChannels + ")"); + throw (new IllegalArgumentException( + "Virtualizer: array for channel / angle pairs is too small: is " + angles.length + + ", should be " + (nbChannels * 3))); + } + + ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4); + paramsConverter.order(ByteOrder.nativeOrder()); + paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES); + // convert channel mask to internal native representation + paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask)); + // convert Java device type to internal representation + paramsConverter.putInt(AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType)); + // allocate an array to store the results + byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/]; + + // call into the effect framework + int status = getParameter(paramsConverter.array(), result); + if (DEBUG) { + Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x" + + Integer.toHexString(deviceType) + ") returns " + status); + } + + if (status >= 0) { + if (angles != null) { + // convert and copy the results + ByteBuffer resultConverter = ByteBuffer.wrap(result); + resultConverter.order(ByteOrder.nativeOrder()); + for (int i = 0 ; i < nbChannels ; i++) { + // write the channel mask + angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask( + resultConverter.getInt((i * 4 * 3))); + // write the azimuth + angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4); + // write the elevation + angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8); + if (DEBUG) { + Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase() + + " at az=" + angles[3*i+1] + "deg" + + " elev=" + angles[3*i+2] + "deg"); + } + } + } + return true; + } else if (status == AudioEffect.ERROR_BAD_VALUE) { + // a BAD_VALUE return from getParameter indicates the configuration is not supported + // don't throw an exception, just return false + return false; + } else { + // something wrong may have happened + checkStatus(status); + } + // unexpected virtualizer behavior + Log.e(TAG, "unexpected status code " + status + + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)"); + return false; + } + + /** + * A virtualization mode indicating virtualization processing is not active. + * See {@link #getVirtualizationMode()} as one of the possible return value. + */ + public static final int VIRTUALIZATION_MODE_OFF = 0; + + /** + * A virtualization mode used to indicate the virtualizer effect must stop forcing the + * processing to a particular mode in {@link #forceVirtualizationMode(int)}. + */ + public static final int VIRTUALIZATION_MODE_AUTO = 1; + /** + * A virtualization mode typically used over headphones. + * Binaural virtualization describes an audio processing configuration for virtualization + * where the left and right channels are respectively reaching the left and right ear of the + * user, without also feeding the opposite ear (as is the case when listening over speakers). + * <p>Such a mode is therefore meant to be used when audio is playing over stereo wired + * headphones or headsets, but also stereo headphones through a wireless A2DP Bluetooth link. + * <p>See {@link #canVirtualize(int, int)} to verify this mode is supported by this Virtualizer. + */ + public final static int VIRTUALIZATION_MODE_BINAURAL = 2; + + /** + * A virtualization mode typically used over speakers. + * Transaural virtualization describes an audio processing configuration that differs from + * binaural (as described in {@link #VIRTUALIZATION_MODE_BINAURAL} in that cross-talk is + * present, i.e. audio played from the left channel also reaches the right ear of the user, + * and vice-versa. + * <p>When supported, such a mode is therefore meant to be used when audio is playing over the + * built-in stereo speakers of a device, if they are featured. + * <p>See {@link #canVirtualize(int, int)} to verify this mode is supported by this Virtualizer. + */ + public final static int VIRTUALIZATION_MODE_TRANSAURAL = 3; + + /** @hide */ + @IntDef( { + VIRTUALIZATION_MODE_BINAURAL, + VIRTUALIZATION_MODE_TRANSAURAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface VirtualizationMode {} + + /** @hide */ + @IntDef( { + VIRTUALIZATION_MODE_AUTO, + VIRTUALIZATION_MODE_BINAURAL, + VIRTUALIZATION_MODE_TRANSAURAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ForceVirtualizationMode {} + + private static int getDeviceForModeQuery(@VirtualizationMode int virtualizationMode) + throws IllegalArgumentException { + switch (virtualizationMode) { + case VIRTUALIZATION_MODE_BINAURAL: + return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; + case VIRTUALIZATION_MODE_TRANSAURAL: + return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + default: + throw (new IllegalArgumentException( + "Virtualizer: illegal virtualization mode " + virtualizationMode)); + } + } + + private static int getDeviceForModeForce(@ForceVirtualizationMode int virtualizationMode) + throws IllegalArgumentException { + if (virtualizationMode == VIRTUALIZATION_MODE_AUTO) { + return AudioDeviceInfo.TYPE_UNKNOWN; + } else { + return getDeviceForModeQuery(virtualizationMode); + } + } + + private static int deviceToMode(int deviceType) { + switch (deviceType) { + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + case AudioDeviceInfo.TYPE_BLUETOOTH_SCO: + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + case AudioDeviceInfo.TYPE_USB_HEADSET: + return VIRTUALIZATION_MODE_BINAURAL; + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + case AudioDeviceInfo.TYPE_LINE_ANALOG: + case AudioDeviceInfo.TYPE_LINE_DIGITAL: + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_HDMI_ARC: + case AudioDeviceInfo.TYPE_USB_DEVICE: + case AudioDeviceInfo.TYPE_USB_ACCESSORY: + case AudioDeviceInfo.TYPE_DOCK: + case AudioDeviceInfo.TYPE_FM: + case AudioDeviceInfo.TYPE_AUX_LINE: + return VIRTUALIZATION_MODE_TRANSAURAL; + case AudioDeviceInfo.TYPE_UNKNOWN: + default: + return VIRTUALIZATION_MODE_OFF; + } + } + + /** + * Checks if the combination of a channel mask and virtualization mode is supported by this + * virtualizer. + * Some virtualizer implementations may only support binaural processing (i.e. only support + * headphone output, see {@link #VIRTUALIZATION_MODE_BINAURAL}), some may support transaural + * processing (i.e. for speaker output, see {@link #VIRTUALIZATION_MODE_TRANSAURAL}) for the + * built-in speakers. Use this method to query the virtualizer implementation capabilities. + * @param inputChannelMask the channel mask of the content to virtualize. + * @param virtualizationMode the mode for which virtualization processing is to be performed, + * one of {@link #VIRTUALIZATION_MODE_BINAURAL}, {@link #VIRTUALIZATION_MODE_TRANSAURAL}. + * @return true if the combination of channel mask and virtualization mode is supported, false + * otherwise. + * <br>An indication that a certain channel mask is not supported doesn't necessarily mean + * you cannot play content with that channel mask, it more likely implies the content will + * be downmixed before being virtualized. For instance a virtualizer that only supports a + * mask such as {@link AudioFormat#CHANNEL_OUT_STEREO} + * will still be able to process content with a mask of + * {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and + * then will virtualize, as opposed to virtualizing each channel individually. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public boolean canVirtualize(int inputChannelMask, @VirtualizationMode int virtualizationMode) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + return getAnglesInt(inputChannelMask, getDeviceForModeQuery(virtualizationMode), null); + } + + /** + * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel + * mask and virtualization mode. + * If the virtualization configuration (mask and mode) is supported (see + * {@link #canVirtualize(int, int)}, the array angles will contain upon return the + * definition of each virtual speaker and its azimuth and elevation angles relative to the + * listener. + * <br>Note that in some virtualizer implementations, the angles may be strength-dependent. + * @param inputChannelMask the channel mask of the content to virtualize. + * @param virtualizationMode the mode for which virtualization processing is to be performed, + * one of {@link #VIRTUALIZATION_MODE_BINAURAL}, {@link #VIRTUALIZATION_MODE_TRANSAURAL}. + * @param angles a non-null array whose length is 3 times the number of channels in the channel + * mask. + * If the method indicates the configuration is supported, the array will contain upon return + * triplets of values: for each channel <code>i</code> among the channels of the mask: + * <ul> + * <li>the element at index <code>3*i</code> in the array contains the speaker + * identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li> + * <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle + * expressed in degrees, where 0 is the direction the listener faces, 180 is behind + * the listener, and -90 is to her/his left,</li> + * <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle + * where +90 is directly above the listener, 0 is the horizontal plane, and -90 is + * directly below the listener.</li> + * @return true if the combination of channel mask and virtualization mode is supported, false + * otherwise. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public boolean getSpeakerAngles(int inputChannelMask, + @VirtualizationMode int virtualizationMode, int[] angles) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + if (angles == null) { + throw (new IllegalArgumentException( + "Virtualizer: illegal null channel / angle array")); + } + + return getAnglesInt(inputChannelMask, getDeviceForModeQuery(virtualizationMode), angles); + } + + /** + * Forces the virtualizer effect to use the given processing mode. + * The effect must be enabled for the forced mode to be applied. + * @param virtualizationMode one of {@link #VIRTUALIZATION_MODE_BINAURAL}, + * {@link #VIRTUALIZATION_MODE_TRANSAURAL} to force a particular processing mode, or + * {@value #VIRTUALIZATION_MODE_AUTO} to stop forcing a mode. + * @return true if the processing mode is supported, and it is successfully set, or + * forcing was successfully disabled, false otherwise. + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public boolean forceVirtualizationMode(@ForceVirtualizationMode int virtualizationMode) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + // convert Java device type to internal representation + int deviceType = getDeviceForModeForce(virtualizationMode); + int internalDevice = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType); + + int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice); + + if (status >= 0) { + return true; + } else if (status == AudioEffect.ERROR_BAD_VALUE) { + // a BAD_VALUE return from setParameter indicates the mode can't be forced + // don't throw an exception, just return false + return false; + } else { + // something wrong may have happened + checkStatus(status); + } + // unexpected virtualizer behavior + Log.e(TAG, "unexpected status code " + status + + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)"); + return false; + } + + /** + * Return the virtualization mode being used, if any. + * @return the virtualization mode being used. + * If virtualization is not active, the virtualization mode will be + * {@link #VIRTUALIZATION_MODE_OFF}. Otherwise the value will be + * {@link #VIRTUALIZATION_MODE_BINAURAL} or {@link #VIRTUALIZATION_MODE_TRANSAURAL}. + * Virtualization may not be active either because the effect is not enabled or + * because the current output device is not compatible with this virtualization + * implementation. + * @throws IllegalStateException + * @throws UnsupportedOperationException + */ + public int getVirtualizationMode() + throws IllegalStateException, UnsupportedOperationException { + int[] value = new int[1]; + int status = getParameter(PARAM_VIRTUALIZATION_MODE, value); + if (status >= 0) { + return deviceToMode(AudioDeviceInfo.convertInternalDeviceToDeviceType(value[0])); + } else if (status == AudioEffect.ERROR_BAD_VALUE) { + return VIRTUALIZATION_MODE_OFF; + } else { + // something wrong may have happened + checkStatus(status); + } + // unexpected virtualizer behavior + Log.e(TAG, "unexpected status code " + status + + " after getParameter(PARAM_VIRTUALIZATION_MODE)"); + return VIRTUALIZATION_MODE_OFF; + } + + /** + * The OnParameterChangeListener interface defines a method called by the Virtualizer when a + * parameter value has changed. + */ + public interface OnParameterChangeListener { + /** + * Method called when a parameter value has changed. The method is called only if the + * parameter was changed by another application having the control of the same + * Virtualizer engine. + * @param effect the Virtualizer on which the interface is registered. + * @param status status of the set parameter operation. + * @param param ID of the modified parameter. See {@link #PARAM_STRENGTH} ... + * @param value the new parameter value. + */ + void onParameterChange(Virtualizer effect, int status, int param, short value); + } + + /** + * Listener used internally to receive unformatted parameter change events from AudioEffect + * super class. + */ + private class BaseParameterListener implements AudioEffect.OnParameterChangeListener { + private BaseParameterListener() { + + } + public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) { + OnParameterChangeListener l = null; + + synchronized (mParamListenerLock) { + if (mParamListener != null) { + l = mParamListener; + } + } + if (l != null) { + int p = -1; + short v = -1; + + if (param.length == 4) { + p = byteArrayToInt(param, 0); + } + if (value.length == 2) { + v = byteArrayToShort(value, 0); + } + if (p != -1 && v != -1) { + l.onParameterChange(Virtualizer.this, status, p, v); + } + } + } + } + + /** + * Registers an OnParameterChangeListener interface. + * @param listener OnParameterChangeListener interface registered + */ + public void setParameterListener(OnParameterChangeListener listener) { + synchronized (mParamListenerLock) { + if (mParamListener == null) { + mParamListener = listener; + mBaseParamListener = new BaseParameterListener(); + super.setParameterListener(mBaseParamListener); + } + } + } + + /** + * The Settings class regroups all virtualizer parameters. It is used in + * conjuntion with getProperties() and setProperties() methods to backup and restore + * all parameters in a single call. + */ + public static class Settings { + public short strength; + + public Settings() { + } + + /** + * Settings class constructor from a key=value; pairs formatted string. The string is + * typically returned by Settings.toString() method. + * @throws IllegalArgumentException if the string is not correctly formatted. + */ + public Settings(String settings) { + StringTokenizer st = new StringTokenizer(settings, "=;"); + int tokens = st.countTokens(); + if (st.countTokens() != 3) { + throw new IllegalArgumentException("settings: " + settings); + } + String key = st.nextToken(); + if (!key.equals("Virtualizer")) { + throw new IllegalArgumentException( + "invalid settings for Virtualizer: " + key); + } + try { + key = st.nextToken(); + if (!key.equals("strength")) { + throw new IllegalArgumentException("invalid key name: " + key); + } + strength = Short.parseShort(st.nextToken()); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException("invalid value for key: " + key); + } + } + + @Override + public String toString() { + String str = new String ( + "Virtualizer"+ + ";strength="+Short.toString(strength) + ); + return str; + } + }; + + + /** + * Gets the virtualizer properties. This method is useful when a snapshot of current + * virtualizer settings must be saved by the application. + * @return a Virtualizer.Settings object containing all current parameters values + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public Virtualizer.Settings getProperties() + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + Settings settings = new Settings(); + short[] value = new short[1]; + checkStatus(getParameter(PARAM_STRENGTH, value)); + settings.strength = value[0]; + return settings; + } + + /** + * Sets the virtualizer properties. This method is useful when virtualizer settings have to + * be applied from a previous backup. + * @param settings a Virtualizer.Settings object containing the properties to apply + * @throws IllegalStateException + * @throws IllegalArgumentException + * @throws UnsupportedOperationException + */ + public void setProperties(Virtualizer.Settings settings) + throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException { + checkStatus(setParameter(PARAM_STRENGTH, settings.strength)); + } +} diff --git a/android/media/audiofx/Visualizer.java b/android/media/audiofx/Visualizer.java new file mode 100644 index 00000000..0fe7246e --- /dev/null +++ b/android/media/audiofx/Visualizer.java @@ -0,0 +1,772 @@ +/* + * 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.app.ActivityThread; +import android.util.Log; +import java.lang.ref.WeakReference; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +/** + * The Visualizer class enables application to retrieve part of the currently playing audio for + * visualization purpose. It is not an audio recording interface and only returns partial and low + * quality audio content. However, to protect privacy of certain audio data (e.g voice mail) the use + * of the visualizer requires the permission android.permission.RECORD_AUDIO. + * <p>The audio session ID passed to the constructor indicates which audio content should be + * visualized:<br> + * <ul> + * <li>If the session is 0, the audio output mix is visualized</li> + * <li>If the session is not 0, the audio from a particular {@link android.media.MediaPlayer} or + * {@link android.media.AudioTrack} + * using this audio session is visualized </li> + * </ul> + * <p>Two types of representation of audio content can be captured: <br> + * <ul> + * <li>Waveform data: consecutive 8-bit (unsigned) mono samples by using the + * {@link #getWaveForm(byte[])} method</li> + * <li>Frequency data: 8-bit magnitude FFT by using the {@link #getFft(byte[])} method</li> + * </ul> + * <p>The length of the capture can be retrieved or specified by calling respectively + * {@link #getCaptureSize()} and {@link #setCaptureSize(int)} methods. The capture size must be a + * power of 2 in the range returned by {@link #getCaptureSizeRange()}. + * <p>In addition to the polling capture mode described above with {@link #getWaveForm(byte[])} and + * {@link #getFft(byte[])} methods, a callback mode is also available by installing a listener by + * use of the {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. + * The rate at which the listener capture method is called as well as the type of data returned is + * specified. + * <p>Before capturing data, the Visualizer must be enabled by calling the + * {@link #setEnabled(boolean)} method. + * When data capture is not needed any more, the Visualizer should be disabled. + * <p>It is good practice to call the {@link #release()} method when the Visualizer is not used + * anymore to free up native resources associated to the Visualizer instance. + * <p>Creating a Visualizer on the output mix (audio session 0) requires permission + * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} + * <p>The Visualizer class can also be used to perform measurements on the audio being played back. + * The measurements to perform are defined by setting a mask of the requested measurement modes with + * {@link #setMeasurementMode(int)}. Supported values are {@link #MEASUREMENT_MODE_NONE} to cancel + * any measurement, and {@link #MEASUREMENT_MODE_PEAK_RMS} for peak and RMS monitoring. + * Measurements can be retrieved through {@link #getMeasurementPeakRms(MeasurementPeakRms)}. + */ + +public class Visualizer { + + static { + System.loadLibrary("audioeffect_jni"); + native_init(); + } + + private final static String TAG = "Visualizer-JAVA"; + + /** + * State of a Visualizer object that was not successfully initialized upon creation + */ + public static final int STATE_UNINITIALIZED = 0; + /** + * State of a Visualizer object that is ready to be used. + */ + public static final int STATE_INITIALIZED = 1; + /** + * State of a Visualizer object that is active. + */ + public static final int STATE_ENABLED = 2; + + // to keep in sync with system/media/audio_effects/include/audio_effects/effect_visualizer.h + /** + * Defines a capture mode where amplification is applied based on the content of the captured + * data. This is the default Visualizer mode, and is suitable for music visualization. + */ + public static final int SCALING_MODE_NORMALIZED = 0; + /** + * Defines a capture mode where the playback volume will affect (scale) the range of the + * captured data. A low playback volume will lead to low sample and fft values, and vice-versa. + */ + public static final int SCALING_MODE_AS_PLAYED = 1; + + /** + * Defines a measurement mode in which no measurements are performed. + */ + public static final int MEASUREMENT_MODE_NONE = 0; + + /** + * Defines a measurement mode which computes the peak and RMS value in mB, where 0mB is the + * maximum sample value, and -9600mB is the minimum value. + * Values for peak and RMS can be retrieved with + * {@link #getMeasurementPeakRms(MeasurementPeakRms)}. + */ + public static final int MEASUREMENT_MODE_PEAK_RMS = 1 << 0; + + // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp + private static final int NATIVE_EVENT_PCM_CAPTURE = 0; + private static final int NATIVE_EVENT_FFT_CAPTURE = 1; + private static final int NATIVE_EVENT_SERVER_DIED = 2; + + // Error codes: + /** + * 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; + + //-------------------------------------------------------------------------- + // Member variables + //-------------------- + /** + * Indicates the state of the Visualizer instance + */ + private int mState = STATE_UNINITIALIZED; + /** + * Lock to synchronize access to mState + */ + private final Object mStateLock = new Object(); + /** + * System wide unique Identifier of the visualizer engine used by this Visualizer instance + */ + private int mId; + + /** + * Lock to protect listeners updates against event notifications + */ + private final Object mListenerLock = new Object(); + /** + * Handler for events coming from the native code + */ + private NativeEventHandler mNativeEventHandler = null; + /** + * PCM and FFT capture listener registered by client + */ + private OnDataCaptureListener mCaptureListener = null; + /** + * Server Died listener registered by client + */ + private OnServerDiedListener mServerDiedListener = null; + + // accessed by native methods + private long mNativeVisualizer; + private long mJniData; + + //-------------------------------------------------------------------------- + // Constructor, Finalize + //-------------------- + /** + * Class constructor. + * @param audioSession system wide unique audio session identifier. If audioSession + * is not 0, the visualizer will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the Visualizer will apply to the output mix. + * + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + + public Visualizer(int audioSession) + throws UnsupportedOperationException, RuntimeException { + int[] id = new int[1]; + + synchronized (mStateLock) { + mState = STATE_UNINITIALIZED; + // native initialization + int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id, + ActivityThread.currentOpPackageName()); + if (result != SUCCESS && result != ALREADY_EXISTS) { + Log.e(TAG, "Error code "+result+" when initializing Visualizer."); + switch (result) { + case ERROR_INVALID_OPERATION: + throw (new UnsupportedOperationException("Effect library not loaded")); + default: + throw (new RuntimeException("Cannot initialize Visualizer engine, error: " + +result)); + } + } + mId = id[0]; + if (native_getEnabled()) { + mState = STATE_ENABLED; + } else { + mState = STATE_INITIALIZED; + } + } + } + + /** + * Releases the native Visualizer resources. It is a good practice to release the + * visualization engine when not in use. + */ + public void release() { + synchronized (mStateLock) { + native_release(); + mState = STATE_UNINITIALIZED; + } + } + + @Override + protected void finalize() { + native_finalize(); + } + + /** + * Enable or disable the visualization engine. + * @param enabled 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 { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("setEnabled() called in wrong state: "+mState)); + } + int status = SUCCESS; + if ((enabled && (mState == STATE_INITIALIZED)) || + (!enabled && (mState == STATE_ENABLED))) { + status = native_setEnabled(enabled); + if (status == SUCCESS) { + mState = enabled ? STATE_ENABLED : STATE_INITIALIZED; + } + } + return status; + } + } + + /** + * Get current activation state of the visualizer. + * @return true if the visualizer is active, false otherwise + */ + public boolean getEnabled() + { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getEnabled() called in wrong state: "+mState)); + } + return native_getEnabled(); + } + } + + /** + * Returns the capture size range. + * @return the mininum capture size is returned in first array element and the maximum in second + * array element. + */ + public static native int[] getCaptureSizeRange(); + + /** + * Returns the maximum capture rate for the callback capture method. This is the maximum value + * for the rate parameter of the + * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. + * @return the maximum capture rate expressed in milliHertz + */ + public static native int getMaxCaptureRate(); + + /** + * Sets the capture size, i.e. the number of bytes returned by {@link #getWaveForm(byte[])} and + * {@link #getFft(byte[])} methods. The capture size must be a power of 2 in the range returned + * by {@link #getCaptureSizeRange()}. + * This method must not be called when the Visualizer is enabled. + * @param size requested capture size + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR_BAD_VALUE} in case of failure. + * @throws IllegalStateException + */ + public int setCaptureSize(int size) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState != STATE_INITIALIZED) { + throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState)); + } + return native_setCaptureSize(size); + } + } + + /** + * Returns current capture size. + * @return the capture size in bytes. + */ + public int getCaptureSize() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getCaptureSize() called in wrong state: "+mState)); + } + return native_getCaptureSize(); + } + } + + /** + * Set the type of scaling applied on the captured visualization data. + * @param mode see {@link #SCALING_MODE_NORMALIZED} + * and {@link #SCALING_MODE_AS_PLAYED} + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR_BAD_VALUE} in case of failure. + * @throws IllegalStateException + */ + public int setScalingMode(int mode) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("setScalingMode() called in wrong state: " + + mState)); + } + return native_setScalingMode(mode); + } + } + + /** + * Returns the current scaling mode on the captured visualization data. + * @return the scaling mode, see {@link #SCALING_MODE_NORMALIZED} + * and {@link #SCALING_MODE_AS_PLAYED}. + * @throws IllegalStateException + */ + public int getScalingMode() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getScalingMode() called in wrong state: " + + mState)); + } + return native_getScalingMode(); + } + } + + /** + * Sets the combination of measurement modes to be performed by this audio effect. + * @param mode a mask of the measurements to perform. The valid values are + * {@link #MEASUREMENT_MODE_NONE} (to cancel any measurement) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE} in case of failure. + * @throws IllegalStateException + */ + public int setMeasurementMode(int mode) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("setMeasurementMode() called in wrong state: " + + mState)); + } + return native_setMeasurementMode(mode); + } + } + + /** + * Returns the current measurement modes performed by this audio effect + * @return the mask of the measurements, + * {@link #MEASUREMENT_MODE_NONE} (when no measurements are performed) + * or {@link #MEASUREMENT_MODE_PEAK_RMS}. + * @throws IllegalStateException + */ + public int getMeasurementMode() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getMeasurementMode() called in wrong state: " + + mState)); + } + return native_getMeasurementMode(); + } + } + + /** + * Returns the sampling rate of the captured audio. + * @return the sampling rate in milliHertz. + */ + public int getSamplingRate() + throws IllegalStateException { + synchronized (mStateLock) { + if (mState == STATE_UNINITIALIZED) { + throw(new IllegalStateException("getSamplingRate() called in wrong state: "+mState)); + } + return native_getSamplingRate(); + } + } + + /** + * Returns a waveform capture of currently playing audio content. The capture consists in + * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned + * by {@link #getCaptureSize()}. + * <p>This method must be called when the Visualizer is enabled. + * @param waveform array of bytes where the waveform should be returned + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} + * in case of failure. + * @throws IllegalStateException + */ + public int getWaveForm(byte[] waveform) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState != STATE_ENABLED) { + throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState)); + } + return native_getWaveForm(waveform); + } + } + /** + * Returns a frequency capture of currently playing audio content. + * <p>This method must be called when the Visualizer is enabled. + * <p>The capture is an 8-bit magnitude FFT, the frequency range covered being 0 (DC) to half of + * the sampling rate returned by {@link #getSamplingRate()}. The capture returns the real and + * imaginary parts of a number of frequency points equal to half of the capture size plus one. + * <p>Note: only the real part is returned for the first point (DC) and the last point + * (sampling frequency / 2). + * <p>The layout in the returned byte array is as follows: + * <ul> + * <li> n is the capture size returned by getCaptureSize()</li> + * <li> Rfk, Ifk are respectively the real and imaginary parts of the kth frequency + * component</li> + * <li> If Fs is the sampling frequency retuned by getSamplingRate() the kth frequency is: + * (k*Fs)/(n/2) </li> + * </ul> + * <table border="0" cellspacing="0" cellpadding="0"> + * <tr><td>Index </p></td> + * <td>0 </p></td> + * <td>1 </p></td> + * <td>2 </p></td> + * <td>3 </p></td> + * <td>4 </p></td> + * <td>5 </p></td> + * <td>... </p></td> + * <td>n - 2 </p></td> + * <td>n - 1 </p></td></tr> + * <tr><td>Data </p></td> + * <td>Rf0 </p></td> + * <td>Rf(n/2) </p></td> + * <td>Rf1 </p></td> + * <td>If1 </p></td> + * <td>Rf2 </p></td> + * <td>If2 </p></td> + * <td>... </p></td> + * <td>Rf(n-1)/2 </p></td> + * <td>If(n-1)/2 </p></td></tr> + * </table> + * @param fft array of bytes where the FFT should be returned + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} + * in case of failure. + * @throws IllegalStateException + */ + public int getFft(byte[] fft) + throws IllegalStateException { + synchronized (mStateLock) { + if (mState != STATE_ENABLED) { + throw(new IllegalStateException("getFft() called in wrong state: "+mState)); + } + return native_getFft(fft); + } + } + + /** + * A class to store peak and RMS values. + * Peak and RMS are expressed in mB, as described in the + * {@link Visualizer#MEASUREMENT_MODE_PEAK_RMS} measurement mode. + */ + public static final class MeasurementPeakRms { + /** + * The peak value in mB. + */ + public int mPeak; + /** + * The RMS value in mB. + */ + public int mRms; + } + + /** + * Retrieves the latest peak and RMS measurement. + * Sets the peak and RMS fields of the supplied {@link Visualizer.MeasurementPeakRms} to the + * latest measured values. + * @param measurement a non-null {@link Visualizer.MeasurementPeakRms} instance to store + * the measurement values. + * @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. + */ + public int getMeasurementPeakRms(MeasurementPeakRms measurement) { + if (measurement == null) { + Log.e(TAG, "Cannot store measurements in a null object"); + return ERROR_BAD_VALUE; + } + synchronized (mStateLock) { + if (mState != STATE_ENABLED) { + throw (new IllegalStateException("getMeasurementPeakRms() called in wrong state: " + + mState)); + } + return native_getPeakRms(measurement); + } + } + + //--------------------------------------------------------- + // Interface definitions + //-------------------- + /** + * The OnDataCaptureListener interface defines methods called by the Visualizer to periodically + * update the audio visualization capture. + * The client application can implement this interface and register the listener with the + * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. + */ + public interface OnDataCaptureListener { + /** + * Method called when a new waveform capture is available. + * <p>Data in the waveform buffer is valid only within the scope of the callback. + * Applications which needs access to the waveform data after returning from the callback + * should make a copy of the data instead of holding a reference. + * @param visualizer Visualizer object on which the listener is registered. + * @param waveform array of bytes containing the waveform representation. + * @param samplingRate sampling rate of the audio visualized. + */ + void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate); + + /** + * Method called when a new frequency capture is available. + * <p>Data in the fft buffer is valid only within the scope of the callback. + * Applications which needs access to the fft data after returning from the callback + * should make a copy of the data instead of holding a reference. + * @param visualizer Visualizer object on which the listener is registered. + * @param fft array of bytes containing the frequency representation. + * @param samplingRate sampling rate of the audio visualized. + */ + void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate); + } + + /** + * Registers an OnDataCaptureListener interface and specifies the rate at which the capture + * should be updated as well as the type of capture requested. + * <p>Call this method with a null listener to stop receiving the capture updates. + * @param listener OnDataCaptureListener registered + * @param rate rate in milliHertz at which the capture should be updated + * @param waveform true if a waveform capture is requested: the onWaveFormDataCapture() + * method will be called on the OnDataCaptureListener interface. + * @param fft true if a frequency capture is requested: the onFftDataCapture() method will be + * called on the OnDataCaptureListener interface. + * @return {@link #SUCCESS} in case of success, + * {@link #ERROR_NO_INIT} or {@link #ERROR_BAD_VALUE} in case of failure. + */ + public int setDataCaptureListener(OnDataCaptureListener listener, + int rate, boolean waveform, boolean fft) { + synchronized (mListenerLock) { + mCaptureListener = listener; + } + if (listener == null) { + // make sure capture callback is stopped in native code + waveform = false; + fft = false; + } + int status = native_setPeriodicCapture(rate, waveform, fft); + if (status == SUCCESS) { + if ((listener != null) && (mNativeEventHandler == null)) { + 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; + status = ERROR_NO_INIT; + } + } + } + return status; + } + + /** + * @hide + * + * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that + * the connection to the native media server has been broken and that the Visualizer object will + * need to be released and re-created. + * The client application can implement this interface and register the listener with the + * {@link #setServerDiedListener(OnServerDiedListener)} method. + */ + public interface OnServerDiedListener { + /** + * @hide + * + * Method called when the native media server has died. + * <p>If the native media server encounters a fatal error and needs to restart, the binder + * connection from the {@link #Visualizer} to the media server will be broken. Data capture + * callbacks will stop happening, and client initiated calls to the {@link #Visualizer} + * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality, + * clients should {@link #release()} their old visualizer and create a new instance. + */ + void onServerDied(); + } + + /** + * @hide + * + * Registers an OnServerDiedListener interface. + * <p>Call this method with a null listener to stop receiving server death notifications. + * @return {@link #SUCCESS} in case of success, + */ + public int setServerDiedListener(OnServerDiedListener listener) { + synchronized (mListenerLock) { + mServerDiedListener = listener; + } + return SUCCESS; + } + + /** + * Helper class to handle the forwarding of native events to the appropriate listeners + */ + private class NativeEventHandler extends Handler + { + private Visualizer mVisualizer; + + public NativeEventHandler(Visualizer v, Looper looper) { + super(looper); + mVisualizer = v; + } + + private void handleCaptureMessage(Message msg) { + OnDataCaptureListener l = null; + synchronized (mListenerLock) { + l = mVisualizer.mCaptureListener; + } + + if (l != null) { + byte[] data = (byte[])msg.obj; + int samplingRate = msg.arg1; + + switch(msg.what) { + case NATIVE_EVENT_PCM_CAPTURE: + l.onWaveFormDataCapture(mVisualizer, data, samplingRate); + break; + case NATIVE_EVENT_FFT_CAPTURE: + l.onFftDataCapture(mVisualizer, data, samplingRate); + break; + default: + Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what); + break; + } + } + } + + private void handleServerDiedMessage(Message msg) { + OnServerDiedListener l = null; + synchronized (mListenerLock) { + l = mVisualizer.mServerDiedListener; + } + + if (l != null) + l.onServerDied(); + } + + @Override + public void handleMessage(Message msg) { + if (mVisualizer == null) { + return; + } + + switch(msg.what) { + case NATIVE_EVENT_PCM_CAPTURE: + case NATIVE_EVENT_FFT_CAPTURE: + handleCaptureMessage(msg); + break; + case NATIVE_EVENT_SERVER_DIED: + handleServerDiedMessage(msg); + break; + default: + Log.e(TAG,"Unknown native event: "+msg.what); + break; + } + } + } + + //--------------------------------------------------------- + // Interface definitions + //-------------------- + + private static native final void native_init(); + + private native final int native_setup(Object audioeffect_this, + int audioSession, + int[] id, + 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 int native_setCaptureSize(int size); + + private native final int native_getCaptureSize(); + + private native final int native_setScalingMode(int mode); + + private native final int native_getScalingMode(); + + private native final int native_setMeasurementMode(int mode); + + private native final int native_getMeasurementMode(); + + private native final int native_getSamplingRate(); + + private native final int native_getWaveForm(byte[] waveform); + + private native final int native_getFft(byte[] fft); + + private native final int native_getPeakRms(MeasurementPeakRms measurement); + + private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft); + + //--------------------------------------------------------- + // Java methods called from the native side + //-------------------- + @SuppressWarnings("unused") + private static void postEventFromNative(Object effect_ref, + int what, int arg1, int arg2, Object obj) { + Visualizer visu = (Visualizer)((WeakReference)effect_ref).get(); + if (visu == null) { + return; + } + + if (visu.mNativeEventHandler != null) { + Message m = visu.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj); + visu.mNativeEventHandler.sendMessage(m); + } + + } +} + |