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