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