diff options
Diffstat (limited to 'media/java')
| -rw-r--r-- | media/java/android/media/AudioEffect.java | 1044 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 49 | ||||
| -rw-r--r-- | media/java/android/media/AudioTrack.java | 122 | ||||
| -rw-r--r-- | media/java/android/media/BassBoost.java | 290 | ||||
| -rw-r--r-- | media/java/android/media/CamcorderProfile.java | 18 | ||||
| -rw-r--r-- | media/java/android/media/CameraProfile.java | 38 | ||||
| -rw-r--r-- | media/java/android/media/EnvironmentalReverb.java | 668 | ||||
| -rw-r--r-- | media/java/android/media/Equalizer.java | 562 | ||||
| -rw-r--r-- | media/java/android/media/ExifInterface.java | 40 | ||||
| -rw-r--r-- | media/java/android/media/MediaFile.java | 12 | ||||
| -rw-r--r-- | media/java/android/media/MediaPlayer.java | 92 | ||||
| -rw-r--r-- | media/java/android/media/MediaRecorder.java | 3 | ||||
| -rw-r--r-- | media/java/android/media/PresetReverb.java | 309 | ||||
| -rw-r--r-- | media/java/android/media/Virtualizer.java | 291 | ||||
| -rwxr-xr-x | media/java/android/media/Visualizer.java | 508 |
15 files changed, 4010 insertions, 36 deletions
diff --git a/media/java/android/media/AudioEffect.java b/media/java/android/media/AudioEffect.java new file mode 100644 index 000000000000..35038fa5ae92 --- /dev/null +++ b/media/java/android/media/AudioEffect.java @@ -0,0 +1,1044 @@ +/* + * 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; + +import android.util.Log; +import java.lang.ref.WeakReference; +import java.io.IOException; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.util.UUID; + +/** + * AudioEffect is the base class for implementing audio effect control in Java + * applications. + * <p>Creating an AudioEffect object will create the effect engine in + * 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 appropiate listener. + * <p>If the effect is to be applied to a specific AudioTrack or MediaPlayer instance, + * the application must specify the audio session ID of that instance when calling the AudioEffect + * constructor. + */ +public class AudioEffect { + static { + System.loadLibrary("audioeffect_jni"); + native_init(); + } + + private final static String TAG = "AudioEffect-JAVA"; + + /** + * 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 reverb effect + */ + public static final UUID EFFECT_TYPE_ENV_REVERB = UUID + .fromString("c2e5d5f0-94bd-4763-9cac-4e234d06839e"); + /** + * UUID for preset reverb 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"); + + /** + * Null effect UUID. Used when the UUID for effect type of + */ + 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 + */ + public static final int STATE_UNINITIALIZED = 0; + /** + * State of an AudioEffect object that is ready to be used. + */ + 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. + */ + public static final int NATIVE_EVENT_CONTROL_STATUS = 0; + /** + * Event id for engine state change notification. + */ + public static final int NATIVE_EVENT_ENABLED_STATUS = 1; + /** + * Event id for engine parameter change notification. + */ + 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 opreation 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 necessary information to facilitate + * effects enumeration:<br> + * <ul> + * <li>mType: UUID corresponding to the OpenSL ES interface implemented by this effect</li> + * <li>mUuid: UUID for this particular implementation</li> + * <li>mConnectMode: {@link #EFFECT_INSERT} or {@link #EFFECT_AUXILIARY}</li> + * <li>mName: human readable effect name</li> + * <li>mImplementor: human readable effect implementor name</li> + * </ul> + */ + public static class Descriptor { + + public Descriptor() { + } + + public Descriptor(String type, String uuid, String connectMode, + String name, String implementor) { + mType = UUID.fromString(type); + mUuid = UUID.fromString(uuid); + mConnectMode = connectMode; + mName = name; + mImplementor = implementor; + } + + public UUID mType; + public UUID mUuid; + public String mConnectMode; + public String mName; + public String mImplementor; + }; + + /** + * 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"; + + // -------------------------------------------------------------------------- + // 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 + */ + public final Object mListenerLock = new Object(); + /** + * Handler for events coming from the native code + */ + 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. If audioSession + * is not 0, the effect will be attached to the MediaPlayer or + * AudioTrack in the same audio session. Otherwise, the effect + * will apply to the output mix. + * + * @throws java.lang.IllegalArgumentException + * @throws java.lang.UnsupportedOperationException + * @throws java.lang.RuntimeException + */ + + 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); + 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.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.AudioEffect.Descriptor} objects + * + * @throws IllegalStateException + */ + + static public Descriptor[] queryEffects() { + return (Descriptor[]) native_query_effects(); + } + + // -------------------------------------------------------------------------- + // Control methods + // -------------------- + + /** + * Enable or disable effect engine. + * + * @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 + */ + 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[]) + */ + 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[]) + */ + 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[]) + */ + 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[]) + */ + 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[]) + */ + 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[]) + */ + 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 {@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 When called, value.length + * indicates the maximum size of the returned parameters value. When + * returning, value.length is updated with the actual size of the + * returned value. + * @throws IllegalStateException + */ + public int getParameter(byte[] param, byte[] value) + throws IllegalStateException { + checkState("getParameter()"); + int[] vSize = new int[1]; + vSize[0] = value.length; + int status = native_getParameter(param.length, param, vSize, value); + if (value.length > vSize[0]) { + byte[] resizedValue = new byte[vSize[0]]; + System.arraycopy(value, 0, resizedValue, 0, vSize[0]); + value = resizedValue; + } + return status; + } + + /** + * Get effect parameter. The parameter is an integer and the value is an + * array of bytes. + * + * @see #getParameter(byte[], byte[]) + */ + 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[]) + */ + 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); + + value[0] = byteArrayToInt(v); + if (v.length > 4) { + value[1] = byteArrayToInt(v, 4); + } + 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[]) + */ + 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); + + value[0] = byteArrayToShort(v); + if (v.length > 2) { + value[1] = byteArrayToShort(v, 2); + } + 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[]) + */ + 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); + + value[0] = byteArrayToInt(v); + if (v.length > 4) { + value[1] = byteArrayToInt(v, 4); + } + 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[]) + */ + 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); + + value[0] = byteArrayToShort(v); + if (v.length > 2) { + value[1] = byteArrayToShort(v, 2); + } + 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[]) + */ + 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. + * + */ + public int command(int cmdCode, byte[] command, byte[] reply) + throws IllegalStateException { + checkState("command()"); + int[] replySize = new int[1]; + replySize[0] = reply.length; + + int status = native_command(cmdCode, command.length, command, + replySize, reply); + + if (reply.length > replySize[0]) { + byte[] resizedReply = new byte[replySize[0]]; + System.arraycopy(reply, 0, resizedReply, 0, replySize[0]); + reply = resizedReply; + } + return status; + } + + // -------------------------------------------------------------------------- + // 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 engine enable 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 + */ + 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. + */ + 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); + } + + // --------------------------------------------------------- + // 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(); + + // --------------------------------------------------------- + // Utility methods + // ------------------ + + public void checkState(String methodName) throws IllegalStateException { + synchronized (mStateLock) { + if (mState != STATE_INITIALIZED) { + throw (new IllegalStateException(methodName + + " called on uninitialized AudioEffect.")); + } + } + } + + public void checkStatus(int status) { + switch (status) { + case AudioEffect.SUCCESS: + break; + 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")); + } + } + + public int byteArrayToInt(byte[] valueBuf) { + return byteArrayToInt(valueBuf, 0); + + } + + public int byteArrayToInt(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getInt(offset); + + } + + public byte[] intToByteArray(int value) { + ByteBuffer converter = ByteBuffer.allocate(4); + converter.order(ByteOrder.nativeOrder()); + converter.putInt(value); + return converter.array(); + } + + public short byteArrayToShort(byte[] valueBuf) { + return byteArrayToShort(valueBuf, 0); + } + + public short byteArrayToShort(byte[] valueBuf, int offset) { + ByteBuffer converter = ByteBuffer.wrap(valueBuf); + converter.order(ByteOrder.nativeOrder()); + return converter.getShort(offset); + + } + + public byte[] shortToByteArray(short value) { + ByteBuffer converter = ByteBuffer.allocate(2); + converter.order(ByteOrder.nativeOrder()); + short sValue = (short) value; + converter.putShort(sValue); + return converter.array(); + } + + 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; + } + +} diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 08fc782c2cbc..41d2cc5697d4 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -239,6 +239,9 @@ public class AudioService extends IAudioService.Stub { // independently change its priority) private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver(); + // Used to alter media button redirection when the phone is ringing. + private boolean mIsRinging = false; + // Devices currently connected private HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>(); @@ -1103,16 +1106,20 @@ public class AudioService extends IAudioService.Stub { private BluetoothHeadset.ServiceListener mBluetoothHeadsetServiceListener = new BluetoothHeadset.ServiceListener() { public void onServiceConnected() { - if (mBluetoothHeadset != null && - mBluetoothHeadset.getState() == BluetoothHeadset.STATE_CONNECTED) { - mBluetoothHeadsetConnected = true; + if (mBluetoothHeadset != null) { + BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset(); + if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_CONNECTED) { + mBluetoothHeadsetConnected = true; + } } } public void onServiceDisconnected() { - if (mBluetoothHeadset != null && - mBluetoothHeadset.getState() == BluetoothHeadset.STATE_DISCONNECTED) { - mBluetoothHeadsetConnected = false; - clearAllScoClients(); + if (mBluetoothHeadset != null) { + BluetoothDevice device = mBluetoothHeadset.getCurrentHeadset(); + if (mBluetoothHeadset.getState(device) == BluetoothHeadset.STATE_DISCONNECTED) { + mBluetoothHeadsetConnected = false; + clearAllScoClients(); + } } } }; @@ -1634,6 +1641,12 @@ public class AudioService extends IAudioService.Stub { case MSG_MEDIA_SERVER_STARTED: Log.e(TAG, "Media server started."); + // indicate to audio HAL that we start the reconfiguration phase after a media + // server crash + // Note that MSG_MEDIA_SERVER_STARTED message is only received when the media server + // process restarts after a crash, not the first time it is started. + AudioSystem.setParameters("restarting=true"); + // Restore device connection states Set set = mConnectedDevices.entrySet(); Iterator i = set.iterator(); @@ -1667,6 +1680,9 @@ public class AudioService extends IAudioService.Stub { // Restore ringer mode setRingerModeInt(getRingerMode(), false); + + // indicate the end of reconfiguration phase to audio HAL + AudioSystem.setParameters("restarting=false"); break; case MSG_PLAY_SOUND_EFFECT: @@ -1943,11 +1959,16 @@ public class AudioService extends IAudioService.Stub { private final static Object mAudioFocusLock = new Object(); + private final static Object mRingingLock = new Object(); + private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { if (state == TelephonyManager.CALL_STATE_RINGING) { //Log.v(TAG, " CALL_STATE_RINGING"); + synchronized(mRingingLock) { + mIsRinging = true; + } int ringVolume = AudioService.this.getStreamVolume(AudioManager.STREAM_RING); if (ringVolume > 0) { requestAudioFocus(AudioManager.STREAM_RING, @@ -1957,12 +1978,18 @@ public class AudioService extends IAudioService.Stub { } } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) { //Log.v(TAG, " CALL_STATE_OFFHOOK"); + synchronized(mRingingLock) { + mIsRinging = false; + } requestAudioFocus(AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, null, null /* both allowed to be null only for this clientId */, IN_VOICE_COMM_FOCUS_ID /*clientId*/); } else if (state == TelephonyManager.CALL_STATE_IDLE) { //Log.v(TAG, " CALL_STATE_IDLE"); + synchronized(mRingingLock) { + mIsRinging = false; + } abandonAudioFocus(null, IN_VOICE_COMM_FOCUS_ID); } } @@ -2230,9 +2257,11 @@ public class AudioService extends IAudioService.Stub { // if in a call or ringing, do not break the current phone app behavior // TODO modify this to let the phone app specifically get the RC focus // add modify the phone app to take advantage of the new API - if ((getMode() == AudioSystem.MODE_IN_CALL) || - (getMode() == AudioSystem.MODE_RINGTONE)) { - return; + synchronized(mRingingLock) { + if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) || + (getMode() == AudioSystem.MODE_RINGTONE) ) { + return; + } } synchronized(mRCStack) { if (!mRCStack.empty()) { diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 1e8d72f47a34..74691334f03f 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -210,6 +210,10 @@ public class AudioTrack * @see AudioFormat#ENCODING_PCM_16BIT */ private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT; + /** + * Audio session ID + */ + private int mSessionId = 0; //-------------------------------- @@ -258,6 +262,46 @@ public class AudioTrack public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException { + this(streamType, sampleRateInHz, channelConfig, audioFormat, + bufferSizeInBytes, mode, 0); + } + + /** + * Class constructor with audio session. Use this constructor when the AudioTrack must be + * attached to a particular audio session. The primary use of the audio session ID is to + * associate audio effects to a particular instance of AudioTrack: if an audio session ID + * is provided when creating an AudioEffect, this effect will be applied only to audio tracks + * and media players in the same session and not to the output mix. + * When an AudioTrack is created without specifying a session, it will create its own session + * which can be retreived by calling the {@link #getAudioSessionId()} method. + * If a session ID is provided, this AudioTrack will share effects attached to this session + * with all other media players or audio tracks in the same session. + * @param streamType the type of the audio stream. See + * {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM}, + * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC} and + * {@link AudioManager#STREAM_ALARM} + * @param sampleRateInHz the sample rate expressed in Hertz. Examples of rates are (but + * not limited to) 44100, 22050 and 11025. + * @param channelConfig describes the configuration of the audio channels. + * See {@link AudioFormat#CHANNEL_OUT_MONO} and + * {@link AudioFormat#CHANNEL_OUT_STEREO} + * @param audioFormat the format in which the audio data is represented. + * See {@link AudioFormat#ENCODING_PCM_16BIT} and + * {@link AudioFormat#ENCODING_PCM_8BIT} + * @param bufferSizeInBytes the total size (in bytes) of the buffer where audio data is read + * from for playback. If using the AudioTrack in streaming mode, you can write data into + * this buffer in smaller chunks than this size. If using the AudioTrack in static mode, + * this is the maximum size of the sound that will be played for this instance. + * See {@link #getMinBufferSize(int, int, int)} to determine the minimum required buffer size + * for the successful creation of an AudioTrack instance in streaming mode. Using values + * smaller than getMinBufferSize() will result in an initialization failure. + * @param mode streaming or static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM} + * @param sessionId Id of audio session the AudioTrack must be attached to + * @throws java.lang.IllegalArgumentException + */ + public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, + int bufferSizeInBytes, int mode, int sessionId) + throws IllegalArgumentException { mState = STATE_UNINITIALIZED; // remember which looper is associated with the AudioTrack instanciation @@ -269,15 +313,23 @@ public class AudioTrack audioBuffSizeCheck(bufferSizeInBytes); + if (sessionId < 0) { + throw (new IllegalArgumentException("Invalid audio session ID: "+sessionId)); + } + + int[] session = new int[1]; + session[0] = sessionId; // native initialization int initResult = native_setup(new WeakReference<AudioTrack>(this), mStreamType, mSampleRate, mChannels, mAudioFormat, - mNativeBufferSizeInBytes, mDataLoadMode); + mNativeBufferSizeInBytes, mDataLoadMode, session); if (initResult != SUCCESS) { loge("Error code "+initResult+" when initializing AudioTrack."); return; // with mState == STATE_UNINITIALIZED } + mSessionId = session[0]; + if (mDataLoadMode == MODE_STATIC) { mState = STATE_NO_STATIC_DATA; } else { @@ -590,6 +642,14 @@ public class AudioTrack } } + /** + * Returns the audio session ID. + * + * @return the ID of the audio session this AudioTrack belongs to. + */ + public int getAudioSessionId() { + return mSessionId; + } //-------------------------------------------------------------------------- // Initialization / configuration @@ -898,6 +958,60 @@ public class AudioTrack return native_reload_static(); } + //-------------------------------------------------------------------------- + // Audio effects management + //-------------------- + + /** + * Attaches an auxiliary effect to the audio track. A typical auxiliary effect is a + * reverberation effect which can be applied on any sound source that directs a certain + * amount of its energy to this effect. This amount is defined by setAuxEffectSendLevel(). + * {@see #setAuxEffectSendLevel(float)}. + * <p>After creating an auxiliary effect (e.g. {@link android.media.EnvironmentalReverb}), + * retrieve its ID with {@link android.media.AudioEffect#getId()} and use it when calling + * this method to attach the audio track to the effect. + * <p>To detach the effect from the audio track, call this method with a null effect id. + * + * @param effectId system wide unique id of the effect to attach + * @return error code or success, see {@link #SUCCESS}, + * {@link #ERROR_INVALID_OPERATION}, {@link #ERROR_BAD_VALUE} + */ + public int attachAuxEffect(int effectId) { + if (mState != STATE_INITIALIZED) { + return ERROR_INVALID_OPERATION; + } + return native_attachAuxEffect(effectId); + } + + /** + * Sets the send level of the audio track to the attached auxiliary effect + * {@see #attachAuxEffect(int)}. The level value range is 0 to 1.0. + * <p>By default the send level is 0, so even if an effect is attached to the player + * this method must be called for the effect to be applied. + * <p>Note that the passed level value is a raw scalar. UI controls should be scaled + * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, + * so an appropriate conversion from linear UI input x to level is: + * x == 0 -> level = 0 + * 0 < x <= R -> level = 10^(72*(x-R)/20/R) + * + * @param level send level scalar + * @return error code or success, see {@link #SUCCESS}, + * {@link #ERROR_INVALID_OPERATION} + */ + public int setAuxEffectSendLevel(float level) { + if (mState != STATE_INITIALIZED) { + return ERROR_INVALID_OPERATION; + } + // clamp the level + if (level < getMinVolume()) { + level = getMinVolume(); + } + if (level > getMaxVolume()) { + level = getMaxVolume(); + } + native_setAuxEffectSendLevel(level); + return SUCCESS; + } //--------------------------------------------------------- // Interface definitions @@ -1012,7 +1126,7 @@ public class AudioTrack private native final int native_setup(Object audiotrack_this, int streamType, int sampleRate, int nbChannels, int audioFormat, - int buffSizeInBytes, int mode); + int buffSizeInBytes, int mode, int[] sessionId); private native final void native_finalize(); @@ -1056,6 +1170,10 @@ public class AudioTrack static private native final int native_get_min_buff_size( int sampleRateInHz, int channelConfig, int audioFormat); + private native final int native_get_session_id(); + + private native final int native_attachAuxEffect(int effectId); + private native final void native_setAuxEffectSendLevel(float level); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/BassBoost.java b/media/java/android/media/BassBoost.java new file mode 100644 index 000000000000..476b0562ed42 --- /dev/null +++ b/media/java/android/media/BassBoost.java @@ -0,0 +1,290 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioEffect; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +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. If the audio session ID 0 + * is specified, the BassBoost applies to the main audio output mix. + * <p> See {@link android.media.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.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. If audioSession + * is not 0, the BassBoost will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the BassBoost will apply to the output mix. + * + * @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); + + 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. + * See {@link android.media.AudioEffect#setParameter(byte[], byte[])}. + * @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/media/java/android/media/CamcorderProfile.java b/media/java/android/media/CamcorderProfile.java index 64d6460dbd68..a27df575749f 100644 --- a/media/java/android/media/CamcorderProfile.java +++ b/media/java/android/media/CamcorderProfile.java @@ -119,15 +119,26 @@ public class CamcorderProfile public int audioChannels; /** - * Returns the camcorder profile for the given quality level. + * Returns the camcorder profile for the default camera at the given + * quality level. * @param quality the target quality level for the camcorder profile */ public static CamcorderProfile get(int quality) { + return get(0, quality); + } + + /** + * Returns the camcorder profile for the given camera at the given + * quality level. + * @param cameraId the id for the camera + * @param quality the target quality level for the camcorder profile + */ + public static CamcorderProfile get(int cameraId, int quality) { if (quality < QUALITY_LOW || quality > QUALITY_HIGH) { String errMessage = "Unsupported quality level: " + quality; throw new IllegalArgumentException(errMessage); } - return native_get_camcorder_profile(quality); + return native_get_camcorder_profile(cameraId, quality); } static { @@ -165,5 +176,6 @@ public class CamcorderProfile // Methods implemented by JNI private static native final void native_init(); - private static native final CamcorderProfile native_get_camcorder_profile(int quality); + private static native final CamcorderProfile native_get_camcorder_profile( + int cameraId, int quality); } diff --git a/media/java/android/media/CameraProfile.java b/media/java/android/media/CameraProfile.java index f8d393512502..6a0be08defdd 100644 --- a/media/java/android/media/CameraProfile.java +++ b/media/java/android/media/CameraProfile.java @@ -17,6 +17,7 @@ package android.media; import java.util.Arrays; +import java.util.HashMap; /** * The CameraProfile class is used to retrieve the pre-defined still image @@ -40,36 +41,55 @@ public class CameraProfile /* * Cache the Jpeg encoding quality parameters */ - private static final int[] sJpegEncodingQualityParameters; + private static final HashMap<Integer, int[]> sCache = new HashMap<Integer, int[]>(); /** * Returns a pre-defined still image capture (jpeg) quality level - * used for the given quality level in the Camera application. + * used for the given quality level in the Camera application for + * the default camera. * * @param quality The target quality level */ public static int getJpegEncodingQualityParameter(int quality) { + return getJpegEncodingQualityParameter(0, quality); + } + + /** + * Returns a pre-defined still image capture (jpeg) quality level + * used for the given quality level in the Camera application for + * the specified camera. + * + * @param cameraId The id of the camera + * @param quality The target quality level + */ + public static int getJpegEncodingQualityParameter(int cameraId, int quality) { if (quality < QUALITY_LOW || quality > QUALITY_HIGH) { throw new IllegalArgumentException("Unsupported quality level: " + quality); } - return sJpegEncodingQualityParameters[quality]; + synchronized (sCache) { + int[] levels = sCache.get(cameraId); + if (levels == null) { + levels = getImageEncodingQualityLevels(cameraId); + sCache.put(cameraId, levels); + } + return levels[quality]; + } } static { System.loadLibrary("media_jni"); native_init(); - sJpegEncodingQualityParameters = getImageEncodingQualityLevels(); } - private static int[] getImageEncodingQualityLevels() { - int nLevels = native_get_num_image_encoding_quality_levels(); + private static int[] getImageEncodingQualityLevels(int cameraId) { + int nLevels = native_get_num_image_encoding_quality_levels(cameraId); if (nLevels != QUALITY_HIGH + 1) { throw new RuntimeException("Unexpected Jpeg encoding quality levels " + nLevels); } int[] levels = new int[nLevels]; for (int i = 0; i < nLevels; ++i) { - levels[i] = native_get_image_encoding_quality_level(i); + levels[i] = native_get_image_encoding_quality_level(cameraId, i); } Arrays.sort(levels); // Lower quality level ALWAYS comes before higher one return levels; @@ -77,6 +97,6 @@ public class CameraProfile // Methods implemented by JNI private static native final void native_init(); - private static native final int native_get_num_image_encoding_quality_levels(); - private static native final int native_get_image_encoding_quality_level(int index); + private static native final int native_get_num_image_encoding_quality_levels(int cameraId); + private static native final int native_get_image_encoding_quality_level(int cameraId, int index); } diff --git a/media/java/android/media/EnvironmentalReverb.java b/media/java/android/media/EnvironmentalReverb.java new file mode 100644 index 000000000000..b50febce5d00 --- /dev/null +++ b/media/java/android/media/EnvironmentalReverb.java @@ -0,0 +1,668 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioEffect; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +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.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> See {@link android.media.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.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. + * See {@link android.media.AudioEffect#setParameter(byte[], byte[])}. + * @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/media/java/android/media/Equalizer.java b/media/java/android/media/Equalizer.java new file mode 100644 index 000000000000..6fa48c574ce0 --- /dev/null +++ b/media/java/android/media/Equalizer.java @@ -0,0 +1,562 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioEffect; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +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. If the audio session ID 0 + * is specified, the Equalizer applies to the main audio output mix. + * <p> See {@link android.media.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.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. If audioSession + * is not 0, the Equalizer will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the Equalizer will apply to the output mix. + * + * @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); + + 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. + * See {@link android.media.AudioEffect#setParameter(byte[], byte[])}. + * @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/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index b26906d1412f..74488c5e3394 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -51,6 +51,19 @@ public class ExifInterface { public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef"; /** Type is String. */ public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; + + /** + * The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF. + * Type is rational. + */ + public static final String TAG_GPS_ALTITUDE = "GPSAltitude"; + + /** + * 0 if the altitude is above sea level. 1 if the altitude is below sea + * level. Type is int. + */ + public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef"; + /** Type is String. */ public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp"; /** Type is String. */ @@ -289,6 +302,23 @@ public class ExifInterface { } /** + * Return the altitude in meters. If the exif tag does not exist, return + * <var>defaultValue</var>. + * + * @param defaultValue the value to return if the tag is not available. + */ + public double getAltitude(double defaultValue) { + double altitude = getAttributeDouble(TAG_GPS_ALTITUDE, -1); + int ref = getAttributeInt(TAG_GPS_ALTITUDE_REF, -1); + + if (altitude >= 0 && ref >= 0) { + return (double) (altitude * ((ref == 1) ? -1 : 1)); + } else { + return defaultValue; + } + } + + /** * Returns number of milliseconds since Jan. 1, 1970, midnight. * Returns -1 if the date time information if not available. * @hide @@ -345,14 +375,14 @@ public class ExifInterface { / Float.parseFloat(pair[1].trim()))); pair = parts[2].split("/"); - float seconds = Float.parseFloat(pair[0].trim()) - / Float.parseFloat(pair[1].trim()); + double seconds = Double.parseDouble(pair[0].trim()) + / Double.parseDouble(pair[1].trim()); - float result = degrees + (minutes / 60F) + (seconds / (60F * 60F)); + double result = degrees + (minutes / 60.0) + (seconds / 3600.0); if ((ref.equals("S") || ref.equals("W"))) { - return -result; + return (float) -result; } - return result; + return (float) result; } catch (RuntimeException ex) { // if for whatever reason we can't parse the lat long then return // null diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 9d1d4201cb24..6e527d9b1649 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -46,8 +46,9 @@ public class MediaFile { public static final int FILE_TYPE_WMA = 6; public static final int FILE_TYPE_OGG = 7; public static final int FILE_TYPE_AAC = 8; + public static final int FILE_TYPE_MKA = 9; private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3; - private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_AAC; + private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_MKA; // MIDI file types public static final int FILE_TYPE_MID = 11; @@ -63,8 +64,10 @@ public class MediaFile { public static final int FILE_TYPE_3GPP2 = 24; public static final int FILE_TYPE_WMV = 25; public static final int FILE_TYPE_ASF = 26; + public static final int FILE_TYPE_MKV = 27; + public static final int FILE_TYPE_MP2TS = 28; private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; - private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_ASF; + private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_MP2TS; // Image file types public static final int FILE_TYPE_JPEG = 31; @@ -134,6 +137,7 @@ public class MediaFile { addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); addFileType("OGA", FILE_TYPE_OGG, "application/ogg"); addFileType("AAC", FILE_TYPE_AAC, "audio/aac"); + addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); addFileType("MID", FILE_TYPE_MID, "audio/midi"); addFileType("MIDI", FILE_TYPE_MID, "audio/midi"); @@ -151,6 +155,10 @@ public class MediaFile { addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); + addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); + addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska"); + addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); + if (isWMVEnabled()) { addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index 38b1582c3fd0..b8403e175235 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -133,7 +133,7 @@ import java.lang.ref.WeakReference; * <li>It is good programming practice to have your application * register a OnErrorListener to look out for error notifications from * the internal player engine.</li> - * <li>IlleglStateException is + * <li>IllegalStateException is * thrown to prevent programming errors such as calling {@link #prepare()}, * {@link #prepareAsync()}, or one of the overloaded <code>setDataSource * </code> methods in an invalid state. </li> @@ -273,6 +273,16 @@ import java.lang.ref.WeakReference; * <td>Valid Sates </p></td> * <td>Invalid States </p></td> * <td>Comments </p></td></tr> + * <tr><td>attachAuxEffect </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td> + * <td>{Idle, Error} </p></td> + * <td>This method must be called after setDataSource. + * Calling it does not change the object state. </p></td></tr> + * <tr><td>getAudioSessionId </p></td> + * <td>any </p></td> + * <td>{} </p></td> + * <td>This method can be called in any state and calling it does not change + * the object state. </p></td></tr> * <tr><td>getCurrentPosition </p></td> * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped, * PlaybackCompleted} </p></td> @@ -340,6 +350,12 @@ import java.lang.ref.WeakReference; * <td>Successful invoke of this method in a valid state does not change * the state. Calling this method in an invalid state transfers the * object to the <em>Error</em> state. </p></td></tr> + * <tr><td>setAudioSessionId </p></td> + * <td>{Idle} </p></td> + * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, + * Error} </p></td> + * <td>This method must be called in idle state as the audio session ID must be known before + * calling setDataSource. Calling it does not change the object state. </p></td></tr> * <tr><td>setAudioStreamType </p></td> * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused, * PlaybackCompleted}</p></td> @@ -347,6 +363,10 @@ import java.lang.ref.WeakReference; * <td>Successful invoke of this method does not change the state. In order for the * target audio stream type to become effective, this method must be called before * prepare() or prepareAsync().</p></td></tr> + * <tr><td>setAuxEffectSendLevel </p></td> + * <td>any</p></td> + * <td>{} </p></td> + * <td>Calling this method does not change the object state. </p></td></tr> * <tr><td>setDataSource </p></td> * <td>{Idle} </p></td> * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, @@ -423,6 +443,7 @@ import java.lang.ref.WeakReference; * <td>Successful invoke of this method in a valid state transfers the * object to the <em>Stopped</em> state. Calling this method in an * invalid state transfers the object to the <em>Error</em> state.</p></td></tr> + * * </table> * * <a name="Permissions"></a> @@ -1159,6 +1180,62 @@ public class MediaPlayer public native Bitmap getFrameAt(int msec) throws IllegalStateException; /** + * Sets the audio session ID. + * + * @param sessionId the audio session ID. + * The audio session ID is a system wide unique identifier for the audio stream played by + * this MediaPlayer instance. + * The primary use of the audio session ID is to associate audio effects to a particular + * instance of MediaPlayer: if an audio session ID is provided when creating an audio effect, + * this effect will be applied only to the audio content of media players within the same + * audio session and not to the output mix. + * When created, a MediaPlayer instance automatically generates its own audio session ID. + * However, it is possible to force this player to be part of an already existing audio session + * by calling this method. + * This method must be called before one of the overloaded <code> setDataSource </code> methods. + * @throws IllegalStateException if it is called in an invalid state + */ + public native void setAudioSessionId(int sessionId) throws IllegalArgumentException, IllegalStateException; + + /** + * Returns the audio session ID. + * + * @return the audio session ID. {@see #setAudioSessionId(int)} + * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer was contructed. + */ + public native int getAudioSessionId(); + + /** + * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation + * effect which can be applied on any sound source that directs a certain amount of its + * energy to this effect. This amount is defined by setAuxEffectSendLevel(). + * {@see #setAuxEffectSendLevel(float)}. + // TODO when AudioEffect is unhidden + * <p>After creating an auxiliary effect (e.g. {@link android.media.EnvironmentalReverb}), + * retrieve its ID with {@link android.media.AudioEffect#getId()} and use it when calling + * this method to attach the player to the effect. + * <p>To detach the effect from the player, call this method with a null effect id. + * <p>This method must be called after one of the overloaded <code> setDataSource </code> + * methods. + * @param effectId system wide unique id of the effect to attach + */ + public native void attachAuxEffect(int effectId); + + /** + * Sets the send level of the player to the attached auxiliary effect + * {@see #attachAuxEffect(int)}. The level value range is 0 to 1.0. + * <p>By default the send level is 0, so even if an effect is attached to the player + * this method must be called for the effect to be applied. + * <p>Note that the passed level value is a raw scalar. UI controls should be scaled + * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB, + * so an appropriate conversion from linear UI input x to level is: + * x == 0 -> level = 0 + * 0 < x <= R -> level = 10^(72*(x-R)/20/R) + * @param level send level scalar + */ + public native void setAuxEffectSendLevel(float level); + + /** * @param request Parcel destinated to the media player. The * Interface token must be set to the IMediaPlayer * one to be routed correctly through the system. @@ -1523,6 +1600,15 @@ public class MediaPlayer */ public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; + /** MediaPlayer is temporarily pausing playback internally in order to + * buffer more data. + */ + public static final int MEDIA_INFO_BUFFERING_START = 701; + + /** MediaPlayer is resuming playback after filling buffers. + */ + public static final int MEDIA_INFO_BUFFERING_END = 702; + /** Bad interleaving means that a media has been improperly interleaved or * not interleaved at all, e.g has all the video samples first then all the * audio ones. Video is playing but a lot of disk seeks may be happening. @@ -1579,8 +1665,4 @@ public class MediaPlayer private OnInfoListener mOnInfoListener; - /** - * @hide - */ - public native static int snoop(short [] outData, int kind); } diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 47a8cfc1c07b..34a86ec9d466 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -188,6 +188,9 @@ public class MediaRecorder public static final int AAC_ADIF = 5; /** @hide AAC ADTS file format */ public static final int AAC_ADTS = 6; + + /** @hide Stream over a socket, limited to a single stream */ + public static final int OUTPUT_FORMAT_RTP_AVP = 7; }; /** diff --git a/media/java/android/media/PresetReverb.java b/media/java/android/media/PresetReverb.java new file mode 100644 index 000000000000..65175ffdb722 --- /dev/null +++ b/media/java/android/media/PresetReverb.java @@ -0,0 +1,309 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioEffect; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +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.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> See {@link android.media.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.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. + * See {@link android.media.AudioEffect#setParameter(byte[], byte[])}. + * @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/media/java/android/media/Virtualizer.java b/media/java/android/media/Virtualizer.java new file mode 100644 index 000000000000..b08f36e5d8e3 --- /dev/null +++ b/media/java/android/media/Virtualizer.java @@ -0,0 +1,291 @@ +/* + * 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; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.media.AudioEffect; +import android.os.Bundle; +import android.util.Log; + +import java.nio.ByteOrder; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +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. If the audio session ID 0 + * is specified, the Virtualizer applies to the main audio output mix. + * <p> See {@link android.media.AudioEffect} class for more details on controlling audio effects. + */ + +public class Virtualizer extends AudioEffect { + + private final static String TAG = "Virtualizer"; + + // These constants must be synchronized with those in frameworks/base/include/media/EffectVirtualizerApi.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.Virtualizer.OnParameterChangeListener} + */ + public static final int PARAM_STRENGTH = 1; + + /** + * 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. If audioSession + * is not 0, the Virtualizer will be attached to the MediaPlayer or AudioTrack in the + * same audio session. Otherwise, the Virtualizer will apply to the output mix. + * + * @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); + + 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]; + } + + /** + * 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. + * See {@link android.media.AudioEffect#setParameter(byte[], byte[])}. + * @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/media/java/android/media/Visualizer.java b/media/java/android/media/Visualizer.java new file mode 100755 index 000000000000..453fc042b447 --- /dev/null +++ b/media/java/android/media/Visualizer.java @@ -0,0 +1,508 @@ +/* + * 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; + +import android.util.Log; +import java.lang.ref.WeakReference; +import java.io.IOException; +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 MediaPlayer} or + * {@link 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. Note that the size of the FFT + * is half of the specified capture size but both sides of the spectrum are returned yielding in a + * number of bytes equal to the capture size. 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. + */ + +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 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; + + // Error codes: + /** + * Successful operation. + */ + public static final int SUCCESS = 0; + /** + * Unspecified error. + */ + public static final int ERROR = -1; + /** + * Internal opreation 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; + + // accessed by native methods + private int mNativeVisualizer; + private int 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); + 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 ((enabled && mState != STATE_INITIALIZED) || + (!enabled && mState != STATE_ENABLED)) { + throw(new IllegalStateException("setEnabled() called in wrong state: "+mState)); + } + int 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(); + } + } + + /** + * 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. The capture is a 8-bit + * magnitude FFT. Note that the size of the FFT is half of the specified capture size but both + * sides of the spectrum are returned yielding in a number of bytes equal to the capture size. + * {@see #getCaptureSize()}. + * <p>This method must be called when the Visualizer is enabled. + * @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); + } + } + + //--------------------------------------------------------- + // 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. + * @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. + * @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; + } + + /** + * 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; + } + + @Override + public void handleMessage(Message msg) { + if (mVisualizer == null) { + return; + } + 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: "+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); + + 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_getSamplingRate(); + + private native final int native_getWaveForm(byte[] waveform); + + private native final int native_getFft(byte[] fft); + + 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); + } + + } + +} + |
