Skip to content

Commit c980f46

Browse files
committed
address comments
1 parent 1c4aed8 commit c980f46

11 files changed

Lines changed: 323 additions & 112 deletions

apps/OboeTester/app/src/main/cpp/cpu/AudioWorkloadTest.cpp

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
#include "AudioWorkloadTest.h"
218

319
AudioWorkloadTest::AudioWorkloadTest() : mStream(nullptr) {}
@@ -19,8 +35,6 @@ int32_t AudioWorkloadTest::open() {
1935

2036
mFramesPerBurst = mStream->getFramesPerBurst();
2137
mSampleRate = mStream->getSampleRate();
22-
mStream->setBufferSizeInFrames(mBufferSizeInBursts * mFramesPerBurst);
23-
mBufferSizeInFrames = mStream->getBufferSizeInFrames();
2438
mPreviousXRunCount = 0;
2539
mXRunCount = 0;
2640

@@ -39,8 +53,10 @@ int32_t AudioWorkloadTest::getBufferSizeInFrames() const {
3953
return mBufferSizeInFrames;
4054
}
4155

42-
int32_t AudioWorkloadTest::start(int32_t numCallbacks, int32_t bufferSizeInBursts, int32_t numVoices, int32_t alternateNumVoices, int32_t alternatingPeriodMs, bool adpfEnabled, bool sineEnabled) {
43-
mNumCallbacks = numCallbacks;
56+
int32_t AudioWorkloadTest::start(int32_t targetDurationMs, int32_t bufferSizeInBursts, int32_t numVoices,
57+
int32_t alternateNumVoices, int32_t alternatingPeriodMs, bool adpfEnabled,
58+
bool hearWorkload) {
59+
mTargetDurationMs = targetDurationMs;
4460
mBufferSizeInBursts = bufferSizeInBursts;
4561
mNumVoices = numVoices;
4662
mAlternateNumVoices = alternateNumVoices;
@@ -51,8 +67,10 @@ int32_t AudioWorkloadTest::start(int32_t numCallbacks, int32_t bufferSizeInBurst
5167
mPreviousXRunCount = mXRunCount.load();
5268
mXRunCount = 0;
5369
mRunning = true;
54-
mSineEnabled = sineEnabled;
70+
mHearWorkload = hearWorkload;
5571
mStream->setPerformanceHintEnabled(adpfEnabled);
72+
mStream->setBufferSizeInFrames(mBufferSizeInBursts * mFramesPerBurst);
73+
mBufferSizeInFrames = mStream->getBufferSizeInFrames();
5674
mSynthWorkload = SynthWorkload((int) 0.2 * mSampleRate, (int) 0.3 * mSampleRate);
5775
oboe::Result result = mStream->start();
5876
if (result != oboe::Result::OK) {
@@ -83,11 +101,11 @@ int32_t AudioWorkloadTest::getCpuCount() {
83101
return sysconf(_SC_NPROCESSORS_CONF);
84102
}
85103

86-
int32_t AudioWorkloadTest::getXRunCount() {
104+
int32_t AudioWorkloadTest::getXRunCount() const {
87105
return mXRunCount - mPreviousXRunCount;
88106
}
89107

90-
int32_t AudioWorkloadTest::getCallbackCount() {
108+
int32_t AudioWorkloadTest::getCallbackCount() const {
91109
return mCallbackCount;
92110
}
93111

@@ -122,12 +140,15 @@ std::vector<AudioWorkloadTest::CallbackStatus> AudioWorkloadTest::getCallbackSta
122140
return mCallbackStatistics;
123141
}
124142

125-
oboe::DataCallbackResult AudioWorkloadTest::onAudioReady(oboe::AudioStream* audioStream, void* audioData, int32_t numFrames) {
126-
int64_t beginTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
143+
oboe::DataCallbackResult AudioWorkloadTest::onAudioReady(oboe::AudioStream* audioStream,
144+
void* audioData, int32_t numFrames) {
145+
int64_t beginTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(
146+
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
127147

128148
int currentVoices = mNumVoices;
129149
if (mAlternatingPeriodMs > 0) {
130-
int64_t timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
150+
int64_t timeMs = std::chrono::duration_cast<std::chrono::milliseconds>(
151+
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
131152
if (mStartTimeMs == 0) {
132153
mStartTimeMs = timeMs;
133154
}
@@ -136,15 +157,30 @@ oboe::DataCallbackResult AudioWorkloadTest::onAudioReady(oboe::AudioStream* audi
136157
}
137158
}
138159

160+
auto floatData = static_cast<float *>(audioData);
161+
int channelCount = audioStream->getChannelCount();
162+
163+
// Fill buffer with a sine wave.
164+
for (int i = 0; i < numFrames; i++) {
165+
float value = sinf(mPhase) * 0.2f;
166+
for (int j = 0; j < channelCount; j++) {
167+
*floatData++ = value;
168+
}
169+
mPhase = mPhase + kPhaseIncrement;
170+
// Wrap the phase around in a circle.
171+
if (mPhase >= M_PI) mPhase = mPhase - 2.0f * M_PI;
172+
}
173+
139174
mSynthWorkload.onCallback(currentVoices);
140175
if (currentVoices > 0) {
141-
// Render into the buffer or discard the synth voices.
142-
float *buffer = (audioStream->getChannelCount() == 2 && mSineEnabled)
176+
// Render synth workload into the buffer or discard the synth voices.
177+
float *buffer = (audioStream->getChannelCount() == 2 && mHearWorkload)
143178
? static_cast<float *>(audioData) : nullptr;
144179
mSynthWorkload.renderStereo(buffer, numFrames);
145180
}
146181

147-
int64_t finishTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
182+
int64_t finishTimeNs = std::chrono::duration_cast<std::chrono::nanoseconds>(
183+
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
148184

149185
mXRunCount = audioStream->getXRunCount().value();
150186

@@ -159,7 +195,10 @@ oboe::DataCallbackResult AudioWorkloadTest::onAudioReady(oboe::AudioStream* audi
159195
mCallbackCount++;
160196
mLastDurationNs = finishTimeNs - beginTimeNs;
161197

162-
if (mCallbackCount >= mNumCallbacks) {
198+
int64_t currentTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(
199+
std::chrono::high_resolution_clock::now().time_since_epoch()).count();
200+
201+
if (currentTimeMs - mStartTimeMs > mTargetDurationMs) {
163202
mRunning = false;
164203
stop();
165204
return oboe::DataCallbackResult::Stop;

apps/OboeTester/app/src/main/cpp/cpu/AudioWorkloadTest.h

Lines changed: 135 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,174 @@
2828
#include <unistd.h> // For CPU affinity
2929
#include "SynthWorkload.h"
3030

31+
/**
32+
* @class AudioWorkloadTest
33+
* @brief A class designed to test audio workload performance using the Oboe library.
34+
*
35+
* This class sets up an audio stream, generates a synthetic audio load (sine wave and/or
36+
* a more complex synth workload), and collects statistics about the audio callback performance,
37+
* such as callback duration, XRun counts, and CPU usage.
38+
*/
3139
class AudioWorkloadTest : oboe::AudioStreamDataCallback {
3240
public:
41+
/**
42+
* @struct CallbackStatus
43+
* @brief Structure to store statistics for each audio callback invocation.
44+
*/
3345
struct CallbackStatus {
34-
int32_t numVoices;
35-
int64_t beginTimeNs;
36-
int64_t finishTimeNs;
37-
int32_t xRunCount;
38-
int32_t cpuIndex;
46+
int32_t numVoices; // Number of synthesizer voices active during this callback
47+
int64_t beginTimeNs; // Timestamp (nanoseconds) when the callback started
48+
int64_t finishTimeNs; // Timestamp (nanoseconds) when the callback finished
49+
int32_t xRunCount; // Cumulative XRun (underrun/overrun) count at this point
50+
int32_t cpuIndex; // CPU core index on which the callback executed
3951
};
4052

53+
/**
54+
* @brief Constructor for AudioWorkloadTest.
55+
* Initializes the audio stream pointer to nullptr.
56+
*/
4157
AudioWorkloadTest();
58+
59+
/**
60+
* @brief Opens an audio stream with specified parameters.
61+
* Configures the stream for low latency output.
62+
* @return 0 on success, or a negative Oboe error code on failure.
63+
*/
4264
int32_t open();
65+
66+
/**
67+
* @brief Gets the number of frames processed in a single audio callback burst.
68+
* @return The number of frames per burst.
69+
*/
4370
int32_t getFramesPerBurst() const;
71+
72+
/**
73+
* @brief Gets the sample rate of the audio stream.
74+
* @return The sample rate in Hz.
75+
*/
4476
int32_t getSampleRate() const;
77+
78+
/**
79+
* @brief Gets the current buffer size of the audio stream in frames.
80+
* @return The buffer size in frames.
81+
*/
4582
int32_t getBufferSizeInFrames() const;
46-
int32_t start(int32_t numCallbacks, int32_t bufferSizeInBursts, int32_t numVoices, int32_t alternateNumVoices, int32_t alternatingPeriodMs, bool adpfEnabled, bool sineEnabled);
83+
84+
/**
85+
* @brief Starts the audio stream and the workload test.
86+
* @param targetDurationMillis The desired duration of the test in milliseconds.
87+
* @param bufferSizeInBursts The desired buffer size in terms of multiples of framesPerBurst.
88+
* @param numVoices The primary number of synthesizer voices to simulate.
89+
* @param alternateNumVoices An alternative number of voices for alternating workload.
90+
* @param alternatingPeriodMs The period in milliseconds to alternate between numVoices and
91+
* alternateNumVoices.
92+
* @param adpfEnabled Whether to enable Adaptive Performance (ADPF) hints.
93+
* @param hearWorkload If true, the synthesized audio will be audible; otherwise, it's processed
94+
* silently.
95+
* @return 0 on success, or a negative Oboe error code on failure.
96+
*/
97+
int32_t start(int32_t targetDurationMillis, int32_t bufferSizeInBursts, int32_t numVoices,
98+
int32_t alternateNumVoices, int32_t alternatingPeriodMs, bool adpfEnabled,
99+
bool hearWorkload);
100+
101+
/**
102+
* @brief Gets the number of available CPU cores on the system.
103+
* @return The number of CPU cores.
104+
*/
47105
static int32_t getCpuCount();
106+
107+
/**
108+
* @brief Sets the CPU affinity for the current thread (intended for the audio callback
109+
* thread).
110+
* @param mask A bitmask specifying the allowed CPU cores.
111+
* @return 0 on success, -1 on failure.
112+
*/
48113
static int32_t setCpuAffinityForCallback(uint32_t mask);
49-
int32_t getXRunCount();
50-
int32_t getCallbackCount();
114+
115+
/**
116+
* @brief Gets the number of XRuns (underruns/overruns) that occurred during the last test run.
117+
* @return The XRun count.
118+
*/
119+
int32_t getXRunCount() const;
120+
121+
/**
122+
* @brief Gets the total number of audio callbacks invoked during the last test run.
123+
* @return The callback count.
124+
*/
125+
int32_t getCallbackCount() const;
126+
127+
/**
128+
* @brief Gets the duration of the last audio callback in nanoseconds.
129+
* @return The duration in nanoseconds.
130+
*/
51131
int64_t getLastDurationNs();
132+
133+
/**
134+
* @brief Checks if the audio workload test is currently running.
135+
* @return True if running, false otherwise.
136+
*/
52137
bool isRunning();
138+
139+
/**
140+
* @brief Stops the audio stream.
141+
* @return 0 on success, or a negative Oboe error code on failure.
142+
*/
53143
int32_t stop();
144+
145+
/**
146+
* @brief Closes the audio stream and releases resources.
147+
* @return 0 on success.
148+
*/
54149
int32_t close();
150+
151+
/**
152+
* @brief Retrieves the collected statistics for each audio callback.
153+
* @return A vector of CallbackStatus structures.
154+
*/
55155
std::vector<CallbackStatus> getCallbackStatistics();
56-
oboe::DataCallbackResult onAudioReady(oboe::AudioStream* audioStream, void* audioData, int32_t numFrames) override;
156+
157+
/**
158+
* @brief The Oboe audio callback function.
159+
* This function is called by the Oboe library when it needs more audio data.
160+
* It generates audio, performs workload simulation, and collects statistics.
161+
* @param audioStream Pointer to the Oboe audio stream.
162+
* @param audioData Pointer to the buffer where audio data should be written.
163+
* @param numFrames The number of audio frames to be filled.
164+
* @return oboe::DataCallbackResult::Continue to continue streaming, or
165+
* oboe::DataCallbackResult::Stop to stop.
166+
*/
167+
oboe::DataCallbackResult onAudioReady(oboe::AudioStream* audioStream, void* audioData,
168+
int32_t numFrames) override;
57169

58170
private:
59-
oboe::AudioStream* mStream;
171+
// Member variables
172+
oboe::AudioStream* mStream; // Pointer to the Oboe audio stream instance
173+
174+
// Atomic variables for thread-safe access from audio callback and other threads
60175
std::atomic<int32_t> mFramesPerBurst{0};
61176
std::atomic<int32_t> mSampleRate{0};
62177
std::atomic<int32_t> mCallbackCount{0};
63178
std::atomic<int32_t> mPreviousXRunCount{0};
64179
std::atomic<int32_t> mXRunCount{0};
65-
std::atomic<int32_t> mNumCallbacks{0};
180+
std::atomic<int32_t> mTargetDurationMs{0};
66181
std::atomic<int32_t> mBufferSizeInBursts{0};
67182
std::atomic<int32_t> mBufferSizeInFrames{0};
68183
std::atomic<int32_t> mNumVoices{0};
69184
std::atomic<int32_t> mAlternateNumVoices{0};
70185
std::atomic<int32_t> mAlternatingPeriodMs{0};
71186
std::atomic<int64_t> mLastDurationNs{0};
72187
std::atomic<int64_t> mStartTimeMs{0};
73-
std::atomic<bool> mSineEnabled{false};
188+
std::atomic<bool> mHearWorkload{false};
189+
74190
std::vector<CallbackStatus> mCallbackStatistics;
75191
std::atomic<bool> mRunning{false};
76-
SynthWorkload mSynthWorkload;
192+
193+
// Sine wave generation parameters
194+
std::atomic<float> mPhase{0.0f}; // Current phase of the sine wave oscillator
195+
// Phase increment for a 440 Hz sine wave at a 48000 Hz sample rate
196+
static constexpr float kPhaseIncrement = 2.0f * (float) M_PI * 440.0f / 48000.0f;
197+
198+
SynthWorkload mSynthWorkload; // Instance of the synthetic workload generator
77199
};
78200

79201
#endif // AUDIO_WORKLOAD_TEST_H

0 commit comments

Comments
 (0)