1 /*
2 * Copyright (C) 2018 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 #include "oboe/StabilizedCallback.h"
18 #include "common/AudioClock.h"
19 #include "common/Trace.h"
20
21 constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
22 constexpr float kPercentageOfCallbackToUse = 0.8;
23
24 using namespace oboe;
25
StabilizedCallback(AudioStreamCallback * callback)26 StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){
27 Trace::initialize();
28 }
29
30 /**
31 * An audio callback which attempts to do work for a fixed amount of time.
32 *
33 * @param oboeStream
34 * @param audioData
35 * @param numFrames
36 * @return
37 */
38 DataCallbackResult
onAudioReady(AudioStream * oboeStream,void * audioData,int32_t numFrames)39 StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
40
41 int64_t startTimeNanos = AudioClock::getNanoseconds();
42
43 if (mFrameCount == 0){
44 mEpochTimeNanos = startTimeNanos;
45 }
46
47 int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos;
48
49 // In an ideal world the callback start time will be exactly the same as the duration of the
50 // frames already read/written into the stream. In reality the callback can start early
51 // or late. By finding the delta we can calculate the target duration for our stabilized
52 // callback.
53 int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate();
54 int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos;
55
56 if (lateStartNanos < 0){
57 // This was an early start which indicates that our previous epoch was a late callback.
58 // Update our epoch to this more accurate time.
59 mEpochTimeNanos = startTimeNanos;
60 mFrameCount = 0;
61 }
62
63 int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate();
64 int64_t targetDurationNanos = static_cast<int64_t>(
65 (numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos);
66
67 Trace::beginSection("Actual load");
68 DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames);
69 Trace::endSection();
70
71 int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos;
72 int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos;
73
74 Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos);
75 generateLoad(stabilizingLoadDurationNanos);
76 Trace::endSection();
77
78 // Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years,
79 // significantly longer than the average lifetime of an Android phone.
80 mFrameCount += numFrames;
81 return result;
82 }
83
generateLoad(int64_t durationNanos)84 void StabilizedCallback::generateLoad(int64_t durationNanos) {
85
86 int64_t currentTimeNanos = AudioClock::getNanoseconds();
87 int64_t deadlineTimeNanos = currentTimeNanos + durationNanos;
88
89 // opsPerStep gives us an estimated number of operations which need to be run to fully utilize
90 // the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos).
91 // After each step the opsPerStep value is re-calculated based on the actual time taken to
92 // execute those operations.
93 auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
94 int64_t stepDurationNanos = 0;
95 int64_t previousTimeNanos = 0;
96
97 while (currentTimeNanos <= deadlineTimeNanos){
98
99 for (int i = 0; i < opsPerStep; i++) cpu_relax();
100
101 previousTimeNanos = currentTimeNanos;
102 currentTimeNanos = AudioClock::getNanoseconds();
103 stepDurationNanos = currentTimeNanos - previousTimeNanos;
104
105 // Calculate exponential moving average to smooth out values, this acts as a low pass filter.
106 // @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
107 static const float kFilterCoefficient = 0.1;
108 auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos;
109 mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano;
110 opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos);
111 }
112 }