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