Add LoadStabilizer class
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4f65c2a..533ada1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,6 +18,8 @@
         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})
diff --git a/include/oboe/Oboe.h b/include/oboe/Oboe.h
index e59fa89..c3a0bca 100644
--- a/include/oboe/Oboe.h
+++ b/include/oboe/Oboe.h
@@ -32,5 +32,6 @@
 #include "oboe/AudioStreamBuilder.h"
 #include "oboe/Utilities.h"
 #include "oboe/Version.h"
+#include "oboe/StabilizedCallback.h"
 
 #endif //OBOE_OBOE_H
diff --git a/include/oboe/StabilizedCallback.h b/include/oboe/StabilizedCallback.h
new file mode 100644
index 0000000..b31bb11
--- /dev/null
+++ b/include/oboe/StabilizedCallback.h
@@ -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
diff --git a/samples/MegaDrone/README.md b/samples/MegaDrone/README.md
index ae72768..5865fdb 100644
--- a/samples/MegaDrone/README.md
+++ b/samples/MegaDrone/README.md
@@ -12,6 +12,8 @@
 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.
 
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp b/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
index 0471a12..2a19fde 100644
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
+++ b/samples/MegaDrone/src/main/cpp/AudioEngine.cpp
@@ -25,7 +25,9 @@
 
     mCpuIds = cpuIds;
     AudioStreamBuilder builder;
-    builder.setCallback(this);
+
+    mStabilizedCallback = new StabilizedCallback(this);
+    builder.setCallback(mStabilizedCallback);
     builder.setPerformanceMode(PerformanceMode::LowLatency);
     builder.setSharingMode(SharingMode::Exclusive);
 
diff --git a/samples/MegaDrone/src/main/cpp/AudioEngine.h b/samples/MegaDrone/src/main/cpp/AudioEngine.h
index e6e2cb8..98aa0c9 100644
--- a/samples/MegaDrone/src/main/cpp/AudioEngine.h
+++ b/samples/MegaDrone/src/main/cpp/AudioEngine.h
@@ -38,6 +38,7 @@
 
 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
diff --git a/src/common/StabilizedCallback.cpp b/src/common/StabilizedCallback.cpp
new file mode 100644
index 0000000..855d292
--- /dev/null
+++ b/src/common/StabilizedCallback.cpp
@@ -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);
+    }
+}
\ No newline at end of file
diff --git a/src/common/Trace.cpp b/src/common/Trace.cpp
new file mode 100644
index 0000000..5ed445b
--- /dev/null
+++ b/src/common/Trace.cpp
@@ -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;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/common/Trace.h b/src/common/Trace.h
new file mode 100644
index 0000000..c7965f9
--- /dev/null
+++ b/src/common/Trace.h
@@ -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
\ No newline at end of file