Visualizer.java revision 1a5149e5d7f2dddc8b324f7695e69fd89af73c52
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.util.Log;
20import java.lang.ref.WeakReference;
21import java.io.IOException;
22import android.os.Handler;
23import android.os.Looper;
24import android.os.Message;
25
26/**
27 * The Visualizer class enables application to retrieve part of the currently playing audio for
28 * visualization purpose. It is not an audio recording interface and only returns partial and low
29 * quality audio content. However, to protect privacy of certain audio data (e.g voice mail) the use
30 * of the visualizer requires the permission android.permission.RECORD_AUDIO.
31 * <p>The audio session ID passed to the constructor indicates which audio content should be
32 * visualized:<br>
33 * <ul>
34 *   <li>If the session is 0, the audio output mix is visualized</li>
35 *   <li>If the session is not 0, the audio from a particular {@link android.media.MediaPlayer} or
36 *   {@link android.media.AudioTrack}
37 *   using this audio session is visualized </li>
38 * </ul>
39 * <p>Two types of representation of audio content can be captured: <br>
40 * <ul>
41 *   <li>Waveform data: consecutive 8-bit (unsigned) mono samples by using the
42 *   {@link #getWaveForm(byte[])} method</li>
43 *   <li>Frequency data: 8-bit magnitude FFT by using the {@link #getFft(byte[])} method</li>
44 * </ul>
45 * <p>The length of the capture can be retrieved or specified by calling respectively
46 * {@link #getCaptureSize()} and {@link #setCaptureSize(int)} methods. Note that the size of the FFT
47 * is half of the specified capture size but both sides of the spectrum are returned yielding in a
48 * number of bytes equal to the capture size. The capture size must be a power of 2 in the range
49 * returned by {@link #getCaptureSizeRange()}.
50 * <p>In addition to the polling capture mode described above with {@link #getWaveForm(byte[])} and
51 *  {@link #getFft(byte[])} methods, a callback mode is also available by installing a listener by
52 *  use of the {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method.
53 *  The rate at which the listener capture method is called as well as the type of data returned is
54 *  specified.
55 * <p>Before capturing data, the Visualizer must be enabled by calling the
56 * {@link #setEnabled(boolean)} method.
57 * When data capture is not needed any more, the Visualizer should be disabled.
58 * <p>It is good practice to call the {@link #release()} method when the Visualizer is not used
59 * anymore to free up native resources associated to the Visualizer instance.
60 * <p>Creating a Visualizer on the output mix (audio session 0) requires permission
61 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS}
62 */
63
64public class Visualizer {
65
66    static {
67        System.loadLibrary("audioeffect_jni");
68        native_init();
69    }
70
71    private final static String TAG = "Visualizer-JAVA";
72
73    /**
74     * State of a Visualizer object that was not successfully initialized upon creation
75     */
76    public static final int STATE_UNINITIALIZED = 0;
77    /**
78     * State of a Visualizer object that is ready to be used.
79     */
80    public static final int STATE_INITIALIZED   = 1;
81    /**
82     * State of a Visualizer object that is active.
83     */
84    public static final int STATE_ENABLED   = 2;
85
86    // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp
87    private static final int NATIVE_EVENT_PCM_CAPTURE = 0;
88    private static final int NATIVE_EVENT_FFT_CAPTURE = 1;
89
90    // Error codes:
91    /**
92     * Successful operation.
93     */
94    public  static final int SUCCESS              = 0;
95    /**
96     * Unspecified error.
97     */
98    public  static final int ERROR                = -1;
99    /**
100     * Internal opreation status. Not returned by any method.
101     */
102    public  static final int ALREADY_EXISTS       = -2;
103    /**
104     * Operation failed due to bad object initialization.
105     */
106    public  static final int ERROR_NO_INIT              = -3;
107    /**
108     * Operation failed due to bad parameter value.
109     */
110    public  static final int ERROR_BAD_VALUE            = -4;
111    /**
112     * Operation failed because it was requested in wrong state.
113     */
114    public  static final int ERROR_INVALID_OPERATION    = -5;
115    /**
116     * Operation failed due to lack of memory.
117     */
118    public  static final int ERROR_NO_MEMORY            = -6;
119    /**
120     * Operation failed due to dead remote object.
121     */
122    public  static final int ERROR_DEAD_OBJECT          = -7;
123
124    //--------------------------------------------------------------------------
125    // Member variables
126    //--------------------
127    /**
128     * Indicates the state of the Visualizer instance
129     */
130    private int mState = STATE_UNINITIALIZED;
131    /**
132     * Lock to synchronize access to mState
133     */
134    private final Object mStateLock = new Object();
135    /**
136     * System wide unique Identifier of the visualizer engine used by this Visualizer instance
137     */
138    private int mId;
139
140    /**
141     * Lock to protect listeners updates against event notifications
142     */
143    private final Object mListenerLock = new Object();
144    /**
145     * Handler for events coming from the native code
146     */
147    private NativeEventHandler mNativeEventHandler = null;
148    /**
149     *  PCM and FFT capture listener registered by client
150     */
151    private OnDataCaptureListener mCaptureListener = null;
152
153    // accessed by native methods
154    private int mNativeVisualizer;
155    private int mJniData;
156
157    //--------------------------------------------------------------------------
158    // Constructor, Finalize
159    //--------------------
160    /**
161     * Class constructor.
162     * @param audioSession system wide unique audio session identifier. If audioSession
163     *  is not 0, the visualizer will be attached to the MediaPlayer or AudioTrack in the
164     *  same audio session. Otherwise, the Visualizer will apply to the output mix.
165     *
166     * @throws java.lang.UnsupportedOperationException
167     * @throws java.lang.RuntimeException
168     */
169
170    public Visualizer(int audioSession)
171    throws UnsupportedOperationException, RuntimeException {
172        int[] id = new int[1];
173
174        synchronized (mStateLock) {
175            mState = STATE_UNINITIALIZED;
176            // native initialization
177            int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id);
178            if (result != SUCCESS && result != ALREADY_EXISTS) {
179                Log.e(TAG, "Error code "+result+" when initializing Visualizer.");
180                switch (result) {
181                case ERROR_INVALID_OPERATION:
182                    throw (new UnsupportedOperationException("Effect library not loaded"));
183                default:
184                    throw (new RuntimeException("Cannot initialize Visualizer engine, error: "
185                            +result));
186                }
187            }
188            mId = id[0];
189            if (native_getEnabled()) {
190                mState = STATE_ENABLED;
191            } else {
192                mState = STATE_INITIALIZED;
193            }
194        }
195    }
196
197    /**
198     * Releases the native Visualizer resources. It is a good practice to release the
199     * visualization engine when not in use.
200     */
201    public void release() {
202        synchronized (mStateLock) {
203            native_release();
204            mState = STATE_UNINITIALIZED;
205        }
206    }
207
208    @Override
209    protected void finalize() {
210        native_finalize();
211    }
212
213    /**
214     * Enable or disable the visualization engine.
215     * @param enabled requested enable state
216     * @return {@link #SUCCESS} in case of success,
217     * {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} in case of failure.
218     * @throws IllegalStateException
219     */
220    public int setEnabled(boolean enabled)
221    throws IllegalStateException {
222        synchronized (mStateLock) {
223            if (mState == STATE_UNINITIALIZED) {
224                throw(new IllegalStateException("setEnabled() called in wrong state: "+mState));
225            }
226            int status = SUCCESS;
227            if ((enabled && (mState == STATE_INITIALIZED)) ||
228                    (!enabled && (mState == STATE_ENABLED))) {
229                status = native_setEnabled(enabled);
230                if (status == SUCCESS) {
231                    mState = enabled ? STATE_ENABLED : STATE_INITIALIZED;
232                }
233            }
234            return status;
235        }
236    }
237
238    /**
239     * Get current activation state of the visualizer.
240     * @return true if the visualizer is active, false otherwise
241     */
242    public boolean getEnabled()
243    {
244        synchronized (mStateLock) {
245            if (mState == STATE_UNINITIALIZED) {
246                throw(new IllegalStateException("getEnabled() called in wrong state: "+mState));
247            }
248            return native_getEnabled();
249        }
250    }
251
252    /**
253     * Returns the capture size range.
254     * @return the mininum capture size is returned in first array element and the maximum in second
255     * array element.
256     */
257    public static native int[] getCaptureSizeRange();
258
259    /**
260     * Returns the maximum capture rate for the callback capture method. This is the maximum value
261     * for the rate parameter of the
262     * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method.
263     * @return the maximum capture rate expressed in milliHertz
264     */
265    public static native int getMaxCaptureRate();
266
267    /**
268     * Sets the capture size, i.e. the number of bytes returned by {@link #getWaveForm(byte[])} and
269     * {@link #getFft(byte[])} methods. The capture size must be a power of 2 in the range returned
270     * by {@link #getCaptureSizeRange()}.
271     * This method must not be called when the Visualizer is enabled.
272     * @param size requested capture size
273     * @return {@link #SUCCESS} in case of success,
274     * {@link #ERROR_BAD_VALUE} in case of failure.
275     * @throws IllegalStateException
276     */
277    public int setCaptureSize(int size)
278    throws IllegalStateException {
279        synchronized (mStateLock) {
280            if (mState != STATE_INITIALIZED) {
281                throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState));
282            }
283            return native_setCaptureSize(size);
284        }
285    }
286
287    /**
288     * Returns current capture size.
289     * @return the capture size in bytes.
290     */
291    public int getCaptureSize()
292    throws IllegalStateException {
293        synchronized (mStateLock) {
294            if (mState == STATE_UNINITIALIZED) {
295                throw(new IllegalStateException("getCaptureSize() called in wrong state: "+mState));
296            }
297            return native_getCaptureSize();
298        }
299    }
300
301    /**
302     * Returns the sampling rate of the captured audio.
303     * @return the sampling rate in milliHertz.
304     */
305    public int getSamplingRate()
306    throws IllegalStateException {
307        synchronized (mStateLock) {
308            if (mState == STATE_UNINITIALIZED) {
309                throw(new IllegalStateException("getSamplingRate() called in wrong state: "+mState));
310            }
311            return native_getSamplingRate();
312        }
313    }
314
315    /**
316     * Returns a waveform capture of currently playing audio content. The capture consists in
317     * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned
318     * by {@link #getCaptureSize()}.
319     * <p>This method must be called when the Visualizer is enabled.
320     * @param waveform array of bytes where the waveform should be returned
321     * @return {@link #SUCCESS} in case of success,
322     * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
323     * in case of failure.
324     * @throws IllegalStateException
325     */
326    public int getWaveForm(byte[] waveform)
327    throws IllegalStateException {
328        synchronized (mStateLock) {
329            if (mState != STATE_ENABLED) {
330                throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState));
331            }
332            return native_getWaveForm(waveform);
333        }
334    }
335    /**
336     * Returns a frequency capture of currently playing audio content. The capture is a 8-bit
337     * magnitude FFT. Note that the size of the FFT is half of the specified capture size but both
338     * sides of the spectrum are returned yielding in a number of bytes equal to the capture size.
339     * {@see #getCaptureSize()}.
340     * <p>This method must be called when the Visualizer is enabled.
341     * @param fft array of bytes where the FFT should be returned
342     * @return {@link #SUCCESS} in case of success,
343     * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
344     * in case of failure.
345     * @throws IllegalStateException
346     */
347    public int getFft(byte[] fft)
348    throws IllegalStateException {
349        synchronized (mStateLock) {
350            if (mState != STATE_ENABLED) {
351                throw(new IllegalStateException("getFft() called in wrong state: "+mState));
352            }
353            return native_getFft(fft);
354        }
355    }
356
357    //---------------------------------------------------------
358    // Interface definitions
359    //--------------------
360    /**
361     * The OnDataCaptureListener interface defines methods called by the Visualizer to periodically
362     * update the audio visualization capture.
363     * The client application can implement this interface and register the listener with the
364     * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method.
365     */
366    public interface OnDataCaptureListener  {
367        /**
368         * Method called when a new waveform capture is available.
369         * @param visualizer Visualizer object on which the listener is registered.
370         * @param waveform array of bytes containing the waveform representation.
371         * @param samplingRate sampling rate of the audio visualized.
372         */
373        void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate);
374
375        /**
376         * Method called when a new frequency capture is available.
377         * @param visualizer Visualizer object on which the listener is registered.
378         * @param fft array of bytes containing the frequency representation.
379         * @param samplingRate sampling rate of the audio visualized.
380         */
381        void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate);
382    }
383
384    /**
385     * Registers an OnDataCaptureListener interface and specifies the rate at which the capture
386     * should be updated as well as the type of capture requested.
387     * <p>Call this method with a null listener to stop receiving the capture updates.
388     * @param listener OnDataCaptureListener registered
389     * @param rate rate in milliHertz at which the capture should be updated
390     * @param waveform true if a waveform capture is requested: the onWaveFormDataCapture()
391     * method will be called on the OnDataCaptureListener interface.
392     * @param fft true if a frequency capture is requested: the onFftDataCapture() method will be
393     * called on the OnDataCaptureListener interface.
394     * @return {@link #SUCCESS} in case of success,
395     * {@link #ERROR_NO_INIT} or {@link #ERROR_BAD_VALUE} in case of failure.
396     */
397    public int setDataCaptureListener(OnDataCaptureListener listener,
398            int rate, boolean waveform, boolean fft) {
399        synchronized (mListenerLock) {
400            mCaptureListener = listener;
401        }
402        if (listener == null) {
403            // make sure capture callback is stopped in native code
404            waveform = false;
405            fft = false;
406        }
407        int status = native_setPeriodicCapture(rate, waveform, fft);
408        if (status == SUCCESS) {
409            if ((listener != null) && (mNativeEventHandler == null)) {
410                Looper looper;
411                if ((looper = Looper.myLooper()) != null) {
412                    mNativeEventHandler = new NativeEventHandler(this, looper);
413                } else if ((looper = Looper.getMainLooper()) != null) {
414                    mNativeEventHandler = new NativeEventHandler(this, looper);
415                } else {
416                    mNativeEventHandler = null;
417                    status = ERROR_NO_INIT;
418                }
419            }
420        }
421        return status;
422    }
423
424    /**
425     * Helper class to handle the forwarding of native events to the appropriate listeners
426     */
427    private class NativeEventHandler extends Handler
428    {
429        private Visualizer mVisualizer;
430
431        public NativeEventHandler(Visualizer v, Looper looper) {
432            super(looper);
433            mVisualizer = v;
434        }
435
436        @Override
437        public void handleMessage(Message msg) {
438            if (mVisualizer == null) {
439                return;
440            }
441            OnDataCaptureListener l = null;
442            synchronized (mListenerLock) {
443                l = mVisualizer.mCaptureListener;
444            }
445
446            if (l != null) {
447                byte[] data = (byte[])msg.obj;
448                int samplingRate = msg.arg1;
449                switch(msg.what) {
450                case NATIVE_EVENT_PCM_CAPTURE:
451                    l.onWaveFormDataCapture(mVisualizer, data, samplingRate);
452                    break;
453                case NATIVE_EVENT_FFT_CAPTURE:
454                    l.onFftDataCapture(mVisualizer, data, samplingRate);
455                    break;
456                default:
457                    Log.e(TAG,"Unknown native event: "+msg.what);
458                    break;
459                }
460            }
461        }
462    }
463
464    //---------------------------------------------------------
465    // Interface definitions
466    //--------------------
467
468    private static native final void native_init();
469
470    private native final int native_setup(Object audioeffect_this,
471                                          int audioSession,
472                                          int[] id);
473
474    private native final void native_finalize();
475
476    private native final void native_release();
477
478    private native final int native_setEnabled(boolean enabled);
479
480    private native final boolean native_getEnabled();
481
482    private native final int native_setCaptureSize(int size);
483
484    private native final int native_getCaptureSize();
485
486    private native final int native_getSamplingRate();
487
488    private native final int native_getWaveForm(byte[] waveform);
489
490    private native final int native_getFft(byte[] fft);
491
492    private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft);
493
494    //---------------------------------------------------------
495    // Java methods called from the native side
496    //--------------------
497    @SuppressWarnings("unused")
498    private static void postEventFromNative(Object effect_ref,
499            int what, int arg1, int arg2, Object obj) {
500        Visualizer visu = (Visualizer)((WeakReference)effect_ref).get();
501        if (visu == null) {
502            return;
503        }
504
505        if (visu.mNativeEventHandler != null) {
506            Message m = visu.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
507            visu.mNativeEventHandler.sendMessage(m);
508        }
509
510    }
511
512}
513
514