/* * 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.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import java.io.IOException; 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. *
Applications should not use the AudioEffect class directly but one of its derived classes to * control specific effects: *
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). *
NOTE: attaching insert effects (equalizer, bass boost, virtualizer) to the global audio output * mix by use of session 0 is deprecated. *
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. *
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
* appropiate 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");
/**
* 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:
*
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. *
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 int mNativeAudioEffect;
private int 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 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.
* The intent carries a number of extras used by the player application to communicate
* necessary pieces of information to the control panel application.
* 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.
* 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:
* - 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.
* - 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.
* 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.
* 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.
* 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.
* 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.
* 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.
* The effect control application receiving this intent will delete all effects on
* this session and store current settings in package specific storage.
* 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.
* 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.
* 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.
* 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.
* This extra is for use with {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and
* {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents.
* 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.
* This extra is for use with {@link #ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL} and
* {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} intents.
* 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:
*
*
* 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);
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 int byteArrayToInt(byte[] valueBuf) {
return byteArrayToInt(valueBuf, 0);
}
/**
* @hide
*/
public int byteArrayToInt(byte[] valueBuf, int offset) {
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
converter.order(ByteOrder.nativeOrder());
return converter.getInt(offset);
}
/**
* @hide
*/
public byte[] intToByteArray(int value) {
ByteBuffer converter = ByteBuffer.allocate(4);
converter.order(ByteOrder.nativeOrder());
converter.putInt(value);
return converter.array();
}
/**
* @hide
*/
public short byteArrayToShort(byte[] valueBuf) {
return byteArrayToShort(valueBuf, 0);
}
/**
* @hide
*/
public short byteArrayToShort(byte[] valueBuf, int offset) {
ByteBuffer converter = ByteBuffer.wrap(valueBuf);
converter.order(ByteOrder.nativeOrder());
return converter.getShort(offset);
}
/**
* @hide
*/
public 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 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;
}
}