diff options
author | Robert Wu <85952307+robertwu1@users.noreply.github.com> | 2023-08-16 08:42:10 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-16 08:42:10 -0700 |
commit | e94d769135fdddfc21d752b353f9d32f8ea79f3f (patch) | |
tree | 7cc1d68adc93c12d2e13a831c3112e90e1ad446c | |
parent | a2a5e63047ed80befabf2e8fc0b3d06e300ae4ef (diff) | |
download | oboe-e94d769135fdddfc21d752b353f9d32f8ea79f3f.tar.gz |
OboeTester: Cold Start Latency Tests (#1887)
9 files changed, 646 insertions, 0 deletions
diff --git a/apps/OboeTester/app/src/main/AndroidManifest.xml b/apps/OboeTester/app/src/main/AndroidManifest.xml index 4c769014..fdfd8d95 100644 --- a/apps/OboeTester/app/src/main/AndroidManifest.xml +++ b/apps/OboeTester/app/src/main/AndroidManifest.xml @@ -115,6 +115,11 @@ android:label="@string/title_dynamic_load" android:exported="true" /> + <activity + android:name=".TestColdStartLatencyActivity" + android:label="@string/title_cold_start_latency" + android:exported="true" /> + <service android:name=".MidiTapTester" android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE" diff --git a/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.cpp b/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.cpp new file mode 100644 index 00000000..7de1c7ab --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 2023 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. + */ + +#include <stdlib.h> +#include <aaudio/AAudioExtensions.h> + +#include "common/OboeDebug.h" +#include "common/AudioClock.h" +#include "TestColdStartLatency.h" +#include "OboeTools.h" + +using namespace oboe; + +int32_t TestColdStartLatency::open(bool useInput, bool useLowLatency, bool useMmap, bool + useExclusive) { + + mDataCallback = std::make_shared<MyDataCallback>(); + + // Enable MMAP if needed + bool wasMMapEnabled = AAudioExtensions::getInstance().isMMapEnabled(); + AAudioExtensions::getInstance().setMMapEnabled(useMmap); + + int64_t beginOpenNanos = AudioClock::getNanoseconds(); + + AudioStreamBuilder builder; + Result result = builder.setFormat(AudioFormat::Float) + ->setPerformanceMode(useLowLatency ? PerformanceMode::LowLatency : + PerformanceMode::None) + ->setDirection(useInput ? Direction::Input : Direction::Output) + ->setChannelCount(kChannelCount) + ->setDataCallback(mDataCallback) + ->setSharingMode(useExclusive ? SharingMode::Exclusive : SharingMode::Shared) + ->openStream(mStream); + + int64_t endOpenNanos = AudioClock::getNanoseconds(); + int64_t actualDurationNanos = endOpenNanos - beginOpenNanos; + mOpenTimeMicros = actualDurationNanos / NANOS_PER_MICROSECOND; + + // Revert MMAP back to its previous state + AAudioExtensions::getInstance().setMMapEnabled(wasMMapEnabled); + + mDeviceId = mStream->getDeviceId(); + + return (int32_t) result; +} + +int32_t TestColdStartLatency::start() { + mBeginStartNanos = AudioClock::getNanoseconds(); + Result result = mStream->requestStart(); + int64_t endStartNanos = AudioClock::getNanoseconds(); + int64_t actualDurationNanos = endStartNanos - mBeginStartNanos; + mStartTimeMicros = actualDurationNanos / NANOS_PER_MICROSECOND; + return (int32_t) result; +} + +int32_t TestColdStartLatency::close() { + Result result1 = mStream->requestStop(); + Result result2 = mStream->close(); + return (int32_t)((result1 != Result::OK) ? result1 : result2); +} + +int32_t TestColdStartLatency::getColdStartTimeMicros() { + int64_t position; + int64_t timestampNanos; + if (mStream->getDirection() == Direction::Output) { + auto result = mStream->getTimestamp(CLOCK_MONOTONIC); + if (!result) { + return -1; // ERROR + } + auto frameTimestamp = result.value(); + // Calculate the time that frame[0] would have been played by the speaker. + position = frameTimestamp.position; + timestampNanos = frameTimestamp.timestamp; + } else { + position = mStream->getFramesRead(); + timestampNanos = AudioClock::getNanoseconds(); + } + double sampleRate = (double) mStream->getSampleRate(); + + int64_t elapsedNanos = NANOS_PER_SECOND * (position / sampleRate); + int64_t timeOfFrameZero = timestampNanos - elapsedNanos; + int64_t coldStartLatencyNanos = timeOfFrameZero - mBeginStartNanos; + return coldStartLatencyNanos / NANOS_PER_MICROSECOND; +} + +// Callback that sleeps then touches the audio buffer. +DataCallbackResult TestColdStartLatency::MyDataCallback::onAudioReady( + AudioStream *audioStream, + void *audioData, + int32_t numFrames) { + float *floatData = (float *) audioData; + const int numSamples = numFrames * kChannelCount; + if (audioStream->getDirection() == Direction::Output) { + // Fill mono buffer with a sine wave. + for (int i = 0; i < numSamples; i++) { + *floatData++ = sinf(mPhase) * 0.2f; + if ((i % kChannelCount) == (kChannelCount - 1)) { + mPhase += kPhaseIncrement; + // Wrap the phase around in a circle. + if (mPhase >= M_PI) mPhase -= 2 * M_PI; + } + } + } + return DataCallbackResult::Continue; +} diff --git a/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.h b/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.h new file mode 100644 index 00000000..7c70687e --- /dev/null +++ b/apps/OboeTester/app/src/main/cpp/TestColdStartLatency.h @@ -0,0 +1,76 @@ +/* + * Copyright 2023 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. + */ + +#ifndef OBOETESTER_TEST_COLD_START_LATENCY_H +#define OBOETESTER_TEST_COLD_START_LATENCY_H + +#include "oboe/Oboe.h" +#include <thread> + +/** + * Test for getting the cold start latency + */ +class TestColdStartLatency { +public: + + int32_t open(bool useInput, bool useLowLatency, bool useMmap, bool useExclusive); + int32_t start(); + int32_t close(); + + int32_t getColdStartTimeMicros(); + + int32_t getOpenTimeMicros() { + return (int32_t) (mOpenTimeMicros.load()); + } + + int32_t getStartTimeMicros() { + return (int32_t) (mStartTimeMicros.load()); + } + + int32_t getDeviceId() { + return mDeviceId; + } + +protected: + std::atomic<int64_t> mBeginStartNanos{0}; + std::atomic<double> mOpenTimeMicros{0}; + std::atomic<double> mStartTimeMicros{0}; + std::atomic<double> mColdStartTimeMicros{0}; + std::atomic<int32_t> mDeviceId{0}; + +private: + + class MyDataCallback : public oboe::AudioStreamDataCallback { public: + + MyDataCallback() {} + + oboe::DataCallbackResult onAudioReady( + oboe::AudioStream *audioStream, + void *audioData, + int32_t numFrames) override; + private: + // For sine generator. + float mPhase = 0.0f; + static constexpr float kPhaseIncrement = 2.0f * (float) M_PI * 440.0f / 48000.0f; + }; + + std::shared_ptr<oboe::AudioStream> mStream; + std::shared_ptr<MyDataCallback> mDataCallback; + + static constexpr int kChannelCount = 1; +}; + +#endif //OBOETESTER_TEST_COLD_START_LATENCY_H diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index 2907f947..7b9dfcf9 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -28,6 +28,7 @@ #include "oboe/Oboe.h" #include "NativeAudioContext.h" +#include "TestColdStartLatency.h" #include "TestErrorCallback.h" #include "TestRoutingCrash.h" @@ -885,4 +886,49 @@ Java_com_mobileer_oboetester_TestRouteDuringCallbackActivity_getSleepTimeMicros( return sRoutingCrash.getSleepTimeMicros(); } +static TestColdStartLatency sColdStartLatency; + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_openStream( + JNIEnv *env, jobject instance, + jboolean useInput, jboolean useLowLatency, jboolean useMmap, jboolean useExclusive) { + return sColdStartLatency.open(useInput, useLowLatency, useMmap, useExclusive); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_startStream( + JNIEnv *env, jobject instance) { + return sColdStartLatency.start(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_closeStream( + JNIEnv *env, jobject instance) { + return sColdStartLatency.close(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_getOpenTimeMicros( + JNIEnv *env, jobject instance) { + return sColdStartLatency.getOpenTimeMicros(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_getStartTimeMicros( + JNIEnv *env, jobject instance) { + return sColdStartLatency.getStartTimeMicros(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_getColdStartTimeMicros( + JNIEnv *env, jobject instance) { + return sColdStartLatency.getColdStartTimeMicros(); +} + +JNIEXPORT jint JNICALL +Java_com_mobileer_oboetester_TestColdStartLatencyActivity_getDeviceId( + JNIEnv *env, jobject instance) { + return sColdStartLatency.getDeviceId(); +} + } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java index 6f52bfca..c744329b 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java @@ -36,4 +36,8 @@ public class ExtraTestsActivity extends BaseOboeTesterActivity { public void onLaunchDynamicWorkloadTest(View view) { launchTestActivity(DynamicWorkloadActivity.class); } + + public void onLaunchColdStartLatencyTest(View view) { + launchTestActivity(TestColdStartLatencyActivity.class); + } } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java new file mode 100644 index 00000000..b16bcd4a --- /dev/null +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java @@ -0,0 +1,210 @@ +/* + * Copyright 2023 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.mobileer.oboetester; + +import static com.mobileer.oboetester.TestAudioActivity.TAG; + +import android.app.Activity; +import android.content.Context; +import android.media.AudioManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.RadioButton; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.Random; + +/** + * Test for getting the cold start latency + */ +public class TestColdStartLatencyActivity extends Activity { + + private TextView mStatusView; + private MyStreamSniffer mStreamSniffer; + private AudioManager mAudioManager; + private RadioButton mOutputButton; + private RadioButton mInputButton; + private CheckBox mLowLatencyCheckBox; + private CheckBox mMmapCheckBox; + private CheckBox mExclusiveCheckBox; + private Spinner mStartStabilizeDelaySpinner; + private Spinner mCloseOpenDelaySpinner; + private Spinner mOpenStartDelaySpinner; + private Button mStartButton; + private Button mStopButton; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_cold_start_latency); + mStatusView = (TextView) findViewById(R.id.text_status); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + + mStartButton = (Button) findViewById(R.id.button_start_test); + mStopButton = (Button) findViewById(R.id.button_stop_test); + mOutputButton = (RadioButton) findViewById(R.id.direction_output); + mInputButton = (RadioButton) findViewById(R.id.direction_input); + mMmapCheckBox = (CheckBox) findViewById(R.id.checkbox_mmap); + mExclusiveCheckBox = (CheckBox) findViewById(R.id.checkbox_exclusive); + mLowLatencyCheckBox = (CheckBox) findViewById(R.id.checkbox_low_latency); + mStartStabilizeDelaySpinner = (Spinner) findViewById(R.id.spinner_start_stabilize_time); + mStartStabilizeDelaySpinner.setSelection(7); // Set to 1000 ms by default + mCloseOpenDelaySpinner = (Spinner) findViewById(R.id.spinner_close_open_time); + mOpenStartDelaySpinner = (Spinner) findViewById(R.id.spinner_open_start_time); + + setButtonsEnabled(false); + } + + public void onStartColdStartLatencyTest(View view) { + keepScreenOn(true); + stopSniffer(); + mStreamSniffer = new MyStreamSniffer(); + mStreamSniffer.start(); + setButtonsEnabled(true); + } + + public void onStopColdStartLatencyTest(View view) { + keepScreenOn(false); + stopSniffer(); + setButtonsEnabled(false); + } + + private void setButtonsEnabled(boolean running) { + mStartButton.setEnabled(!running); + mStopButton.setEnabled(running); + mOutputButton.setEnabled(!running); + mInputButton.setEnabled(!running); + mLowLatencyCheckBox.setEnabled(!running); + mMmapCheckBox.setEnabled(!running); + mExclusiveCheckBox.setEnabled(!running); + mStartStabilizeDelaySpinner.setEnabled(!running); + mCloseOpenDelaySpinner.setEnabled(!running); + mOpenStartDelaySpinner.setEnabled(!running); + } + + protected class MyStreamSniffer extends Thread { + boolean enabled = true; + StringBuffer statusBuffer = new StringBuffer(); + int loopCount; + + @Override + public void run() { + boolean useInput = mInputButton.isChecked(); + boolean useLowLatency = mLowLatencyCheckBox.isChecked(); + boolean useMmap = mMmapCheckBox.isChecked(); + boolean useExclusive = mExclusiveCheckBox.isChecked(); + Log.d(TAG,(useInput ? "IN" : "OUT") + + ", " + (useLowLatency ? "LOW_LATENCY" : "NOT LOW_LATENCY") + + ", " + (useMmap ? "MMAP" : "NOT MMAP") + + ", " + (useExclusive ? "EXCLUSIVE" : "SHARED")); + String closeSleepTimeText = + (String) mCloseOpenDelaySpinner.getAdapter().getItem( + mCloseOpenDelaySpinner.getSelectedItemPosition()); + int closedSleepTimeMillis = Integer.parseInt(closeSleepTimeText); + Log.d(TAG, "Sleep before open time = " + closedSleepTimeMillis + " msec"); + String openSleepTimeText = (String) mOpenStartDelaySpinner.getAdapter().getItem( + mOpenStartDelaySpinner.getSelectedItemPosition()); + int openSleepTimeMillis = Integer.parseInt(openSleepTimeText); + Log.d(TAG, "Sleep after open Time = " + openSleepTimeMillis + " msec"); + String startStabilizeTimeText = (String) mStartStabilizeDelaySpinner.getAdapter().getItem( + mStartStabilizeDelaySpinner.getSelectedItemPosition()); + int startSleepTimeMillis = Integer.parseInt(startStabilizeTimeText); + Log.d(TAG, "Sleep after start Time = " + startSleepTimeMillis + " msec"); + while (enabled) { + loopCount++; + try { + sleep(closedSleepTimeMillis); + openStream(useInput, useLowLatency, useMmap, useExclusive); + log("-------#" + loopCount + " Device Id: " + getDeviceId()); + log("open() Latency: " + getOpenTimeMicros() / 1000 + " msec"); + sleep(openSleepTimeMillis); + startStream(); + log("requestStart() Latency: " + getStartTimeMicros() / 1000 + " msec"); + sleep(startSleepTimeMillis); + log("Cold Start Latency: " + getColdStartTimeMicros() / 1000 + " msec"); + closeStream(); + } catch (InterruptedException e) { + enabled = false; + } finally { + closeStream(); + } + } + } + + // Log to screen and logcat. + private void log(String text) { + statusBuffer.append(text + "\n"); + showStatus(statusBuffer.toString()); + } + + // Stop the test thread. + void finish() { + enabled = false; + interrupt(); + try { + join(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + protected void showStatus(final String message) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mStatusView.setText(message); + } + }); + } + + private native int openStream(boolean useInput, boolean useLowLatency, boolean useMmap, + boolean useExclusive); + private native int startStream(); + private native int closeStream(); + private native int getOpenTimeMicros(); + private native int getStartTimeMicros(); + private native int getColdStartTimeMicros(); + private native int getDeviceId(); + + @Override + public void onPause() { + super.onPause(); + stopSniffer(); + } + + private void stopSniffer() { + if (mStreamSniffer != null) { + mStreamSniffer.finish(); + mStreamSniffer = null; + } + } + + protected void keepScreenOn(boolean on) { + if (on) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } +} diff --git a/apps/OboeTester/app/src/main/res/layout/activity_cold_start_latency.xml b/apps/OboeTester/app/src/main/res/layout/activity_cold_start_latency.xml new file mode 100644 index 00000000..b4798e45 --- /dev/null +++ b/apps/OboeTester/app/src/main/res/layout/activity_cold_start_latency.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".TestColdStartLatencyActivity"> + + <LinearLayout + android:id="@+id/buttonGrid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <RadioGroup + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:checkedButton="@+id/direction_output" + android:orientation="horizontal"> + + <RadioButton + android:id="@+id/direction_output" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Output" /> + + <RadioButton + android:id="@+id/direction_input" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Input" /> + </RadioGroup> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <CheckBox + android:id="@+id/checkbox_mmap" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8sp" + android:text="MMAP" + android:checked="true" /> + + <CheckBox + android:id="@+id/checkbox_low_latency" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8sp" + android:text="LOW_LATENCY" + android:checked="true" /> + + <CheckBox + android:id="@+id/checkbox_exclusive" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginRight="8sp" + android:text="EXCLUSIVE" + android:checked="true" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/start_stabilize_prompt" /> + + <Spinner + android:id="@+id/spinner_start_stabilize_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:entries="@array/sleep_times" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/close_open_prompt" /> + + <Spinner + android:id="@+id/spinner_close_open_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:entries="@array/sleep_times" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/open_start_prompt" /> + + <Spinner + android:id="@+id/spinner_open_start_time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:entries="@array/sleep_times" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/button_start_test" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:backgroundTint="@color/button_tint" + android:onClick="onStartColdStartLatencyTest" + android:text="Start Test" /> + <Button + android:id="@+id/button_stop_test" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:backgroundTint="@color/button_tint" + android:onClick="onStopColdStartLatencyTest" + android:text="Stop Test" /> + </LinearLayout> + + <ScrollView + android:id="@+id/text_log_scroller" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/text_status" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fontFamily="monospace" + android:gravity="bottom" + android:scrollbars="vertical" + android:text="@string/init_status" /> + </ScrollView> + + </LinearLayout> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml b/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml index 37f587f6..347db970 100644 --- a/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml +++ b/apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml @@ -71,5 +71,15 @@ android:backgroundTint="@color/button_tint" android:onClick="onLaunchDynamicWorkloadTest" android:text="CPU Load" /> + + <Button + android:id="@+id/buttonColdStartLatency" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_columnWeight="1" + android:layout_gravity="fill" + android:backgroundTint="@color/button_tint" + android:onClick="onLaunchColdStartLatencyTest" + android:text="Cold Start Latency" /> </GridLayout> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/apps/OboeTester/app/src/main/res/values/strings.xml b/apps/OboeTester/app/src/main/res/values/strings.xml index f1ef155a..ad708b77 100644 --- a/apps/OboeTester/app/src/main/res/values/strings.xml +++ b/apps/OboeTester/app/src/main/res/values/strings.xml @@ -227,6 +227,7 @@ <string name="title_error_callback">Error Callback Test</string> <string name="title_route_during_callback">Route Callback Test</string> <string name="title_dynamic_load">Dynamic CPU Load</string> + <string name="title_cold_start_latency">Cold Start Latency</string> <string name="need_record_audio_permission">"This app needs RECORD_AUDIO permission"</string> <string name="share">Share</string> @@ -270,4 +271,23 @@ <string name="channel_mask_prompt">ChannelMask:</string> + <string name="close_open_prompt">, Sleep before open (ms)</string> + <string name="open_start_prompt">, Sleep between open and start (ms)</string> + <string name="start_stabilize_prompt">, Sleep for start to stabilize (ms)</string> + <string-array name="sleep_times"> + <item>0</item> + <item>4</item> + <item>10</item> + <item>20</item> + <item>30</item> + <item>100</item> + <item>300</item> + <item>1000</item> + <item>2000</item> + <item>5000</item> + <item>10000</item> + <item>20000</item> + <item>50000</item> + </string-array> + </resources> |