Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f7d6359 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Merge "EffectsTest: Add multi-threaded client for Visualizer" am:...

Merge "Merge "EffectsTest: Add multi-threaded client for Visualizer" am: 6fed7a9d am: 2d281436" into rvc-dev-plus-aosp am: 799f445c am: 30f14b8a am: dfa5b14b

Change-Id: I29d0f1ca2ed28e511b4c5aba933f61a66d90a334
parents 7ff16c42 dfa5b14b
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
@@ -50,6 +50,37 @@

    </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/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"
+2 −0
Original line number Diff line number Diff line
@@ -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>
+25 −0
Original line number Diff line number Diff line
/*
 * 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);
}
+113 −0
Original line number Diff line number Diff line
/*
 * 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));
    }
}
+170 −0
Original line number Diff line number Diff line
/*
 * 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)));
            }
        }
    }
}
Loading