Visualizer.java revision 3540a0197f56c4bcd7d7419f4502bfca34257de2
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    private static final int NATIVE_EVENT_SERVER_DIED = 2;
88
89    // Error codes:
90    /**
91     * Successful operation.
92     */
93    public  static final int SUCCESS              = 0;
94    /**
95     * Unspecified error.
96     */
97    public  static final int ERROR                = -1;
98    /**
99     * Internal operation status. Not returned by any method.
100     */
101    public  static final int ALREADY_EXISTS       = -2;
102    /**
103     * Operation failed due to bad object initialization.
104     */
105    public  static final int ERROR_NO_INIT              = -3;
106    /**
107     * Operation failed due to bad parameter value.
108     */
109    public  static final int ERROR_BAD_VALUE            = -4;
110    /**
111     * Operation failed because it was requested in wrong state.
112     */
113    public  static final int ERROR_INVALID_OPERATION    = -5;
114    /**
115     * Operation failed due to lack of memory.
116     */
117    public  static final int ERROR_NO_MEMORY            = -6;
118    /**
119     * Operation failed due to dead remote object.
120     */
121    public  static final int ERROR_DEAD_OBJECT          = -7;
122
123    //--------------------------------------------------------------------------
124    // Member variables
125    //--------------------
126    /**
127     * Indicates the state of the Visualizer instance
128     */
129    private int mState = STATE_UNINITIALIZED;
130    /**
131     * Lock to synchronize access to mState
132     */
133    private final Object mStateLock = new Object();
134    /**
135     * System wide unique Identifier of the visualizer engine used by this Visualizer instance
136     */
137    private int mId;
138
139    /**
140     * Lock to protect listeners updates against event notifications
141     */
142    private final Object mListenerLock = new Object();
143    /**
144     * Handler for events coming from the native code
145     */
146    private NativeEventHandler mNativeEventHandler = null;
147    /**
148     *  PCM and FFT capture listener registered by client
149     */
150    private OnDataCaptureListener mCaptureListener = null;
151    /**
152     *  Server Died listener registered by client
153     */
154    private OnServerDiedListener mServerDiedListener = null;
155
156    // accessed by native methods
157    private int mNativeVisualizer;
158    private int mJniData;
159
160    //--------------------------------------------------------------------------
161    // Constructor, Finalize
162    //--------------------
163    /**
164     * Class constructor.
165     * @param audioSession system wide unique audio session identifier. If audioSession
166     *  is not 0, the visualizer will be attached to the MediaPlayer or AudioTrack in the
167     *  same audio session. Otherwise, the Visualizer will apply to the output mix.
168     *
169     * @throws java.lang.UnsupportedOperationException
170     * @throws java.lang.RuntimeException
171     */
172
173    public Visualizer(int audioSession)
174    throws UnsupportedOperationException, RuntimeException {
175        int[] id = new int[1];
176
177        synchronized (mStateLock) {
178            mState = STATE_UNINITIALIZED;
179            // native initialization
180            int result = native_setup(new WeakReference<Visualizer>(this), audioSession, id);
181            if (result != SUCCESS && result != ALREADY_EXISTS) {
182                Log.e(TAG, "Error code "+result+" when initializing Visualizer.");
183                switch (result) {
184                case ERROR_INVALID_OPERATION:
185                    throw (new UnsupportedOperationException("Effect library not loaded"));
186                default:
187                    throw (new RuntimeException("Cannot initialize Visualizer engine, error: "
188                            +result));
189                }
190            }
191            mId = id[0];
192            if (native_getEnabled()) {
193                mState = STATE_ENABLED;
194            } else {
195                mState = STATE_INITIALIZED;
196            }
197        }
198    }
199
200    /**
201     * Releases the native Visualizer resources. It is a good practice to release the
202     * visualization engine when not in use.
203     */
204    public void release() {
205        synchronized (mStateLock) {
206            native_release();
207            mState = STATE_UNINITIALIZED;
208        }
209    }
210
211    @Override
212    protected void finalize() {
213        native_finalize();
214    }
215
216    /**
217     * Enable or disable the visualization engine.
218     * @param enabled requested enable state
219     * @return {@link #SUCCESS} in case of success,
220     * {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} in case of failure.
221     * @throws IllegalStateException
222     */
223    public int setEnabled(boolean enabled)
224    throws IllegalStateException {
225        synchronized (mStateLock) {
226            if (mState == STATE_UNINITIALIZED) {
227                throw(new IllegalStateException("setEnabled() called in wrong state: "+mState));
228            }
229            int status = SUCCESS;
230            if ((enabled && (mState == STATE_INITIALIZED)) ||
231                    (!enabled && (mState == STATE_ENABLED))) {
232                status = native_setEnabled(enabled);
233                if (status == SUCCESS) {
234                    mState = enabled ? STATE_ENABLED : STATE_INITIALIZED;
235                }
236            }
237            return status;
238        }
239    }
240
241    /**
242     * Get current activation state of the visualizer.
243     * @return true if the visualizer is active, false otherwise
244     */
245    public boolean getEnabled()
246    {
247        synchronized (mStateLock) {
248            if (mState == STATE_UNINITIALIZED) {
249                throw(new IllegalStateException("getEnabled() called in wrong state: "+mState));
250            }
251            return native_getEnabled();
252        }
253    }
254
255    /**
256     * Returns the capture size range.
257     * @return the mininum capture size is returned in first array element and the maximum in second
258     * array element.
259     */
260    public static native int[] getCaptureSizeRange();
261
262    /**
263     * Returns the maximum capture rate for the callback capture method. This is the maximum value
264     * for the rate parameter of the
265     * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method.
266     * @return the maximum capture rate expressed in milliHertz
267     */
268    public static native int getMaxCaptureRate();
269
270    /**
271     * Sets the capture size, i.e. the number of bytes returned by {@link #getWaveForm(byte[])} and
272     * {@link #getFft(byte[])} methods. The capture size must be a power of 2 in the range returned
273     * by {@link #getCaptureSizeRange()}.
274     * This method must not be called when the Visualizer is enabled.
275     * @param size requested capture size
276     * @return {@link #SUCCESS} in case of success,
277     * {@link #ERROR_BAD_VALUE} in case of failure.
278     * @throws IllegalStateException
279     */
280    public int setCaptureSize(int size)
281    throws IllegalStateException {
282        synchronized (mStateLock) {
283            if (mState != STATE_INITIALIZED) {
284                throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState));
285            }
286            return native_setCaptureSize(size);
287        }
288    }
289
290    /**
291     * Returns current capture size.
292     * @return the capture size in bytes.
293     */
294    public int getCaptureSize()
295    throws IllegalStateException {
296        synchronized (mStateLock) {
297            if (mState == STATE_UNINITIALIZED) {
298                throw(new IllegalStateException("getCaptureSize() called in wrong state: "+mState));
299            }
300            return native_getCaptureSize();
301        }
302    }
303
304    /**
305     * Returns the sampling rate of the captured audio.
306     * @return the sampling rate in milliHertz.
307     */
308    public int getSamplingRate()
309    throws IllegalStateException {
310        synchronized (mStateLock) {
311            if (mState == STATE_UNINITIALIZED) {
312                throw(new IllegalStateException("getSamplingRate() called in wrong state: "+mState));
313            }
314            return native_getSamplingRate();
315        }
316    }
317
318    /**
319     * Returns a waveform capture of currently playing audio content. The capture consists in
320     * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned
321     * by {@link #getCaptureSize()}.
322     * <p>This method must be called when the Visualizer is enabled.
323     * @param waveform array of bytes where the waveform should be returned
324     * @return {@link #SUCCESS} in case of success,
325     * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
326     * in case of failure.
327     * @throws IllegalStateException
328     */
329    public int getWaveForm(byte[] waveform)
330    throws IllegalStateException {
331        synchronized (mStateLock) {
332            if (mState != STATE_ENABLED) {
333                throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState));
334            }
335            return native_getWaveForm(waveform);
336        }
337    }
338    /**
339     * Returns a frequency capture of currently playing audio content.
340     * <p>This method must be called when the Visualizer is enabled.
341     * <p>The capture is an 8-bit magnitude FFT, the frequency range covered being 0 (DC) to half of
342     * the sampling rate returned by {@link #getSamplingRate()}. The capture returns the real and
343     * imaginary parts of a number of frequency points equal to half of the capture size plus one.
344     * <p>Note: only the real part is returned for the first point (DC) and the last point
345     * (sampling frequency / 2).
346     * <p>The layout in the returned byte array is as follows:
347     * <ul>
348     *   <li> n is the capture size returned by getCaptureSize()</li>
349     *   <li> Rfk, Ifk are respectively  the real and imaginary parts of the kth frequency
350     *   component</li>
351     *   <li> If Fs is the sampling frequency retuned by getSamplingRate() the kth frequency is:
352     *   (k*Fs)/(n/2) </li>
353     * </ul>
354     * <table border="0" cellspacing="0" cellpadding="0">
355     * <tr><td>Index </p></td>
356     *     <td>0 </p></td>
357     *     <td>1 </p></td>
358     *     <td>2 </p></td>
359     *     <td>3 </p></td>
360     *     <td>4 </p></td>
361     *     <td>5 </p></td>
362     *     <td>... </p></td>
363     *     <td>n - 2 </p></td>
364     *     <td>n - 1 </p></td></tr>
365     * <tr><td>Data </p></td>
366     *     <td>Rf0 </p></td>
367     *     <td>Rf(n/2) </p></td>
368     *     <td>Rf1 </p></td>
369     *     <td>If1 </p></td>
370     *     <td>Rf2 </p></td>
371     *     <td>If2 </p></td>
372     *     <td>... </p></td>
373     *     <td>Rf(n-1)/2 </p></td>
374     *     <td>If(n-1)/2 </p></td></tr>
375     * </table>
376     * @param fft array of bytes where the FFT should be returned
377     * @return {@link #SUCCESS} in case of success,
378     * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT}
379     * in case of failure.
380     * @throws IllegalStateException
381     */
382    public int getFft(byte[] fft)
383    throws IllegalStateException {
384        synchronized (mStateLock) {
385            if (mState != STATE_ENABLED) {
386                throw(new IllegalStateException("getFft() called in wrong state: "+mState));
387            }
388            return native_getFft(fft);
389        }
390    }
391
392    //---------------------------------------------------------
393    // Interface definitions
394    //--------------------
395    /**
396     * The OnDataCaptureListener interface defines methods called by the Visualizer to periodically
397     * update the audio visualization capture.
398     * The client application can implement this interface and register the listener with the
399     * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method.
400     */
401    public interface OnDataCaptureListener  {
402        /**
403         * Method called when a new waveform capture is available.
404         * <p>Data in the waveform buffer is valid only within the scope of the callback.
405         * Applications which needs access to the waveform data after returning from the callback
406         * should make a copy of the data instead of holding a reference.
407         * @param visualizer Visualizer object on which the listener is registered.
408         * @param waveform array of bytes containing the waveform representation.
409         * @param samplingRate sampling rate of the audio visualized.
410         */
411        void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate);
412
413        /**
414         * Method called when a new frequency capture is available.
415         * <p>Data in the fft buffer is valid only within the scope of the callback.
416         * Applications which needs access to the fft data after returning from the callback
417         * should make a copy of the data instead of holding a reference.
418         * @param visualizer Visualizer object on which the listener is registered.
419         * @param fft array of bytes containing the frequency representation.
420         * @param samplingRate sampling rate of the audio visualized.
421         */
422        void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate);
423    }
424
425    /**
426     * Registers an OnDataCaptureListener interface and specifies the rate at which the capture
427     * should be updated as well as the type of capture requested.
428     * <p>Call this method with a null listener to stop receiving the capture updates.
429     * @param listener OnDataCaptureListener registered
430     * @param rate rate in milliHertz at which the capture should be updated
431     * @param waveform true if a waveform capture is requested: the onWaveFormDataCapture()
432     * method will be called on the OnDataCaptureListener interface.
433     * @param fft true if a frequency capture is requested: the onFftDataCapture() method will be
434     * called on the OnDataCaptureListener interface.
435     * @return {@link #SUCCESS} in case of success,
436     * {@link #ERROR_NO_INIT} or {@link #ERROR_BAD_VALUE} in case of failure.
437     */
438    public int setDataCaptureListener(OnDataCaptureListener listener,
439            int rate, boolean waveform, boolean fft) {
440        synchronized (mListenerLock) {
441            mCaptureListener = listener;
442        }
443        if (listener == null) {
444            // make sure capture callback is stopped in native code
445            waveform = false;
446            fft = false;
447        }
448        int status = native_setPeriodicCapture(rate, waveform, fft);
449        if (status == SUCCESS) {
450            if ((listener != null) && (mNativeEventHandler == null)) {
451                Looper looper;
452                if ((looper = Looper.myLooper()) != null) {
453                    mNativeEventHandler = new NativeEventHandler(this, looper);
454                } else if ((looper = Looper.getMainLooper()) != null) {
455                    mNativeEventHandler = new NativeEventHandler(this, looper);
456                } else {
457                    mNativeEventHandler = null;
458                    status = ERROR_NO_INIT;
459                }
460            }
461        }
462        return status;
463    }
464
465    /**
466     * @hide
467     *
468     * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that
469     * the connection to the native media server has been broken and that the Visualizer object will
470     * need to be released and re-created.
471     * The client application can implement this interface and register the listener with the
472     * {@link #setServerDiedListener(OnServerDiedListener)} method.
473     */
474    public interface OnServerDiedListener  {
475        /**
476         * @hide
477         *
478         * Method called when the native media server has died.
479         * <p>If the native media server encounters a fatal error and needs to restart, the binder
480         * connection from the {@link #Visualizer} to the media server will be broken.  Data capture
481         * callbacks will stop happening, and client initiated calls to the {@link #Visualizer}
482         * instance will fail with the error code {@link #DEAD_OBJECT}.  To restore functionality,
483         * clients should {@link #release()} their old visualizer and create a new instance.
484         */
485        void onServerDied();
486    }
487
488    /**
489     * @hide
490     *
491     * Registers an OnServerDiedListener interface.
492     * <p>Call this method with a null listener to stop receiving server death notifications.
493     * @return {@link #SUCCESS} in case of success,
494     */
495    public int setServerDiedListener(OnServerDiedListener listener) {
496        synchronized (mListenerLock) {
497            mServerDiedListener = listener;
498        }
499        return SUCCESS;
500    }
501
502    /**
503     * Helper class to handle the forwarding of native events to the appropriate listeners
504     */
505    private class NativeEventHandler extends Handler
506    {
507        private Visualizer mVisualizer;
508
509        public NativeEventHandler(Visualizer v, Looper looper) {
510            super(looper);
511            mVisualizer = v;
512        }
513
514        private void handleCaptureMessage(Message msg) {
515            OnDataCaptureListener l = null;
516            synchronized (mListenerLock) {
517                l = mVisualizer.mCaptureListener;
518            }
519
520            if (l != null) {
521                byte[] data = (byte[])msg.obj;
522                int samplingRate = msg.arg1;
523
524                switch(msg.what) {
525                case NATIVE_EVENT_PCM_CAPTURE:
526                    l.onWaveFormDataCapture(mVisualizer, data, samplingRate);
527                    break;
528                case NATIVE_EVENT_FFT_CAPTURE:
529                    l.onFftDataCapture(mVisualizer, data, samplingRate);
530                    break;
531                default:
532                    Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what);
533                    break;
534                }
535            }
536        }
537
538        private void handleServerDiedMessage(Message msg) {
539            OnServerDiedListener l = null;
540            synchronized (mListenerLock) {
541                l = mVisualizer.mServerDiedListener;
542            }
543
544            if (l != null)
545                l.onServerDied();
546        }
547
548        @Override
549        public void handleMessage(Message msg) {
550            if (mVisualizer == null) {
551                return;
552            }
553
554            switch(msg.what) {
555            case NATIVE_EVENT_PCM_CAPTURE:
556            case NATIVE_EVENT_FFT_CAPTURE:
557                handleCaptureMessage(msg);
558                break;
559            case NATIVE_EVENT_SERVER_DIED:
560                handleServerDiedMessage(msg);
561                break;
562            default:
563                Log.e(TAG,"Unknown native event: "+msg.what);
564                break;
565            }
566        }
567    }
568
569    //---------------------------------------------------------
570    // Interface definitions
571    //--------------------
572
573    private static native final void native_init();
574
575    private native final int native_setup(Object audioeffect_this,
576                                          int audioSession,
577                                          int[] id);
578
579    private native final void native_finalize();
580
581    private native final void native_release();
582
583    private native final int native_setEnabled(boolean enabled);
584
585    private native final boolean native_getEnabled();
586
587    private native final int native_setCaptureSize(int size);
588
589    private native final int native_getCaptureSize();
590
591    private native final int native_getSamplingRate();
592
593    private native final int native_getWaveForm(byte[] waveform);
594
595    private native final int native_getFft(byte[] fft);
596
597    private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft);
598
599    //---------------------------------------------------------
600    // Java methods called from the native side
601    //--------------------
602    @SuppressWarnings("unused")
603    private static void postEventFromNative(Object effect_ref,
604            int what, int arg1, int arg2, Object obj) {
605        Visualizer visu = (Visualizer)((WeakReference)effect_ref).get();
606        if (visu == null) {
607            return;
608        }
609
610        if (visu.mNativeEventHandler != null) {
611            Message m = visu.mNativeEventHandler.obtainMessage(what, arg1, arg2, obj);
612            visu.mNativeEventHandler.sendMessage(m);
613        }
614
615    }
616
617}
618
619