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