Home | History | Annotate | Download | only in audiofx
      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 
     17 package android.media.audiofx;
     18 
     19 import android.util.Log;
     20 import java.lang.ref.WeakReference;
     21 import java.io.IOException;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import 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 
     64 public 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