xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/OboeStreamCallbackProxy.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright 2017 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 
17 #ifndef NATIVEOBOE_OBOESTREAMCALLBACKPROXY_H
18 #define NATIVEOBOE_OBOESTREAMCALLBACKPROXY_H
19 
20 #include <unistd.h>
21 #include <sys/types.h>
22 #include <sys/sysinfo.h>
23 
24 #include "oboe/Oboe.h"
25 #include "synth/Synthesizer.h"
26 #include "synth/SynthTools.h"
27 #include "OboeTesterStreamCallback.h"
28 
29 class DoubleStatistics {
30 public:
add(double statistic)31     void add(double statistic) {
32         if (skipCount < kNumberStatisticsToSkip) {
33             skipCount++;
34         } else {
35             if (statistic <= 0.0) return;
36             sum = statistic + sum;
37             count++;
38             minimum = std::min(statistic, minimum.load());
39             maximum = std::max(statistic, maximum.load());
40         }
41     }
42 
getAverage()43     double getAverage() const {
44         return sum / count;
45     }
46 
dump()47     std::string dump() const {
48         if (count == 0) return "?";
49         char buff[100];
50         snprintf(buff, sizeof(buff), "%3.1f/%3.1f/%3.1f ms", minimum.load(), getAverage(), maximum.load());
51         std::string buffAsStr = buff;
52         return buffAsStr;
53     }
54 
clear()55     void clear() {
56         skipCount = 0;
57         sum = 0;
58         count = 0;
59         minimum = DBL_MAX;
60         maximum = 0;
61     }
62 
63 private:
64     static constexpr double kNumberStatisticsToSkip = 5; // Skip the first 5 frames
65     std::atomic<int> skipCount { 0 };
66     std::atomic<double> sum { 0 };
67     std::atomic<int> count { 0 };
68     std::atomic<double> minimum { DBL_MAX };
69     std::atomic<double> maximum { 0 };
70 };
71 
72 /**
73  * Manage the synthesizer workload that burdens the CPU.
74  * Adjust the number of voices according to the requested workload.
75  * Trigger noteOn and noteOff messages.
76  */
77 class SynthWorkload {
78 public:
SynthWorkload()79     SynthWorkload() {
80         mSynth.setup(marksynth::kSynthmarkSampleRate, marksynth::kSynthmarkMaxVoices);
81     }
82 
onCallback(double workload)83     void onCallback(double workload) {
84         // If workload changes then restart notes.
85         if (workload != mPreviousWorkload) {
86             mSynth.allNotesOff();
87             mAreNotesOn = false;
88             mCountdown = 0; // trigger notes on
89             mPreviousWorkload = workload;
90         }
91         if (mCountdown <= 0) {
92             if (mAreNotesOn) {
93                 mSynth.allNotesOff();
94                 mAreNotesOn = false;
95                 mCountdown = mOffFrames;
96             } else {
97                 mSynth.notesOn((int)mPreviousWorkload);
98                 mAreNotesOn = true;
99                 mCountdown = mOnFrames;
100             }
101         }
102     }
103 
104     /**
105      * Render the notes into a stereo buffer.
106      * Passing a nullptr will cause the calculated results to be discarded.
107      * The workload should be the same.
108      * @param buffer a real stereo buffer or nullptr
109      * @param numFrames
110      */
renderStereo(float * buffer,int numFrames)111     void renderStereo(float *buffer, int numFrames) {
112         if (buffer == nullptr) {
113             int framesLeft = numFrames;
114             while (framesLeft > 0) {
115                 int framesThisTime = std::min(kDummyBufferSizeInFrames, framesLeft);
116                 // Do the work then throw it away.
117                 mSynth.renderStereo(&mDummyStereoBuffer[0], framesThisTime);
118                 framesLeft -= framesThisTime;
119             }
120         } else {
121             mSynth.renderStereo(buffer, numFrames);
122         }
123         mCountdown -= numFrames;
124     }
125 
126 private:
127     marksynth::Synthesizer   mSynth;
128     static constexpr int     kDummyBufferSizeInFrames = 32;
129     float                    mDummyStereoBuffer[kDummyBufferSizeInFrames * 2];
130     double                   mPreviousWorkload = 1.0;
131     bool                     mAreNotesOn = false;
132     int                      mCountdown = 0;
133     int                      mOnFrames = (int) (0.2 * 48000);
134     int                      mOffFrames = (int) (0.3 * 48000);
135 };
136 
137 class OboeStreamCallbackProxy : public OboeTesterStreamCallback {
138 public:
139 
setDataCallback(oboe::AudioStreamDataCallback * callback)140     void setDataCallback(oboe::AudioStreamDataCallback *callback) {
141         mCallback = callback;
142         setCallbackCount(0);
143         mStatistics.clear();
144         mPreviousMask = 0;
145     }
146 
setCallbackReturnStop(bool b)147     static void setCallbackReturnStop(bool b) {
148         mCallbackReturnStop = b;
149     }
150 
getCallbackCount()151     int64_t getCallbackCount() {
152         return mCallbackCount;
153     }
154 
setCallbackCount(int64_t count)155     void setCallbackCount(int64_t count) {
156         mCallbackCount = count;
157     }
158 
getFramesPerCallback()159     int32_t getFramesPerCallback() {
160         return mFramesPerCallback.load();
161     }
162 
163     /**
164      * Called when the stream is ready to process audio.
165      */
166     oboe::DataCallbackResult onAudioReady(
167             oboe::AudioStream *audioStream,
168             void *audioData,
169             int numFrames) override;
170 
171     /**
172      * Specify the amount of artificial workload that will waste CPU cycles
173      * and increase the CPU load.
174      * @param workload typically ranges from 0 to 400
175      */
setWorkload(int32_t workload)176     void setWorkload(int32_t workload) {
177         mNumWorkloadVoices = std::max(0, workload);
178     }
179 
getWorkload()180     int32_t getWorkload() const {
181         return mNumWorkloadVoices;
182     }
183 
setHearWorkload(bool enabled)184     void setHearWorkload(bool enabled) {
185         mHearWorkload = enabled;
186     }
187 
188     /**
189      * This is the callback duration relative to the real-time equivalent.
190      * So it may be higher than 1.0.
191      * @return low pass filtered value for the fractional CPU load
192      */
getCpuLoad()193     float getCpuLoad() const {
194         return mCpuLoad;
195     }
196 
197     /**
198      * Calling this will atomically reset the max to zero so only call
199      * this from one client.
200      *
201      * @return last value of the maximum unfiltered CPU load.
202      */
getAndResetMaxCpuLoad()203     float getAndResetMaxCpuLoad() {
204         return mMaxCpuLoad.exchange(0.0f);
205     }
206 
getCallbackTimeString()207     std::string getCallbackTimeString() const {
208         return mStatistics.dump();
209     }
210 
211     /**
212      * @return mask of the CPUs used since the last reset
213      */
getAndResetCpuMask()214     uint32_t getAndResetCpuMask() {
215         return mCpuMask.exchange(0);
216     }
orCurrentCpuMask(int cpuIndex)217     void orCurrentCpuMask(int cpuIndex) {
218         mCpuMask |= (1 << cpuIndex);
219     }
220 
221     /**
222      * @param cpuIndex
223      * @return 0 on success or a negative errno
224      */
setCpuAffinity(int cpuIndex)225     int setCpuAffinity(int cpuIndex) {
226         cpu_set_t cpu_set;
227         CPU_ZERO(&cpu_set);
228         CPU_SET(cpuIndex, &cpu_set);
229         int err = sched_setaffinity((pid_t) 0, sizeof(cpu_set_t), &cpu_set);
230         return err == 0 ? 0 : -errno;
231     }
232 
233     /**
234      *
235      * @param mask bits for each CPU or zero for all
236      * @return
237      */
238     int applyCpuAffinityMask(uint32_t mask);
239 
setCpuAffinityMask(uint32_t mask)240     void setCpuAffinityMask(uint32_t mask) {
241         mCpuAffinityMask = mask;
242     }
243 
244 private:
245     static constexpr double    kNsToMsScaler = 0.000001;
246     std::atomic<float>         mCpuLoad{0.0f};
247     std::atomic<float>         mMaxCpuLoad{0.0f};
248     int64_t                    mPreviousCallbackTimeNs = 0;
249     DoubleStatistics           mStatistics;
250     int32_t                    mNumWorkloadVoices = 0;
251     SynthWorkload              mSynthWorkload;
252     bool                       mHearWorkload = false;
253 
254     oboe::AudioStreamDataCallback *mCallback = nullptr;
255     static bool                mCallbackReturnStop;
256 
257     int64_t                    mCallbackCount = 0;
258     std::atomic<int32_t>       mFramesPerCallback{0};
259 
260     std::atomic<uint32_t>      mCpuAffinityMask{0};
261     std::atomic<uint32_t>      mPreviousMask{0};
262     std::atomic<uint32_t>      mCpuMask{0};
263     cpu_set_t                  mOriginalCpuSet;
264     bool                       mIsOriginalCpuSetValid = false;
265 
266 };
267 
268 #endif //NATIVEOBOE_OBOESTREAMCALLBACKPROXY_H
269