Skip to content

Commit

Permalink
Oboe: add reportWorkload() (#2116)
Browse files Browse the repository at this point in the history
Give ADPF advance warning when the applicaiton workload changes.
This can reduce glitching when the workload increases suddenly.
It works by estimating what the callback duration will be
and then reporting that to ADPF as if it had already happened.

Add "Wkload" checkbox to CPU Load test

Use a calibration factor that is only updated when the workload is >0.
Improve docs and comments.
  • Loading branch information
philburk authored Dec 10, 2024
1 parent de84a4a commit aa616aa
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 10 deletions.
4 changes: 4 additions & 0 deletions apps/OboeTester/app/src/main/cpp/NativeAudioContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ class ActivityContext {
oboeCallbackProxy.setCpuAffinityMask(mask);
}

void setWorkloadReportingEnabled(bool enabled) {
oboeCallbackProxy.setWorkloadReportingEnabled(enabled);
}

protected:
std::shared_ptr<oboe::AudioStream> getInputStream();
std::shared_ptr<oboe::AudioStream> getOutputStream();
Expand Down
5 changes: 5 additions & 0 deletions apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ oboe::DataCallbackResult OboeStreamCallbackProxy::onAudioReady(
// Record which CPU this is running on.
orCurrentCpuMask(sched_getcpu());

// Tell ADPF in advance what our workload will be.
if (mWorkloadReportingEnabled) {
audioStream->reportWorkload(mNumWorkloadVoices);
}

// Change affinity if app requested a change.
uint32_t mask = mCpuAffinityMask;
if (mask != mPreviousMask) {
Expand Down
5 changes: 5 additions & 0 deletions apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,10 @@ class OboeStreamCallbackProxy : public OboeTesterStreamCallback {
mCpuAffinityMask = mask;
}

void setWorkloadReportingEnabled(bool enabled) {
mWorkloadReportingEnabled = enabled;
}

private:
static constexpr double kNsToMsScaler = 0.000001;
std::atomic<float> mCpuLoad{0.0f};
Expand All @@ -250,6 +254,7 @@ class OboeStreamCallbackProxy : public OboeTesterStreamCallback {
int32_t mNumWorkloadVoices = 0;
SynthWorkload mSynthWorkload;
bool mHearWorkload = false;
bool mWorkloadReportingEnabled = false;

oboe::AudioStreamDataCallback *mCallback = nullptr;
static bool mCallbackReturnStop;
Expand Down
9 changes: 8 additions & 1 deletion apps/OboeTester/app/src/main/cpp/jni-bridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,19 @@ Java_com_mobileer_oboetester_NativeEngine_getCpuCount(JNIEnv *env, jclass type)
}

JNIEXPORT void JNICALL
Java_com_mobileer_oboetester_NativeEngine_setCpuAffinityMask(JNIEnv *env,
Java_com_mobileer_oboetester_NativeEngine_setCpuAffinityMask(JNIEnv *env,
jclass type,
jint mask) {
engine.getCurrentActivity()->setCpuAffinityMask(mask);
}

JNIEXPORT void JNICALL
Java_com_mobileer_oboetester_NativeEngine_setWorkloadReportingEnabled(JNIEnv *env,
jclass type,
jboolean enabled) {
engine.getCurrentActivity()->setWorkloadReportingEnabled(enabled);
}

JNIEXPORT jint JNICALL
Java_com_mobileer_oboetester_OboeAudioStream_openNative(
JNIEnv *env, jobject synth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class DynamicWorkloadActivity extends TestOutputActivityBase {

public static final String KEY_USE_ADPF = "use_adpf";
public static final boolean VALUE_DEFAULT_USE_ADPF = false;
public static final String KEY_USE_WORKLOAD = "use_workload";
public static final boolean VALUE_DEFAULT_USE_WORKLOAD = false;
public static final String KEY_SCROLL_GRAPHICS = "scroll_graphics";
public static final boolean VALUE_DEFAULT_SCROLL_GRAPHICS = false;

Expand All @@ -68,10 +70,12 @@ public class DynamicWorkloadActivity extends TestOutputActivityBase {
private MultiLineChart.Trace mWorkloadTrace;
private CheckBox mUseAltAdpfBox;
private CheckBox mPerfHintBox;
private CheckBox mWorkloadReportBox;
private boolean mDrawChartAlways = true;
private CheckBox mDrawAlwaysBox;
private int mCpuCount;
private boolean mShouldUseADPF;
private boolean mShouldUseWorkloadReporting;

private static final int WORKLOAD_LOW = 1;
private int mWorkloadHigh; // this will get set later
Expand Down Expand Up @@ -287,6 +291,7 @@ public void onClick(View view) {
0.0f, (MARGIN_ABOVE_WORKLOAD_FOR_CPU * WORKLOAD_HIGH_MAX));

mPerfHintBox = (CheckBox) findViewById(R.id.enable_perf_hint);
mWorkloadReportBox = (CheckBox) findViewById(R.id.enable_workload_report);

// TODO remove when finished with ADPF experiments.
mUseAltAdpfBox = (CheckBox) findViewById(R.id.use_alternative_adpf);
Expand All @@ -302,8 +307,16 @@ public void onClick(View view) {
mShouldUseADPF = checkBox.isChecked();
setPerformanceHintEnabled(mShouldUseADPF);
mUseAltAdpfBox.setEnabled(!mShouldUseADPF);
mWorkloadReportBox.setEnabled(mShouldUseADPF);
});

mWorkloadReportBox.setOnClickListener(buttonView -> {
CheckBox checkBox = (CheckBox) buttonView;
mShouldUseWorkloadReporting = checkBox.isChecked();
setWorkloadReportingEnabled(mShouldUseWorkloadReporting);
});
mWorkloadReportBox.setEnabled(mShouldUseADPF);

CheckBox hearWorkloadBox = (CheckBox) findViewById(R.id.hear_workload);
hearWorkloadBox.setOnClickListener(buttonView -> {
CheckBox checkBox = (CheckBox) buttonView;
Expand Down Expand Up @@ -336,13 +349,18 @@ private void setHearWorkload(boolean checked) {
}

private void setPerformanceHintEnabled(boolean checked) {
mAudioOutTester.getCurrentAudioStream().setPerformanceHintEnabled(checked);
mAudioOutTester.getCurrentAudioStream().setPerformanceHintEnabled(checked);
}

private void setWorkloadReportingEnabled(boolean enabled) {
NativeEngine.setWorkloadReportingEnabled(enabled);
}

private void updateButtons(boolean running) {
mStartButton.setEnabled(!running);
mStopButton.setEnabled(running);
mPerfHintBox.setEnabled(running);
mWorkloadReportBox.setEnabled(running);
}

private void postResult(final String text) {
Expand Down Expand Up @@ -407,6 +425,8 @@ public void startTestUsingBundle() {
// Specific options.
mShouldUseADPF = mBundleFromIntent.getBoolean(KEY_USE_ADPF,
VALUE_DEFAULT_USE_ADPF);
mShouldUseWorkloadReporting = mBundleFromIntent.getBoolean(KEY_USE_WORKLOAD,
VALUE_DEFAULT_USE_WORKLOAD);
mDrawChartAlways =
mBundleFromIntent.getBoolean(KEY_SCROLL_GRAPHICS,
VALUE_DEFAULT_SCROLL_GRAPHICS);
Expand All @@ -416,6 +436,8 @@ public void startTestUsingBundle() {
runOnUiThread(() -> {
mPerfHintBox.setChecked(mShouldUseADPF);
setPerformanceHintEnabled(mShouldUseADPF);
mWorkloadReportBox.setChecked(mShouldUseWorkloadReporting);
setWorkloadReportingEnabled(mShouldUseWorkloadReporting);
mDrawAlwaysBox.setChecked(mDrawChartAlways);
});

Expand All @@ -442,6 +464,7 @@ void stopAutomaticTest() {
AudioStreamBase outputStream =mAudioOutTester.getCurrentAudioStream();
report += "out.xruns = " + outputStream.getXRunCount() + "\n";
report += "use.adpf = " + (mShouldUseADPF ? "yes" : "no") + "\n";
report += "use.workload = " + (mShouldUseWorkloadReporting ? "yes" : "no") + "\n";
report += "scroll.graphics = " + (mDrawChartAlways ? "yes" : "no") + "\n";
onStopTest();
maybeWriteTestResult(report);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public class NativeEngine {
static native int getCpuCount();

static native void setCpuAffinityMask(int mask);

static native void setWorkloadReportingEnabled(boolean enabled);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
android:layout_marginRight="8sp"
android:text="ADPF" />

<CheckBox
android:id="@+id/enable_workload_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8sp"
android:text="Wkload" />

<CheckBox
android:id="@+id/use_alternative_adpf"
android:layout_width="wrap_content"
Expand Down
11 changes: 9 additions & 2 deletions apps/OboeTester/docs/AutomatedTesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,14 @@ For example:

There are two required parameters for all tests:

--es test {latency, glitch, data_paths, input, output}
--es test {latency, glitch, data_paths, input, output, cpu_load}
The "latency" test will perform a Round Trip Latency test.
It will request EXCLUSIVE mode for minimal latency.
The "glitch" test will perform a single Glitch test.
The "data_paths" test will verify input and output streams in many possible configurations.
The "input" test will open and start an input stream.
The "output" test will open and start an output stream.
The "cpu_load" test will run the CPU LOAD activity.

--es file {name of resulting file}

Expand Down Expand Up @@ -125,12 +126,18 @@ These parameters were used with the "data_paths" test prior to v2.5.11.

--ez use_input_devices {"true", 1, "false", 0} // Whether to test various input devices.
--ez use_output_devices {"true", 1, "false", 0} // Whether to test various output devices.
--ez use_all_output_channel_masks {"true", 1, "false", 0} // Whether to test all output channel masks. Default is false
--ez use_all_output_channel_masks {"true", 1, "false", 0} // Whether to test all output channel masks. Default is false.

There are some optional parameters for just the "output" test:

--es signal_type {sine, sawtooth, freq_sweep, pitch_sweep, white_noise} // type of sound to play, default is sine

There are some optional parameters for just the "cpu_load" test:

--ez use_adpf {true, false} // if true, use work boost from performance hints. Default is false.
--ez use_workload {true, false} // if true and using ADPF then report workload changes. Default is false.
--ez scroll_graphics {true, false} // if true then continually update the power scope. Default is false.

For example, a complete command for a "latency" test might be:

adb shell am start -n com.mobileer.oboetester/.MainActivity \
Expand Down
40 changes: 34 additions & 6 deletions include/oboe/AudioStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
#include "oboe/AudioStreamBuilder.h"
#include "oboe/AudioStreamBase.h"

/** WARNING - UNDER CONSTRUCTION - THIS API WILL CHANGE. */

namespace oboe {

/**
Expand Down Expand Up @@ -517,18 +515,18 @@ class AudioStream : public AudioStreamBase {
*
* The flag will be checked in the Oboe data callback. If it transitions from false to true
* then the PerformanceHint feature will be started.
* This only needs to be called once.
* This only needs to be called once for each stream.
*
* You may want to enable this if you have a dynamically changing workload
* and you notice that you are getting underruns and glitches when your workload increases.
* and you notice that you are getting under-runs and glitches when your workload increases.
* This might happen, for example, if you suddenly go from playing one note to
* ten notes on a synthesizer.
*
* Try the CPU Load test in OboeTester if you would like to experiment with this interactively.
* Try the "CPU Load" test in OboeTester if you would like to experiment with this interactively.
*
* On some devices, this may be implemented using the "ADPF" library.
*
* @param enabled true if you would like a performance boost
* @param enabled true if you would like a performance boost, default is false
*/
void setPerformanceHintEnabled(bool enabled) {
mPerformanceHintEnabled = enabled;
Expand All @@ -544,6 +542,36 @@ class AudioStream : public AudioStreamBase {
return mPerformanceHintEnabled;
}

/**
* Use this to give the performance manager more information about your workload.
* You can call this at the beginning of the callback when you figure
* out what your workload will be.
*
* Call this if (1) you have called setPerformanceHintEnabled(true), and
* (2) you have a varying workload, and
* (3) you hear glitches when your workload suddenly increases.
*
* This might happen when you go from a single note to a big chord on a synthesizer.
*
* The workload can be in your own units. If you are synthesizing music
* then the workload could be the number of active voices.
* If your app is a game then it could be the number of sound effects.
* The units are arbitrary. They just have to be proportional to
* the estimated computational load. For example, if some of your voices take 20%
* more computation than a basic voice then assign 6 units to the complex voice
* and 5 units to the basic voice.
*
* The performance hint code can use this as an advance warning that the callback duration
* will probably increase. Rather than wait for the long duration and possibly under-run,
* we can boost the CPU immediately before we start doing the calculations.
*
* @param appWorkload workload in application units, such as number of voices
* @return OK or an error such as ErrorInvalidState if the PerformanceHint was not enabled.
*/
virtual oboe::Result reportWorkload(int32_t appWorkload) {
return oboe::Result::ErrorUnimplemented;
}

protected:

/**
Expand Down
8 changes: 8 additions & 0 deletions src/aaudio/AudioStreamAAudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ class AudioStreamAAudio : public AudioStream {
mAdpfOpenAttempted = false;
}

oboe::Result reportWorkload(int32_t appWorkload) override {
if (!isPerformanceHintEnabled()) {
return oboe::Result::ErrorInvalidState;
}
mAdpfWrapper.reportWorkload(appWorkload);
return oboe::Result::OK;
}

protected:
static void internalErrorCallback(
AAudioStream *stream,
Expand Down
17 changes: 17 additions & 0 deletions src/common/AdpfWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,22 @@ void AdpfWrapper::onEndCallback(double durationScaler) {
int64_t actualDurationNanos = endCallbackNanos - mBeginCallbackNanos;
int64_t scaledDurationNanos = static_cast<int64_t>(actualDurationNanos * durationScaler);
reportActualDuration(scaledDurationNanos);
// When the workload is non-zero, update the conversion factor from workload
// units to nanoseconds duration.
if (mPreviousWorkload > 0) {
mNanosPerWorkloadUnit = ((double) scaledDurationNanos) / mPreviousWorkload;
}
}
}

void AdpfWrapper::reportWorkload(int32_t appWorkload) {
if (isOpen()) {
// Compare with previous workload. If we think we will need more
// time to render the callback then warn ADPF as soon as possible.
if (appWorkload > mPreviousWorkload && mNanosPerWorkloadUnit > 0.0) {
int64_t predictedDuration = (int64_t) (appWorkload * mNanosPerWorkloadUnit);
reportActualDuration(predictedDuration);
}
mPreviousWorkload = appWorkload;
}
}
4 changes: 4 additions & 0 deletions src/common/AdpfWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,15 @@ class AdpfWrapper {
*/
void reportActualDuration(int64_t actualDurationNanos);

void reportWorkload(int32_t appWorkload);

private:
std::mutex mLock;
APerformanceHintSession* mHintSession = nullptr;
int64_t mBeginCallbackNanos = 0;
static bool sUseAlternativeHack;
int32_t mPreviousWorkload = 0;
double mNanosPerWorkloadUnit = 0.0;
};

#endif //SYNTHMARK_ADPF_WRAPPER_H

0 comments on commit aa616aa

Please sign in to comment.