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