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 }