Home | History | Annotate | Download | only in audiofx
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.media.audiofx;
     18 
     19 import android.annotation.IntDef;
     20 import android.media.AudioDeviceInfo;
     21 import android.media.AudioFormat;
     22 import android.media.audiofx.AudioEffect;
     23 import android.util.Log;
     24 
     25 import java.lang.annotation.Retention;
     26 import java.lang.annotation.RetentionPolicy;
     27 import java.nio.ByteBuffer;
     28 import java.nio.ByteOrder;
     29 import java.util.StringTokenizer;
     30 
     31 
     32 /**
     33  * An audio virtualizer is a general name for an effect to spatialize audio channels. The exact
     34  * behavior of this effect is dependent on the number of audio input channels and the types and
     35  * number of audio output channels of the device. For example, in the case of a stereo input and
     36  * stereo headphone output, a stereo widening effect is used when this effect is turned on.
     37  * <p>An application creates a Virtualizer object to instantiate and control a virtualizer engine
     38  * in the audio framework.
     39  * <p>The methods, parameter types and units exposed by the Virtualizer implementation are directly
     40  * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/)
     41  * for the SLVirtualizerItf interface. Please refer to this specification for more details.
     42  * <p>To attach the Virtualizer to a particular AudioTrack or MediaPlayer, specify the audio session
     43  * ID of this AudioTrack or MediaPlayer when constructing the Virtualizer.
     44  * <p>NOTE: attaching a Virtualizer to the global audio output mix by use of session 0 is
     45  * deprecated.
     46  * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
     47  * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
     48  * audio effects.
     49  */
     50 
     51 public class Virtualizer extends AudioEffect {
     52 
     53     private final static String TAG = "Virtualizer";
     54     private final static boolean DEBUG = false;
     55 
     56     // These constants must be synchronized with those in
     57     //        system/media/audio_effects/include/audio_effects/effect_virtualizer.h
     58     /**
     59      * Is strength parameter supported by virtualizer engine. Parameter ID for getParameter().
     60      */
     61     public static final int PARAM_STRENGTH_SUPPORTED = 0;
     62     /**
     63      * Virtualizer effect strength. Parameter ID for
     64      * {@link android.media.audiofx.Virtualizer.OnParameterChangeListener}
     65      */
     66     public static final int PARAM_STRENGTH = 1;
     67     /**
     68      * @hide
     69      * Parameter ID to query the virtual speaker angles for a channel mask / device configuration.
     70      */
     71     public static final int PARAM_VIRTUAL_SPEAKER_ANGLES = 2;
     72     /**
     73      * @hide
     74      * Parameter ID to force the virtualization mode to be that of a specific device
     75      */
     76     public static final int PARAM_FORCE_VIRTUALIZATION_MODE = 3;
     77     /**
     78      * @hide
     79      * Parameter ID to query the current virtualization mode.
     80      */
     81     public static final int PARAM_VIRTUALIZATION_MODE = 4;
     82 
     83     /**
     84      * Indicates if strength parameter is supported by the virtualizer engine
     85      */
     86     private boolean mStrengthSupported = false;
     87 
     88     /**
     89      * Registered listener for parameter changes.
     90      */
     91     private OnParameterChangeListener mParamListener = null;
     92 
     93     /**
     94      * Listener used internally to to receive raw parameter change event from AudioEffect super class
     95      */
     96     private BaseParameterListener mBaseParamListener = null;
     97 
     98     /**
     99      * Lock for access to mParamListener
    100      */
    101     private final Object mParamListenerLock = new Object();
    102 
    103     /**
    104      * Class constructor.
    105      * @param priority the priority level requested by the application for controlling the Virtualizer
    106      * engine. As the same engine can be shared by several applications, this parameter indicates
    107      * how much the requesting application needs control of effect parameters. The normal priority
    108      * is 0, above normal is a positive number, below normal a negative number.
    109      * @param audioSession  system wide unique audio session identifier. The Virtualizer will
    110      * be attached to the MediaPlayer or AudioTrack in the same audio session.
    111      *
    112      * @throws java.lang.IllegalStateException
    113      * @throws java.lang.IllegalArgumentException
    114      * @throws java.lang.UnsupportedOperationException
    115      * @throws java.lang.RuntimeException
    116      */
    117     public Virtualizer(int priority, int audioSession)
    118     throws IllegalStateException, IllegalArgumentException,
    119            UnsupportedOperationException, RuntimeException {
    120         super(EFFECT_TYPE_VIRTUALIZER, EFFECT_TYPE_NULL, priority, audioSession);
    121 
    122         if (audioSession == 0) {
    123             Log.w(TAG, "WARNING: attaching a Virtualizer to global output mix is deprecated!");
    124         }
    125 
    126         int[] value = new int[1];
    127         checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value));
    128         mStrengthSupported = (value[0] != 0);
    129     }
    130 
    131     /**
    132      * Indicates whether setting strength is supported. If this method returns false, only one
    133      * strength is supported and the setStrength() method always rounds to that value.
    134      * @return true is strength parameter is supported, false otherwise
    135      */
    136     public boolean getStrengthSupported() {
    137        return mStrengthSupported;
    138     }
    139 
    140     /**
    141      * Sets the strength of the virtualizer effect. If the implementation does not support per mille
    142      * accuracy for setting the strength, it is allowed to round the given strength to the nearest
    143      * supported value. You can use the {@link #getRoundedStrength()} method to query the
    144      * (possibly rounded) value that was actually set.
    145      * @param strength strength of the effect. The valid range for strength strength is [0, 1000],
    146      * where 0 per mille designates the mildest effect and 1000 per mille designates the strongest.
    147      * @throws IllegalStateException
    148      * @throws IllegalArgumentException
    149      * @throws UnsupportedOperationException
    150      */
    151     public void setStrength(short strength)
    152     throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    153         checkStatus(setParameter(PARAM_STRENGTH, strength));
    154     }
    155 
    156     /**
    157      * Gets the current strength of the effect.
    158      * @return the strength of the effect. The valid range for strength is [0, 1000], where 0 per
    159      * mille designates the mildest effect and 1000 per mille the strongest
    160      * @throws IllegalStateException
    161      * @throws IllegalArgumentException
    162      * @throws UnsupportedOperationException
    163      */
    164     public short getRoundedStrength()
    165     throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    166         short[] value = new short[1];
    167         checkStatus(getParameter(PARAM_STRENGTH, value));
    168         return value[0];
    169     }
    170 
    171     /**
    172      * Checks if a configuration is supported, and query the virtual speaker angles.
    173      * @param inputChannelMask
    174      * @param deviceType
    175      * @param angles if non-null: array in which the angles will be written. If null, no angles
    176      *    are returned
    177      * @return true if the combination of channel mask and output device type is supported, false
    178      *    otherwise
    179      * @throws IllegalStateException
    180      * @throws IllegalArgumentException
    181      * @throws UnsupportedOperationException
    182      */
    183     private boolean getAnglesInt(int inputChannelMask, int deviceType, int[] angles)
    184             throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    185         // parameter check
    186         if (inputChannelMask == AudioFormat.CHANNEL_INVALID) {
    187             throw (new IllegalArgumentException(
    188                     "Virtualizer: illegal CHANNEL_INVALID channel mask"));
    189         }
    190         int channelMask = inputChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT ?
    191                 AudioFormat.CHANNEL_OUT_STEREO : inputChannelMask;
    192         int nbChannels = AudioFormat.channelCountFromOutChannelMask(channelMask);
    193         if ((angles != null) && (angles.length < (nbChannels * 3))) {
    194             Log.e(TAG, "Size of array for angles cannot accomodate number of channels in mask ("
    195                     + nbChannels + ")");
    196             throw (new IllegalArgumentException(
    197                     "Virtualizer: array for channel / angle pairs is too small: is " + angles.length
    198                     + ", should be " + (nbChannels * 3)));
    199         }
    200 
    201         ByteBuffer paramsConverter = ByteBuffer.allocate(3 /* param + mask + device*/ * 4);
    202         paramsConverter.order(ByteOrder.nativeOrder());
    203         paramsConverter.putInt(PARAM_VIRTUAL_SPEAKER_ANGLES);
    204         // convert channel mask to internal native representation
    205         paramsConverter.putInt(AudioFormat.convertChannelOutMaskToNativeMask(channelMask));
    206         // convert Java device type to internal representation
    207         paramsConverter.putInt(AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType));
    208         // allocate an array to store the results
    209         byte[] result = new byte[nbChannels * 4/*int to byte*/ * 3/*for mask, azimuth, elevation*/];
    210 
    211         // call into the effect framework
    212         int status = getParameter(paramsConverter.array(), result);
    213         if (DEBUG) {
    214             Log.v(TAG, "getAngles(0x" + Integer.toHexString(inputChannelMask) + ", 0x"
    215                     + Integer.toHexString(deviceType) + ") returns " + status);
    216         }
    217 
    218         if (status >= 0) {
    219             if (angles != null) {
    220                 // convert and copy the results
    221                 ByteBuffer resultConverter = ByteBuffer.wrap(result);
    222                 resultConverter.order(ByteOrder.nativeOrder());
    223                 for (int i = 0 ; i < nbChannels ; i++) {
    224                     // write the channel mask
    225                     angles[3 * i] = AudioFormat.convertNativeChannelMaskToOutMask(
    226                             resultConverter.getInt((i * 4 * 3)));
    227                     // write the azimuth
    228                     angles[3 * i + 1] = resultConverter.getInt(i * 4 * 3 + 4);
    229                     // write the elevation
    230                     angles[3 * i + 2] = resultConverter.getInt(i * 4 * 3 + 8);
    231                     if (DEBUG) {
    232                         Log.v(TAG, "channel 0x" + Integer.toHexString(angles[3*i]).toUpperCase()
    233                                 + " at az=" + angles[3*i+1] + "deg"
    234                                 + " elev="  + angles[3*i+2] + "deg");
    235                     }
    236                 }
    237             }
    238             return true;
    239         } else if (status == AudioEffect.ERROR_BAD_VALUE) {
    240             // a BAD_VALUE return from getParameter indicates the configuration is not supported
    241             // don't throw an exception, just return false
    242             return false;
    243         } else {
    244             // something wrong may have happened
    245             checkStatus(status);
    246         }
    247         // unexpected virtualizer behavior
    248         Log.e(TAG, "unexpected status code " + status
    249                 + " after getParameter(PARAM_VIRTUAL_SPEAKER_ANGLES)");
    250         return false;
    251     }
    252 
    253     /**
    254      * A virtualization mode indicating virtualization processing is not active.
    255      * See {@link #getVirtualizationMode()} as one of the possible return value.
    256      */
    257     public static final int VIRTUALIZATION_MODE_OFF = 0;
    258 
    259     /**
    260      * A virtualization mode used to indicate the virtualizer effect must stop forcing the
    261      * processing to a particular mode in {@link #forceVirtualizationMode(int)}.
    262      */
    263     public static final int VIRTUALIZATION_MODE_AUTO = 1;
    264     /**
    265      * A virtualization mode typically used over headphones.
    266      * Binaural virtualization describes an audio processing configuration for virtualization
    267      * where the left and right channels are respectively reaching the left and right ear of the
    268      * user, without also feeding the opposite ear (as is the case when listening over speakers).
    269      * <p>Such a mode is therefore meant to be used when audio is playing over stereo wired
    270      * headphones or headsets, but also stereo headphones through a wireless A2DP Bluetooth link.
    271      * <p>See {@link #canVirtualize(int, int)} to verify this mode is supported by this Virtualizer.
    272      */
    273     public final static int VIRTUALIZATION_MODE_BINAURAL = 2;
    274 
    275     /**
    276      * A virtualization mode typically used over speakers.
    277      * Transaural virtualization describes an audio processing configuration that differs from
    278      * binaural (as described in {@link #VIRTUALIZATION_MODE_BINAURAL} in that cross-talk is
    279      * present, i.e. audio played from the left channel also reaches the right ear of the user,
    280      * and vice-versa.
    281      * <p>When supported, such a mode is therefore meant to be used when audio is playing over the
    282      * built-in stereo speakers of a device, if they are featured.
    283      * <p>See {@link #canVirtualize(int, int)} to verify this mode is supported by this Virtualizer.
    284      */
    285     public final static int VIRTUALIZATION_MODE_TRANSAURAL = 3;
    286 
    287     /** @hide */
    288     @IntDef( {
    289         VIRTUALIZATION_MODE_BINAURAL,
    290         VIRTUALIZATION_MODE_TRANSAURAL
    291     })
    292     @Retention(RetentionPolicy.SOURCE)
    293     public @interface VirtualizationMode {}
    294 
    295     /** @hide */
    296     @IntDef( {
    297         VIRTUALIZATION_MODE_AUTO,
    298         VIRTUALIZATION_MODE_BINAURAL,
    299         VIRTUALIZATION_MODE_TRANSAURAL
    300     })
    301     @Retention(RetentionPolicy.SOURCE)
    302     public @interface ForceVirtualizationMode {}
    303 
    304     private static int getDeviceForModeQuery(@VirtualizationMode int virtualizationMode)
    305             throws IllegalArgumentException {
    306         switch (virtualizationMode) {
    307             case VIRTUALIZATION_MODE_BINAURAL:
    308                 return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
    309             case VIRTUALIZATION_MODE_TRANSAURAL:
    310                 return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
    311             default:
    312                 throw (new IllegalArgumentException(
    313                         "Virtualizer: illegal virtualization mode " + virtualizationMode));
    314         }
    315     }
    316 
    317     private static int getDeviceForModeForce(@ForceVirtualizationMode int virtualizationMode)
    318             throws IllegalArgumentException {
    319         if (virtualizationMode == VIRTUALIZATION_MODE_AUTO) {
    320             return AudioDeviceInfo.TYPE_UNKNOWN;
    321         } else {
    322             return getDeviceForModeQuery(virtualizationMode);
    323         }
    324     }
    325 
    326     private static int deviceToMode(int deviceType) {
    327         switch (deviceType) {
    328             case AudioDeviceInfo.TYPE_WIRED_HEADSET:
    329             case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
    330             case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
    331             case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
    332             case AudioDeviceInfo.TYPE_USB_HEADSET:
    333                 return VIRTUALIZATION_MODE_BINAURAL;
    334             case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
    335             case AudioDeviceInfo.TYPE_LINE_ANALOG:
    336             case AudioDeviceInfo.TYPE_LINE_DIGITAL:
    337             case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
    338             case AudioDeviceInfo.TYPE_HDMI:
    339             case AudioDeviceInfo.TYPE_HDMI_ARC:
    340             case AudioDeviceInfo.TYPE_USB_DEVICE:
    341             case AudioDeviceInfo.TYPE_USB_ACCESSORY:
    342             case AudioDeviceInfo.TYPE_DOCK:
    343             case AudioDeviceInfo.TYPE_FM:
    344             case AudioDeviceInfo.TYPE_AUX_LINE:
    345                 return VIRTUALIZATION_MODE_TRANSAURAL;
    346             case AudioDeviceInfo.TYPE_UNKNOWN:
    347             default:
    348                 return VIRTUALIZATION_MODE_OFF;
    349         }
    350     }
    351 
    352     /**
    353      * Checks if the combination of a channel mask and virtualization mode is supported by this
    354      * virtualizer.
    355      * Some virtualizer implementations may only support binaural processing (i.e. only support
    356      * headphone output, see {@link #VIRTUALIZATION_MODE_BINAURAL}), some may support transaural
    357      * processing (i.e. for speaker output, see {@link #VIRTUALIZATION_MODE_TRANSAURAL}) for the
    358      * built-in speakers. Use this method to query the virtualizer implementation capabilities.
    359      * @param inputChannelMask the channel mask of the content to virtualize.
    360      * @param virtualizationMode the mode for which virtualization processing is to be performed,
    361      *    one of {@link #VIRTUALIZATION_MODE_BINAURAL}, {@link #VIRTUALIZATION_MODE_TRANSAURAL}.
    362      * @return true if the combination of channel mask and virtualization mode is supported, false
    363      *    otherwise.
    364      *    <br>An indication that a certain channel mask is not supported doesn't necessarily mean
    365      *    you cannot play content with that channel mask, it more likely implies the content will
    366      *    be downmixed before being virtualized. For instance a virtualizer that only supports a
    367      *    mask such as {@link AudioFormat#CHANNEL_OUT_STEREO}
    368      *    will still be able to process content with a mask of
    369      *    {@link AudioFormat#CHANNEL_OUT_5POINT1}, but will downmix the content to stereo first, and
    370      *    then will virtualize, as opposed to virtualizing each channel individually.
    371      * @throws IllegalStateException
    372      * @throws IllegalArgumentException
    373      * @throws UnsupportedOperationException
    374      */
    375     public boolean canVirtualize(int inputChannelMask, @VirtualizationMode int virtualizationMode)
    376             throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    377         return getAnglesInt(inputChannelMask, getDeviceForModeQuery(virtualizationMode), null);
    378     }
    379 
    380     /**
    381      * Queries the virtual speaker angles (azimuth and elevation) for a combination of a channel
    382      * mask and virtualization mode.
    383      * If the virtualization configuration (mask and mode) is supported (see
    384      * {@link #canVirtualize(int, int)}, the array angles will contain upon return the
    385      * definition of each virtual speaker and its azimuth and elevation angles relative to the
    386      * listener.
    387      * <br>Note that in some virtualizer implementations, the angles may be strength-dependent.
    388      * @param inputChannelMask the channel mask of the content to virtualize.
    389      * @param virtualizationMode the mode for which virtualization processing is to be performed,
    390      *    one of {@link #VIRTUALIZATION_MODE_BINAURAL}, {@link #VIRTUALIZATION_MODE_TRANSAURAL}.
    391      * @param angles a non-null array whose length is 3 times the number of channels in the channel
    392      *    mask.
    393      *    If the method indicates the configuration is supported, the array will contain upon return
    394      *    triplets of values: for each channel <code>i</code> among the channels of the mask:
    395      *    <ul>
    396      *      <li>the element at index <code>3*i</code> in the array contains the speaker
    397      *          identification (e.g. {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT}),</li>
    398      *      <li>the element at index <code>3*i+1</code> contains its corresponding azimuth angle
    399      *          expressed in degrees, where 0 is the direction the listener faces, 180 is behind
    400      *          the listener, and -90 is to her/his left,</li>
    401      *      <li>the element at index <code>3*i+2</code> contains its corresponding elevation angle
    402      *          where +90 is directly above the listener, 0 is the horizontal plane, and -90 is
    403      *          directly below the listener.</li>
    404      * @return true if the combination of channel mask and virtualization mode is supported, false
    405      *    otherwise.
    406      * @throws IllegalStateException
    407      * @throws IllegalArgumentException
    408      * @throws UnsupportedOperationException
    409      */
    410     public boolean getSpeakerAngles(int inputChannelMask,
    411             @VirtualizationMode int virtualizationMode, int[] angles)
    412             throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    413         if (angles == null) {
    414             throw (new IllegalArgumentException(
    415                     "Virtualizer: illegal null channel / angle array"));
    416         }
    417 
    418         return getAnglesInt(inputChannelMask, getDeviceForModeQuery(virtualizationMode), angles);
    419     }
    420 
    421     /**
    422      * Forces the virtualizer effect to use the given processing mode.
    423      * The effect must be enabled for the forced mode to be applied.
    424      * @param virtualizationMode one of {@link #VIRTUALIZATION_MODE_BINAURAL},
    425      *     {@link #VIRTUALIZATION_MODE_TRANSAURAL} to force a particular processing mode, or
    426      *     {@value #VIRTUALIZATION_MODE_AUTO} to stop forcing a mode.
    427      * @return true if the processing mode is supported, and it is successfully set, or
    428      *     forcing was successfully disabled, false otherwise.
    429      * @throws IllegalStateException
    430      * @throws IllegalArgumentException
    431      * @throws UnsupportedOperationException
    432      */
    433     public boolean forceVirtualizationMode(@ForceVirtualizationMode int virtualizationMode)
    434             throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    435         // convert Java device type to internal representation
    436         int deviceType = getDeviceForModeForce(virtualizationMode);
    437         int internalDevice = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType);
    438 
    439         int status = setParameter(PARAM_FORCE_VIRTUALIZATION_MODE, internalDevice);
    440 
    441         if (status >= 0) {
    442             return true;
    443         } else if (status == AudioEffect.ERROR_BAD_VALUE) {
    444             // a BAD_VALUE return from setParameter indicates the mode can't be forced
    445             // don't throw an exception, just return false
    446             return false;
    447         } else {
    448             // something wrong may have happened
    449             checkStatus(status);
    450         }
    451         // unexpected virtualizer behavior
    452         Log.e(TAG, "unexpected status code " + status
    453                 + " after setParameter(PARAM_FORCE_VIRTUALIZATION_MODE)");
    454         return false;
    455     }
    456 
    457     /**
    458      * Return the virtualization mode being used, if any.
    459      * @return the virtualization mode being used.
    460      *     If virtualization is not active, the virtualization mode will be
    461      *     {@link #VIRTUALIZATION_MODE_OFF}. Otherwise the value will be
    462      *     {@link #VIRTUALIZATION_MODE_BINAURAL} or {@link #VIRTUALIZATION_MODE_TRANSAURAL}.
    463      *     Virtualization may not be active either because the effect is not enabled or
    464      *     because the current output device is not compatible with this virtualization
    465      *     implementation.
    466      * @throws IllegalStateException
    467      * @throws UnsupportedOperationException
    468      */
    469     public int getVirtualizationMode()
    470             throws IllegalStateException, UnsupportedOperationException {
    471         int[] value = new int[1];
    472         int status = getParameter(PARAM_VIRTUALIZATION_MODE, value);
    473         if (status >= 0) {
    474             return deviceToMode(AudioDeviceInfo.convertInternalDeviceToDeviceType(value[0]));
    475         } else if (status == AudioEffect.ERROR_BAD_VALUE) {
    476             return VIRTUALIZATION_MODE_OFF;
    477         } else {
    478             // something wrong may have happened
    479             checkStatus(status);
    480         }
    481         // unexpected virtualizer behavior
    482         Log.e(TAG, "unexpected status code " + status
    483                 + " after getParameter(PARAM_VIRTUALIZATION_MODE)");
    484         return VIRTUALIZATION_MODE_OFF;
    485     }
    486 
    487     /**
    488      * The OnParameterChangeListener interface defines a method called by the Virtualizer when a
    489      * parameter value has changed.
    490      */
    491     public interface OnParameterChangeListener  {
    492         /**
    493          * Method called when a parameter value has changed. The method is called only if the
    494          * parameter was changed by another application having the control of the same
    495          * Virtualizer engine.
    496          * @param effect the Virtualizer on which the interface is registered.
    497          * @param status status of the set parameter operation.
    498          * @param param ID of the modified parameter. See {@link #PARAM_STRENGTH} ...
    499          * @param value the new parameter value.
    500          */
    501         void onParameterChange(Virtualizer effect, int status, int param, short value);
    502     }
    503 
    504     /**
    505      * Listener used internally to receive unformatted parameter change events from AudioEffect
    506      * super class.
    507      */
    508     private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
    509         private BaseParameterListener() {
    510 
    511         }
    512         public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
    513             OnParameterChangeListener l = null;
    514 
    515             synchronized (mParamListenerLock) {
    516                 if (mParamListener != null) {
    517                     l = mParamListener;
    518                 }
    519             }
    520             if (l != null) {
    521                 int p = -1;
    522                 short v = -1;
    523 
    524                 if (param.length == 4) {
    525                     p = byteArrayToInt(param, 0);
    526                 }
    527                 if (value.length == 2) {
    528                     v = byteArrayToShort(value, 0);
    529                 }
    530                 if (p != -1 && v != -1) {
    531                     l.onParameterChange(Virtualizer.this, status, p, v);
    532                 }
    533             }
    534         }
    535     }
    536 
    537     /**
    538      * Registers an OnParameterChangeListener interface.
    539      * @param listener OnParameterChangeListener interface registered
    540      */
    541     public void setParameterListener(OnParameterChangeListener listener) {
    542         synchronized (mParamListenerLock) {
    543             if (mParamListener == null) {
    544                 mParamListener = listener;
    545                 mBaseParamListener = new BaseParameterListener();
    546                 super.setParameterListener(mBaseParamListener);
    547             }
    548         }
    549     }
    550 
    551     /**
    552      * The Settings class regroups all virtualizer parameters. It is used in
    553      * conjuntion with getProperties() and setProperties() methods to backup and restore
    554      * all parameters in a single call.
    555      */
    556     public static class Settings {
    557         public short strength;
    558 
    559         public Settings() {
    560         }
    561 
    562         /**
    563          * Settings class constructor from a key=value; pairs formatted string. The string is
    564          * typically returned by Settings.toString() method.
    565          * @throws IllegalArgumentException if the string is not correctly formatted.
    566          */
    567         public Settings(String settings) {
    568             StringTokenizer st = new StringTokenizer(settings, "=;");
    569             int tokens = st.countTokens();
    570             if (st.countTokens() != 3) {
    571                 throw new IllegalArgumentException("settings: " + settings);
    572             }
    573             String key = st.nextToken();
    574             if (!key.equals("Virtualizer")) {
    575                 throw new IllegalArgumentException(
    576                         "invalid settings for Virtualizer: " + key);
    577             }
    578             try {
    579                 key = st.nextToken();
    580                 if (!key.equals("strength")) {
    581                     throw new IllegalArgumentException("invalid key name: " + key);
    582                 }
    583                 strength = Short.parseShort(st.nextToken());
    584              } catch (NumberFormatException nfe) {
    585                 throw new IllegalArgumentException("invalid value for key: " + key);
    586             }
    587         }
    588 
    589         @Override
    590         public String toString() {
    591             String str = new String (
    592                     "Virtualizer"+
    593                     ";strength="+Short.toString(strength)
    594                     );
    595             return str;
    596         }
    597     };
    598 
    599 
    600     /**
    601      * Gets the virtualizer properties. This method is useful when a snapshot of current
    602      * virtualizer settings must be saved by the application.
    603      * @return a Virtualizer.Settings object containing all current parameters values
    604      * @throws IllegalStateException
    605      * @throws IllegalArgumentException
    606      * @throws UnsupportedOperationException
    607      */
    608     public Virtualizer.Settings getProperties()
    609     throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    610         Settings settings = new Settings();
    611         short[] value = new short[1];
    612         checkStatus(getParameter(PARAM_STRENGTH, value));
    613         settings.strength = value[0];
    614         return settings;
    615     }
    616 
    617     /**
    618      * Sets the virtualizer properties. This method is useful when virtualizer settings have to
    619      * be applied from a previous backup.
    620      * @param settings a Virtualizer.Settings object containing the properties to apply
    621      * @throws IllegalStateException
    622      * @throws IllegalArgumentException
    623      * @throws UnsupportedOperationException
    624      */
    625     public void setProperties(Virtualizer.Settings settings)
    626     throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
    627         checkStatus(setParameter(PARAM_STRENGTH, settings.strength));
    628     }
    629 }
    630