OboeAudioService: add thread to service for passing timestamps

Cleanup several TODOs.

Test: test_aaudio in CTS
Change-Id: I7fc956b6a21cbb592f98e1e5a8f43ebd6926d796
Signed-off-by: Phil Burk <philburk@google.com>
diff --git a/services/oboeservice/Android.mk b/services/oboeservice/Android.mk
index 07b4d76..5a79b80 100644
--- a/services/oboeservice/Android.mk
+++ b/services/oboeservice/Android.mk
@@ -42,7 +42,9 @@
     OboeAudioService.cpp \
     OboeServiceStreamBase.cpp \
     OboeServiceStreamFakeHal.cpp \
-    OboeServiceMain.cpp
+    TimestampScheduler.cpp \
+    OboeServiceMain.cpp \
+    OboeThread.cpp
 
 LOCAL_CFLAGS += -Wno-unused-parameter
 LOCAL_CFLAGS += -Wall -Werror
diff --git a/services/oboeservice/OboeAudioService.cpp b/services/oboeservice/OboeAudioService.cpp
index caddc1d..001569c 100644
--- a/services/oboeservice/OboeAudioService.cpp
+++ b/services/oboeservice/OboeAudioService.cpp
@@ -34,11 +34,20 @@
 
 typedef enum
 {
+    OBOE_HANDLE_TYPE_DUMMY1, // TODO remove DUMMYs
+    OBOE_HANDLE_TYPE_DUMMY2, // make server handles different than client
     OBOE_HANDLE_TYPE_STREAM,
     OBOE_HANDLE_TYPE_COUNT
 } oboe_service_handle_type_t;
 static_assert(OBOE_HANDLE_TYPE_COUNT <= HANDLE_TRACKER_MAX_TYPES, "Too many handle types.");
 
