/* * 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 "oboe/StabilizedCallback.h" #include "common/AudioClock.h" #include "common/Trace.h" constexpr int32_t kLoadGenerationStepSizeNanos = 20000; constexpr float kPercentageOfCallbackToUse = 0.8; using namespace oboe; StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){ Trace::initialize(); } /** * 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( (numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos); Trace::beginSection("Actual load"); DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames); Trace::endSection(); int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos; int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos; Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos); generateLoad(stabilizingLoadDurationNanos); Trace::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); } }