/* * Copyright (C) 2014 Haruki Hasegawa * * 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 com.h6ah4i.android.media.standard.audiofx; import android.content.Context; import android.media.audiofx.Visualizer; import android.os.Build; import android.util.Log; import com.h6ah4i.android.media.audiofx.IVisualizer; import com.h6ah4i.android.media.utils.AudioSystemUtils; import java.lang.ref.WeakReference; public class StandardVisualizer implements IVisualizer { private static final String TAG = "StandardVisualizer"; private static final boolean mIsDebuggable = false; private static final int MIN_CAPTURE_RATE; private static final int MAX_CAPTURE_RATE; private Visualizer mVisualizer; private OnDataCaptureListenerWrapper mCaptureListenerWrapper; private int mCaptureRate = 0; private boolean mCaptureWaveform = false; private boolean mCaptureFft = false; private boolean mNeedToSetCaptureListener = false; private int mCaptureSize; private int mCorrectSamplingRate; private static final VisualizerCompatBase COMPAT_HELPER; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { COMPAT_HELPER = new VisualizerCompatKitKat(); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { COMPAT_HELPER = new VisualizerCompatJB(); } else { COMPAT_HELPER = new VisualizerCompatGB(); } MIN_CAPTURE_RATE = 100; MAX_CAPTURE_RATE = Visualizer.getMaxCaptureRate(); } public static int[] sGetCaptureSizeRange() { return Visualizer.getCaptureSizeRange(); } public static int sGetMaxCaptureRate() { return Visualizer.getMaxCaptureRate(); } public StandardVisualizer(Context context, int audioSession) { mVisualizer = new Visualizer(audioSession); getCaptureSizeAndUpdateField(); // NOTE: // Default implementation always tells the sampling rate is 44100000 mHz // but actually that is not correct! mCorrectSamplingRate = AudioSystemUtils.getProperties(context).outputSampleRate * 1000; } @Override public synchronized boolean getEnabled() { throwIllegalStateExceptionIfReleased(); return mVisualizer.getEnabled(); } @Override public synchronized int setEnabled(boolean enabled) throws IllegalStateException { int result = IVisualizer.ERROR; result = onSetEnabled(enabled); if (mIsDebuggable) { boolean actual = mVisualizer.getEnabled(); Log.i(TAG, "setEnabled(" + enabled + ") - actual = " + actual + " (tid= " + Thread.currentThread().getId() + ")"); if (enabled != actual) { Log.w(TAG, "setEnabled() called in illegal state"); } if (result != IVisualizer.SUCCESS) { Log.w(TAG, "setEnabled() failed - " + result); } } return result; } @Override public synchronized int getSamplingRate() throws IllegalStateException { return mCorrectSamplingRate; } @Override public synchronized int getWaveForm(byte[] waveform) throws IllegalStateException { if (waveform == null) return Visualizer.ERROR_BAD_VALUE; // workaround: Fix mState variable mVisualizer.setEnabled(mVisualizer.getEnabled()); getCaptureSizeAndUpdateField(); if (waveform.length != mCaptureSize) { // NOTE: To avoid app crash in native layer... return IVisualizer.ERROR_BAD_VALUE; } return mVisualizer.getWaveForm(waveform); } @Override public synchronized int getFft(byte[] fft) throws IllegalStateException { if (fft == null) return Visualizer.ERROR_BAD_VALUE; // workaround: Fix mState variable mVisualizer.setEnabled(mVisualizer.getEnabled()); getCaptureSizeAndUpdateField(); if (fft.length != mCaptureSize) { // NOTE: To avoid app crash in native layer... return IVisualizer.ERROR_BAD_VALUE; } return mVisualizer.getFft(fft); } @Override public synchronized int getCaptureSize() throws IllegalStateException { return getCaptureSizeAndUpdateField(); } @Override public synchronized int setCaptureSize(int size) throws IllegalStateException { int result = mVisualizer.setCaptureSize(size); if (result == IVisualizer.SUCCESS) { mCaptureSize = size; } return result; } @Override public synchronized int setDataCaptureListener( IVisualizer.OnDataCaptureListener listener, int rate, boolean waveform, boolean fft) { // check and correct arguments if ((listener != null) && (waveform || fft)) { if (!(rate >= MIN_CAPTURE_RATE && rate <= MAX_CAPTURE_RATE)) return IVisualizer.ERROR_BAD_VALUE; } else { rate = 0; waveform = false; fft = false; } OnDataCaptureListenerWrapper wrapper; int result = IVisualizer.ERROR; wrapper = mCaptureListenerWrapper; if (wrapper == null || (wrapper != null && wrapper.getInternalListener() != listener)) { if (listener != null) { wrapper = new OnDataCaptureListenerWrapper(this, listener, mCorrectSamplingRate); } else { wrapper = null; } } try { result = mVisualizer.setDataCaptureListener(wrapper, rate, waveform, fft); } finally { if (result == IVisualizer.SUCCESS) { mCaptureListenerWrapper = wrapper; mCaptureRate = rate; mCaptureWaveform = waveform; mCaptureFft = fft; mNeedToSetCaptureListener = false; } } if (mIsDebuggable) { final String strListener = (listener == null) ? "(null)" : listener.getClass().getSimpleName(); Log.i(TAG, "setDataCaptureListener(" + strListener + ", " + rate + ", " + waveform + ", " + fft + ")"); } return result; } @Override public synchronized int[] getCaptureSizeRange() throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return android.media.audiofx.Visualizer.getCaptureSizeRange(); } @Override public synchronized int getMaxCaptureRate() throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return android.media.audiofx.Visualizer.getMaxCaptureRate(); } @Override public synchronized int getScalingMode() throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return COMPAT_HELPER.getScalingModeCompat(mVisualizer); } @Override public synchronized int setScalingMode(int mode) throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return COMPAT_HELPER.setScalingModeCompat(mVisualizer, mode); } @Override public synchronized int getMeasurementMode() throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return COMPAT_HELPER.getMeasurementModeCompat(mVisualizer); } @Override public synchronized int setMeasurementMode(int mode) throws IllegalStateException { throwIllegalStateExceptionIfReleased(); return COMPAT_HELPER.setMeasurementModeCompat(mVisualizer, mode); } @Override public synchronized int getMeasurementPeakRms(IVisualizer.MeasurementPeakRms measurement) { if (measurement == null) { return IVisualizer.ERROR_BAD_VALUE; } throwIllegalStateExceptionIfReleased(); return COMPAT_HELPER.getMeasurementPeakRmsCompat(mVisualizer, measurement); } private static void throwUseIVisualizerVersionMethod() { throw new IllegalStateException( "This method is not supported, please use IVisualizer version"); } /* * This wrapper class is needed to avoid deadlock bug. See * http://code.google.com/p/android/issues/detail?id=37999. */ private static class OnDataCaptureListenerWrapper implements Visualizer.OnDataCaptureListener { private WeakReference<StandardVisualizer> mHolder; private IVisualizer.OnDataCaptureListener mListener; private final int mCorrectSamplingRate; public OnDataCaptureListenerWrapper(StandardVisualizer holder, IVisualizer.OnDataCaptureListener listener, int correctSamplingRate) { if (holder == null) throw new IllegalArgumentException("holder must not be null"); if (listener == null) throw new IllegalArgumentException("listener must not be null"); mHolder = new WeakReference<StandardVisualizer>(holder); mListener = listener; mCorrectSamplingRate = correctSamplingRate; } @Override public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) { final StandardVisualizer holder = mHolder.get(); if (holder != null) { mListener.onWaveFormDataCapture(holder, waveform, mCorrectSamplingRate); } } @Override public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) { final StandardVisualizer holder = mHolder.get(); if (holder != null) { mListener.onFftDataCapture(holder, fft, mCorrectSamplingRate); } } public IVisualizer.OnDataCaptureListener getInternalListener() { return mListener; } public void release() { mListener = null; } } private int onSetEnabled(boolean enabled) { throwIllegalStateExceptionIfReleased(); final boolean currentEnabledState = mVisualizer.getEnabled(); int result = IVisualizer.ERROR; if (enabled && !currentEnabledState) { if (mNeedToSetCaptureListener) { int result2 = mVisualizer.setDataCaptureListener( mCaptureListenerWrapper, mCaptureRate, mCaptureWaveform, mCaptureFft); if (result2 != IVisualizer.SUCCESS) { throw new IllegalStateException(); } } } try { result = mVisualizer.setEnabled(enabled); } catch (IllegalStateException ex) { throw ex; } finally { if (result == IVisualizer.SUCCESS) { if (!enabled) { // need to unregister capture listener to avoid // deadlock! mNeedToSetCaptureListener = true; mVisualizer.setDataCaptureListener(null, 0, false, false); } } } final boolean actual = mVisualizer.getEnabled(); if ((result == IVisualizer.SUCCESS) && (enabled != actual)) { result = IVisualizer.ERROR; Log.w(TAG, "onSetEnabled() - expected : " + enabled + " / actual: " + actual); } return result; } @Override public synchronized void release() { if (mIsDebuggable) { Log.i(TAG, "release()"); } try { // Unregister the listener to avoid NullPointerException if (mVisualizer != null) { mVisualizer.setDataCaptureListener(null, 0, false, false); } } catch (Exception e) { } if (mCaptureListenerWrapper != null) { mCaptureListenerWrapper.release(); mCaptureListenerWrapper = null; } } private int getCaptureSizeAndUpdateField() { int size = mVisualizer.getCaptureSize(); if (size > 0 && isPowOfTwo(size)) { mCaptureSize = size; } return size; } private void throwIllegalStateExceptionIfReleased() { if (mVisualizer == null) { throw new IllegalStateException(); } } private static boolean isPowOfTwo(int x) { return (x != 0) && ((x & (x - 1)) == 0); } }