Visualizer.java revision 03a4090b4d5965ff01cbb03dcf6d96b30d634fa3
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 opreation 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