diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml
index 50ac7bbd8cd05..18d7a3648fbfe 100644
--- a/media/tests/EffectsTest/res/layout/visualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/visualizertest.xml
@@ -50,6 +50,37 @@
+
+
+
+
+
+
+
+
+
+
Attach
Detach
Send Level
+
+ Multithreaded Use
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstance.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstance.java
new file mode 100644
index 0000000000000..817bd3d7bf5ea
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstance.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 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 com.android.effectstest;
+
+interface VisualizerInstance {
+ void enableDataCaptureListener(boolean enable);
+ boolean getEnabled();
+ void release();
+ void setEnabled(boolean enabled);
+ void startStopCapture(boolean start);
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceMT.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceMT.java
new file mode 100644
index 0000000000000..89cfbebe17cef
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceMT.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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 com.android.effectstest;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+class VisualizerInstanceMT implements VisualizerInstance {
+
+ private static final String TAG = "VisualizerInstanceMT";
+
+ private final Object mLock = new Object();
+ private final int mThreadCount;
+ @GuardedBy("mLock")
+ private Handler mVisualizerHandler;
+ @GuardedBy("mLock")
+ private VisualizerInstanceSync mVisualizer;
+
+ VisualizerInstanceMT(int session, Handler uiHandler, int extraThreadCount) {
+ Log.d(TAG, "Multi-threaded constructor");
+ mThreadCount = 1 + extraThreadCount;
+ Thread t = new Thread() {
+ @Override public void run() {
+ Looper.prepare();
+ VisualizerInstanceSync v = new VisualizerInstanceSync(session, uiHandler);
+ synchronized (mLock) {
+ mVisualizerHandler = new Handler();
+ mVisualizer = v;
+ }
+ Looper.loop();
+ }
+ };
+ t.start();
+ }
+
+ private VisualizerInstance getVisualizer() {
+ synchronized (mLock) {
+ return mVisualizer != null ? new VisualizerInstanceSync(mVisualizer) : null;
+ }
+ }
+
+ private interface VisualizerOperation {
+ void run(VisualizerInstance v);
+ }
+
+ private void runOperationMt(VisualizerOperation op) {
+ final VisualizerInstance v = getVisualizer();
+ if (v == null) return;
+ for (int i = 0; i < mThreadCount; ++i) {
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ op.run(v);
+ }
+ };
+ t.start();
+ }
+ }
+
+ @Override
+ public void enableDataCaptureListener(boolean enable) {
+ runOperationMt(v -> v.enableDataCaptureListener(enable));
+ }
+
+ @Override
+ public boolean getEnabled() {
+ final VisualizerInstance v = getVisualizer();
+ return v != null ? v.getEnabled() : false;
+ }
+
+ @Override
+ public void release() {
+ runOperationMt(v -> v.release());
+ synchronized (mLock) {
+ if (mVisualizerHandler == null) return;
+ mVisualizerHandler.post(() -> {
+ synchronized (mLock) {
+ mVisualizerHandler = null;
+ mVisualizer = null;
+ Looper.myLooper().quitSafely();
+ }
+ Log.d(TAG, "Exiting looper");
+ });
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ runOperationMt(v -> v.setEnabled(enabled));
+ }
+
+ @Override
+ public void startStopCapture(boolean start) {
+ runOperationMt(v -> v.startStopCapture(start));
+ }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceSync.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceSync.java
new file mode 100644
index 0000000000000..e64f4e5785c83
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerInstanceSync.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2020 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 com.android.effectstest;
+
+import android.media.audiofx.Visualizer;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+// This class only has `final' members, thus any thread-safety concerns
+// can only come from the Visualizer effect class.
+class VisualizerInstanceSync implements VisualizerInstance {
+
+ private static final String TAG = "VisualizerInstance";
+
+ private final Handler mUiHandler;
+ private final Visualizer mVisualizer;
+ private final VisualizerTestHandler mVisualizerTestHandler;
+ private final VisualizerListener mVisualizerListener;
+
+ VisualizerInstanceSync(int session, Handler uiHandler) {
+ mUiHandler = uiHandler;
+ try {
+ mVisualizer = new Visualizer(session);
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Visualizer library not loaded");
+ throw new RuntimeException("Cannot initialize effect");
+ } catch (RuntimeException e) {
+ throw e;
+ }
+ mVisualizerTestHandler = new VisualizerTestHandler();
+ mVisualizerListener = new VisualizerListener();
+ }
+
+ // Not a "deep" copy, only copies the references.
+ VisualizerInstanceSync(VisualizerInstanceSync other) {
+ mUiHandler = other.mUiHandler;
+ mVisualizer = other.mVisualizer;
+ mVisualizerTestHandler = other.mVisualizerTestHandler;
+ mVisualizerListener = other.mVisualizerListener;
+ }
+
+ @Override
+ public void enableDataCaptureListener(boolean enable) {
+ mVisualizer.setDataCaptureListener(enable ? mVisualizerListener : null,
+ 10000, enable, enable);
+ }
+
+ @Override
+ public boolean getEnabled() {
+ return mVisualizer.getEnabled();
+ }
+
+ @Override
+ public void release() {
+ mVisualizer.release();
+ Log.d(TAG, "Visualizer released");
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mVisualizer.setEnabled(enabled);
+ }
+
+ @Override
+ public void startStopCapture(boolean start) {
+ mVisualizerTestHandler.sendMessage(mVisualizerTestHandler.obtainMessage(
+ start ? MSG_START_CAPTURE : MSG_STOP_CAPTURE));
+ }
+
+ private static final int MSG_START_CAPTURE = 0;
+ private static final int MSG_STOP_CAPTURE = 1;
+ private static final int MSG_NEW_CAPTURE = 2;
+ private static final int CAPTURE_PERIOD_MS = 100;
+
+ private static int[] dataToMinMaxCenter(byte[] data, int len) {
+ int[] minMaxCenter = new int[3];
+ minMaxCenter[0] = data[0];
+ minMaxCenter[1] = data[len - 1];
+ minMaxCenter[2] = data[len / 2];
+ return minMaxCenter;
+ }
+
+ private class VisualizerTestHandler extends Handler {
+ private final int mCaptureSize;
+ private boolean mActive = false;
+
+ VisualizerTestHandler() {
+ mCaptureSize = mVisualizer.getCaptureSize();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START_CAPTURE:
+ if (!mActive) {
+ Log.d(TAG, "Start capture");
+ mActive = true;
+ sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE), CAPTURE_PERIOD_MS);
+ }
+ break;
+ case MSG_STOP_CAPTURE:
+ if (mActive) {
+ Log.d(TAG, "Stop capture");
+ mActive = false;
+ }
+ break;
+ case MSG_NEW_CAPTURE:
+ if (mActive) {
+ if (mCaptureSize > 0) {
+ byte[] data = new byte[mCaptureSize];
+ if (mVisualizer.getWaveForm(data) == Visualizer.SUCCESS) {
+ int len = data.length < mCaptureSize ? data.length : mCaptureSize;
+ mUiHandler.sendMessage(
+ mUiHandler.obtainMessage(
+ VisualizerTest.MSG_DISPLAY_WAVEFORM_VAL,
+ dataToMinMaxCenter(data, len)));
+ }
+ if (mVisualizer.getFft(data) == Visualizer.SUCCESS) {
+ int len = data.length < mCaptureSize ? data.length : mCaptureSize;
+ mUiHandler.sendMessage(
+ mUiHandler.obtainMessage(VisualizerTest.MSG_DISPLAY_FFT_VAL,
+ dataToMinMaxCenter(data, len)));
+ }
+ }
+ sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE), CAPTURE_PERIOD_MS);
+ }
+ break;
+ }
+ }
+ }
+
+ private class VisualizerListener implements Visualizer.OnDataCaptureListener {
+ @Override
+ public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform,
+ int samplingRate) {
+ if (visualizer == mVisualizer && waveform.length > 0) {
+ Log.d(TAG, "onWaveFormDataCapture(): " + waveform[0]
+ + " smp rate: " + samplingRate / 1000);
+ mUiHandler.sendMessage(
+ mUiHandler.obtainMessage(VisualizerTest.MSG_DISPLAY_WAVEFORM_VAL,
+ dataToMinMaxCenter(waveform, waveform.length)));
+ }
+ }
+
+ @Override
+ public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
+ if (visualizer == mVisualizer && fft.length > 0) {
+ Log.d(TAG, "onFftDataCapture(): " + fft[0]);
+ mUiHandler.sendMessage(
+ mUiHandler.obtainMessage(VisualizerTest.MSG_DISPLAY_FFT_VAL,
+ dataToMinMaxCenter(fft, fft.length)));
+ }
+ }
+ }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
index 7db1d8d8625e7..2e141c5ef7c8a 100644
--- a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
@@ -17,51 +17,42 @@
package com.android.effectstest;
import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
-import android.view.Menu;
import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.ToggleButton;
-import android.widget.SeekBar;
-import java.nio.ByteOrder;
-import java.nio.ByteBuffer;
import java.util.HashMap;
-import java.util.Map;
public class VisualizerTest extends Activity implements OnCheckedChangeListener {
private final static String TAG = "Visualizer Test";
- private Visualizer mVisualizer;
+ private VisualizerInstance mVisualizer;
+ ToggleButton mMultithreadedButton;
ToggleButton mOnOffButton;
ToggleButton mReleaseButton;
+ boolean mUseMTInstance;
boolean mEnabled;
EditText mSessionText;
static int sSession = 0;
- int mCaptureSize;
ToggleButton mCallbackButton;
boolean mCallbackOn;
- VisualizerListener mVisualizerListener;
- private static HashMap sInstances = new HashMap(10);
- private VisualizerTestHandler mVisualizerTestHandler = null;
+ private static HashMap sInstances =
+ new HashMap(10);
+ private Handler mUiHandler;
public VisualizerTest() {
Log.d(TAG, "contructor");
+ mUiHandler = new UiHandler(Looper.getMainLooper());
}
@Override
@@ -76,109 +67,45 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
mSessionText.setOnKeyListener(mSessionKeyListener);
mSessionText.setText(Integer.toString(sSession));
- mReleaseButton = (ToggleButton)findViewById(R.id.visuReleaseButton);
- mOnOffButton = (ToggleButton)findViewById(R.id.visualizerOnOff);
- mCallbackButton = (ToggleButton)findViewById(R.id.visuCallbackOnOff);
+ mMultithreadedButton = (ToggleButton) findViewById(R.id.visuMultithreadedOnOff);
+ mReleaseButton = (ToggleButton) findViewById(R.id.visuReleaseButton);
+ mOnOffButton = (ToggleButton) findViewById(R.id.visualizerOnOff);
+ mCallbackButton = (ToggleButton) findViewById(R.id.visuCallbackOnOff);
mCallbackOn = false;
mCallbackButton.setChecked(mCallbackOn);
- mVisualizerTestHandler = new VisualizerTestHandler();
- mVisualizerListener = new VisualizerListener();
-
- getEffect(sSession);
-
- if (mVisualizer != null) {
+ mMultithreadedButton.setOnCheckedChangeListener(this);
+ if (getEffect(sSession) != null) {
mReleaseButton.setOnCheckedChangeListener(this);
mOnOffButton.setOnCheckedChangeListener(this);
mCallbackButton.setOnCheckedChangeListener(this);
}
}
- private static final int MSG_START_CAPTURE = 0;
- private static final int MSG_STOP_CAPTURE = 1;
- private static final int MSG_NEW_CAPTURE = 2;
- private static final int CAPTURE_PERIOD_MS = 100;
+ public static final int MSG_DISPLAY_WAVEFORM_VAL = 0;
+ public static final int MSG_DISPLAY_FFT_VAL = 1;
+
+ private class UiHandler extends Handler {
+ UiHandler(Looper looper) {
+ super(looper);
+ }
- private class VisualizerTestHandler extends Handler {
- boolean mActive = false;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_START_CAPTURE:
- if (!mActive) {
- Log.d(TAG, "Start capture");
- mActive = true;
- sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS);
+ case MSG_DISPLAY_WAVEFORM_VAL:
+ case MSG_DISPLAY_FFT_VAL:
+ int[] minMaxCenter = (int[]) msg.obj;
+ boolean waveform = msg.what == MSG_DISPLAY_WAVEFORM_VAL;
+ displayVal(waveform ? R.id.waveformMin : R.id.fftMin, minMaxCenter[0]);
+ displayVal(waveform ? R.id.waveformMax : R.id.fftMax, minMaxCenter[1]);
+ displayVal(waveform ? R.id.waveformCenter : R.id.fftCenter, minMaxCenter[2]);
+ break;
}
- break;
- case MSG_STOP_CAPTURE:
- if (mActive) {
- Log.d(TAG, "Stop capture");
- mActive = false;
- }
- break;
- case MSG_NEW_CAPTURE:
- if (mActive && mVisualizer != null) {
- if (mCaptureSize > 0) {
- byte[] data = new byte[mCaptureSize];
- if (mVisualizer.getWaveForm(data) == Visualizer.SUCCESS) {
- int len = data.length < mCaptureSize ? data.length : mCaptureSize;
- displayVal(R.id.waveformMin, data[0]);
- displayVal(R.id.waveformMax, data[len-1]);
- displayVal(R.id.waveformCenter, data[len/2]);
- };
- if (mVisualizer.getFft(data) == Visualizer.SUCCESS) {
- int len = data.length < mCaptureSize ? data.length : mCaptureSize;
- displayVal(R.id.fftMin, data[0]);
- displayVal(R.id.fftMax, data[len-1]);
- displayVal(R.id.fftCenter, data[len/2]);
- };
- }
- sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS);
- }
- break;
- }
}
}
- private class VisualizerListener implements Visualizer.OnDataCaptureListener {
-
- public VisualizerListener() {
- }
- public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
- if (visualizer == mVisualizer) {
- if (waveform.length > 0) {
- Log.d(TAG, "onWaveFormDataCapture(): "+waveform[0]+" smp rate: "+samplingRate/1000);
- displayVal(R.id.waveformMin, waveform[0]);
- displayVal(R.id.waveformMax, waveform[waveform.length - 1]);
- displayVal(R.id.waveformCenter, waveform[waveform.length/2]);
- }
- }
- }
- public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
- if (visualizer == mVisualizer) {
- if (fft.length > 0) {
- Log.d(TAG, "onFftDataCapture(): "+fft[0]);
- displayVal(R.id.fftMin, fft[0]);
- displayVal(R.id.fftMax, fft[fft.length - 1]);
- displayVal(R.id.fftCenter, fft[fft.length/2]);
- }
- }
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- }
-
- private View.OnKeyListener mSessionKeyListener
- = new View.OnKeyListener() {
+ private View.OnKeyListener mSessionKeyListener = new View.OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (keyCode) {
@@ -199,29 +126,26 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
};
// OnCheckedChangeListener
+ @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (buttonView.getId() == R.id.visuMultithreadedOnOff) {
+ mUseMTInstance = isChecked;
+ Log.d(TAG, "Multi-threaded client: " + (isChecked ? "enabled" : "disabled"));
+ }
if (buttonView.getId() == R.id.visualizerOnOff) {
if (mVisualizer != null) {
mEnabled = isChecked;
mCallbackButton.setEnabled(!mEnabled);
if (mCallbackOn && mEnabled) {
- mVisualizer.setDataCaptureListener(mVisualizerListener,
- 10000,
- true,
- true);
+ mVisualizer.enableDataCaptureListener(true);
}
mVisualizer.setEnabled(mEnabled);
if (mCallbackOn) {
if (!mEnabled) {
- mVisualizer.setDataCaptureListener(null,
- 10000,
- false,
- false);
+ mVisualizer.enableDataCaptureListener(false);
}
} else {
- int msg = isChecked ? MSG_START_CAPTURE : MSG_STOP_CAPTURE;
- mVisualizerTestHandler.sendMessage(
- mVisualizerTestHandler.obtainMessage(msg, 0, 0, null));
+ mVisualizer.startStopCapture(isChecked);
}
}
}
@@ -248,16 +172,15 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
}
- private void getEffect(int session) {
+ private VisualizerInstance getEffect(int session) {
synchronized (sInstances) {
if (sInstances.containsKey(session)) {
mVisualizer = sInstances.get(session);
} else {
- try{
- mVisualizer = new Visualizer(session);
- } catch (UnsupportedOperationException e) {
- Log.e(TAG,"Visualizer library not loaded");
- throw (new RuntimeException("Cannot initialize effect"));
+ try {
+ mVisualizer = mUseMTInstance
+ ? new VisualizerInstanceMT(session, mUiHandler, 0 /*extraThreadCount*/)
+ : new VisualizerInstanceSync(session, mUiHandler);
} catch (RuntimeException e) {
throw e;
}
@@ -267,8 +190,6 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
mReleaseButton.setEnabled(false);
mOnOffButton.setEnabled(false);
if (mVisualizer != null) {
- mCaptureSize = mVisualizer.getCaptureSize();
-
mReleaseButton.setChecked(true);
mReleaseButton.setEnabled(true);
@@ -278,6 +199,7 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
mCallbackButton.setEnabled(!mEnabled);
}
+ return mVisualizer;
}
private void putEffect(int session) {
@@ -286,9 +208,8 @@ public class VisualizerTest extends Activity implements OnCheckedChangeListener
synchronized (sInstances) {
if (mVisualizer != null) {
mVisualizer.release();
- Log.d(TAG,"Visualizer released");
- mVisualizer = null;
sInstances.remove(session);
+ mVisualizer = null;
}
}
}