| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.media.audiofx; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.AttributionSource; |
| import android.content.AttributionSource.ScopedParcelState; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Parcel; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| |
| import java.lang.ref.WeakReference; |
| |
| /** |
| * The Visualizer class enables application to retrieve part of the currently playing audio for |
| * visualization purpose. It is not an audio recording interface and only returns partial and low |
| * quality audio content. However, to protect privacy of certain audio data (e.g voice mail) the use |
| * of the visualizer requires the permission android.permission.RECORD_AUDIO. |
| * <p>The audio session ID passed to the constructor indicates which audio content should be |
| * visualized:<br> |
| * <ul> |
| * <li>If the session is 0, the audio output mix is visualized</li> |
| * <li>If the session is not 0, the audio from a particular {@link android.media.MediaPlayer} or |
| * {@link android.media.AudioTrack} |
| * using this audio session is visualized </li> |
| * </ul> |
| * <p>Two types of representation of audio content can be captured: <br> |
| * <ul> |
| * <li>Waveform data: consecutive 8-bit (unsigned) mono samples by using the |
| * {@link #getWaveForm(byte[])} method</li> |
| * <li>Frequency data: 8-bit magnitude FFT by using the {@link #getFft(byte[])} method</li> |
| * </ul> |
| * <p>The length of the capture can be retrieved or specified by calling respectively |
| * {@link #getCaptureSize()} and {@link #setCaptureSize(int)} methods. The capture size must be a |
| * power of 2 in the range returned by {@link #getCaptureSizeRange()}. |
| * <p>In addition to the polling capture mode described above with {@link #getWaveForm(byte[])} and |
| * {@link #getFft(byte[])} methods, a callback mode is also available by installing a listener by |
| * use of the {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. |
| * The rate at which the listener capture method is called as well as the type of data returned is |
| * specified. |
| * <p>Before capturing data, the Visualizer must be enabled by calling the |
| * {@link #setEnabled(boolean)} method. |
| * When data capture is not needed any more, the Visualizer should be disabled. |
| * <p>It is good practice to call the {@link #release()} method when the Visualizer is not used |
| * anymore to free up native resources associated to the Visualizer instance. |
| * <p>Creating a Visualizer on the output mix (audio session 0) requires permission |
| * {@link android.Manifest.permission#MODIFY_AUDIO_SETTINGS} |
| * <p>The Visualizer class can also be used to perform measurements on the audio being played back. |
| * The measurements to perform are defined by setting a mask of the requested measurement modes with |
| * {@link #setMeasurementMode(int)}. Supported values are {@link #MEASUREMENT_MODE_NONE} to cancel |
| * any measurement, and {@link #MEASUREMENT_MODE_PEAK_RMS} for peak and RMS monitoring. |
| * Measurements can be retrieved through {@link #getMeasurementPeakRms(MeasurementPeakRms)}. |
| */ |
| |
| public class Visualizer { |
| |
| static { |
| System.loadLibrary("audioeffect_jni"); |
| native_init(); |
| } |
| |
| private final static String TAG = "Visualizer-JAVA"; |
| |
| /** |
| * State of a Visualizer object that was not successfully initialized upon creation |
| */ |
| public static final int STATE_UNINITIALIZED = 0; |
| /** |
| * State of a Visualizer object that is ready to be used. |
| */ |
| public static final int STATE_INITIALIZED = 1; |
| /** |
| * State of a Visualizer object that is active. |
| */ |
| public static final int STATE_ENABLED = 2; |
| |
| // to keep in sync with system/media/audio_effects/include/audio_effects/effect_visualizer.h |
| /** |
| * Defines a capture mode where amplification is applied based on the content of the captured |
| * data. This is the default Visualizer mode, and is suitable for music visualization. |
| */ |
| public static final int SCALING_MODE_NORMALIZED = 0; |
| /** |
| * Defines a capture mode where the playback volume will affect (scale) the range of the |
| * captured data. A low playback volume will lead to low sample and fft values, and vice-versa. |
| */ |
| public static final int SCALING_MODE_AS_PLAYED = 1; |
| |
| /** |
| * Defines a measurement mode in which no measurements are performed. |
| */ |
| public static final int MEASUREMENT_MODE_NONE = 0; |
| |
| /** |
| * Defines a measurement mode which computes the peak and RMS value in mB below the |
| * "full scale", where 0mB is normally the maximum sample value (but see the note |
| * below). Minimum value depends on the resolution of audio samples used by the audio |
| * framework. The value of -9600mB is the minimum value for 16-bit audio systems and |
| * -14400mB or below for "high resolution" systems. Values for peak and RMS can be |
| * retrieved with {@link #getMeasurementPeakRms(MeasurementPeakRms)}. |
| * |
| * <p class=note><strong>Note:</strong> when Visualizer effect is attached to the |
| * global session (with session ID 0), it is possible to observe RMS peaks higher than |
| * 0 dBFS, for example in the case when there are multiple audio sources playing |
| * simultaneously. In this case {@link #getMeasurementPeakRms(MeasurementPeakRms)} |
| * method can return a positive value. |
| */ |
| public static final int MEASUREMENT_MODE_PEAK_RMS = 1 << 0; |
| |
| // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp |
| private static final int NATIVE_EVENT_PCM_CAPTURE = 0; |
| private static final int NATIVE_EVENT_FFT_CAPTURE = 1; |
| private static final int NATIVE_EVENT_SERVER_DIED = 2; |
| |
| // Error codes: |
| /** |
| * Successful operation. |
| */ |
| public static final int SUCCESS = 0; |
| /** |
| * Unspecified error. |
| */ |
| public static final int ERROR = -1; |
| /** |
| * Internal operation status. Not returned by any method. |
| */ |
| public static final int ALREADY_EXISTS = -2; |
| /** |
| * Operation failed due to bad object initialization. |
| */ |
| public static final int ERROR_NO_INIT = -3; |
| /** |
| * Operation failed due to bad parameter value. |
| */ |
| public static final int ERROR_BAD_VALUE = -4; |
| /** |
| * Operation failed because it was requested in wrong state. |
| */ |
| public static final int ERROR_INVALID_OPERATION = -5; |
| /** |
| * Operation failed due to lack of memory. |
| */ |
| public static final int ERROR_NO_MEMORY = -6; |
| /** |
| * Operation failed due to dead remote object. |
| */ |
| public static final int ERROR_DEAD_OBJECT = -7; |
| |
| //-------------------------------------------------------------------------- |
| // Member variables |
| //-------------------- |
| /** |
| * Indicates the state of the Visualizer instance |
| */ |
| @GuardedBy("mStateLock") |
| private int mState = STATE_UNINITIALIZED; |
| /** |
| * Lock to synchronize access to mState |
| */ |
| private final Object mStateLock = new Object(); |
| /** |
| * System wide unique Identifier of the visualizer engine used by this Visualizer instance |
| */ |
| @GuardedBy("mStateLock") |
| @UnsupportedAppUsage |
| private int mId; |
| |
| /** |
| * Lock to protect listeners updates against event notifications |
| */ |
| private final Object mListenerLock = new Object(); |
| /** |
| * Handler for events coming from the native code |
| */ |
| @GuardedBy("mListenerLock") |
| @Nullable private Handler mNativeEventHandler = null; |
| /** |
| * PCM and FFT capture listener registered by client |
| */ |
| @GuardedBy("mListenerLock") |
| @Nullable private OnDataCaptureListener mCaptureListener = null; |
| /** |
| * Server Died listener registered by client |
| */ |
| @GuardedBy("mListenerLock") |
| @Nullable private OnServerDiedListener mServerDiedListener = null; |
| |
| // accessed by native methods |
| private long mNativeVisualizer; // guarded by a static lock in native code |
| private long mJniData; // set in native_setup, _release; |
| // get in native_release, _setEnabled, _setPeriodicCapture |
| // thus, effectively guarded by mStateLock |
| |
| //-------------------------------------------------------------------------- |
| // Constructor, Finalize |
| //-------------------- |
| /** |
| * Class constructor. |
| * @param audioSession system wide unique audio session identifier. If audioSession |
| * is not 0, the visualizer will be attached to the MediaPlayer or AudioTrack in the |
| * same audio session. Otherwise, the Visualizer will apply to the output mix. |
| * |
| * @throws java.lang.UnsupportedOperationException |
| * @throws java.lang.RuntimeException |
| */ |
| |
| public Visualizer(int audioSession) |
| throws UnsupportedOperationException, RuntimeException { |
| int[] id = new int[1]; |
| |
| synchronized (mStateLock) { |
| mState = STATE_UNINITIALIZED; |
| |
| // native initialization |
| // TODO b/182469354: make consistent with AudioRecord |
| int result; |
| try (ScopedParcelState attributionSourceState = AttributionSource.myAttributionSource() |
| .asScopedParcelState()) { |
| result = native_setup(new WeakReference<>(this), audioSession, id, |
| attributionSourceState.getParcel()); |
| } |
| if (result != SUCCESS && result != ALREADY_EXISTS) { |
| Log.e(TAG, "Error code "+result+" when initializing Visualizer."); |
| switch (result) { |
| case ERROR_INVALID_OPERATION: |
| throw (new UnsupportedOperationException("Effect library not loaded")); |
| default: |
| throw (new RuntimeException("Cannot initialize Visualizer engine, error: " |
| +result)); |
| } |
| } |
| mId = id[0]; |
| if (native_getEnabled()) { |
| mState = STATE_ENABLED; |
| } else { |
| mState = STATE_INITIALIZED; |
| } |
| } |
| } |
| |
| /** |
| * Releases the native Visualizer resources. It is a good practice to release the |
| * visualization engine when not in use. |
| */ |
| public void release() { |
| synchronized (mStateLock) { |
| native_release(); |
| mState = STATE_UNINITIALIZED; |
| } |
| } |
| |
| @Override |
| protected void finalize() { |
| synchronized (mStateLock) { |
| native_finalize(); |
| } |
| } |
| |
| /** |
| * Enable or disable the visualization engine. |
| * @param enabled requested enable state |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} in case of failure. |
| * @throws IllegalStateException |
| */ |
| public int setEnabled(boolean enabled) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("setEnabled() called in wrong state: "+mState)); |
| } |
| int status = SUCCESS; |
| if ((enabled && (mState == STATE_INITIALIZED)) || |
| (!enabled && (mState == STATE_ENABLED))) { |
| status = native_setEnabled(enabled); |
| if (status == SUCCESS) { |
| mState = enabled ? STATE_ENABLED : STATE_INITIALIZED; |
| } |
| } |
| return status; |
| } |
| } |
| |
| /** |
| * Get current activation state of the visualizer. |
| * @return true if the visualizer is active, false otherwise |
| */ |
| public boolean getEnabled() |
| { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("getEnabled() called in wrong state: "+mState)); |
| } |
| return native_getEnabled(); |
| } |
| } |
| |
| /** |
| * Returns the capture size range. |
| * @return the mininum capture size is returned in first array element and the maximum in second |
| * array element. |
| */ |
| public static native int[] getCaptureSizeRange(); |
| |
| /** |
| * Returns the maximum capture rate for the callback capture method. This is the maximum value |
| * for the rate parameter of the |
| * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. |
| * @return the maximum capture rate expressed in milliHertz |
| */ |
| public static native int getMaxCaptureRate(); |
| |
| /** |
| * Sets the capture size, i.e. the number of bytes returned by {@link #getWaveForm(byte[])} and |
| * {@link #getFft(byte[])} methods. The capture size must be a power of 2 in the range returned |
| * by {@link #getCaptureSizeRange()}. |
| * This method must not be called when the Visualizer is enabled. |
| * @param size requested capture size |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_INVALID_OPERATION} if Visualizer effect enginer not enabled. |
| * @throws IllegalStateException if the effect is not in proper state. |
| * @throws IllegalArgumentException if the size parameter is invalid (out of supported range). |
| */ |
| public int setCaptureSize(int size) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState != STATE_INITIALIZED) { |
| throw(new IllegalStateException("setCaptureSize() called in wrong state: "+mState)); |
| } |
| |
| int ret = native_setCaptureSize(size); |
| if (ret == ERROR_BAD_VALUE) { |
| throw(new IllegalArgumentException("setCaptureSize to " + size + " failed")); |
| } |
| |
| return ret; |
| } |
| } |
| |
| /** |
| * Returns current capture size. |
| * @return the capture size in bytes. |
| */ |
| public int getCaptureSize() |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("getCaptureSize() called in wrong state: "+mState)); |
| } |
| return native_getCaptureSize(); |
| } |
| } |
| |
| /** |
| * Set the type of scaling applied on the captured visualization data. |
| * @param mode see {@link #SCALING_MODE_NORMALIZED} |
| * and {@link #SCALING_MODE_AS_PLAYED} |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_BAD_VALUE} in case of failure. |
| * @throws IllegalStateException |
| */ |
| public int setScalingMode(int mode) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("setScalingMode() called in wrong state: " |
| + mState)); |
| } |
| return native_setScalingMode(mode); |
| } |
| } |
| |
| /** |
| * Returns the current scaling mode on the captured visualization data. |
| * @return the scaling mode, see {@link #SCALING_MODE_NORMALIZED} |
| * and {@link #SCALING_MODE_AS_PLAYED}. |
| * @throws IllegalStateException |
| */ |
| public int getScalingMode() |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("getScalingMode() called in wrong state: " |
| + mState)); |
| } |
| return native_getScalingMode(); |
| } |
| } |
| |
| /** |
| * Sets the combination of measurement modes to be performed by this audio effect. |
| * @param mode a mask of the measurements to perform. The valid values are |
| * {@link #MEASUREMENT_MODE_NONE} (to cancel any measurement) |
| * or {@link #MEASUREMENT_MODE_PEAK_RMS}. |
| * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE} in case of failure. |
| * @throws IllegalStateException |
| */ |
| public int setMeasurementMode(int mode) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("setMeasurementMode() called in wrong state: " |
| + mState)); |
| } |
| return native_setMeasurementMode(mode); |
| } |
| } |
| |
| /** |
| * Returns the current measurement modes performed by this audio effect |
| * @return the mask of the measurements, |
| * {@link #MEASUREMENT_MODE_NONE} (when no measurements are performed) |
| * or {@link #MEASUREMENT_MODE_PEAK_RMS}. |
| * @throws IllegalStateException |
| */ |
| public int getMeasurementMode() |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("getMeasurementMode() called in wrong state: " |
| + mState)); |
| } |
| return native_getMeasurementMode(); |
| } |
| } |
| |
| /** |
| * Returns the sampling rate of the captured audio. |
| * @return the sampling rate in milliHertz. |
| */ |
| public int getSamplingRate() |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState == STATE_UNINITIALIZED) { |
| throw(new IllegalStateException("getSamplingRate() called in wrong state: "+mState)); |
| } |
| return native_getSamplingRate(); |
| } |
| } |
| |
| /** |
| * Returns a waveform capture of currently playing audio content. The capture consists in |
| * a number of consecutive 8-bit (unsigned) mono PCM samples equal to the capture size returned |
| * by {@link #getCaptureSize()}. |
| * <p>This method must be called when the Visualizer is enabled. |
| * @param waveform array of bytes where the waveform should be returned, array length must be |
| * at least equals to the capture size returned by {@link #getCaptureSize()}. |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} |
| * in case of failure. |
| * @throws IllegalStateException |
| * @throws IllegalArgumentException |
| */ |
| public int getWaveForm(byte[] waveform) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState != STATE_ENABLED) { |
| throw(new IllegalStateException("getWaveForm() called in wrong state: "+mState)); |
| } |
| int captureSize = getCaptureSize(); |
| if (captureSize > waveform.length) { |
| throw(new IllegalArgumentException("getWaveForm() called with illegal size: " |
| + waveform.length + " expecting at least " |
| + captureSize + " bytes")); |
| } |
| return native_getWaveForm(waveform); |
| } |
| } |
| /** |
| * Returns a frequency capture of currently playing audio content. |
| * <p>This method must be called when the Visualizer is enabled. |
| * <p>The capture is an 8-bit magnitude FFT, the frequency range covered being 0 (DC) to half of |
| * the sampling rate returned by {@link #getSamplingRate()}. The capture returns the real and |
| * imaginary parts of a number of frequency points equal to half of the capture size plus one. |
| * <p>Note: only the real part is returned for the first point (DC) and the last point |
| * (sampling frequency / 2). |
| * <p>The layout in the returned byte array is as follows: |
| * <ul> |
| * <li> n is the capture size returned by getCaptureSize()</li> |
| * <li> Rfk, Ifk are respectively the real and imaginary parts of the kth frequency |
| * component</li> |
| * <li> If Fs is the sampling frequency retuned by getSamplingRate() the kth frequency is: |
| * k * Fs / n </li> |
| * </ul> |
| * <table border="0" cellspacing="0" cellpadding="0"> |
| * <tr><td>Index </p></td> |
| * <td>0 </p></td> |
| * <td>1 </p></td> |
| * <td>2 </p></td> |
| * <td>3 </p></td> |
| * <td>4 </p></td> |
| * <td>5 </p></td> |
| * <td>... </p></td> |
| * <td>n - 2 </p></td> |
| * <td>n - 1 </p></td></tr> |
| * <tr><td>Data </p></td> |
| * <td>Rf0 </p></td> |
| * <td>Rf(n/2) </p></td> |
| * <td>Rf1 </p></td> |
| * <td>If1 </p></td> |
| * <td>Rf2 </p></td> |
| * <td>If2 </p></td> |
| * <td>... </p></td> |
| * <td>Rf(n/2-1) </p></td> |
| * <td>If(n/2-1) </p></td></tr> |
| * </table> |
| * <p>In order to obtain magnitude and phase values the following code can |
| * be used: |
| * <pre class="prettyprint"> |
| * int n = fft.size(); |
| * float[] magnitudes = new float[n / 2 + 1]; |
| * float[] phases = new float[n / 2 + 1]; |
| * magnitudes[0] = (float)Math.abs(fft[0]); // DC |
| * magnitudes[n / 2] = (float)Math.abs(fft[1]); // Nyquist |
| * phases[0] = phases[n / 2] = 0; |
| * for (int k = 1; k < n / 2; k++) { |
| * int i = k * 2; |
| * magnitudes[k] = (float)Math.hypot(fft[i], fft[i + 1]); |
| * phases[k] = (float)Math.atan2(fft[i + 1], fft[i]); |
| * }</pre> |
| * @param fft array of bytes where the FFT should be returned |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} |
| * in case of failure. |
| * @throws IllegalStateException |
| */ |
| public int getFft(byte[] fft) |
| throws IllegalStateException { |
| synchronized (mStateLock) { |
| if (mState != STATE_ENABLED) { |
| throw(new IllegalStateException("getFft() called in wrong state: "+mState)); |
| } |
| return native_getFft(fft); |
| } |
| } |
| |
| /** |
| * A class to store peak and RMS values. |
| * Peak and RMS are expressed in mB, as described in the |
| * {@link Visualizer#MEASUREMENT_MODE_PEAK_RMS} measurement mode. |
| */ |
| public static final class MeasurementPeakRms { |
| /** |
| * The peak value in mB. |
| */ |
| public int mPeak; |
| /** |
| * The RMS value in mB. |
| */ |
| public int mRms; |
| } |
| |
| /** |
| * Retrieves the latest peak and RMS measurement. |
| * Sets the peak and RMS fields of the supplied {@link Visualizer.MeasurementPeakRms} to the |
| * latest measured values. |
| * @param measurement a non-null {@link Visualizer.MeasurementPeakRms} instance to store |
| * the measurement values. |
| * @return {@link #SUCCESS} in case of success, {@link #ERROR_BAD_VALUE}, |
| * {@link #ERROR_NO_MEMORY}, {@link #ERROR_INVALID_OPERATION} or {@link #ERROR_DEAD_OBJECT} |
| * in case of failure. |
| */ |
| public int getMeasurementPeakRms(MeasurementPeakRms measurement) { |
| if (measurement == null) { |
| Log.e(TAG, "Cannot store measurements in a null object"); |
| return ERROR_BAD_VALUE; |
| } |
| synchronized (mStateLock) { |
| if (mState != STATE_ENABLED) { |
| throw (new IllegalStateException("getMeasurementPeakRms() called in wrong state: " |
| + mState)); |
| } |
| return native_getPeakRms(measurement); |
| } |
| } |
| |
| //--------------------------------------------------------- |
| // Interface definitions |
| //-------------------- |
| /** |
| * The OnDataCaptureListener interface defines methods called by the Visualizer to periodically |
| * update the audio visualization capture. |
| * The client application can implement this interface and register the listener with the |
| * {@link #setDataCaptureListener(OnDataCaptureListener, int, boolean, boolean)} method. |
| */ |
| public interface OnDataCaptureListener { |
| /** |
| * Method called when a new waveform capture is available. |
| * <p>Data in the waveform buffer is valid only within the scope of the callback. |
| * Applications which need access to the waveform data after returning from the callback |
| * should make a copy of the data instead of holding a reference. |
| * @param visualizer Visualizer object on which the listener is registered. |
| * @param waveform array of bytes containing the waveform representation. |
| * @param samplingRate sampling rate of the visualized audio. |
| */ |
| void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate); |
| |
| /** |
| * Method called when a new frequency capture is available. |
| * <p>Data in the fft buffer is valid only within the scope of the callback. |
| * Applications which need access to the fft data after returning from the callback |
| * should make a copy of the data instead of holding a reference. |
| * <p>For the explanation of the fft data array layout, and the example |
| * code for processing it, please see the documentation for {@link #getFft(byte[])} method. |
| * |
| * @param visualizer Visualizer object on which the listener is registered. |
| * @param fft array of bytes containing the frequency representation. |
| * @param samplingRate sampling rate of the visualized audio. |
| */ |
| void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate); |
| } |
| |
| /** |
| * Registers an OnDataCaptureListener interface and specifies the rate at which the capture |
| * should be updated as well as the type of capture requested. |
| * <p>Call this method with a null listener to stop receiving the capture updates. |
| * @param listener OnDataCaptureListener registered |
| * @param rate rate in milliHertz at which the capture should be updated |
| * @param waveform true if a waveform capture is requested: the onWaveFormDataCapture() |
| * method will be called on the OnDataCaptureListener interface. |
| * @param fft true if a frequency capture is requested: the onFftDataCapture() method will be |
| * called on the OnDataCaptureListener interface. |
| * @return {@link #SUCCESS} in case of success, |
| * {@link #ERROR_NO_INIT} or {@link #ERROR_BAD_VALUE} in case of failure. |
| */ |
| public int setDataCaptureListener(@Nullable OnDataCaptureListener listener, |
| int rate, boolean waveform, boolean fft) { |
| if (listener == null) { |
| // make sure capture callback is stopped in native code |
| waveform = false; |
| fft = false; |
| } |
| int status; |
| synchronized (mStateLock) { |
| status = native_setPeriodicCapture(rate, waveform, fft); |
| } |
| if (status == SUCCESS) { |
| synchronized (mListenerLock) { |
| mCaptureListener = listener; |
| if ((listener != null) && (mNativeEventHandler == null)) { |
| Looper looper; |
| if ((looper = Looper.myLooper()) != null) { |
| mNativeEventHandler = new Handler(looper); |
| } else if ((looper = Looper.getMainLooper()) != null) { |
| mNativeEventHandler = new Handler(looper); |
| } else { |
| mNativeEventHandler = null; |
| status = ERROR_NO_INIT; |
| } |
| } |
| } |
| } |
| return status; |
| } |
| |
| /** |
| * @hide |
| * |
| * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that |
| * the connection to the native media server has been broken and that the Visualizer object will |
| * need to be released and re-created. |
| * The client application can implement this interface and register the listener with the |
| * {@link #setServerDiedListener(OnServerDiedListener)} method. |
| */ |
| public interface OnServerDiedListener { |
| /** |
| * @hide |
| * |
| * Method called when the native media server has died. |
| * <p>If the native media server encounters a fatal error and needs to restart, the binder |
| * connection from the {@link #Visualizer} to the media server will be broken. Data capture |
| * callbacks will stop happening, and client initiated calls to the {@link #Visualizer} |
| * instance will fail with the error code {@link #DEAD_OBJECT}. To restore functionality, |
| * clients should {@link #release()} their old visualizer and create a new instance. |
| */ |
| void onServerDied(); |
| } |
| |
| /** |
| * @hide |
| * |
| * Registers an OnServerDiedListener interface. |
| * <p>Call this method with a null listener to stop receiving server death notifications. |
| * @return {@link #SUCCESS} in case of success, |
| */ |
| public int setServerDiedListener(@Nullable OnServerDiedListener listener) { |
| synchronized (mListenerLock) { |
| mServerDiedListener = listener; |
| } |
| return SUCCESS; |
| } |
| |
| //--------------------------------------------------------- |
| // Interface definitions |
| //-------------------- |
| |
| private static native final void native_init(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setup(Object audioeffect_this, |
| int audioSession, |
| int[] id, |
| @NonNull Parcel attributionSource); |
| |
| @GuardedBy("mStateLock") |
| private native final void native_finalize(); |
| |
| @GuardedBy("mStateLock") |
| private native final void native_release(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setEnabled(boolean enabled); |
| |
| @GuardedBy("mStateLock") |
| private native final boolean native_getEnabled(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setCaptureSize(int size); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getCaptureSize(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setScalingMode(int mode); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getScalingMode(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setMeasurementMode(int mode); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getMeasurementMode(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getSamplingRate(); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getWaveForm(byte[] waveform); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getFft(byte[] fft); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_getPeakRms(MeasurementPeakRms measurement); |
| |
| @GuardedBy("mStateLock") |
| private native final int native_setPeriodicCapture(int rate, boolean waveForm, boolean fft); |
| |
| //--------------------------------------------------------- |
| // Java methods called from the native side |
| //-------------------- |
| @SuppressWarnings("unused") |
| private static void postEventFromNative(Object effect_ref, |
| int what, int samplingRate, byte[] data) { |
| final Visualizer visualizer = (Visualizer) ((WeakReference) effect_ref).get(); |
| if (visualizer == null) return; |
| |
| final Handler handler; |
| synchronized (visualizer.mListenerLock) { |
| handler = visualizer.mNativeEventHandler; |
| } |
| if (handler == null) return; |
| |
| switch (what) { |
| case NATIVE_EVENT_PCM_CAPTURE: |
| case NATIVE_EVENT_FFT_CAPTURE: |
| handler.post(() -> { |
| final OnDataCaptureListener l; |
| synchronized (visualizer.mListenerLock) { |
| l = visualizer.mCaptureListener; |
| } |
| if (l != null) { |
| if (what == NATIVE_EVENT_PCM_CAPTURE) { |
| l.onWaveFormDataCapture(visualizer, data, samplingRate); |
| } else { // what == NATIVE_EVENT_FFT_CAPTURE |
| l.onFftDataCapture(visualizer, data, samplingRate); |
| } |
| } |
| }); |
| break; |
| case NATIVE_EVENT_SERVER_DIED: |
| handler.post(() -> { |
| final OnServerDiedListener l; |
| synchronized (visualizer.mListenerLock) { |
| l = visualizer.mServerDiedListener; |
| } |
| if (l != null) { |
| l.onServerDied(); |
| } |
| }); |
| break; |
| default: |
| Log.e(TAG, "Unknown native event in postEventFromNative: " + what); |
| break; |
| } |
| } |
| } |