mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-27 04:08:53 +01:00
112 lines
4.5 KiB
C++
112 lines
4.5 KiB
C++
|
/*
|
||
|
* 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_oboe_StabilizedCallback_android.h"
|
||
|
#include "oboe_common_AudioClock_android.h"
|
||
|
#include "oboe_common_Trace_android.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<int64_t>(
|
||
|
(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);
|
||
|
}
|
||
|
}
|