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
17package android.media.audiofx;
18
19import android.media.audiofx.AudioEffect;
20import android.util.Log;
21
22import java.util.StringTokenizer;
23
24
25/**
26 * An Equalizer is used to alter the frequency response of a particular music source or of the main
27 * output mix.
28 * <p>An application creates an Equalizer object to instantiate and control an Equalizer engine
29 * in the audio framework. The application can either simply use predefined presets or have a more
30 * precise control of the gain in each frequency band controlled by the equalizer.
31 * <p>The methods, parameter types and units exposed by the Equalizer implementation are directly
32 * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/)
33 * for the SLEqualizerItf interface. Please refer to this specification for more details.
34 * <p>To attach the Equalizer to a particular AudioTrack or MediaPlayer, specify the audio session
35 * ID of this AudioTrack or MediaPlayer when constructing the Equalizer.
36 * <p>NOTE: attaching an Equalizer to the global audio output mix by use of session 0 is deprecated.
37 * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
38 * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling audio
39 * effects.
40 */
41
42public class Equalizer extends AudioEffect {
43
44    private final static String TAG = "Equalizer";
45
46    // These constants must be synchronized with those in
47    // frameworks/base/include/media/EffectEqualizerApi.h
48    /**
49     * Number of bands. Parameter ID for OnParameterChangeListener
50     */
51    public static final int PARAM_NUM_BANDS = 0;
52    /**
53     * Band level range. Parameter ID for OnParameterChangeListener
54     */
55    public static final int PARAM_LEVEL_RANGE = 1;
56    /**
57     * Band level. Parameter ID for OnParameterChangeListener
58     */
59    public static final int PARAM_BAND_LEVEL = 2;
60    /**
61     * Band center frequency. Parameter ID for OnParameterChangeListener
62     */
63    public static final int PARAM_CENTER_FREQ = 3;
64    /**
65     * Band frequency range. Parameter ID for
66     * {@link android.media.audiofx.Equalizer.OnParameterChangeListener}
67     */
68    public static final int PARAM_BAND_FREQ_RANGE = 4;
69    /**
70     * Band for a given frequency. Parameter ID for OnParameterChangeListener
71     *
72     */
73    public static final int PARAM_GET_BAND = 5;
74    /**
75     * Current preset. Parameter ID for OnParameterChangeListener
76     */
77    public static final int PARAM_CURRENT_PRESET = 6;
78    /**
79     * Request number of presets. Parameter ID for OnParameterChangeListener
80     */
81    public static final int PARAM_GET_NUM_OF_PRESETS = 7;
82    /**
83     * Request preset name. Parameter ID for OnParameterChangeListener
84     */
85    public static final int PARAM_GET_PRESET_NAME = 8;
86    // used by setProperties()/getProperties
87    private static final int PARAM_PROPERTIES = 9;
88    /**
89     * Maximum size for preset name
90     */
91    public static final int PARAM_STRING_SIZE_MAX = 32;
92
93    /**
94     * Number of bands implemented by Equalizer engine
95     */
96    private short mNumBands = 0;
97
98    /**
99     * Number of presets implemented by Equalizer engine
100     */
101    private int mNumPresets;
102    /**
103     * Names of presets implemented by Equalizer engine
104     */
105    private String[] mPresetNames;
106
107    /**
108     * Registered listener for parameter changes.
109     */
110    private OnParameterChangeListener mParamListener = null;
111
112    /**
113     * Listener used internally to to receive raw parameter change event from AudioEffect super class
114     */
115    private BaseParameterListener mBaseParamListener = null;
116
117    /**
118     * Lock for access to mParamListener
119     */
120    private final Object mParamListenerLock = new Object();
121
122    /**
123     * Class constructor.
124     * @param priority the priority level requested by the application for controlling the Equalizer
125     * engine. As the same engine can be shared by several applications, this parameter indicates
126     * how much the requesting application needs control of effect parameters. The normal priority
127     * is 0, above normal is a positive number, below normal a negative number.
128     * @param audioSession  system wide unique audio session identifier. The Equalizer will be
129     * attached to the MediaPlayer or AudioTrack in the same audio session.
130     *
131     * @throws java.lang.IllegalStateException
132     * @throws java.lang.IllegalArgumentException
133     * @throws java.lang.UnsupportedOperationException
134     * @throws java.lang.RuntimeException
135     */
136    public Equalizer(int priority, int audioSession)
137    throws IllegalStateException, IllegalArgumentException,
138           UnsupportedOperationException, RuntimeException {
139        super(EFFECT_TYPE_EQUALIZER, EFFECT_TYPE_NULL, priority, audioSession);
140
141        if (audioSession == 0) {
142            Log.w(TAG, "WARNING: attaching an Equalizer to global output mix is deprecated!");
143        }
144
145        getNumberOfBands();
146
147        mNumPresets = (int)getNumberOfPresets();
148
149        if (mNumPresets != 0) {
150            mPresetNames = new String[mNumPresets];
151            byte[] value = new byte[PARAM_STRING_SIZE_MAX];
152            int[] param = new int[2];
153            param[0] = PARAM_GET_PRESET_NAME;
154            for (int i = 0; i < mNumPresets; i++) {
155                param[1] = i;
156                checkStatus(getParameter(param, value));
157                int length = 0;
158                while (value[length] != 0) length++;
159                try {
160                    mPresetNames[i] = new String(value, 0, length, "ISO-8859-1");
161                } catch (java.io.UnsupportedEncodingException e) {
162                    Log.e(TAG, "preset name decode error");
163                }
164            }
165        }
166    }
167
168    /**
169     * Gets the number of frequency bands supported by the Equalizer engine.
170     * @return the number of bands
171     * @throws IllegalStateException
172     * @throws IllegalArgumentException
173     * @throws UnsupportedOperationException
174     */
175    public short getNumberOfBands()
176    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
177        if (mNumBands != 0) {
178            return mNumBands;
179        }
180        int[] param = new int[1];
181        param[0] = PARAM_NUM_BANDS;
182        short[] result = new short[1];
183        checkStatus(getParameter(param, result));
184        mNumBands = result[0];
185        return mNumBands;
186    }
187
188    /**
189     * Gets the level range for use by {@link #setBandLevel(short,short)}. The level is expressed in
190     * milliBel.
191     * @return the band level range in an array of short integers. The first element is the lower
192     * limit of the range, the second element the upper limit.
193     * @throws IllegalStateException
194     * @throws IllegalArgumentException
195     * @throws UnsupportedOperationException
196     */
197    public short[] getBandLevelRange()
198    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
199        short[] result = new short[2];
200        checkStatus(getParameter(PARAM_LEVEL_RANGE, result));
201        return result;
202    }
203
204    /**
205     * Sets the given equalizer band to the given gain value.
206     * @param band frequency band that will have the new gain. The numbering of the bands starts
207     * from 0 and ends at (number of bands - 1).
208     * @param level new gain in millibels that will be set to the given band. getBandLevelRange()
209     * will define the maximum and minimum values.
210     * @throws IllegalStateException
211     * @throws IllegalArgumentException
212     * @throws UnsupportedOperationException
213     * @see #getNumberOfBands()
214     */
215    public void setBandLevel(short band, short level)
216    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
217        int[] param = new int[2];
218        short[] value = new short[1];
219
220        param[0] = PARAM_BAND_LEVEL;
221        param[1] = (int)band;
222        value[0] = level;
223        checkStatus(setParameter(param, value));
224    }
225
226    /**
227     * Gets the gain set for the given equalizer band.
228     * @param band frequency band whose gain is requested. The numbering of the bands starts
229     * from 0 and ends at (number of bands - 1).
230     * @return the gain in millibels of the given band.
231     * @throws IllegalStateException
232     * @throws IllegalArgumentException
233     * @throws UnsupportedOperationException
234     */
235    public short getBandLevel(short band)
236    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
237        int[] param = new int[2];
238        short[] result = new short[1];
239
240        param[0] = PARAM_BAND_LEVEL;
241        param[1] = (int)band;
242        checkStatus(getParameter(param, result));
243
244        return result[0];
245    }
246
247
248    /**
249     * Gets the center frequency of the given band.
250     * @param band frequency band whose center frequency is requested. The numbering of the bands
251     * starts from 0 and ends at (number of bands - 1).
252     * @return the center frequency in milliHertz
253     * @throws IllegalStateException
254     * @throws IllegalArgumentException
255     * @throws UnsupportedOperationException
256     */
257    public int getCenterFreq(short band)
258    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
259        int[] param = new int[2];
260        int[] result = new int[1];
261
262        param[0] = PARAM_CENTER_FREQ;
263        param[1] = (int)band;
264        checkStatus(getParameter(param, result));
265
266        return result[0];
267    }
268
269    /**
270     * Gets the frequency range of the given frequency band.
271     * @param band frequency band whose frequency range is requested. The numbering of the bands
272     * starts from 0 and ends at (number of bands - 1).
273     * @return the frequency range in millHertz in an array of integers. The first element is the
274     * lower limit of the range, the second element the upper limit.
275     * @throws IllegalStateException
276     * @throws IllegalArgumentException
277     * @throws UnsupportedOperationException
278     */
279    public int[] getBandFreqRange(short band)
280    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
281        int[] param = new int[2];
282        int[] result = new int[2];
283        param[0] = PARAM_BAND_FREQ_RANGE;
284        param[1] = (int)band;
285        checkStatus(getParameter(param, result));
286
287        return result;
288    }
289
290    /**
291     * Gets the band that has the most effect on the given frequency.
292     * @param frequency frequency in milliHertz which is to be equalized via the returned band.
293     * @return the frequency band that has most effect on the given frequency.
294     * @throws IllegalStateException
295     * @throws IllegalArgumentException
296     * @throws UnsupportedOperationException
297     */
298    public short getBand(int frequency)
299    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
300        int[] param = new int[2];
301        short[] result = new short[1];
302
303        param[0] = PARAM_GET_BAND;
304        param[1] = frequency;
305        checkStatus(getParameter(param, result));
306
307        return result[0];
308    }
309
310    /**
311     * Gets current preset.
312     * @return the preset that is set at the moment.
313     * @throws IllegalStateException
314     * @throws IllegalArgumentException
315     * @throws UnsupportedOperationException
316     */
317    public short getCurrentPreset()
318    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
319        short[] result = new short[1];
320        checkStatus(getParameter(PARAM_CURRENT_PRESET, result));
321        return result[0];
322    }
323
324    /**
325     * Sets the equalizer according to the given preset.
326     * @param preset new preset that will be taken into use. The valid range is [0,
327     * number of presets-1].
328     * @throws IllegalStateException
329     * @throws IllegalArgumentException
330     * @throws UnsupportedOperationException
331     * @see #getNumberOfPresets()
332     */
333    public void usePreset(short preset)
334    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
335        checkStatus(setParameter(PARAM_CURRENT_PRESET, preset));
336    }
337
338    /**
339     * Gets the total number of presets the equalizer supports. The presets will have indices
340     * [0, number of presets-1].
341     * @return the number of presets the equalizer supports.
342     * @throws IllegalStateException
343     * @throws IllegalArgumentException
344     * @throws UnsupportedOperationException
345     */
346    public short getNumberOfPresets()
347    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
348        short[] result = new short[1];
349        checkStatus(getParameter(PARAM_GET_NUM_OF_PRESETS, result));
350        return result[0];
351    }
352
353    /**
354     * Gets the preset name based on the index.
355     * @param preset index of the preset. The valid range is [0, number of presets-1].
356     * @return a string containing the name of the given preset.
357     * @throws IllegalStateException
358     * @throws IllegalArgumentException
359     * @throws UnsupportedOperationException
360     */
361    public String getPresetName(short preset)
362    {
363        if (preset >= 0 && preset < mNumPresets) {
364            return mPresetNames[preset];
365        } else {
366            return "";
367        }
368    }
369
370    /**
371     * The OnParameterChangeListener interface defines a method called by the Equalizer when a
372     * parameter value has changed.
373     */
374    public interface OnParameterChangeListener  {
375        /**
376         * Method called when a parameter value has changed. The method is called only if the
377         * parameter was changed by another application having the control of the same
378         * Equalizer engine.
379         * @param effect the Equalizer on which the interface is registered.
380         * @param status status of the set parameter operation.
381         * @param param1 ID of the modified parameter. See {@link #PARAM_BAND_LEVEL} ...
382         * @param param2 additional parameter qualifier (e.g the band for band level parameter).
383         * @param value the new parameter value.
384         */
385        void onParameterChange(Equalizer effect, int status, int param1, int param2, int value);
386    }
387
388    /**
389     * Listener used internally to receive unformatted parameter change events from AudioEffect
390     * super class.
391     */
392    private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
393        private BaseParameterListener() {
394
395        }
396        public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
397            OnParameterChangeListener l = null;
398
399            synchronized (mParamListenerLock) {
400                if (mParamListener != null) {
401                    l = mParamListener;
402                }
403            }
404            if (l != null) {
405                int p1 = -1;
406                int p2 = -1;
407                int v = -1;
408
409                if (param.length >= 4) {
410                    p1 = byteArrayToInt(param, 0);
411                    if (param.length >= 8) {
412                        p2 = byteArrayToInt(param, 4);
413                    }
414                }
415                if (value.length == 2) {
416                    v = (int)byteArrayToShort(value, 0);;
417                } else if (value.length == 4) {
418                    v = byteArrayToInt(value, 0);
419                }
420
421                if (p1 != -1 && v != -1) {
422                    l.onParameterChange(Equalizer.this, status, p1, p2, v);
423                }
424            }
425        }
426    }
427
428    /**
429     * Registers an OnParameterChangeListener interface.
430     * @param listener OnParameterChangeListener interface registered
431     */
432    public void setParameterListener(OnParameterChangeListener listener) {
433        synchronized (mParamListenerLock) {
434            if (mParamListener == null) {
435                mParamListener = listener;
436                mBaseParamListener = new BaseParameterListener();
437                super.setParameterListener(mBaseParamListener);
438            }
439        }
440    }
441
442    /**
443     * The Settings class regroups all equalizer parameters. It is used in
444     * conjuntion with getProperties() and setProperties() methods to backup and restore
445     * all parameters in a single call.
446     */
447    public static class Settings {
448        public short curPreset;
449        public short numBands = 0;
450        public short[] bandLevels = null;
451
452        public Settings() {
453        }
454
455        /**
456         * Settings class constructor from a key=value; pairs formatted string. The string is
457         * typically returned by Settings.toString() method.
458         * @throws IllegalArgumentException if the string is not correctly formatted.
459         */
460        public Settings(String settings) {
461            StringTokenizer st = new StringTokenizer(settings, "=;");
462            int tokens = st.countTokens();
463            if (st.countTokens() < 5) {
464                throw new IllegalArgumentException("settings: " + settings);
465            }
466            String key = st.nextToken();
467            if (!key.equals("Equalizer")) {
468                throw new IllegalArgumentException(
469                        "invalid settings for Equalizer: " + key);
470            }
471            try {
472                key = st.nextToken();
473                if (!key.equals("curPreset")) {
474                    throw new IllegalArgumentException("invalid key name: " + key);
475                }
476                curPreset = Short.parseShort(st.nextToken());
477                key = st.nextToken();
478                if (!key.equals("numBands")) {
479                    throw new IllegalArgumentException("invalid key name: " + key);
480                }
481                numBands = Short.parseShort(st.nextToken());
482                if (st.countTokens() != numBands*2) {
483                    throw new IllegalArgumentException("settings: " + settings);
484                }
485                bandLevels = new short[numBands];
486                for (int i = 0; i < numBands; i++) {
487                    key = st.nextToken();
488                    if (!key.equals("band"+(i+1)+"Level")) {
489                        throw new IllegalArgumentException("invalid key name: " + key);
490                    }
491                    bandLevels[i] = Short.parseShort(st.nextToken());
492                }
493             } catch (NumberFormatException nfe) {
494                throw new IllegalArgumentException("invalid value for key: " + key);
495            }
496        }
497
498        @Override
499        public String toString() {
500
501            String str = new String (
502                    "Equalizer"+
503                    ";curPreset="+Short.toString(curPreset)+
504                    ";numBands="+Short.toString(numBands)
505                    );
506            for (int i = 0; i < numBands; i++) {
507                str = str.concat(";band"+(i+1)+"Level="+Short.toString(bandLevels[i]));
508            }
509            return str;
510        }
511    };
512
513
514    /**
515     * Gets the equalizer properties. This method is useful when a snapshot of current
516     * equalizer settings must be saved by the application.
517     * @return an Equalizer.Settings object containing all current parameters values
518     * @throws IllegalStateException
519     * @throws IllegalArgumentException
520     * @throws UnsupportedOperationException
521     */
522    public Equalizer.Settings getProperties()
523    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
524        byte[] param = new byte[4 + mNumBands * 2];
525        checkStatus(getParameter(PARAM_PROPERTIES, param));
526        Settings settings = new Settings();
527        settings.curPreset = byteArrayToShort(param, 0);
528        settings.numBands = byteArrayToShort(param, 2);
529        settings.bandLevels = new short[mNumBands];
530        for (int i = 0; i < mNumBands; i++) {
531            settings.bandLevels[i] = byteArrayToShort(param, 4 + 2*i);
532        }
533        return settings;
534    }
535
536    /**
537     * Sets the equalizer properties. This method is useful when equalizer settings have to
538     * be applied from a previous backup.
539     * @param settings an Equalizer.Settings object containing the properties to apply
540     * @throws IllegalStateException
541     * @throws IllegalArgumentException
542     * @throws UnsupportedOperationException
543     */
544    public void setProperties(Equalizer.Settings settings)
545    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
546        if (settings.numBands != settings.bandLevels.length ||
547            settings.numBands != mNumBands) {
548            throw new IllegalArgumentException("settings invalid band count: " +settings.numBands);
549        }
550
551        byte[] param = concatArrays(shortToByteArray(settings.curPreset),
552                                    shortToByteArray(mNumBands));
553        for (int i = 0; i < mNumBands; i++) {
554            param = concatArrays(param,
555                                 shortToByteArray(settings.bandLevels[i]));
556        }
557        checkStatus(setParameter(PARAM_PROPERTIES, param));
558    }
559}
560