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