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.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.compat.annotation.UnsupportedAppUsage; 22 import android.content.AttributionSource; 23 import android.content.AttributionSource.ScopedParcelState; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.Parcel; 27 import android.util.Log; 28 29 import com.android.internal.annotations.GuardedBy; 30 31 import java.lang.ref.WeakReference; 32 33 /** 34 * The Visualizer class enables application to retrieve part of the currently playing audio for 35 * visualization purpose. It is not an audio recording interface and only returns partial and low 36 * quality audio content. However, to protect privacy of certain audio data (e.g voice mail) the use 37 * of the visualizer requires the permission android.permission.RECORD_AUDIO. 38 * <p>The audio session ID passed to the constructor indicates which audio content should be 39 * visualized:<br> 40 * <ul> 41 * <li>If the session is 0, the audio output mix is visualized</li> 42 * <li>If the session is not 0, the audio from a particular {@link android.media.MediaPlayer} or 43 * {@link android.media.AudioTrack} 44 * using this audio session is visualized </li> 45 * </ul> 46 * <p>Two types of representation of audio content can be captured: <br> 47 * <ul> 48 * <li>Waveform data: consecutive 8-bit (unsigned) mono samples by using the 49 * {@link #getWaveForm(byte[])} method</li> 50 * <li>Frequency data: 8-bit magnitude FFT by using the {@link #getFft(byte[])} method</li> 51 * </ul> 52 * <p>The length of the capture can be retrieved or specified by calling respectively 53 * {@link #getCaptureSize()} and {@link #setCaptureSize(int)} methods. The capture size must be a 54 * power of 2 in the range returned by {@link #getCaptureSizeRange()}. 55 * <p>In addition to the polling capture mode described above with {@link #getWaveForm(byte[])} and 56 * {@link #getFft(byte[])} methods, a callback mode is also available by installing a listener by 57 * use of the {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. 58 * The rate at which the listener capture method is called as well as the type of data returned is 59 * specified. 60 * <p>Before capturing data, the Visualizer must be enabled by calling the 61 * {@link #setEnabled(boolean)} method. 62 * When data capture is not needed any more, the Visualizer should be disabled. 63 * <p>It is good practice to call the {@link #release()} method when the Visualizer is not used 64 * anymore to free up native resources associated to the Visualizer instance. 65 * <p>Creating a Visualizer on the output mix (audio session 0) requires permission 66 * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} 67 * <p>The Visualizer class can also be used to perform measurements on the audio being played back. 68 * The measurements to perform are defined by setting a mask of the requested measurement modes with 69 * {@link #setMeasurementMode(int)}. Supported values are {@link #MEASUREMENT_MODE_NONE} to cancel 70 * any measurement, and {@link #MEASUREMENT_MODE_PEAK_RMS} for peak and RMS monitoring. 71 * Measurements can be retrieved through {@link #getMeasurementPeakRms(MeasurementPeakRms)}. 72 */ 73 74 public class Visualizer { 75 76 static { 77 System.loadLibrary("audioeffect_jni"); native_init()78 native_init(); 79 } 80 81 private final static String TAG = "Visualizer-JAVA"; 82 83 /** 84 * State of a Visualizer object that was not successfully initialized upon creation 85 */ 86 public static final int STATE_UNINITIALIZED = 0; 87 /** 88 * State of a Visualizer object that is ready to be used. 89 */ 90 public static final int STATE_INITIALIZED = 1; 91 /** 92 * State of a Visualizer object that is active. 93 */ 94 public static final int STATE_ENABLED = 2; 95 96 // to keep in sync with system/media/audio_effects/include/audio_effects/effect_visualizer.h 97 /** 98 * Defines a capture mode where amplification is applied based on the content of the captured 99 * data. This is the default Visualizer mode, and is suitable for music visualization. 100 */ 101 public static final int SCALING_MODE_NORMALIZED = 0; 102 /** 103 * Defines a capture mode where the playback volume will affect (scale) the range of the 104 * captured data. A low playback volume will lead to low sample and fft values, and vice-versa. 105 */ 106 public static final int SCALING_MODE_AS_PLAYED = 1; 107 108 /** 109 * Defines a measurement mode in which no measurements are performed. 110 */ 111 public static final int MEASUREMENT_MODE_NONE = 0; 112 113 /** 114 * Defines a measurement mode which computes the peak and RMS value in mB below the 115 * "full scale", where 0mB is normally the maximum sample value (but see the note 116 * below). Minimum value depends on the resolution of audio samples used by the audio 117 * framework. The value of -9600mB is the minimum value for 16-bit audio systems and 118 * -14400mB or below for "high resolution" systems. Values for peak and RMS can be 119 * retrieved with {@link #getMeasurementPeakRms(MeasurementPeakRms)}. 120 * 121 * <p class=note><strong>Note:</strong> when Visualizer effect is attached to the 122 * global session (with session ID 0), it is possible to observe RMS peaks higher than 123 * 0 dBFS, for example in the case when there are multiple audio sources playing 124 * simultaneously. In this case {@link #getMeasurementPeakRms(MeasurementPeakRms)} 125 * method can return a positive value. 126 */ 127 public static final int MEASUREMENT_MODE_PEAK_RMS = 1 << 0; 128 129 // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp 130 private static final int NATIVE_EVENT_PCM_CAPTURE = 0; 131 private static final int NATIVE_EVENT_FFT_CAPTURE = 1; 132 private static final int NATIVE_EVENT_SERVER_DIED = 2; 133 134 // Error codes: 135 /** 136 * Successful operation. 137 */ 138 public static final int SUCCESS = 0; 139 /** 140 * Unspecified error. 141 */ 142 public static final int ERROR = -1; 143 /** 144 * Internal operation status. Not returned by any method. 145 */ 146 public static final int ALREADY_EXISTS = -2; 147 /** 148 * Operation failed due to bad object initialization. 149 */ 150 public static final int ERROR_NO_INIT = -3; 151 /** 152 * Operation failed due to bad parameter value. 153 */ 154 public static final int ERROR_BAD_VALUE = -4; 155 /** 156 * Operation failed because it was requested in wrong state. 157 */ 158 public static final int ERROR_INVALID_OPERATION = -5; 159 /** 160 * Operation failed due to lack of memory. 161 */ 162 public static final int ERROR_NO_MEMORY = -6; 163 /** 164 * Operation failed due to dead remote object. 165 */ 166 public static final int ERROR_DEAD_OBJECT = -7; 167 168 //-------------------------------------------------------------------------- 169 // Member variables 170 //-------------------- 171 /** 172 * Indicates the state of the Visualizer instance 173 */ 174 @GuardedBy("mStateLock") 175 private int mState = STATE_UNINITIALIZED; 176 /** 177 * Lock to synchronize access to mState 178 */ 179 private final Object mStateLock = new Object(); 180 /** 181 * System wide unique Identifier of the visualizer engine used by this Visualizer instance 182 */ 183 @GuardedBy("mStateLock") 184 @UnsupportedAppUsage 185 private int mId; 186 187 /** 188 * Lock to protect listeners updates against event notifications 189 */ 190 private final Object mListenerLock = new Object(); 191 /** 192 * Handler for events coming from the native code 193 */ 194 @GuardedBy("mListenerLock") 195 @Nullable private Handler mNativeEventHandler = null; 196 /** 197 * PCM and FFT capture listener registered by client 198 */ 199 @GuardedBy("mListenerLock") 200 @Nullable private OnDataCaptureListener mCaptureListener = null; 201 /** 202 * Server Died listener registered by client 203 */ 204 @GuardedBy("mListenerLock") 205 @Nullable private OnServerDiedListener mServerDiedListener = null; 206 207 // accessed by native methods 208 private long mNativeVisualizer; // guarded by a static lock in native code 209 private long mJniData; // set in native_setup, _release; 210 // get in native_release, _setEnabled, _setPeriodicCapture 211 // thus, effectively guarded by mStateLock 212 213 //-------------------------------------------------------------------------- 214 // Constructor, Finalize 215 //-------------------- 216 /** 217 * Class constructor. 218 * @param audioSession system wide unique audio session identifier. If audioSession 219 * is not 0, the visualizer will be attached to the MediaPlayer or AudioTrack in the 220 * same audio session. Otherwise, the Visualizer will apply to the output mix. 221 * 222 * @throws java.lang.UnsupportedOperationException 223 * @throws java.lang.RuntimeException 224 */ 225 Visualizer(int audioSession)226 public Visualizer(int audioSession) 227 throws UnsupportedOperationException, RuntimeException { 228 int[] id = new int[1]; 229 230 synchronized (mStateLock) { 231 mState = STATE_UNINITIALIZED; 232 233 // native initialization 234 // TODO b/182469354: make consistent with AudioRecord 235 int result; 236 try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource() 237 .asScopedParcelState()) { 238 result = native_setup(new WeakReference<>(this), audioSession, id, 239 attributionSourceState.getParcel()); 240 } 241 if (result != SUCCESS && result != ALREADY_EXISTS) { 242 Log.e(TAG, "Error code "+result+" when initializing Visualizer."); 243 switch (result) { 244 case ERROR_INVALID_OPERATION: 245 throw (new UnsupportedOperationException("Effect library not loaded")); 246 default: 247 throw (new RuntimeException("Cannot initialize Visualizer engine, error: " 248 +result)); 249 } 250 } 251 mId = id[0]; 252 if (native_getEnabled()) { 253 mState = STATE_ENABLED; 254 } else { 255 mState = STATE_INITIALIZED; 256 } 257 } 258 } 259 260 /** 261 * Releases the native Visualizer resources. It is a good practice to release the 262 * visualization engine when not in use. 263 */ release()264 public void release() { 265 synchronized (mStateLock) { 266 native_release(); 267 mState = STATE_UNINITIALIZED; 268 } 269 } 270 271 @Override finalize()272 protected void finalize() { 273 synchronized (mStateLock) { 274 native_finalize(); 275 } 276 } 277 278 /** 279 * Enable or disable the visualization engine. 280 * @param enabled requested enable state 281 * @return {@link #SUCCESS} in case of success, 282 * {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} in case of failure. 283 * @throws IllegalStateException 284 */ setEnabled(boolean enabled)285 public int setEnabled(boolean enabled) 286 throws IllegalStateException { 287 synchronized (mStateLock) { 288 if (mState == STATE_UNINITIALIZED) { 289 throw(new IllegalStateException("setEnabled() called in wrong state: "+mState)); 290 } 291 int status = SUCCESS; 292 if ((enabled && (mState == STATE_INITIALIZED)) || 293 (!enabled && (mState == STATE_ENABLED))) { 294 status = native_setEnabled(enabled); 295 if (status == SUCCESS) { 296 mState = enabled ? STATE_ENABLED : STATE_INITIALIZED; 297 } 298 } 299 return status; 300 } 301 } 302 303 /** 304 * Get current activation state of the visualizer. 305 * @return true if the visualizer is active, false otherwise 306 */ getEnabled()307 public boolean getEnabled() 308 { 309 synchronized (mStateLock) { 310 if (mState == STATE_UNINITIALIZED) { 311 throw(new IllegalStateException("getEnabled() called in wrong state: "+mState)); 312 } 313 return native_getEnabled(); 314 } 315 } 316 317 /** 318 * Returns the capture size range. 319 * @return the mininum capture size is returned in first array element and the maximum in second 320 * array element. 321 */ getCaptureSizeRange()322 public static native int[] getCaptureSizeRange(); 323 324 /** 325 * Returns the maximum capture rate for the callback capture method. This is the maximum value 326 * for the rate parameter of the 327 * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. 328 * @return the maximum capture rate expressed in milliHertz 329 */ getMaxCaptureRate()330 public static native int getMaxCaptureRate(); 331 332 /** 333 * Sets the capture size, i.e. the number of bytes returned by {@link #getWaveForm(byte[])} and 334 * {@link #getFft(byte[])} methods. The capture size must be a power of 2 in the range returned 335 * by {@link #getCaptureSizeRange()}. 336 * This method must not be called when the Visualizer is enabled. 337 * @param size requested capture size 338 * @return {@link #SUCCESS} in case of success, 339 * {@link #ERROR_INVALID_OPERATION} if Visualizer effect enginer not enabled. 340 * @throws IllegalStateException if the effect is not in proper state. 341 * @throws IllegalArgumentException if the size parameter is invalid (out of supported range). 342 */ setCaptureSize(int size)343 public int setCaptureSize(int size) 344 throws IllegalStateException { 345 synchronized (mStateLock) { 346 if (mState != STATE_INITIALIZED) { 347 throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState)); 348 } 349 350 int ret = native_setCaptureSize(size); 351 if (ret == ERROR_BAD_VALUE) { 352 throw(new IllegalArgumentException("setCaptureSize to " + size + " failed")); 353 } 354 355 return ret; 356 } 357 } 358 359 /** 360 * Returns current capture size. 361 * @return the capture size in bytes. 362 */ getCaptureSize()363 public int getCaptureSize() 364 throws IllegalStateException { 365 synchronized (mStateLock) { 366 if (mState == STATE_UNINITIALIZED) { 367 throw(new IllegalStateException("getCaptureSize() called in wrong state: "+mState)); 368 } 369 return native_getCaptureSize(); 370 } 371 } 372 373 /** 374 * Set the type of scaling applied on the captured visualization data. 375 * @param mode see {@link #SCALING_MODE_NORMALIZED} 376 * and {@link #SCALING_MODE_AS_PLAYED} 377 * @return {@link #SUCCESS} in case of success, 378 * {@link #ERROR_BAD_VALUE} in case of failure. 379 * @throws IllegalStateException 380 */ setScalingMode(int mode)381 public int setScalingMode(int mode) 382 throws IllegalStateException { 383 synchronized (mStateLock) { 384 if (mState == STATE_UNINITIALIZED) { 385 throw(new IllegalStateException("setScalingMode() called in wrong state: " 386 + mState)); 387 } 388 return native_setScalingMode(mode); 389 } 390 } 391 392 /** 393 * Returns the current scaling mode on the captured visualization data. 394 * @return the scaling mode, see {@link #SCALING_MODE_NORMALIZED} 395 * and {@link #SCALING_MODE_AS_PLAYED}. 396 * @throws IllegalStateException 397 */ getScalingMode()398 public int getScalingMode() 399 throws IllegalStateException { 400 synchronized (mStateLock) { 401 if (mState == STATE_UNINITIALIZED) { 402 throw(new IllegalStateException("getScalingMode() called in wrong state: " 403 + mState)); 404 } 405 return native_getScalingMode(); 406 } 407 } 408 409 /** 410 * Sets the combination of measurement modes to be performed by this audio effect. 411 * @param mode a mask of the measurements to perform. The valid values are 412 * {@link #MEASUREMENT_MODE_NONE} (to cancel any measurement) 413 * or {@link #MEASUREMENT_MODE_PEAK_RMS}. 414 * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE} in case of failure. 415 * @throws IllegalStateException 416 */ setMeasurementMode(int mode)417 public int setMeasurementMode(int mode) 418 throws IllegalStateException { 419 synchronized (mStateLock) { 420 if (mState == STATE_UNINITIALIZED) { 421 throw(new IllegalStateException("setMeasurementMode() called in wrong state: " 422 + mState)); 423 } 424 return native_setMeasurementMode(mode); 425 } 426 } 427 428 /** 429 * Returns the current measurement modes performed by this audio effect 430 * @return the mask of the measurements, 431 * {@link #MEASUREMENT_MODE_NONE} (when no measurements are performed) 432 * or {@link #MEASUREMENT_MODE_PEAK_RMS}. 433 * @throws IllegalStateException 434 */ getMeasurementMode()435 public int getMeasurementMode() 436 throws IllegalStateException { 437 synchronized (mStateLock) { 438 if (mState == STATE_UNINITIALIZED) { 439 throw(new IllegalStateException("getMeasurementMode() called in wrong state: " 440 + mState)); 441 } 442 return native_getMeasurementMode(); 443 } 444 } 445 446 /** 447 * Returns the sampling rate of the captured audio. 448 * @return the sampling rate in milliHertz. 449 */ getSamplingRate()450 public int getSamplingRate() 451 throws IllegalStateException { 452 synchronized (mStateLock) { 453 if (mState == STATE_UNINITIALIZED) { 454 throw(new IllegalStateException("getSamplingRate() called in wrong state: "+mState)); 455 } 456 return native_getSamplingRate(); 457 } 458 } 459 460 /** 461 * Returns a waveform capture of currently playing audio content. The capture consists in 462 * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned 463 * by {@link #getCaptureSize()}. 464 * <p>This method must be called when the Visualizer is enabled. 465 * @param waveform array of bytes where the waveform should be returned, array length must be 466 * at least equals to the capture size returned by {@link #getCaptureSize()}. 467 * @return {@link #SUCCESS} in case of success, 468 * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} 469 * in case of failure. 470 * @throws IllegalStateException 471 * @throws IllegalArgumentException 472 */ getWaveForm(byte[] waveform)473 public int getWaveForm(byte[] waveform) 474 throws IllegalStateException { 475 synchronized (mStateLock) { 476 if (mState != STATE_ENABLED) { 477 throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState)); 478 } 479 int captureSize = getCaptureSize(); 480 if (captureSize > waveform.length) { 481 throw(new IllegalArgumentException("getWaveForm() called with illegal size: " 482 + waveform.length + " expecting at least " 483 + captureSize + " bytes")); 484 } 485 return native_getWaveForm(waveform); 486 } 487 } 488 /** 489 * Returns a frequency capture of currently playing audio content. 490 * <p>This method must be called when the Visualizer is enabled. 491 * <p>The capture is an 8-bit magnitude FFT, the frequency range covered being 0 (DC) to half of 492 * the sampling rate returned by {@link #getSamplingRate()}. The capture returns the real and 493 * imaginary parts of a number of frequency points equal to half of the capture size plus one. 494 * <p>Note: only the real part is returned for the first point (DC) and the last point 495 * (sampling frequency / 2). 496 * <p>The layout in the returned byte array is as follows: 497 * <ul> 498 * <li> n is the capture size returned by getCaptureSize()</li> 499 * <li> Rfk, Ifk are respectively the real and imaginary parts of the kth frequency 500 * component</li> 501 * <li> If Fs is the sampling frequency retuned by getSamplingRate() the kth frequency is: 502 * k * Fs / n </li> 503 * </ul> 504 * <table border="0" cellspacing="0" cellpadding="0"> 505 * <tr><td>Index </p></td> 506 * <td>0 </p></td> 507 * <td>1 </p></td> 508 * <td>2 </p></td> 509 * <td>3 </p></td> 510 * <td>4 </p></td> 511 * <td>5 </p></td> 512 * <td>... </p></td> 513 * <td>n - 2 </p></td> 514 * <td>n - 1 </p></td></tr> 515 * <tr><td>Data </p></td> 516 * <td>Rf0 </p></td> 517 * <td>Rf(n/2) </p></td> 518 * <td>Rf1 </p></td> 519 * <td>If1 </p></td> 520 * <td>Rf2 </p></td> 521 * <td>If2 </p></td> 522 * <td>... </p></td> 523 * <td>Rf(n/2-1) </p></td> 524 * <td>If(n/2-1) </p></td></tr> 525 * </table> 526 * <p>In order to obtain magnitude and phase values the following code can 527 * be used: 528 * <pre class="prettyprint"> 529 * int n = fft.size(); 530 * float[] magnitudes = new float[n / 2 + 1]; 531 * float[] phases = new float[n / 2 + 1]; 532 * magnitudes[0] = (float)Math.abs(fft[0]); // DC 533 * magnitudes[n / 2] = (float)Math.abs(fft[1]); // Nyquist 534 * phases[0] = phases[n / 2] = 0; 535 * for (int k = 1; k < n / 2; k++) { 536 * int i = k * 2; 537 * magnitudes[k] = (float)Math.hypot(fft[i], fft[i + 1]); 538 * phases[k] = (float)Math.atan2(fft[i + 1], fft[i]); 539 * }</pre> 540 * @param fft array of bytes where the FFT should be returned 541 * @return {@link #SUCCESS} in case of success, 542 * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} 543 * in case of failure. 544 * @throws IllegalStateException 545 */ getFft(byte[] fft)546 public int getFft(byte[] fft) 547 throws IllegalStateException { 548 synchronized (mStateLock) { 549 if (mState != STATE_ENABLED) { 550 throw(new IllegalStateException("getFft() called in wrong state: "+mState)); 551 } 552 return native_getFft(fft); 553 } 554 } 555 556 /** 557 * A class to store peak and RMS values. 558 * Peak and RMS are expressed in mB, as described in the 559 * {@link Visualizer#MEASUREMENT_MODE_PEAK_RMS} measurement mode. 560 */ 561 public static final class MeasurementPeakRms { 562 /** 563 * The peak value in mB. 564 */ 565 public int mPeak; 566 /** 567 * The RMS value in mB. 568 */ 569 public int mRms; 570 } 571 572 /** 573 * Retrieves the latest peak and RMS measurement. 574 * Sets the peak and RMS fields of the supplied {@link Visualizer.MeasurementPeakRms} to the 575 * latest measured values. 576 * @param measurement a non-null {@link Visualizer.MeasurementPeakRms} instance to store 577 * the measurement values. 578 * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE}, 579 * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} 580 * in case of failure. 581 */ getMeasurementPeakRms(MeasurementPeakRms measurement)582 public int getMeasurementPeakRms(MeasurementPeakRms measurement) { 583 if (measurement == null) { 584 Log.e(TAG, "Cannot store measurements in a null object"); 585 return ERROR_BAD_VALUE; 586 } 587 synchronized (mStateLock) { 588 if (mState != STATE_ENABLED) { 589 throw (new IllegalStateException("getMeasurementPeakRms() called in wrong state: " 590 + mState)); 591 } 592 return native_getPeakRms(measurement); 593 } 594 } 595 596 //--------------------------------------------------------- 597 // Interface definitions 598 //-------------------- 599 /** 600 * The OnDataCaptureListener interface defines methods called by the Visualizer to periodically 601 * update the audio visualization capture. 602 * The client application can implement this interface and register the listener with the 603 * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. 604 */ 605 public interface OnDataCaptureListener { 606 /** 607 * Method called when a new waveform capture is available. 608 * <p>Data in the waveform buffer is valid only within the scope of the callback. 609 * Applications which need access to the waveform data after returning from the callback 610 * should make a copy of the data instead of holding a reference. 611 * @param visualizer Visualizer object on which the listener is registered. 612 * @param waveform array of bytes containing the waveform representation. 613 * @param samplingRate sampling rate of the visualized audio. 614 */ onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate)615 void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate); 616 617 /** 618 * Method called when a new frequency capture is available. 619 * <p>Data in the fft buffer is valid only within the scope of the callback. 620 * Applications which need access to the fft data after returning from the callback 621 * should make a copy of the data instead of holding a reference. 622 * <p>For the explanation of the fft data array layout, and the example 623 * code for processing it, please see the documentation for {@link #getFft(byte[])} method. 624 * 625 * @param visualizer Visualizer object on which the listener is registered. 626 * @param fft array of bytes containing the frequency representation. 627 * @param samplingRate sampling rate of the visualized audio. 628 */ onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate)629 void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate); 630 } 631 632 /** 633 * Registers an OnDataCaptureListener interface and specifies the rate at which the capture 634 * should be updated as well as the type of capture requested. 635 * <p>Call this method with a null listener to stop receiving the capture updates. 636 * @param listener OnDataCaptureListener registered 637 * @param rate rate in milliHertz at which the capture should be updated 638 * @param waveform true if a waveform capture is requested: the onWaveFormDataCapture() 639 * method will be called on the OnDataCaptureListener interface. 640 * @param fft true if a frequency capture is requested: the onFftDataCapture() method will be 641 * called on the OnDataCaptureListener interface. 642 * @return {@link #SUCCESS} in case of success, 643 * {@link #ERROR_NO_INIT} or {@link #ERROR_BAD_VALUE} in case of failure. 644 */ setDataCaptureListener(@ullable OnDataCaptureListener listener, int rate, boolean waveform, boolean fft)645 public int setDataCaptureListener(@Nullable OnDataCaptureListener listener, 646 int rate, boolean waveform, boolean fft) { 647 if (listener == null) { 648 // make sure capture callback is stopped in native code 649 waveform = false; 650 fft = false; 651 } 652 int status; 653 synchronized (mStateLock) { 654 status = native_setPeriodicCapture(rate, waveform, fft); 655 } 656 if (status == SUCCESS) { 657 synchronized (mListenerLock) { 658 mCaptureListener = listener; 659 if ((listener != null) && (mNativeEventHandler == null)) { 660 Looper looper; 661 if ((looper = Looper.myLooper()) != null) { 662 mNativeEventHandler = new Handler(looper); 663 } else if ((looper = Looper.getMainLooper()) != null) { 664 mNativeEventHandler = new Handler(looper); 665 } else { 666 mNativeEventHandler = null; 667 status = ERROR_NO_INIT; 668 } 669 } 670 } 671 } 672 return status; 673 } 674 675 /** 676 * @hide 677 * 678 * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that 679 * the connection to the native media server has been broken and that the Visualizer object will 680 * need to be released and re-created. 681 * The client application can implement this interface and register the listener with the 682 * {@link #setServerDiedListener(OnServerDiedListener)} method. 683 */ 684 public interface OnServerDiedListener { 685 /** 686 * @hide 687 * 688 * Method called when the native media server has died. 689 * <p>If the native media server encounters a fatal error and needs to restart, the binder 690 * connection from the {@link #Visualizer} to the media server will be broken. Data capture 691 * callbacks will stop happening, and client initiated calls to the {@link #Visualizer} 692 * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality, 693 * clients should {@link #release()} their old visualizer and create a new instance. 694 */ onServerDied()695 void onServerDied(); 696 } 697 698 /** 699 * @hide 700 * 701 * Registers an OnServerDiedListener interface. 702 * <p>Call this method with a null listener to stop receiving server death notifications. 703 * @return {@link #SUCCESS} in case of success, 704 */ setServerDiedListener(@ullable OnServerDiedListener listener)705 public int setServerDiedListener(@Nullable OnServerDiedListener listener) { 706 synchronized (mListenerLock) { 707 mServerDiedListener = listener; 708 } 709 return SUCCESS; 710 } 711 712 //--------------------------------------------------------- 713 // Interface definitions 714 //-------------------- 715 native_init()716 private static native final void native_init(); 717 718 @GuardedBy("mStateLock") native_setup(Object audioeffect_this, int audioSession, int[] id, @NonNull Parcel attributionSource)719 private native final int native_setup(Object audioeffect_this, 720 int audioSession, 721 int[] id, 722 @NonNull Parcel attributionSource); 723 724 @GuardedBy("mStateLock") native_finalize()725 private native final void native_finalize(); 726 727 @GuardedBy("mStateLock") native_release()728 private native final void native_release(); 729 730 @GuardedBy("mStateLock") native_setEnabled(boolean enabled)731 private native final int native_setEnabled(boolean enabled); 732 733 @GuardedBy("mStateLock") native_getEnabled()734 private native final boolean native_getEnabled(); 735 736 @GuardedBy("mStateLock") native_setCaptureSize(int size)737 private native final int native_setCaptureSize(int size); 738 739 @GuardedBy("mStateLock") native_getCaptureSize()740 private native final int native_getCaptureSize(); 741 742 @GuardedBy("mStateLock") native_setScalingMode(int mode)743 private native final int native_setScalingMode(int mode); 744 745 @GuardedBy("mStateLock") native_getScalingMode()746 private native final int native_getScalingMode(); 747 748 @GuardedBy("mStateLock") native_setMeasurementMode(int mode)749 private native final int native_setMeasurementMode(int mode); 750 751 @GuardedBy("mStateLock") native_getMeasurementMode()752 private native final int native_getMeasurementMode(); 753 754 @GuardedBy("mStateLock") native_getSamplingRate()755 private native final int native_getSamplingRate(); 756 757 @GuardedBy("mStateLock") native_getWaveForm(byte[] waveform)758 private native final int native_getWaveForm(byte[] waveform); 759 760 @GuardedBy("mStateLock") native_getFft(byte[] fft)761 private native final int native_getFft(byte[] fft); 762 763 @GuardedBy("mStateLock") native_getPeakRms(MeasurementPeakRms measurement)764 private native final int native_getPeakRms(MeasurementPeakRms measurement); 765 766 @GuardedBy("mStateLock") native_setPeriodicCapture(int rate, boolean waveForm, boolean fft)767 private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft); 768 769 //--------------------------------------------------------- 770 // Java methods called from the native side 771 //-------------------- 772 @SuppressWarnings("unused") postEventFromNative(Object effect_ref, int what, int samplingRate, byte[] data)773 private static void postEventFromNative(Object effect_ref, 774 int what, int samplingRate, byte[] data) { 775 final Visualizer visualizer = (Visualizer) ((WeakReference) effect_ref).get(); 776 if (visualizer == null) return; 777 778 final Handler handler; 779 synchronized (visualizer.mListenerLock) { 780 handler = visualizer.mNativeEventHandler; 781 } 782 if (handler == null) return; 783 784 switch (what) { 785 case NATIVE_EVENT_PCM_CAPTURE: 786 case NATIVE_EVENT_FFT_CAPTURE: 787 handler.post(() -> { 788 final OnDataCaptureListener l; 789 synchronized (visualizer.mListenerLock) { 790 l = visualizer.mCaptureListener; 791 } 792 if (l != null) { 793 if (what == NATIVE_EVENT_PCM_CAPTURE) { 794 l.onWaveFormDataCapture(visualizer, data, samplingRate); 795 } else { // what == NATIVE_EVENT_FFT_CAPTURE 796 l.onFftDataCapture(visualizer, data, samplingRate); 797 } 798 } 799 }); 800 break; 801 case NATIVE_EVENT_SERVER_DIED: 802 handler.post(() -> { 803 final OnServerDiedListener l; 804 synchronized (visualizer.mListenerLock) { 805 l = visualizer.mServerDiedListener; 806 } 807 if (l != null) { 808 l.onServerDied(); 809 } 810 }); 811 break; 812 default: 813 Log.e(TAG, "Unknown native event in postEventFromNative: " + what); 814 break; 815 } 816 } 817 } 818