| /* | |
| * Copyright (C) 2018 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "common/Trace.h" | |
| #include "oboe/AudioClock.h" | |
| #include "oboe/StabilizedCallback.h" | |
| constexpr int32_t kLoadGenerationStepSizeNanos = 20000; | |
| constexpr float kPercentageOfCallbackToUse = 0.8; | |
| using namespace oboe; | |
| StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback) { | |
| } | |
| /** | |
| * An audio callback which attempts to do work for a fixed amount of time. | |
| * | |
| * @param oboeStream | |
| * @param audioData | |
| * @param numFrames | |
| * @return | |
| */ | |
| DataCallbackResult | |
| StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) { | |
| int64_t startTimeNanos = AudioClock::getNanoseconds(); | |
| if (mFrameCount == 0){ | |
| mEpochTimeNanos = startTimeNanos; | |
| } | |
| int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos; | |
| // In an ideal world the callback start time will be exactly the same as the duration of the | |
| // frames already read/written into the stream. In reality the callback can start early | |
| // or late. By finding the delta we can calculate the target duration for our stabilized | |
| // callback. | |
| int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate(); | |
| int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos; | |
| if (lateStartNanos < 0){ | |
| // This was an early start which indicates that our previous epoch was a late callback. | |
| // Update our epoch to this more accurate time. | |
| mEpochTimeNanos = startTimeNanos; | |
| mFrameCount = 0; | |
| } | |
| int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate(); | |
| int64_t targetDurationNanos = static_cast<int64_t>( | |
| (numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos); | |
| bool traceEnabled = Trace::getInstance().isEnabled(); | |
| if (traceEnabled) Trace::getInstance().beginSection("Actual load"); | |
| DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames); | |
| if (traceEnabled) Trace::getInstance().endSection(); | |
| int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos; | |
| int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos; | |
| if (traceEnabled) Trace::getInstance().beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos); | |
| generateLoad(stabilizingLoadDurationNanos); | |
| if (traceEnabled) Trace::getInstance().endSection(); | |
| // Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years, | |
| // significantly longer than the average lifetime of an Android phone. | |
| mFrameCount += numFrames; | |
| return result; | |
| } | |
| void StabilizedCallback::generateLoad(int64_t durationNanos) { | |
| int64_t currentTimeNanos = AudioClock::getNanoseconds(); | |
| int64_t deadlineTimeNanos = currentTimeNanos + durationNanos; | |
| // opsPerStep gives us an estimated number of operations which need to be run to fully utilize | |
| // the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos). | |
| // After each step the opsPerStep value is re-calculated based on the actual time taken to | |
| // execute those operations. | |
| auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos); | |
| int64_t stepDurationNanos = 0; | |
| int64_t previousTimeNanos = 0; | |
| while (currentTimeNanos <= deadlineTimeNanos){ | |
| for (int i = 0; i < opsPerStep; i++) cpu_relax(); | |
| previousTimeNanos = currentTimeNanos; | |
| currentTimeNanos = AudioClock::getNanoseconds(); | |
| stepDurationNanos = currentTimeNanos - previousTimeNanos; | |
| // Calculate exponential moving average to smooth out values, this acts as a low pass filter. | |
| // @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average | |
| static const float kFilterCoefficient = 0.1; | |
| auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos; | |
| mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano; | |
| opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos); | |
| } | |
| } |