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 * Bass boost is an audio effect to boost or amplify low frequencies of the sound. It is comparable
27 * to a simple equalizer but limited to one band amplification in the low frequency range.
28 * <p>An application creates a BassBoost object to instantiate and control a bass boost engine in
29 * the audio framework.
30 * <p>The methods, parameter types and units exposed by the BassBoost implementation are directly
31 * mapping those defined by the OpenSL ES 1.0.1 Specification (http://www.khronos.org/opensles/)
32 * for the SLBassBoostItf interface. Please refer to this specification for more details.
33 * <p>To attach the BassBoost to a particular AudioTrack or MediaPlayer, specify the audio session
34 * ID of this AudioTrack or MediaPlayer when constructing the BassBoost.
35 * <p>NOTE: attaching a BassBoost to the global audio output mix by use of session 0 is deprecated.
36 * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
37 * <p>See {@link android.media.audiofx.AudioEffect} class for more details on
38 * controlling audio effects.
39 */
40
41public class BassBoost extends AudioEffect {
42
43    private final static String TAG = "BassBoost";
44
45    // These constants must be synchronized with those in
46    // frameworks/base/include/media/EffectBassBoostApi.h
47    /**
48     * Is strength parameter supported by bass boost engine. Parameter ID for getParameter().
49     */
50    public static final int PARAM_STRENGTH_SUPPORTED = 0;
51    /**
52     * Bass boost effect strength. Parameter ID for
53     * {@link android.media.audiofx.BassBoost.OnParameterChangeListener}
54     */
55    public static final int PARAM_STRENGTH = 1;
56
57    /**
58     * Indicates if strength parameter is supported by the bass boost engine
59     */
60    private boolean mStrengthSupported = false;
61
62    /**
63     * Registered listener for parameter changes.
64     */
65    private OnParameterChangeListener mParamListener = null;
66
67    /**
68     * Listener used internally to to receive raw parameter change event from AudioEffect super class
69     */
70    private BaseParameterListener mBaseParamListener = null;
71
72    /**
73     * Lock for access to mParamListener
74     */
75    private final Object mParamListenerLock = new Object();
76
77    /**
78     * Class constructor.
79     * @param priority the priority level requested by the application for controlling the BassBoost
80     * engine. As the same engine can be shared by several applications, this parameter indicates
81     * how much the requesting application needs control of effect parameters. The normal priority
82     * is 0, above normal is a positive number, below normal a negative number.
83     * @param audioSession system wide unique audio session identifier. The BassBoost will be
84     * attached to the MediaPlayer or AudioTrack in the same audio session.
85     *
86     * @throws java.lang.IllegalStateException
87     * @throws java.lang.IllegalArgumentException
88     * @throws java.lang.UnsupportedOperationException
89     * @throws java.lang.RuntimeException
90     */
91    public BassBoost(int priority, int audioSession)
92    throws IllegalStateException, IllegalArgumentException,
93           UnsupportedOperationException, RuntimeException {
94        super(EFFECT_TYPE_BASS_BOOST, EFFECT_TYPE_NULL, priority, audioSession);
95
96        if (audioSession == 0) {
97            Log.w(TAG, "WARNING: attaching a BassBoost to global output mix is deprecated!");
98        }
99
100        int[] value = new int[1];
101        checkStatus(getParameter(PARAM_STRENGTH_SUPPORTED, value));
102        mStrengthSupported = (value[0] != 0);
103    }
104
105    /**
106     * Indicates whether setting strength is supported. If this method returns false, only one
107     * strength is supported and the setStrength() method always rounds to that value.
108     * @return true is strength parameter is supported, false otherwise
109     */
110    public boolean getStrengthSupported() {
111       return mStrengthSupported;
112    }
113
114    /**
115     * Sets the strength of the bass boost effect. If the implementation does not support per mille
116     * accuracy for setting the strength, it is allowed to round the given strength to the nearest
117     * supported value. You can use the {@link #getRoundedStrength()} method to query the
118     * (possibly rounded) value that was actually set.
119     * @param strength strength of the effect. The valid range for strength strength is [0, 1000],
120     * where 0 per mille designates the mildest effect and 1000 per mille designates the strongest.
121     * @throws IllegalStateException
122     * @throws IllegalArgumentException
123     * @throws UnsupportedOperationException
124     */
125    public void setStrength(short strength)
126    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
127        checkStatus(setParameter(PARAM_STRENGTH, strength));
128    }
129
130    /**
131     * Gets the current strength of the effect.
132     * @return the strength of the effect. The valid range for strength is [0, 1000], where 0 per
133     * mille designates the mildest effect and 1000 per mille the strongest
134     * @throws IllegalStateException
135     * @throws IllegalArgumentException
136     * @throws UnsupportedOperationException
137     */
138    public short getRoundedStrength()
139    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
140        short[] value = new short[1];
141        checkStatus(getParameter(PARAM_STRENGTH, value));
142        return value[0];
143    }
144
145    /**
146     * The OnParameterChangeListener interface defines a method called by the BassBoost when a
147     * parameter value has changed.
148     */
149    public interface OnParameterChangeListener  {
150        /**
151         * Method called when a parameter value has changed. The method is called only if the
152         * parameter was changed by another application having the control of the same
153         * BassBoost engine.
154         * @param effect the BassBoost on which the interface is registered.
155         * @param status status of the set parameter operation.
156         * @param param ID of the modified parameter. See {@link #PARAM_STRENGTH} ...
157         * @param value the new parameter value.
158         */
159        void onParameterChange(BassBoost effect, int status, int param, short value);
160    }
161
162    /**
163     * Listener used internally to receive unformatted parameter change events from AudioEffect
164     * super class.
165     */
166    private class BaseParameterListener implements AudioEffect.OnParameterChangeListener {
167        private BaseParameterListener() {
168
169        }
170        public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
171            OnParameterChangeListener l = null;
172
173            synchronized (mParamListenerLock) {
174                if (mParamListener != null) {
175                    l = mParamListener;
176                }
177            }
178            if (l != null) {
179                int p = -1;
180                short v = -1;
181
182                if (param.length == 4) {
183                    p = byteArrayToInt(param, 0);
184                }
185                if (value.length == 2) {
186                    v = byteArrayToShort(value, 0);
187                }
188                if (p != -1 && v != -1) {
189                    l.onParameterChange(BassBoost.this, status, p, v);
190                }
191            }
192        }
193    }
194
195    /**
196     * Registers an OnParameterChangeListener interface.
197     * @param listener OnParameterChangeListener interface registered
198     */
199    public void setParameterListener(OnParameterChangeListener listener) {
200        synchronized (mParamListenerLock) {
201            if (mParamListener == null) {
202                mParamListener = listener;
203                mBaseParamListener = new BaseParameterListener();
204                super.setParameterListener(mBaseParamListener);
205            }
206        }
207    }
208
209    /**
210     * The Settings class regroups all bass boost parameters. It is used in
211     * conjuntion with getProperties() and setProperties() methods to backup and restore
212     * all parameters in a single call.
213     */
214    public static class Settings {
215        public short strength;
216
217        public Settings() {
218        }
219
220        /**
221         * Settings class constructor from a key=value; pairs formatted string. The string is
222         * typically returned by Settings.toString() method.
223         * @throws IllegalArgumentException if the string is not correctly formatted.
224         */
225        public Settings(String settings) {
226            StringTokenizer st = new StringTokenizer(settings, "=;");
227            int tokens = st.countTokens();
228            if (st.countTokens() != 3) {
229                throw new IllegalArgumentException("settings: " + settings);
230            }
231            String key = st.nextToken();
232            if (!key.equals("BassBoost")) {
233                throw new IllegalArgumentException(
234                        "invalid settings for BassBoost: " + key);
235            }
236            try {
237                key = st.nextToken();
238                if (!key.equals("strength")) {
239                    throw new IllegalArgumentException("invalid key name: " + key);
240                }
241                strength = Short.parseShort(st.nextToken());
242             } catch (NumberFormatException nfe) {
243                throw new IllegalArgumentException("invalid value for key: " + key);
244            }
245        }
246
247        @Override
248        public String toString() {
249            String str = new String (
250                    "BassBoost"+
251                    ";strength="+Short.toString(strength)
252                    );
253            return str;
254        }
255    };
256
257
258    /**
259     * Gets the bass boost properties. This method is useful when a snapshot of current
260     * bass boost settings must be saved by the application.
261     * @return a BassBoost.Settings object containing all current parameters values
262     * @throws IllegalStateException
263     * @throws IllegalArgumentException
264     * @throws UnsupportedOperationException
265     */
266    public BassBoost.Settings getProperties()
267    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
268        Settings settings = new Settings();
269        short[] value = new short[1];
270        checkStatus(getParameter(PARAM_STRENGTH, value));
271        settings.strength = value[0];
272        return settings;
273    }
274
275    /**
276     * Sets the bass boost properties. This method is useful when bass boost settings have to
277     * be applied from a previous backup.
278     * @param settings a BassBoost.Settings object containing the properties to apply
279     * @throws IllegalStateException
280     * @throws IllegalArgumentException
281     * @throws UnsupportedOperationException
282     */
283    public void setProperties(BassBoost.Settings settings)
284    throws IllegalStateException, IllegalArgumentException, UnsupportedOperationException {
285        checkStatus(setParameter(PARAM_STRENGTH, settings.strength));
286    }
287}
288