Skip to content

Commit

Permalink
OboeTester: add a Rapid Cycle test (#2042)
Browse files Browse the repository at this point in the history
This was an attempt to reproduce b/348615156
b/348615156 | P2 | [Oboe/OpenSLES] Deadlock is caused when close function is called immediately after stream playback.
  • Loading branch information
philburk authored Jun 27, 2024
1 parent a502724 commit ca15cd8
Show file tree
Hide file tree
Showing 11 changed files with 438 additions and 6 deletions.
2 changes: 1 addition & 1 deletion apps/OboeTester/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")

link_directories(${CMAKE_CURRENT_LIST_DIR}/..)

# Increment this number when adding files to OboeTester => 104
# Increment this number when adding files to OboeTester => 105
# The change in this file will help Android Studio resync
# and generate new build files that reference the new code.
file(GLOB_RECURSE app_native_sources src/main/cpp/*)
Expand Down
6 changes: 6 additions & 0 deletions apps/OboeTester/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
android:exported="true" />

<activity
android:name=".TestRapidCycleActivity"
android:label="@string/title_rapid_cycle"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
android:exported="true" />

<service
android:name=".MidiTapTester"
android:permission="android.permission.BIND_MIDI_DEVICE_SERVICE"
Expand Down
92 changes: 92 additions & 0 deletions apps/OboeTester/app/src/main/cpp/TestRapidCycle.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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 "TestRapidCycle.h"

using namespace oboe;

// open start start an Oboe stream
int32_t TestRapidCycle::start(bool useOpenSL) {
mThreadEnabled = true;
mCycleCount = 0;
mCycleThread = std::thread([this, useOpenSL]() {
cycleRapidly(useOpenSL);
});
return 0;
}
int32_t TestRapidCycle::stop() {
mThreadEnabled = false;
mCycleThread.join();
return 0;
}

void TestRapidCycle::cycleRapidly(bool useOpenSL) {
while(mThreadEnabled && (oneCycle(useOpenSL) == 0));
}

int32_t TestRapidCycle::oneCycle(bool useOpenSL) {
mCycleCount++;
mDataCallback = std::make_shared<MyDataCallback>();

AudioStreamBuilder builder;
oboe::Result result = builder.setFormat(oboe::AudioFormat::Float)
->setAudioApi(useOpenSL ? oboe::AudioApi::OpenSLES : oboe::AudioApi::AAudio)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setChannelCount(kChannelCount)
->setDataCallback(mDataCallback)
->setUsage(oboe::Usage::Notification)
->openStream(mStream);
if (result != oboe::Result::OK) {
return (int32_t) result;
}

mStream->setDelayBeforeCloseMillis(0);

result = mStream->requestStart();
if (result != oboe::Result::OK) {
mStream->close();
return (int32_t) result;
}
// Sleep for some random time.
int32_t durationMicros = (int32_t)(drand48() * kMaxSleepMicros);
LOGD("TestRapidCycle::oneCycle() - Sleep for %d micros", durationMicros);
usleep(durationMicros);
LOGD("TestRapidCycle::oneCycle() - Woke up, stop stream");
oboe::Result result1 = mStream->requestStop();
oboe::Result result2 = mStream->close();
return (int32_t)((result1 != oboe::Result::OK) ? result1 : result2);
}

// Callback that sleeps then touches the audio buffer.
DataCallbackResult TestRapidCycle::MyDataCallback::onAudioReady(
AudioStream *audioStream,
void *audioData,
int32_t numFrames) {
float *floatData = (float *) audioData;
const int numSamples = numFrames * kChannelCount;

// Fill buffer with white noise.
for (int i = 0; i < numSamples; i++) {
floatData[i] = ((float)drand48() - 0.5f) * 2 * 0.1f;
}

return oboe::DataCallbackResult::Continue;
}
65 changes: 65 additions & 0 deletions apps/OboeTester/app/src/main/cpp/TestRapidCycle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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_RAPID_CYCLE_H
#define OBOETESTER_TEST_RAPID_CYCLE_H

#include "oboe/Oboe.h"
#include <thread>


/**
* Try to cause a crash by changing routing during a data callback.
* We use Use::VoiceCommunication for the stream and
* setSpeakerPhoneOn(b) to force a routing change.
* This works best when connected to a BT headset.
*/
class TestRapidCycle {
public:

int32_t start(bool useOpenSL);
int32_t stop();

int32_t getCycleCount() {
return mCycleCount.load();
}

private:

void cycleRapidly(bool useOpenSL);
int32_t oneCycle(bool useOpenSL);

class MyDataCallback : public oboe::AudioStreamDataCallback { public:

MyDataCallback() {}

oboe::DataCallbackResult onAudioReady(
oboe::AudioStream *audioStream,
void *audioData,
int32_t numFrames) override;
};

std::shared_ptr<oboe::AudioStream> mStream;
std::shared_ptr<MyDataCallback> mDataCallback;
std::atomic<int32_t> mCycleCount{0};
std::atomic<bool> mThreadEnabled{false};
std::thread mCycleThread;

static constexpr int kChannelCount = 1;
static constexpr int kMaxSleepMicros = 25000;
};

#endif //OBOETESTER_TEST_RAPID_CYCLE_H
19 changes: 19 additions & 0 deletions apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "TestColdStartLatency.h"
#include "TestErrorCallback.h"
#include "TestRoutingCrash.h"
#include "TestRapidCycle.h"

static NativeAudioContext engine;

Expand Down Expand Up @@ -978,4 +979,22 @@ Java_com_mobileer_oboetester_TestColdStartLatencyActivity_getAudioDeviceId(
return sColdStartLatency.getDeviceId();
}

static TestRapidCycle sRapidCycle;

JNIEXPORT jint JNICALL
Java_com_mobileer_oboetester_TestRapidCycleActivity_startRapidCycleTest(JNIEnv *env, jobject thiz,
jboolean use_open_sl) {
return sRapidCycle.start(use_open_sl);
}

JNIEXPORT jint JNICALL
Java_com_mobileer_oboetester_TestRapidCycleActivity_stopRapidCycleTest(JNIEnv *env, jobject thiz) {
return sRapidCycle.stop();
}

JNIEXPORT jint JNICALL
Java_com_mobileer_oboetester_TestRapidCycleActivity_getCycleCount(JNIEnv *env, jobject thiz) {
return sRapidCycle.getCycleCount();
}

} // extern "C"
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ public void onLaunchDynamicWorkloadTest(View view) {
public void onLaunchColdStartLatencyTest(View view) {
launchTestActivity(TestColdStartLatencyActivity.class);
}

public void onLaunchRapidCycleTest(View view) {
launchTestActivity(TestRapidCycleActivity.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* 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.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.RadioButton;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

/**
* Try to hang streams by rapidly opening and closing.
* See b/348615156
*/
public class TestRapidCycleActivity extends AppCompatActivity {

private TextView mStatusView;
private MyStreamSniffer mStreamSniffer;
private AudioManager mAudioManager;
private RadioButton mApiOpenSLButton;
private RadioButton mApiAAudioButton;
private Button mStartButton;
private Button mStopButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rapid_cycle);
mStatusView = (TextView) findViewById(R.id.text_callback_status);
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

mStartButton = (Button) findViewById(R.id.button_start_test);
mStopButton = (Button) findViewById(R.id.button_stop_test);
mApiOpenSLButton = (RadioButton) findViewById(R.id.audio_api_opensl);
mApiOpenSLButton.setChecked(true);
mApiAAudioButton = (RadioButton) findViewById(R.id.audio_api_aaudio);
setButtonsEnabled(false);
}

public void onStartCycleTest(View view) { startCycleTest(); }
public void onStopCycleTest(View view) {
stopCycleTest();
}

private void setButtonsEnabled(boolean running) {
mStartButton.setEnabled(!running);
mStopButton.setEnabled(running);
mApiOpenSLButton.setEnabled(!running);
mApiAAudioButton.setEnabled(!running);
}

// Change routing while the stream is playing.
// Keep trying until we crash.
protected class MyStreamSniffer extends Thread {
boolean enabled = true;
StringBuffer statusBuffer = new StringBuffer();

@Override
public void run() {
boolean useOpenSL = mApiOpenSLButton.isChecked();
startRapidCycleTest(useOpenSL);
try {
while (enabled) {
statusBuffer = new StringBuffer();
sleep(100);
log("#" + getCycleCount() + " open/close cycles\n");
}
} catch (InterruptedException e) {
} finally {
stopRapidCycleTest();
}
}

// Log to screen and logcat.
private void log(String text) {
Log.d(TAG, "RapidCycle: " + text);
statusBuffer.append(text);
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 startRapidCycleTest(boolean useOpenSL);
private native int stopRapidCycleTest();
private native int getCycleCount();

@Override
public void onPause() {
super.onPause();
Log.i(TAG, "onPause() called so stop the test =========================");
stopCycleTest();
}

private void startCycleTest() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setButtonsEnabled(true);
mStreamSniffer = new MyStreamSniffer();
mStreamSniffer.start();
}

private void stopCycleTest() {
if (mStreamSniffer != null) {
mStreamSniffer.finish();
mStreamSniffer = null;
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setButtonsEnabled(false);
}
}
}
10 changes: 10 additions & 0 deletions apps/OboeTester/app/src/main/res/layout/activity_extra_tests.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,15 @@
android:backgroundTint="@color/button_tint"
android:onClick="onLaunchColdStartLatencyTest"
android:text="Cold Start Latency" />

<Button
android:id="@+id/buttonRapidCycle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:layout_gravity="fill"
android:backgroundTint="@color/button_tint"
android:onClick="onLaunchRapidCycleTest"
android:text="Rapid Cycle" />
</GridLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Loading

0 comments on commit ca15cd8

Please sign in to comment.