+android::OboeAudioService::OboeAudioService()
+    : BnOboeAudioService() {
+}
+
+OboeAudioService::~OboeAudioService() {
+}
+
 oboe_handle_t OboeAudioService::openStream(oboe::OboeStreamRequest &request,
                                                 oboe::OboeStreamConfiguration &configuration) {
     OboeServiceStreamBase *serviceStream =  new OboeServiceStreamFakeHal();
@@ -61,7 +70,7 @@
     OboeServiceStreamBase *serviceStream = (OboeServiceStreamBase *)
             mHandleTracker.remove(OBOE_HANDLE_TYPE_STREAM,
                                   streamHandle);
-    ALOGI("OboeAudioService.closeStream(0x%08X)", streamHandle);
+    ALOGD("OboeAudioService.closeStream(0x%08X)", streamHandle);
     if (serviceStream != nullptr) {
         ALOGD("OboeAudioService::closeStream(): deleting serviceStream = %p", serviceStream);
         delete serviceStream;
@@ -79,9 +88,8 @@
 oboe_result_t OboeAudioService::getStreamDescription(
                 oboe_handle_t streamHandle,
                 oboe::AudioEndpointParcelable &parcelable) {
-    ALOGI("OboeAudioService::getStreamDescriptor(), streamHandle = 0x%08x", streamHandle);
     OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
-    ALOGI("OboeAudioService::getStreamDescriptor(), serviceStream = %p", serviceStream);
+    ALOGD("OboeAudioService::getStreamDescription(), serviceStream = %p", serviceStream);
     if (serviceStream == nullptr) {
         return OBOE_ERROR_INVALID_HANDLE;
     }
@@ -90,45 +98,38 @@
 
 oboe_result_t OboeAudioService::startStream(oboe_handle_t streamHandle) {
     OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
-    ALOGI("OboeAudioService::startStream(), serviceStream = %p", serviceStream);
+    ALOGD("OboeAudioService::startStream(), serviceStream = %p", serviceStream);
     if (serviceStream == nullptr) {
         return OBOE_ERROR_INVALID_HANDLE;
     }
-    mLatestHandle = streamHandle;
-    return serviceStream->start();
+    oboe_result_t result = serviceStream->start();
+    return result;
 }
 
 oboe_result_t OboeAudioService::pauseStream(oboe_handle_t streamHandle) {
     OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
-    ALOGI("OboeAudioService::pauseStream(), serviceStream = %p", serviceStream);
+    ALOGD("OboeAudioService::pauseStream(), serviceStream = %p", serviceStream);
     if (serviceStream == nullptr) {
         return OBOE_ERROR_INVALID_HANDLE;
     }
-    return serviceStream->pause();
+    oboe_result_t result = serviceStream->pause();
+    return result;
 }
 
 oboe_result_t OboeAudioService::flushStream(oboe_handle_t streamHandle) {
     OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
-    ALOGI("OboeAudioService::flushStream(), serviceStream = %p", serviceStream);
+    ALOGD("OboeAudioService::flushStream(), serviceStream = %p", serviceStream);
     if (serviceStream == nullptr) {
         return OBOE_ERROR_INVALID_HANDLE;
     }
     return serviceStream->flush();
 }
 
-void OboeAudioService::tickle() {
-    OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(mLatestHandle);
-    //ALOGI("OboeAudioService::tickle(), serviceStream = %p", serviceStream);
-    if (serviceStream != nullptr) {
-        serviceStream->tickle();
-    }
-}
-
 oboe_result_t OboeAudioService::registerAudioThread(oboe_handle_t streamHandle,
                                                          pid_t clientThreadId,
                                                          oboe_nanoseconds_t periodNanoseconds) {
     OboeServiceStreamBase *serviceStream = convertHandleToServiceStream(streamHandle);
-    ALOGI("OboeAudioService::registerAudioThread(), serviceStream = %p", serviceStream);
+    ALOGD("OboeAudioService::registerAudioThread(), serviceStream = %p", serviceStream);
     if (serviceStream == nullptr) {
         ALOGE("OboeAudioService::registerAudioThread(), serviceStream == nullptr");
         return OBOE_ERROR_INVALID_HANDLE;
diff --git a/services/oboeservice/OboeAudioService.h b/services/oboeservice/OboeAudioService.h
index df3cbf8..b196f1d 100644
--- a/services/oboeservice/OboeAudioService.h
+++ b/services/oboeservice/OboeAudioService.h
@@ -24,31 +24,32 @@
 
 #include <oboe/OboeDefinitions.h>
 #include <oboe/OboeAudio.h>
-#include "HandleTracker.h"
+#include "utility/HandleTracker.h"
 #include "IOboeAudioService.h"
-#include "OboeService.h"
 #include "OboeServiceStreamBase.h"
 
-using namespace android;
-namespace oboe {
+namespace android {
 
 class OboeAudioService :
     public BinderService<OboeAudioService>,
     public BnOboeAudioService
 {
-    friend class BinderService<OboeAudioService>;   // for OboeAudioService()
+    friend class BinderService<OboeAudioService>;
+
 public:
-// TODO why does this fail?    static const char* getServiceName() ANDROID_API { return "media.audio_oboe"; }
+    OboeAudioService();
+    virtual ~OboeAudioService();
+
     static const char* getServiceName() { return "media.audio_oboe"; }
 
-    virtual oboe_handle_t openStream(OboeStreamRequest &request,
-                                     OboeStreamConfiguration &configuration);
+    virtual oboe_handle_t openStream(oboe::OboeStreamRequest &request,
+                                     oboe::OboeStreamConfiguration &configuration);
 
     virtual oboe_result_t closeStream(oboe_handle_t streamHandle);
 
     virtual oboe_result_t getStreamDescription(
                 oboe_handle_t streamHandle,
-                AudioEndpointParcelable &parcelable);
+                oboe::AudioEndpointParcelable &parcelable);
 
     virtual oboe_result_t startStream(oboe_handle_t streamHandle);
 
@@ -61,16 +62,14 @@
 
     virtual oboe_result_t unregisterAudioThread(oboe_handle_t streamHandle, pid_t pid);
 
-    virtual void tickle();
-
 private:
 
-    OboeServiceStreamBase *convertHandleToServiceStream(oboe_handle_t streamHandle) const;
+    oboe::OboeServiceStreamBase *convertHandleToServiceStream(oboe_handle_t streamHandle) const;
 
     HandleTracker mHandleTracker;
-    oboe_handle_t mLatestHandle = OBOE_ERROR_INVALID_HANDLE; // TODO until we have service threads
+
 };
 
-} /* namespace oboe */
+} /* namespace android */
 
 #endif //OBOE_OBOE_AUDIO_SERVICE_H
diff --git a/services/oboeservice/OboeServiceStreamBase.cpp b/services/oboeservice/OboeServiceStreamBase.cpp
index 6b7e4e5..15b70a5 100644
--- a/services/oboeservice/OboeServiceStreamBase.cpp
+++ b/services/oboeservice/OboeServiceStreamBase.cpp
@@ -40,12 +40,15 @@
 }
 
 OboeServiceStreamBase::~OboeServiceStreamBase() {
+    Mutex::Autolock _l(mLockUpMessageQueue);
     delete mUpMessageQueue;
 }
 
 void OboeServiceStreamBase::sendServiceEvent(oboe_service_event_t event,
                               int32_t data1,
                               int64_t data2) {
+
+    Mutex::Autolock _l(mLockUpMessageQueue);
     OboeServiceMessage command;
     command.what = OboeServiceMessage::code::EVENT;
     command.event.event = event;
diff --git a/services/oboeservice/OboeServiceStreamBase.h b/services/oboeservice/OboeServiceStreamBase.h
index 736c754..33857c6 100644
--- a/services/oboeservice/OboeServiceStreamBase.h
+++ b/services/oboeservice/OboeServiceStreamBase.h
@@ -17,12 +17,14 @@
 #ifndef OBOE_OBOE_SERVICE_STREAM_BASE_H
 #define OBOE_OBOE_SERVICE_STREAM_BASE_H
 
+#include <utils/Mutex.h>
+
 #include "IOboeAudioService.h"
 #include "OboeService.h"
-#include "AudioStream.h"
 #include "fifo/FifoBuffer.h"
 #include "SharedRingBuffer.h"
 #include "AudioEndpointParcelable.h"
+#include "OboeThread.h"
 
 namespace oboe {
 
@@ -30,7 +32,7 @@
 // This should be way more than we need.
 #define QUEUE_UP_CAPACITY_COMMANDS (128)
 
-class OboeServiceStreamBase  {
+class OboeServiceStreamBase {
 
 public:
     OboeServiceStreamBase();
@@ -68,7 +70,11 @@
 
     virtual oboe_result_t close() = 0;
 
-    virtual void tickle() = 0;
+    virtual void sendCurrentTimestamp() = 0;
+
+    oboe_size_frames_t getFramesPerBurst() {
+        return mFramesPerBurst;
+    }
 
     virtual void sendServiceEvent(oboe_service_event_t event,
                                   int32_t data1 = 0,
@@ -77,6 +83,7 @@
     virtual void setRegisteredThread(pid_t pid) {
         mRegisteredClientThread = pid;
     }
+
     virtual pid_t getRegisteredThread() {
         return mRegisteredClientThread;
     }
@@ -92,6 +99,8 @@
     oboe_size_frames_t       mFramesPerBurst = 0;
     oboe_size_frames_t       mCapacityInFrames = 0;
     oboe_size_bytes_t        mCapacityInBytes = 0;
+
+    android::Mutex           mLockUpMessageQueue;
 };
 
 } /* namespace oboe */
diff --git a/services/oboeservice/OboeServiceStreamFakeHal.cpp b/services/oboeservice/OboeServiceStreamFakeHal.cpp
index dbbc860..da4099d 100644
--- a/services/oboeservice/OboeServiceStreamFakeHal.cpp
+++ b/services/oboeservice/OboeServiceStreamFakeHal.cpp
@@ -18,6 +18,8 @@
 //#define LOG_NDEBUG 0
 #include <utils/Log.h>
 
+#include <atomic>
+
 #include "AudioClock.h"
 #include "AudioEndpointParcelable.h"
 
@@ -41,6 +43,7 @@
         : OboeServiceStreamBase()
         , mStreamId(nullptr)
         , mPreviousFrameCounter(0)
+        , mOboeThread()
 {
 }
 
@@ -86,7 +89,8 @@
     // Fill in OboeStreamConfiguration
     configuration.setSampleRate(mSampleRate);
     configuration.setSamplesPerFrame(mmapInfo.channel_count);
-    configuration.setAudioFormat(OBOE_AUDIO_FORMAT_PCM16);
+    configuration.setAudioFormat(OBOE_AUDIO_FORMAT_PCM_I16);
+
     return OBOE_OK;
 }
 
@@ -117,6 +121,10 @@
     oboe_result_t result = fake_hal_start(mStreamId);
     sendServiceEvent(OBOE_SERVICE_EVENT_STARTED);
     mState = OBOE_STREAM_STATE_STARTED;
+    if (result == OBOE_OK) {
+        mThreadEnabled.store(true);
+        result = mOboeThread.start(this);
+    }
     return result;
 }
 
@@ -131,6 +139,8 @@
     mState = OBOE_STREAM_STATE_PAUSED;
     mFramesRead.reset32();
     ALOGD("OboeServiceStreamFakeHal::pause() sent OBOE_SERVICE_EVENT_PAUSED");
+    mThreadEnabled.store(false);
+    result = mOboeThread.stop();
     return result;
 }
 
@@ -166,7 +176,7 @@
         command.what = OboeServiceMessage::code::TIMESTAMP;
         mFramesRead.update32(frameCounter);
         command.timestamp.position = mFramesRead.get();
-        ALOGV("OboeServiceStreamFakeHal::sendCurrentTimestamp() HAL frames = %d, pos = %d",
+        ALOGD("OboeServiceStreamFakeHal::sendCurrentTimestamp() HAL frames = %d, pos = %d",
                 frameCounter, (int)mFramesRead.get());
         command.timestamp.timestamp = AudioClock::getNanoseconds();
         mUpMessageQueue->getFifoBuffer()->write(&command, 1);
@@ -174,17 +184,18 @@
     }
 }
 
-void OboeServiceStreamFakeHal::tickle() {
-    if (mStreamId != nullptr) {
-        switch (mState) {
-            case OBOE_STREAM_STATE_STARTING:
-            case OBOE_STREAM_STATE_STARTED:
-            case OBOE_STREAM_STATE_PAUSING:
-            case OBOE_STREAM_STATE_STOPPING:
-                sendCurrentTimestamp();
-                break;
-            default:
-                break;
+// implement Runnable
+void OboeServiceStreamFakeHal::run() {
+    TimestampScheduler timestampScheduler;
+    timestampScheduler.setBurstPeriod(mFramesPerBurst, mSampleRate);
+    timestampScheduler.start(AudioClock::getNanoseconds());
+    while(mThreadEnabled.load()) {
+        oboe_nanoseconds_t nextTime = timestampScheduler.nextAbsoluteTime();
+        if (AudioClock::getNanoseconds() >= nextTime) {
+            sendCurrentTimestamp();
+        } else  {
+            // Sleep until it is time to send the next timestamp.
+            AudioClock::sleepUntilNanoTime(nextTime);
         }
     }
 }
diff --git a/services/oboeservice/OboeServiceStreamFakeHal.h b/services/oboeservice/OboeServiceStreamFakeHal.h
index b026d34..39b952a 100644
--- a/services/oboeservice/OboeServiceStreamFakeHal.h
+++ b/services/oboeservice/OboeServiceStreamFakeHal.h
@@ -22,10 +22,13 @@
 #include "FakeAudioHal.h"
 #include "MonotonicCounter.h"
 #include "AudioEndpointParcelable.h"
+#include "TimestampScheduler.h"
 
 namespace oboe {
 
-class OboeServiceStreamFakeHal : public OboeServiceStreamBase {
+class OboeServiceStreamFakeHal
+    : public OboeServiceStreamBase
+    , public Runnable {
 
 public:
     OboeServiceStreamFakeHal();
@@ -53,12 +56,10 @@
 
     virtual oboe_result_t close() override;
 
-    virtual void tickle() override;
-
-protected:
-
     void sendCurrentTimestamp();
 
+    virtual void run() override; // to implement Runnable
+
 private:
     fake_hal_stream_ptr    mStreamId; // Move to HAL
 
@@ -68,6 +69,9 @@
     int                    mPreviousFrameCounter = 0;   // from HAL
 
     oboe_stream_state_t    mState = OBOE_STREAM_STATE_UNINITIALIZED;
+
+    OboeThread             mOboeThread;
+    std::atomic<bool>      mThreadEnabled;
 };
 
 } // namespace oboe
diff --git a/services/oboeservice/OboeThread.cpp b/services/oboeservice/OboeThread.cpp
new file mode 100644
index 0000000..9ecfa90
--- /dev/null
+++ b/services/oboeservice/OboeThread.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#define LOG_TAG "OboeService"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <pthread.h>
+
+#include <oboe/OboeDefinitions.h>
+
+#include "OboeThread.h"
+
+using namespace oboe;
+
+
+OboeThread::OboeThread() {
+    // mThread is a pthread_t of unknown size so we need memset.
+    memset(&mThread, 0, sizeof(mThread));
+}
+
+void OboeThread::dispatch() {
+    if (mRunnable != nullptr) {
+        mRunnable->run();
+    } else {
+        run();
+    }
+}
+
+// This is the entry point for the new thread created by createThread().
+// It converts the 'C' function call to a C++ method call.
+static void * OboeThread_internalThreadProc(void *arg) {
+    OboeThread *oboeThread = (OboeThread *) arg;
+    oboeThread->dispatch();
+    return nullptr;
+}
+
+oboe_result_t OboeThread::start(Runnable *runnable) {
+    if (mHasThread) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    mRunnable = runnable; // TODO use atomic?
+    int err = pthread_create(&mThread, nullptr, OboeThread_internalThreadProc, this);
+    if (err != 0) {
+        ALOGE("OboeThread::pthread_create() returned %d", err);
+        // TODO convert errno to oboe_result_t
+        return OBOE_ERROR_INTERNAL;
+    } else {
+        mHasThread = true;
+        return OBOE_OK;
+    }
+}
+
+oboe_result_t OboeThread::stop() {
+    if (!mHasThread) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    int err = pthread_join(mThread, nullptr);
+    mHasThread = false;
+    // TODO convert errno to oboe_result_t
+    return err ? OBOE_ERROR_INTERNAL : OBOE_OK;
+}
+
diff --git a/services/oboeservice/OboeThread.h b/services/oboeservice/OboeThread.h
new file mode 100644
index 0000000..48fafc7
--- /dev/null
+++ b/services/oboeservice/OboeThread.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 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_THREAD_H
+#define OBOE_THREAD_H
+
+#include <atomic>
+#include <pthread.h>
+
+#include <oboe/OboeDefinitions.h>
+
+namespace oboe {
+
+class Runnable {
+public:
+    Runnable() {};
+    virtual ~Runnable() = default;
+
+    virtual void run() {}
+};
+
+/**
+ * Abstraction for a host thread.
+ */
+class OboeThread
+{
+public:
+    OboeThread();
+    OboeThread(Runnable *runnable);
+    virtual ~OboeThread() = default;
+
+    /**
+     * Start the thread running.
+     */
+    oboe_result_t start(Runnable *runnable = nullptr);
+
+    /**
+     * Join the thread.
+     * The caller must somehow tell the thread to exit before calling join().
+     */
+    oboe_result_t stop();
+
+    /**
+     * This will get called in the thread.
+     * Override this or pass a Runnable to start().
+     */
+    virtual void run() {};
+
+    void dispatch(); // called internally from 'C' thread wrapper
+
+private:
+    Runnable*                mRunnable = nullptr; // TODO make atomic with memory barrier?
+    bool                     mHasThread = false;
+    pthread_t                mThread; // initialized in constructor
+
+};
+
+} /* namespace oboe */
+
+#endif ///OBOE_THREAD_H
diff --git a/services/oboeservice/TimestampScheduler.cpp b/services/oboeservice/TimestampScheduler.cpp
new file mode 100644
index 0000000..17d6c63
--- /dev/null
+++ b/services/oboeservice/TimestampScheduler.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// for random()
+#include <stdlib.h>
+
+#include "TimestampScheduler.h"
+
+using namespace oboe;
+
+void TimestampScheduler::start(oboe_nanoseconds_t startTime) {
+    mStartTime = startTime;
+    mLastTime = startTime;
+}
+
+oboe_nanoseconds_t TimestampScheduler::nextAbsoluteTime() {
+    int64_t periodsElapsed = (mLastTime - mStartTime) / mBurstPeriod;
+    // This is an arbitrary schedule that could probably be improved.
+    // It starts out sending a timestamp on every period because we want to
+    // get an accurate picture when the stream starts. Then it slows down
+    // to the occasional timestamps needed to detect a slow drift.
+    int64_t minPeriodsToDelay = (periodsElapsed < 10) ? 1 :
+        (periodsElapsed < 100) ? 3 :
+        (periodsElapsed < 1000) ? 10 : 50;
+    oboe_nanoseconds_t sleepTime = minPeriodsToDelay * mBurstPeriod;
+    // Generate a random rectangular distribution one burst wide so that we get
+    // an uncorrelated sampling of the MMAP pointer.
+    sleepTime += (oboe_nanoseconds_t)(random() * mBurstPeriod / RAND_MAX);
+    mLastTime += sleepTime;
+    return mLastTime;
+}
diff --git a/services/oboeservice/TimestampScheduler.h b/services/oboeservice/TimestampScheduler.h
new file mode 100644
index 0000000..041e432
--- /dev/null
+++ b/services/oboeservice/TimestampScheduler.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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_TIMESTAMP_SCHEDULER_H
+#define OBOE_TIMESTAMP_SCHEDULER_H
+
+//#include <stdlib.h> // random()
+
+#include "IOboeAudioService.h"
+#include "OboeService.h"
+#include "AudioStream.h"
+#include "fifo/FifoBuffer.h"
+#include "SharedRingBuffer.h"
+#include "AudioEndpointParcelable.h"
+
+namespace oboe {
+
+/**
+ * Schedule wakeup time for monitoring the position
+ * of an MMAP/NOIRQ buffer.
+ *
+ * Note that this object is not thread safe. Only call it from a single thread.
+ */
+class TimestampScheduler
+{
+public:
+    TimestampScheduler() {};
+    virtual ~TimestampScheduler() = default;
+
+    /**
+     * Start the schedule at the given time.
+     */
+    void start(oboe_nanoseconds_t startTime);
+
+    /**
+     * Calculate the next time that the read position should be
+     * measured.
+     */
+    oboe_nanoseconds_t nextAbsoluteTime();
+
+    void setBurstPeriod(oboe_nanoseconds_t burstPeriod) {
+        mBurstPeriod = burstPeriod;
+    }
+
+    void setBurstPeriod(oboe_size_frames_t framesPerBurst,
+                        oboe_sample_rate_t sampleRate) {
+        mBurstPeriod = OBOE_NANOS_PER_SECOND * framesPerBurst / sampleRate;
+    }
+
+    oboe_nanoseconds_t getBurstPeriod() {
+        return mBurstPeriod;
+    }
+
+private:
+    // Start with an arbitrary default so we do not divide by zero.
+    oboe_nanoseconds_t mBurstPeriod = OBOE_NANOS_PER_MILLISECOND;
+    oboe_nanoseconds_t mStartTime;
+    oboe_nanoseconds_t mLastTime;
+};
+
+} /* namespace oboe */
+
+#endif /* OBOE_TIMESTAMP_SCHEDULER_H */