Skip to content

Commit

Permalink
Add LoadStabilizer class
Browse files Browse the repository at this point in the history
  • Loading branch information
dturner committed Dec 5, 2018
1 parent 15efeab commit cb527c2
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ set (oboe_sources
src/opensles/EngineOpenSLES.cpp
src/opensles/OpenSLESUtilities.cpp
src/opensles/OutputMixerOpenSLES.cpp
src/common/StabilizedCallback.cpp
src/common/Trace.cpp
)

add_library(oboe STATIC ${oboe_sources})
Expand Down
1 change: 1 addition & 0 deletions include/oboe/Oboe.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
#include "oboe/AudioStreamBuilder.h"
#include "oboe/Utilities.h"
#include "oboe/Version.h"
#include "oboe/StabilizedCallback.h"

#endif //OBOE_OBOE_H
70 changes: 70 additions & 0 deletions include/oboe/StabilizedCallback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 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.
*/

#ifndef OBOE_STABILIZEDCALLBACK_H
#define OBOE_STABILIZEDCALLBACK_H

#include <cstdint>
#include "oboe/AudioStream.h"

namespace oboe {

class StabilizedCallback : public AudioStreamCallback {

public:
StabilizedCallback(AudioStreamCallback *callback);

DataCallbackResult
onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override;

void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {
return mCallback->onErrorBeforeClose(oboeStream, error);
}

void onErrorAfterClose(AudioStream *oboeStream, Result error) override {
return mCallback->onErrorAfterClose(oboeStream, error);
}

private:

AudioStreamCallback *mCallback = nullptr;
int64_t mFrameCount = 0;
int64_t mEpochTimeNanos = 0;
double mOpsPerNano = 1;

void generateLoad(int64_t durationNanos);
};

/**
* cpu_relax is an architecture specific method of telling the CPU that you don't want it to
* do much work. asm volatile keeps the compiler from optimising these instructions out.
*/
#if defined(__i386__) || defined(__x86_64__)
#define cpu_relax() asm volatile("rep; nop" ::: "memory");

#elif defined(__arm__) || defined(__mips__)
#define cpu_relax() asm volatile("":::"memory")

#elif defined(__aarch64__)
#define cpu_relax() asm volatile("yield" ::: "memory")

#else
#error "cpu_relax is not defined for this architecture"
#endif

}

#endif //OBOE_STABILIZEDCALLBACK_H
2 changes: 2 additions & 0 deletions samples/MegaDrone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This sample demonstrates how to obtain the lowest latency and optimal computatio
4) Setting the buffer size to 2 bursts
5) Using the `-Ofast` compiler optimization flag, even when building the `Debug` variant
6) Using [`getExclusiveCores`](https://developer.android.com/reference/android/os/Process#getExclusiveCores()) (API 24+) and thread affinity to bind the audio thread to the best available CPU core(s)
7) Using a `StabilizedCallback` which aims to spend a fixed percentage of the callback time to avoid CPU frequency scaling ([video explanation](https://www.youtube.com/watch?v=C0BPXZIvG-Q&feature=youtu.be&t=1158))


This code was presented at [AES Milan](http://www.aes.org/events/144/) and [Droidcon Berlin](https://www.de.droidcon.com/) as part of a talk on Oboe.

Expand Down
4 changes: 3 additions & 1 deletion samples/MegaDrone/src/main/cpp/AudioEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ void AudioEngine::start(std::vector<int> cpuIds) {

mCpuIds = cpuIds;
AudioStreamBuilder builder;
builder.setCallback(this);

mStabilizedCallback = new StabilizedCallback(this);
builder.setCallback(mStabilizedCallback);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);

Expand Down
1 change: 1 addition & 0 deletions samples/MegaDrone/src/main/cpp/AudioEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class AudioEngine : public AudioStreamCallback {

private:

StabilizedCallback *mStabilizedCallback = nullptr;
AudioStream *mStream = nullptr;
std::unique_ptr<ISynth> mSynth;
std::vector<int> mCpuIds; // IDs of CPU cores which the audio callback should be bound to
Expand Down
108 changes: 108 additions & 0 deletions src/common/StabilizedCallback.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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 = 1000;
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 = (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();

// TODO: Could this be done with oboeStream->getFramesRead or getFramesWritten()?
// 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.
int 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;
mOpsPerNano = (int)(opsPerStep / stepDurationNanos);
opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
}
}
75 changes: 75 additions & 0 deletions src/common/Trace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 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 <dlfcn.h>
#include <cstdio>
#include "Trace.h"
#include "OboeDebug.h"

static char buffer[256];

// Tracing functions
static void *(*ATrace_beginSection)(const char *sectionName);

static void *(*ATrace_endSection)();

typedef void *(*fp_ATrace_beginSection)(const char *sectionName);

typedef void *(*fp_ATrace_endSection)();

bool Trace::mIsTracingSupported = false;

void Trace::beginSection(const char *format, ...){

if (mIsTracingSupported) {
va_list va;
va_start(va, format);
vsprintf(buffer, format, va);
ATrace_beginSection(buffer);
va_end(va);
} else {
LOGE("Tracing is either not initialized (call Trace::initialize()) "
"or not supported on this device");
}
}

void Trace::endSection() {

if (mIsTracingSupported) {
ATrace_endSection();
}
}

void Trace::initialize() {

// Using dlsym allows us to use tracing on API 21+ without needing android/trace.h which wasn't
// published until API 23
void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
if (lib == nullptr) {
LOGE("Could not open libandroid.so to dynamically load tracing symbols");
} else {
ATrace_beginSection =
reinterpret_cast<fp_ATrace_beginSection >(
dlsym(lib, "ATrace_beginSection"));
ATrace_endSection =
reinterpret_cast<fp_ATrace_endSection >(
dlsym(lib, "ATrace_endSection"));

if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr){
mIsTracingSupported = true;
}
}
}
31 changes: 31 additions & 0 deletions src/common/Trace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 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.
*/

#ifndef OBOE_TRACE_H
#define OBOE_TRACE_H

class Trace {

public:
static void beginSection(const char *format, ...);
static void endSection();
static void initialize();

private:
static bool mIsTracingSupported;
};

#endif //OBOE_TRACE_H

0 comments on commit cb527c2

Please sign in to comment.