diff options
Diffstat (limited to 'media/tests')
6 files changed, 385 insertions, 123 deletions
diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml index 50ac7bbd8cd0..18d7a3648fbf 100644 --- a/media/tests/EffectsTest/res/layout/visualizertest.xml +++ b/media/tests/EffectsTest/res/layout/visualizertest.xml @@ -56,6 +56,37 @@ android:layout_height="wrap_content" android:scaleType="fitXY"/> + <LinearLayout android:id="@+id/visuMultithreadedLayout" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="10dip" + android:layout_marginTop="10dip" + android:layout_marginRight="10dip" + android:layout_marginBottom="10dip" > + + <TextView android:id="@+id/visuMultithreaded" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layout_weight="1.0" + android:layout_gravity="center_vertical|left" + android:text="@string/effect_multithreaded" + style="@android:style/TextAppearance.Medium" /> + + <ToggleButton android:id="@+id/visuMultithreadedOnOff" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_gravity="center_vertical|right" + android:layout_weight="0.0" /> + + </LinearLayout> + + <ImageView + android:src="@android:drawable/divider_horizontal_dark" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY"/> + <LinearLayout android:id="@+id/visuControlLayout" android:orientation="horizontal" android:layout_width="fill_parent" diff --git a/media/tests/EffectsTest/res/values/strings.xml b/media/tests/EffectsTest/res/values/strings.xml index 2a8518417565..7c12da1274e3 100644 --- a/media/tests/EffectsTest/res/values/strings.xml +++ b/media/tests/EffectsTest/res/values/strings.xml @@ -35,4 +35,6 @@ <string name="effect_attach_off">Attach</string> <string name="effect_attach_on">Detach</string> <string name="send_level_name">Send Level</string> + <!-- Toggles use of a multi-threaded client for an effect [CHAR LIMIT=24] --> + <string name="effect_multithreaded">Multithreaded Use</string> </resources> 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 000000000000..817bd3d7bf5e --- /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 000000000000..89cfbebe17ce --- /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 000000000000..e64f4e5785c8 --- /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 7db1d8d8625e..2e141c5ef7c8 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<Integer, Visualizer> sInstances = new HashMap<Integer, Visualizer>(10); - private VisualizerTestHandler mVisualizerTestHandler = null; + private static HashMap<Integer, VisualizerInstance> sInstances = + new HashMap<Integer, VisualizerInstance>(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); - } - 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]); + 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; } - } - } - 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; } } } |