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/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp b/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp
index aedcc6e..66f018b 100644
--- a/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp
+++ b/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp
@@ -27,6 +27,7 @@
 #include "SineGenerator.h"
 
 #define NUM_SECONDS   10
+
 #define SHARING_MODE  OBOE_SHARING_MODE_EXCLUSIVE
 //#define SHARING_MODE  OBOE_SHARING_MODE_LEGACY
 
@@ -133,15 +134,18 @@
     }
 
     oboe_result_t close() {
-        stop();
-        OboeStream_close(mStream);
-        mStream = OBOE_HANDLE_INVALID;
-        OboeStreamBuilder_delete(mBuilder);
-        mBuilder = OBOE_HANDLE_INVALID;
-        delete mOutputBuffer;
-        mOutputBuffer = nullptr;
-        delete mConversionBuffer;
-        mConversionBuffer = nullptr;
+        if (mStream != OBOE_HANDLE_INVALID) {
+            stop();
+            printf("call OboeStream_close(0x%08x)\n", mStream);  fflush(stdout);
+            OboeStream_close(mStream);
+            mStream = OBOE_HANDLE_INVALID;
+            OboeStreamBuilder_delete(mBuilder);
+            mBuilder = OBOE_HANDLE_INVALID;
+            delete mOutputBuffer;
+            mOutputBuffer = nullptr;
+            delete mConversionBuffer;
+            mConversionBuffer = nullptr;
+        }
         return OBOE_OK;
     }
 
@@ -274,9 +278,9 @@
     printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
     printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
     myData.sineOsc1.setup(440.0, 48000);
-    myData.sineOsc1.setSweep(300.0, 2000.0, 5.0);
+    myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
     myData.sineOsc2.setup(660.0, 48000);
-    myData.sineOsc2.setSweep(400.0, 3000.0, 7.0);
+    myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
     myData.samplesPerFrame = player.getSamplesPerFrame();
 
     result = player.start();
diff --git a/media/liboboe/include/oboe/OboeAudio.h b/media/liboboe/include/oboe/OboeAudio.h
index 52e3f69..7ad354c 100644
--- a/media/liboboe/include/oboe/OboeAudio.h
+++ b/media/liboboe/include/oboe/OboeAudio.h
@@ -154,7 +154,7 @@
 
 
 /**
- * Request a sample data format, for example OBOE_AUDIO_FORMAT_PCM16.
+ * Request a sample data format, for example OBOE_AUDIO_FORMAT_PCM_I16.
  * The application should query for the actual format after the stream is opened.
  *
  * @return OBOE_OK or a negative error.
diff --git a/media/liboboe/include/oboe/OboeDefinitions.h b/media/liboboe/include/oboe/OboeDefinitions.h
index 9d56a24..9ad7387 100644
--- a/media/liboboe/include/oboe/OboeDefinitions.h
+++ b/media/liboboe/include/oboe/OboeDefinitions.h
@@ -67,12 +67,17 @@
 enum oboe_audio_format_t {
     OBOE_AUDIO_FORMAT_INVALID = -1,
     OBOE_AUDIO_FORMAT_UNSPECIFIED = 0,
-    OBOE_AUDIO_FORMAT_PCM16, // TODO rename to _PCM_I16
+    OBOE_AUDIO_FORMAT_PCM_I16,
     OBOE_AUDIO_FORMAT_PCM_FLOAT,
-    OBOE_AUDIO_FORMAT_PCM824, // TODO rename to _PCM_I8_24
-    OBOE_AUDIO_FORMAT_PCM32  // TODO rename to _PCM_I32
+    OBOE_AUDIO_FORMAT_PCM_I8_24,
+    OBOE_AUDIO_FORMAT_PCM_I32
 };
 
+// TODO These are deprecated. Remove these aliases once all references are replaced.
+#define OBOE_AUDIO_FORMAT_PCM16    OBOE_AUDIO_FORMAT_PCM_I16
+#define OBOE_AUDIO_FORMAT_PCM824   OBOE_AUDIO_FORMAT_PCM_I8_24
+#define OBOE_AUDIO_FORMAT_PCM32    OBOE_AUDIO_FORMAT_PCM_I32
+
 enum {
     OBOE_OK,
     OBOE_ERROR_BASE = -900, // TODO review
@@ -93,7 +98,8 @@
     OBOE_ERROR_TIMEOUT,
     OBOE_ERROR_WOULD_BLOCK,
     OBOE_ERROR_INVALID_ORDER,
-    OBOE_ERROR_OUT_OF_RANGE
+    OBOE_ERROR_OUT_OF_RANGE,
+    OBOE_ERROR_NO_SERVICE
 };
 
 typedef enum {
diff --git a/media/liboboe/src/binding/IOboeAudioService.cpp b/media/liboboe/src/binding/IOboeAudioService.cpp
index a3437b2..2584bc9 100644
--- a/media/liboboe/src/binding/IOboeAudioService.cpp
+++ b/media/liboboe/src/binding/IOboeAudioService.cpp
@@ -20,6 +20,7 @@
 #include "binding/OboeStreamRequest.h"
 #include "binding/OboeStreamConfiguration.h"
 #include "binding/IOboeAudioService.h"
+#include "utility/OboeUtilities.h"
 
 namespace android {
 
@@ -44,7 +45,7 @@
         request.writeToParcel(&data);
         status_t err = remote()->transact(OPEN_STREAM, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_handle_t stream;
@@ -53,14 +54,14 @@
         return stream;
     }
 
-    virtual oboe_result_t closeStream(int32_t streamHandle) override {
+    virtual oboe_result_t closeStream(oboe_handle_t streamHandle) override {
         Parcel data, reply;
         // send command
         data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
         data.writeInt32(streamHandle);
         status_t err = remote()->transact(CLOSE_STREAM, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -69,14 +70,14 @@
     }
 
     virtual oboe_result_t getStreamDescription(oboe_handle_t streamHandle,
-                                               AudioEndpointParcelable &parcelable)   {
+                                               oboe::AudioEndpointParcelable &parcelable)   {
         Parcel data, reply;
         // send command
         data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
         data.writeInt32(streamHandle);
         status_t err = remote()->transact(GET_STREAM_DESCRIPTION, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         parcelable.readFromParcel(&reply);
@@ -97,7 +98,7 @@
         data.writeInt32(streamHandle);
         status_t err = remote()->transact(START_STREAM, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -112,7 +113,7 @@
         data.writeInt32(streamHandle);
         status_t err = remote()->transact(PAUSE_STREAM, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -127,7 +128,7 @@
         data.writeInt32(streamHandle);
         status_t err = remote()->transact(FLUSH_STREAM, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -135,13 +136,6 @@
         return res;
     }
 
-    virtual void tickle() override { // TODO remove after service thread implemented
-        Parcel data;
-        // send command
-        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
-        remote()->transact(TICKLE, data, nullptr);
-    }
-
     virtual oboe_result_t registerAudioThread(oboe_handle_t streamHandle, pid_t clientThreadId,
                                               oboe_nanoseconds_t periodNanoseconds)
     override {
@@ -153,7 +147,7 @@
         data.writeInt64(periodNanoseconds);
         status_t err = remote()->transact(REGISTER_AUDIO_THREAD, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -170,7 +164,7 @@
         data.writeInt32((int32_t) clientThreadId);
         status_t err = remote()->transact(UNREGISTER_AUDIO_THREAD, data, &reply);
         if (err != NO_ERROR) {
-            return OBOE_ERROR_INTERNAL; // TODO consider another error
+            return OboeConvert_androidToOboeResult(err);
         }
         // parse reply
         oboe_result_t res;
@@ -189,8 +183,8 @@
 status_t BnOboeAudioService::onTransact(uint32_t code, const Parcel& data,
                                         Parcel* reply, uint32_t flags) {
     OboeStream stream;
-    OboeStreamRequest request;
-    OboeStreamConfiguration configuration;
+    oboe::OboeStreamRequest request;
+    oboe::OboeStreamConfiguration configuration;
     pid_t pid;
     oboe_nanoseconds_t nanoseconds;
     oboe_result_t result;
@@ -201,7 +195,7 @@
         case OPEN_STREAM: {
             request.readFromParcel(&data);
             stream = openStream(request, configuration);
-            ALOGD("BnOboeAudioService::onTransact OPEN_STREAM 0x%08X", stream);
+            ALOGD("BnOboeAudioService::onTransact OPEN_STREAM server handle = 0x%08X", stream);
             reply->writeInt32(stream);
             configuration.writeToParcel(reply);
             return NO_ERROR;
@@ -221,12 +215,12 @@
             oboe::AudioEndpointParcelable parcelable;
             result = getStreamDescription(stream, parcelable);
             if (result != OBOE_OK) {
-                return -1; // FIXME
+                return OboeConvert_oboeToAndroidStatus(result);
             }
             parcelable.dump();
             result = parcelable.validate();
             if (result != OBOE_OK) {
-                return -1; // FIXME
+                return OboeConvert_oboeToAndroidStatus(result);
             }
             parcelable.writeToParcel(reply);
             reply->writeInt32(result);
@@ -281,12 +275,6 @@
             return NO_ERROR;
         } break;
 
-        case TICKLE: {
-            ALOGV("BnOboeAudioService::onTransact TICKLE");
-            tickle();
-            return NO_ERROR;
-        } break;
-
         default:
             // ALOGW("BnOboeAudioService::onTransact not handled %u", code);
             return BBinder::onTransact(code, data, reply, flags);
diff --git a/media/liboboe/src/binding/IOboeAudioService.h b/media/liboboe/src/binding/IOboeAudioService.h
index 4b4c99c..e2a9c15 100644
--- a/media/liboboe/src/binding/IOboeAudioService.h
+++ b/media/liboboe/src/binding/IOboeAudioService.h
@@ -29,13 +29,6 @@
 #include "binding/OboeStreamRequest.h"
 #include "binding/OboeStreamConfiguration.h"
 
-//using android::status_t;
-//using android::IInterface;
-//using android::BnInterface;
-
-using oboe::AudioEndpointParcelable;
-using oboe::OboeStreamRequest;
-using oboe::OboeStreamConfiguration;
 
 namespace android {
 
@@ -45,16 +38,16 @@
 
     DECLARE_META_INTERFACE(OboeAudioService);
 
-    virtual oboe_handle_t openStream(OboeStreamRequest &request,
-                                     OboeStreamConfiguration &configuration) = 0;
+    virtual oboe_handle_t openStream(oboe::OboeStreamRequest &request,
+                                     oboe::OboeStreamConfiguration &configuration) = 0;
 
-    virtual oboe_result_t closeStream(int32_t streamHandle) = 0;
+    virtual oboe_result_t closeStream(oboe_handle_t streamHandle) = 0;
 
     /* Get an immutable description of the in-memory queues
     * used to communicate with the underlying HAL or Service.
     */
     virtual oboe_result_t getStreamDescription(oboe_handle_t streamHandle,
-                                               AudioEndpointParcelable &parcelable) = 0;
+                                               oboe::AudioEndpointParcelable &parcelable) = 0;
 
     /**
      * Start the flow of data.
@@ -79,14 +72,6 @@
 
     virtual oboe_result_t unregisterAudioThread(oboe_handle_t streamHandle,
                                                 pid_t clientThreadId) = 0;
-
-    /**
-     * Poke server instead of running a background thread.
-     * Cooperative multi-tasking for early development only.
-     * TODO remove tickle() when service has its own thread.
-     */
-    virtual void tickle() { };
-
 };
 
 class BnOboeAudioService : public BnInterface<IOboeAudioService> {
diff --git a/media/liboboe/src/binding/OboeServiceDefinitions.h b/media/liboboe/src/binding/OboeServiceDefinitions.h
index ad00fe2..33a192f 100644
--- a/media/liboboe/src/binding/OboeServiceDefinitions.h
+++ b/media/liboboe/src/binding/OboeServiceDefinitions.h
@@ -37,8 +37,7 @@
     PAUSE_STREAM,
     FLUSH_STREAM,
     REGISTER_AUDIO_THREAD,
-    UNREGISTER_AUDIO_THREAD,
-    TICKLE
+    UNREGISTER_AUDIO_THREAD
 };
 
 } // namespace android
@@ -53,8 +52,7 @@
     PAUSE_STREAM,
     FLUSH_STREAM,
     REGISTER_AUDIO_THREAD,
-    UNREGISTER_AUDIO_THREAD,
-    TICKLE
+    UNREGISTER_AUDIO_THREAD
 };
 
 // TODO Expand this to include all the open parameters.
diff --git a/media/liboboe/src/binding/OboeStreamConfiguration.cpp b/media/liboboe/src/binding/OboeStreamConfiguration.cpp
index 4b8b5b2..124e964 100644
--- a/media/liboboe/src/binding/OboeStreamConfiguration.cpp
+++ b/media/liboboe/src/binding/OboeStreamConfiguration.cpp
@@ -65,10 +65,10 @@
     }
 
     switch (mAudioFormat) {
-    case OBOE_AUDIO_FORMAT_PCM16:
+    case OBOE_AUDIO_FORMAT_PCM_I16:
     case OBOE_AUDIO_FORMAT_PCM_FLOAT:
-    case OBOE_AUDIO_FORMAT_PCM824:
-    case OBOE_AUDIO_FORMAT_PCM32:
+    case OBOE_AUDIO_FORMAT_PCM_I8_24:
+    case OBOE_AUDIO_FORMAT_PCM_I32:
         break;
     default:
         ALOGE("OboeStreamConfiguration.validate() invalid audioFormat = %d", mAudioFormat);
diff --git a/media/liboboe/src/client/AudioStreamInternal.cpp b/media/liboboe/src/client/AudioStreamInternal.cpp
index 0d169e1..dc6fe90 100644
--- a/media/liboboe/src/client/AudioStreamInternal.cpp
+++ b/media/liboboe/src/client/AudioStreamInternal.cpp
@@ -22,6 +22,7 @@
 #include <assert.h>
 
 #include <binder/IServiceManager.h>
+#include <utils/Mutex.h>
 
 #include <oboe/OboeAudio.h>
 
@@ -40,16 +41,40 @@
 using android::IServiceManager;
 using android::defaultServiceManager;
 using android::interface_cast;
+using android::Mutex;
 
 using namespace oboe;
 
+static android::Mutex gServiceLock;
+static sp<IOboeAudioService>  gOboeService;
+
+#define OBOE_SERVICE_NAME   "OboeAudioService"
+
 // Helper function to get access to the "OboeAudioService" service.
-static sp<IOboeAudioService> getOboeAudioService() {
-    sp<IServiceManager> sm = defaultServiceManager();
-    sp<IBinder> binder = sm->getService(String16("OboeAudioService"));
-    // TODO: If the "OboeHack" service is not running, getService times out and binder == 0.
-    sp<IOboeAudioService> service = interface_cast<IOboeAudioService>(binder);
-    return service;
+// This code was modeled after frameworks/av/media/libaudioclient/AudioSystem.cpp
+static const sp<IOboeAudioService> getOboeAudioService() {
+    sp<IBinder> binder;
+    Mutex::Autolock _l(gServiceLock);
+    if (gOboeService == 0) {
+        sp<IServiceManager> sm = defaultServiceManager();
+        // Try several times to get the service.
+        int retries = 4;
+        do {
+            binder = sm->getService(String16(OBOE_SERVICE_NAME)); // This will wait a while.
+            if (binder != 0) {
+                break;
+            }
+        } while (retries-- > 0);
+
+        if (binder != 0) {
+            // TODO Add linkToDeath() like in frameworks/av/media/libaudioclient/AudioSystem.cpp
+            // TODO Create a DeathRecipient that disconnects all active streams.
+            gOboeService = interface_cast<IOboeAudioService>(binder);
+        } else {
+            ALOGE("AudioStreamInternal could not get %s", OBOE_SERVICE_NAME);
+        }
+    }
+    return gOboeService;
 }
 
 AudioStreamInternal::AudioStreamInternal()
@@ -59,9 +84,6 @@
         , mServiceStreamHandle(OBOE_HANDLE_INVALID)
         , mFramesPerBurst(16)
 {
-    // TODO protect against mService being NULL;
-    // TODO Model access to the service on frameworks/av/media/libaudioclient/AudioSystem.cpp
-    mService = getOboeAudioService();
 }
 
 AudioStreamInternal::~AudioStreamInternal() {
@@ -69,6 +91,9 @@
 
 oboe_result_t AudioStreamInternal::open(const AudioStreamBuilder &builder) {
 
+    const sp<IOboeAudioService>& service = getOboeAudioService();
+    if (service == 0) return OBOE_ERROR_NO_SERVICE;
+
     oboe_result_t result = OBOE_OK;
     OboeStreamRequest request;
     OboeStreamConfiguration configuration;
@@ -78,7 +103,7 @@
         return result;
     }
 
-    // Build the request.
+    // Build the request to send to the server.
     request.setUserId(getuid());
     request.setProcessId(getpid());
     request.getConfiguration().setDeviceId(getDeviceId());
@@ -87,7 +112,7 @@
     request.getConfiguration().setAudioFormat(getFormat());
     request.dump();
 
-    mServiceStreamHandle = mService->openStream(request, configuration);
+    mServiceStreamHandle = service->openStream(request, configuration);
     ALOGD("AudioStreamInternal.open(): openStream returned mServiceStreamHandle = 0x%08X",
          (unsigned int)mServiceStreamHandle);
     if (mServiceStreamHandle < 0) {
@@ -105,10 +130,10 @@
         setFormat(configuration.getAudioFormat());
 
         oboe::AudioEndpointParcelable parcelable;
-        result = mService->getStreamDescription(mServiceStreamHandle, parcelable);
+        result = service->getStreamDescription(mServiceStreamHandle, parcelable);
         if (result != OBOE_OK) {
             ALOGE("AudioStreamInternal.open(): getStreamDescriptor returns %d", result);
-            mService->closeStream(mServiceStreamHandle);
+            service->closeStream(mServiceStreamHandle);
             return result;
         }
         // resolve parcelable into a descriptor
@@ -133,11 +158,14 @@
 oboe_result_t AudioStreamInternal::close() {
     ALOGD("AudioStreamInternal.close(): mServiceStreamHandle = 0x%08X", mServiceStreamHandle);
     if (mServiceStreamHandle != OBOE_HANDLE_INVALID) {
-        mService->closeStream(mServiceStreamHandle);
+        oboe_handle_t serviceStreamHandle = mServiceStreamHandle;
         mServiceStreamHandle = OBOE_HANDLE_INVALID;
+        const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+        if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
+        oboeService->closeStream(serviceStreamHandle);
         return OBOE_OK;
     } else {
-        return OBOE_ERROR_INVALID_STATE;
+        return OBOE_ERROR_INVALID_HANDLE;
     }
 }
 
@@ -148,11 +176,13 @@
     if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
         return OBOE_ERROR_INVALID_STATE;
     }
+    const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+    if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
     startTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
     mClockModel.start(startTime);
     processTimestamp(0, startTime);
     setState(OBOE_STREAM_STATE_STARTING);
-    return mService->startStream(mServiceStreamHandle);
+    return oboeService->startStream(mServiceStreamHandle);
 }
 
 oboe_result_t AudioStreamInternal::requestPause()
@@ -161,9 +191,11 @@
     if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
         return OBOE_ERROR_INVALID_STATE;
     }
+    const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+    if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
     mClockModel.stop(Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC));
     setState(OBOE_STREAM_STATE_PAUSING);
-    return mService->pauseStream(mServiceStreamHandle);
+    return oboeService->pauseStream(mServiceStreamHandle);
 }
 
 oboe_result_t AudioStreamInternal::requestFlush() {
@@ -171,8 +203,10 @@
     if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
         return OBOE_ERROR_INVALID_STATE;
     }
-    setState(OBOE_STREAM_STATE_FLUSHING);
-    return mService->flushStream(mServiceStreamHandle);
+    const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+    if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
+setState(OBOE_STREAM_STATE_FLUSHING);
+    return oboeService->flushStream(mServiceStreamHandle);
 }
 
 void AudioStreamInternal::onFlushFromServer() {
@@ -208,7 +242,9 @@
     if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
         return OBOE_ERROR_INVALID_STATE;
     }
-    return mService->registerAudioThread(mServiceStreamHandle,
+    const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+    if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
+    return oboeService->registerAudioThread(mServiceStreamHandle,
                                          gettid(),
                                          getPeriodNanoseconds());
 }
@@ -218,7 +254,9 @@
     if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
         return OBOE_ERROR_INVALID_STATE;
     }
-    return mService->unregisterAudioThread(mServiceStreamHandle, gettid());
+    const sp<IOboeAudioService>& oboeService = getOboeAudioService();
+    if (oboeService == 0) return OBOE_ERROR_NO_SERVICE;
+    return oboeService->unregisterAudioThread(mServiceStreamHandle, gettid());
 }
 
 // TODO use oboe_clockid_t all the way down to AudioClock
@@ -305,9 +343,6 @@
 oboe_result_t AudioStreamInternal::processCommands() {
     oboe_result_t result = OBOE_OK;
 
-    // Let the service run in case it is a fake service simulator.
-    mService->tickle(); // TODO use real service thread
-
     while (result == OBOE_OK) {
         OboeServiceMessage message;
         if (mAudioEndpoint.readUpCommand(&message) != 1) {
diff --git a/media/liboboe/src/client/AudioStreamInternal.h b/media/liboboe/src/client/AudioStreamInternal.h
index 6f37761..9459f97 100644
--- a/media/liboboe/src/client/AudioStreamInternal.h
+++ b/media/liboboe/src/client/AudioStreamInternal.h
@@ -114,7 +114,6 @@
     AudioEndpoint            mAudioEndpoint;
     oboe_handle_t            mServiceStreamHandle;
     EndpointDescriptor       mEndpointDescriptor;
-    sp<IOboeAudioService>    mService;
     // Offset from underlying frame position.
     oboe_position_frames_t   mFramesOffsetFromService = 0;
     oboe_position_frames_t   mLastFramesRead = 0;
diff --git a/media/liboboe/src/core/OboeAudio.cpp b/media/liboboe/src/core/OboeAudio.cpp
index d98ca36..be563b5 100644
--- a/media/liboboe/src/core/OboeAudio.cpp
+++ b/media/liboboe/src/core/OboeAudio.cpp
@@ -96,6 +96,7 @@
         OBOE_CASE_ENUM(OBOE_ERROR_WOULD_BLOCK);
         OBOE_CASE_ENUM(OBOE_ERROR_INVALID_ORDER);
         OBOE_CASE_ENUM(OBOE_ERROR_OUT_OF_RANGE);
+        OBOE_CASE_ENUM(OBOE_ERROR_NO_SERVICE);
     }
     return "Unrecognized Oboe error.";
 }
@@ -285,7 +286,6 @@
 
 OBOE_API oboe_result_t  OboeStreamBuilder_delete(OboeStreamBuilder builder)
 {
-    // TODO protect the remove() with a Mutex
     AudioStreamBuilder *streamBuilder = (AudioStreamBuilder *)
             sHandleTracker.remove(OBOE_HANDLE_TYPE_STREAM_BUILDER, builder);
     if (streamBuilder != nullptr) {
@@ -297,9 +297,9 @@
 
 OBOE_API oboe_result_t  OboeStream_close(OboeStream stream)
 {
-    // TODO protect the remove() with a Mutex
     AudioStream *audioStream = (AudioStream *)
             sHandleTracker.remove(OBOE_HANDLE_TYPE_STREAM, (oboe_handle_t)stream);
+    ALOGD("OboeStream_close(0x%08X), audioStream = %p", stream, audioStream);
     if (audioStream != nullptr) {
         audioStream->close();
         delete audioStream;
diff --git a/media/liboboe/src/legacy/AudioStreamRecord.cpp b/media/liboboe/src/legacy/AudioStreamRecord.cpp
index 5854974..bf4bd36 100644
--- a/media/liboboe/src/legacy/AudioStreamRecord.cpp
+++ b/media/liboboe/src/legacy/AudioStreamRecord.cpp
@@ -90,7 +90,7 @@
     if (status != OK) {
         close();
         ALOGE("AudioStreamRecord::open(), initCheck() returned %d", status);
-        return OboeConvert_androidToOboeError(status);
+        return OboeConvert_androidToOboeResult(status);
     }
 
     // Get the actual rate.
@@ -121,11 +121,11 @@
     // Get current position so we can detect when the track is playing.
     status_t err = mAudioRecord->getPosition(&mPositionWhenStarting);
     if (err != OK) {
-        return OboeConvert_androidToOboeError(err);
+        return OboeConvert_androidToOboeResult(err);
     }
     err = mAudioRecord->start();
     if (err != OK) {
-        return OboeConvert_androidToOboeError(err);
+        return OboeConvert_androidToOboeResult(err);
     } else {
         setState(OBOE_STREAM_STATE_STARTING);
     }
@@ -160,7 +160,7 @@
     case OBOE_STREAM_STATE_STARTING:
         err = mAudioRecord->getPosition(&position);
         if (err != OK) {
-            result = OboeConvert_androidToOboeError(err);
+            result = OboeConvert_androidToOboeResult(err);
         } else if (position != mPositionWhenStarting) {
             setState(OBOE_STREAM_STATE_STARTED);
         }
@@ -193,7 +193,7 @@
     if (bytesRead == WOULD_BLOCK) {
         return 0;
     } else if (bytesRead < 0) {
-        return OboeConvert_androidToOboeError(bytesRead);
+        return OboeConvert_androidToOboeResult(bytesRead);
     }
     oboe_size_frames_t framesRead = (oboe_size_frames_t)(bytesRead / bytesPerFrame);
     return (oboe_result_t) framesRead;
diff --git a/media/liboboe/src/legacy/AudioStreamRecord.h b/media/liboboe/src/legacy/AudioStreamRecord.h
index 02ff220..a884ed2 100644
--- a/media/liboboe/src/legacy/AudioStreamRecord.h
+++ b/media/liboboe/src/legacy/AudioStreamRecord.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef LEGACY_AUDIOSTREAMRECORD_H
-#define LEGACY_AUDIOSTREAMRECORD_H
+#ifndef LEGACY_AUDIO_STREAM_RECORD_H
+#define LEGACY_AUDIO_STREAM_RECORD_H
 
 #include <media/AudioRecord.h>
 #include <oboe/OboeAudio.h>
@@ -75,4 +75,4 @@
 
 } /* namespace oboe */
 
-#endif /* LEGACY_AUDIOSTREAMRECORD_H */
+#endif /* LEGACY_AUDIO_STREAM_RECORD_H */
diff --git a/media/liboboe/src/legacy/AudioStreamTrack.cpp b/media/liboboe/src/legacy/AudioStreamTrack.cpp
index b2c4ee1..291e56c 100644
--- a/media/liboboe/src/legacy/AudioStreamTrack.cpp
+++ b/media/liboboe/src/legacy/AudioStreamTrack.cpp
@@ -87,12 +87,10 @@
     // Did we get a valid track?
     status_t status = mAudioTrack->initCheck();
     ALOGD("AudioStreamTrack::open(), initCheck() returned %d", status);
-    // FIXME - this should work - if (status != NO_ERROR) {
-    //         But initCheck() is returning 1 !
-    if (status < 0) {
+    if (status != NO_ERROR) {
         close();
         ALOGE("AudioStreamTrack::open(), initCheck() returned %d", status);
-        return OboeConvert_androidToOboeError(status);
+        return OboeConvert_androidToOboeResult(status);
     }
 
     // Get the actual values from the AudioTrack.
@@ -123,11 +121,11 @@
     // Get current position so we can detect when the track is playing.
     status_t err = mAudioTrack->getPosition(&mPositionWhenStarting);
     if (err != OK) {
-        return OboeConvert_androidToOboeError(err);
+        return OboeConvert_androidToOboeResult(err);
     }
     err = mAudioTrack->start();
     if (err != OK) {
-        return OboeConvert_androidToOboeError(err);
+        return OboeConvert_androidToOboeResult(err);
     } else {
         setState(OBOE_STREAM_STATE_STARTING);
     }
@@ -147,7 +145,7 @@
     mAudioTrack->pause();
     status_t err = mAudioTrack->getPosition(&mPositionWhenPausing);
     if (err != OK) {
-        return OboeConvert_androidToOboeError(err);
+        return OboeConvert_androidToOboeResult(err);
     }
     return OBOE_OK;
 }
@@ -191,7 +189,7 @@
         if (mAudioTrack->stopped()) {
             err = mAudioTrack->getPosition(&position);
             if (err != OK) {
-                return OboeConvert_androidToOboeError(err);
+                return OboeConvert_androidToOboeResult(err);
             } else if (position == mPositionWhenPausing) {
                 // Has stream really stopped advancing?
                 setState(OBOE_STREAM_STATE_PAUSED);
@@ -203,7 +201,7 @@
         {
             err = mAudioTrack->getPosition(&position);
             if (err != OK) {
-                return OboeConvert_androidToOboeError(err);
+                return OboeConvert_androidToOboeResult(err);
             } else if (position == 0) {
                 // Advance frames read to match written.
                 setState(OBOE_STREAM_STATE_FLUSHED);
@@ -239,7 +237,7 @@
         return 0;
     } else if (bytesWritten < 0) {
         ALOGE("invalid write, returned %d", (int)bytesWritten);
-        return OboeConvert_androidToOboeError(bytesWritten);
+        return OboeConvert_androidToOboeResult(bytesWritten);
     }
     oboe_size_frames_t framesWritten = (oboe_size_frames_t)(bytesWritten / bytesPerFrame);
     incrementFramesWritten(framesWritten);
@@ -251,7 +249,7 @@
 {
     ssize_t result = mAudioTrack->setBufferSizeInFrames(requestedFrames);
     if (result != OK) {
-        return OboeConvert_androidToOboeError(result);
+        return OboeConvert_androidToOboeResult(result);
     } else {
         *actualFrames = result;
         return OBOE_OK;
diff --git a/media/liboboe/src/legacy/AudioStreamTrack.h b/media/liboboe/src/legacy/AudioStreamTrack.h
index 8c40884..0c41331 100644
--- a/media/liboboe/src/legacy/AudioStreamTrack.h
+++ b/media/liboboe/src/legacy/AudioStreamTrack.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef LEGACY_AUDIOSTREAMTRACK_H
-#define LEGACY_AUDIOSTREAMTRACK_H
+#ifndef LEGACY_AUDIO_STREAM_TRACK_H
+#define LEGACY_AUDIO_STREAM_TRACK_H
 
 #include <media/AudioTrack.h>
 #include <oboe/OboeAudio.h>
@@ -75,4 +75,4 @@
 
 } /* namespace oboe */
 
-#endif /* LEGACY_AUDIOSTREAMTRACK_H */
+#endif /* LEGACY_AUDIO_STREAM_TRACK_H */
diff --git a/media/liboboe/src/utility/AudioClock.h b/media/liboboe/src/utility/AudioClock.h
index 1a5c209..1779d8b 100644
--- a/media/liboboe/src/utility/AudioClock.h
+++ b/media/liboboe/src/utility/AudioClock.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef UTILITY_AUDIOCLOCK_H
-#define UTILITY_AUDIOCLOCK_H
+#ifndef UTILITY_AUDIO_CLOCK_H
+#define UTILITY_AUDIO_CLOCK_H
 
 #include <stdint.h>
 #include <time.h>
@@ -95,4 +95,4 @@
 };
 
 
-#endif // UTILITY_AUDIOCLOCK_H
+#endif // UTILITY_AUDIO_CLOCK_H
diff --git a/media/liboboe/src/utility/HandleTracker.cpp b/media/liboboe/src/utility/HandleTracker.cpp
index bf5fb63..27cc1f8 100644
--- a/media/liboboe/src/utility/HandleTracker.cpp
+++ b/media/liboboe/src/utility/HandleTracker.cpp
@@ -19,13 +19,16 @@
 //#define LOG_NDEBUG 0
 #include <utils/Log.h>
 
+#include <assert.h>
 #include <new>
 #include <stdint.h>
-#include <assert.h>
+#include <utils/Mutex.h>
 
 #include <oboe/OboeDefinitions.h>
 #include "HandleTracker.h"
 
+using android::Mutex;
+
 // Handle format is: tgggiiii
 // where each letter is 4 bits, t=type, g=generation, i=index
 
@@ -80,15 +83,17 @@
 
 HandleTracker::~HandleTracker()
 {
+    Mutex::Autolock _l(mLock);
     delete[] mHandleAddresses;
     delete[] mHandleHeaders;
+    mHandleAddresses = nullptr;
 }
 
 bool HandleTracker::isInitialized() const {
     return mHandleAddresses != nullptr;
 }
 
-handle_tracker_slot_t HandleTracker::allocateSlot() {
+handle_tracker_slot_t HandleTracker::allocateSlot_l() {
     void **allocated = mNextFreeAddress;
     if (allocated == nullptr) {
         return SLOT_UNAVAILABLE;
@@ -98,7 +103,7 @@
     return (allocated - mHandleAddresses);
 }
 
-handle_tracker_generation_t HandleTracker::nextGeneration(handle_tracker_slot_t index) {
+handle_tracker_generation_t HandleTracker::nextGeneration_l(handle_tracker_slot_t index) {
     handle_tracker_generation_t generation = (mHandleHeaders[index] + 1) & GENERATION_MASK;
     // Avoid generation zero so that 0x0 is not a valid handle.
     if (generation == GENERATION_INVALID) {
@@ -116,15 +121,17 @@
         return static_cast<oboe_handle_t>(OBOE_ERROR_NO_MEMORY);
     }
 
+    Mutex::Autolock _l(mLock);
+
     // Find an empty slot.
-    handle_tracker_slot_t index = allocateSlot();
+    handle_tracker_slot_t index = allocateSlot_l();
     if (index == SLOT_UNAVAILABLE) {
         ALOGE("HandleTracker::put() no room for more handles");
         return static_cast<oboe_handle_t>(OBOE_ERROR_NO_FREE_HANDLES);
     }
 
     // Cycle the generation counter so stale handles can be detected.
-    handle_tracker_generation_t generation = nextGeneration(index); // reads header table
+    handle_tracker_generation_t generation = nextGeneration_l(index); // reads header table
     handle_tracker_header_t inputHeader = buildHeader(type, generation);
 
     // These two writes may need to be observed by other threads or cores during get().
@@ -150,6 +157,8 @@
     }
     handle_tracker_generation_t handleGeneration = extractGeneration(handle);
     handle_tracker_header_t inputHeader = buildHeader(type, handleGeneration);
+    // We do not need to synchronize this access to mHandleHeaders because it is constant for
+    // the lifetime of the handle.
     if (inputHeader != mHandleHeaders[index]) {
         ALOGE("HandleTracker::handleToIndex() inputHeader = 0x%08x != mHandleHeaders[%d] = 0x%08x",
              inputHeader, index, mHandleHeaders[index]);
@@ -165,6 +174,8 @@
     }
     handle_tracker_slot_t index = handleToIndex(type, handle);
     if (index >= 0) {
+        // We do not need to synchronize this access to mHandleHeaders because this slot
+        // is allocated and, therefore, not part of the linked list of free slots.
         return mHandleAddresses[index];
     } else {
         return nullptr;
@@ -175,6 +186,9 @@
     if (!isInitialized()) {
         return nullptr;
     }
+
+    Mutex::Autolock _l(mLock);
+
     handle_tracker_slot_t index = handleToIndex(type,handle);
     if (index >= 0) {
         handle_tracker_address_t address = mHandleAddresses[index];
diff --git a/media/liboboe/src/utility/HandleTracker.h b/media/liboboe/src/utility/HandleTracker.h
index 4c08321..f1bead8 100644
--- a/media/liboboe/src/utility/HandleTracker.h
+++ b/media/liboboe/src/utility/HandleTracker.h
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#ifndef UTILITY_HANDLETRACKER_H
-#define UTILITY_HANDLETRACKER_H
+#ifndef UTILITY_HANDLE_TRACKER_H
+#define UTILITY_HANDLE_TRACKER_H
 
 #include <stdint.h>
+#include <utils/Mutex.h>
 
 typedef int32_t  handle_tracker_type_t;       // what kind of handle
 typedef int32_t  handle_tracker_slot_t;       // index in allocation table
@@ -53,6 +54,8 @@
     /**
      * Store a pointer and return a handle that can be used to retrieve the pointer.
      *
+     * It is safe to call put() or remove() from multiple threads.
+     *
      * @param expectedType the type of the object to be tracked
      * @param address pointer to be converted to a handle
      * @return a valid handle or a negative error
@@ -75,6 +78,8 @@
      * Free up the storage associated with the handle.
      * Subsequent attempts to use the handle will fail.
      *
+     * Do NOT remove() a handle while get() is being called for the same handle from another thread.
+     *
      * @param expectedType shouldmatch the type we passed to put()
      * @param handle to be removed from tracking
      * @return address associated with handle or nullptr if not found
@@ -83,17 +88,28 @@
 
 private:
     const int32_t               mMaxHandleCount;   // size of array
-    // This is const after initialization.
+    // This address is const after initialization.
     handle_tracker_address_t  * mHandleAddresses;  // address of objects or a free linked list node
-    // This is const after initialization.
+    // This address is const after initialization.
     handle_tracker_header_t   * mHandleHeaders;    // combination of type and generation
-    handle_tracker_address_t  * mNextFreeAddress; // head of the linked list of free nodes in mHandleAddresses
+    // head of the linked list of free nodes in mHandleAddresses
+    handle_tracker_address_t  * mNextFreeAddress;
+
+    // This Mutex protects the linked list of free nodes.
+    // The list is managed using mHandleAddresses and mNextFreeAddress.
+    // The data in mHandleHeaders is only changed by put() and remove().
+    android::Mutex              mLock;
 
     /**
      * Pull slot off of a list of empty slots.
      * @return index or a negative error
      */
-    handle_tracker_slot_t allocateSlot();
+    handle_tracker_slot_t allocateSlot_l();
+
+    /**
+     * Increment the generation for the slot, avoiding zero.
+     */
+    handle_tracker_generation_t nextGeneration_l(handle_tracker_slot_t index);
 
     /**
      * Validate the handle and return the corresponding index.
@@ -107,7 +123,7 @@
      * @param index slot index returned from allocateSlot
      * @return handle or a negative error
      */
-    oboe_handle_t buildHandle(handle_tracker_header_t header, handle_tracker_slot_t index);
+    static oboe_handle_t buildHandle(handle_tracker_header_t header, handle_tracker_slot_t index);
 
     /**
      * Combine a type and a generation field into a header.
@@ -129,11 +145,6 @@
      */
     static handle_tracker_generation_t extractGeneration(oboe_handle_t handle);
 
-    /**
-     * Increment the generation for the slot, avoiding zero.
-     */
-    handle_tracker_generation_t nextGeneration(handle_tracker_slot_t index);
-
 };
 
-#endif //UTILITY_HANDLETRACKER_H
+#endif //UTILITY_HANDLE_TRACKER_H
diff --git a/media/liboboe/src/utility/MonotonicCounter.h b/media/liboboe/src/utility/MonotonicCounter.h
index befad21..81d7f89 100644
--- a/media/liboboe/src/utility/MonotonicCounter.h
+++ b/media/liboboe/src/utility/MonotonicCounter.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef UTILITY_MONOTONICCOUNTER_H
-#define UTILITY_MONOTONICCOUNTER_H
+#ifndef UTILITY_MONOTONIC_COUNTER_H
+#define UTILITY_MONOTONIC_COUNTER_H
 
 #include <stdint.h>
 
@@ -88,4 +88,4 @@
 };
 
 
-#endif //UTILITY_MONOTONICCOUNTER_H
+#endif //UTILITY_MONOTONIC_COUNTER_H
diff --git a/media/liboboe/src/utility/OboeUtilities.cpp b/media/liboboe/src/utility/OboeUtilities.cpp
index d9d2e88..fcf4252 100644
--- a/media/liboboe/src/utility/OboeUtilities.cpp
+++ b/media/liboboe/src/utility/OboeUtilities.cpp
@@ -30,11 +30,11 @@
 oboe_size_bytes_t OboeConvert_formatToSizeInBytes(oboe_audio_format_t format) {
     oboe_size_bytes_t size = OBOE_ERROR_ILLEGAL_ARGUMENT;
     switch (format) {
-        case OBOE_AUDIO_FORMAT_PCM16:
+        case OBOE_AUDIO_FORMAT_PCM_I16:
             size = sizeof(int16_t);
             break;
-        case OBOE_AUDIO_FORMAT_PCM32:
-        case OBOE_AUDIO_FORMAT_PCM824:
+        case OBOE_AUDIO_FORMAT_PCM_I32:
+        case OBOE_AUDIO_FORMAT_PCM_I8_24:
             size = sizeof(int32_t);
             break;
         case OBOE_AUDIO_FORMAT_PCM_FLOAT:
@@ -67,14 +67,47 @@
     }
 }
 
-oboe_result_t OboeConvert_androidToOboeError(status_t error) {
-    if (error >= 0) {
-        return error;
+status_t OboeConvert_oboeToAndroidStatus(oboe_result_t result) {
+    // This covers the case for OBOE_OK and for positive results.
+    if (result >= 0) {
+        return result;
+    }
+    status_t status;
+    switch (result) {
+    case OBOE_ERROR_DISCONNECTED:
+    case OBOE_ERROR_INVALID_HANDLE:
+        status = DEAD_OBJECT;
+        break;
+    case OBOE_ERROR_INVALID_STATE:
+        status = INVALID_OPERATION;
+        break;
+    case OBOE_ERROR_UNEXPECTED_VALUE: // TODO redundant?
+    case OBOE_ERROR_ILLEGAL_ARGUMENT:
+        status = BAD_VALUE;
+        break;
+    case OBOE_ERROR_WOULD_BLOCK:
+        status = WOULD_BLOCK;
+        break;
+    // TODO add more result codes
+    default:
+        status = UNKNOWN_ERROR;
+        break;
+    }
+    return status;
+}
+
+oboe_result_t OboeConvert_androidToOboeResult(status_t status) {
+    // This covers the case for OK and for positive result.
+    if (status >= 0) {
+        return status;
     }
     oboe_result_t result;
-    switch (error) {
-    case OK:
-        result = OBOE_OK;
+    switch (status) {
+    case BAD_TYPE:
+        result = OBOE_ERROR_INVALID_HANDLE;
+        break;
+    case DEAD_OBJECT:
+        result = OBOE_ERROR_DISCONNECTED;
         break;
     case INVALID_OPERATION:
         result = OBOE_ERROR_INVALID_STATE;
@@ -85,7 +118,7 @@
     case WOULD_BLOCK:
         result = OBOE_ERROR_WOULD_BLOCK;
         break;
-    // TODO add more error codes
+    // TODO add more status codes
     default:
         result = OBOE_ERROR_INTERNAL;
         break;
@@ -96,16 +129,16 @@
 audio_format_t OboeConvert_oboeToAndroidDataFormat(oboe_audio_format_t oboeFormat) {
     audio_format_t androidFormat;
     switch (oboeFormat) {
-    case OBOE_AUDIO_FORMAT_PCM16:
+    case OBOE_AUDIO_FORMAT_PCM_I16:
         androidFormat = AUDIO_FORMAT_PCM_16_BIT;
         break;
     case OBOE_AUDIO_FORMAT_PCM_FLOAT:
         androidFormat = AUDIO_FORMAT_PCM_FLOAT;
         break;
-    case OBOE_AUDIO_FORMAT_PCM824:
+    case OBOE_AUDIO_FORMAT_PCM_I8_24:
         androidFormat = AUDIO_FORMAT_PCM_8_24_BIT;
         break;
-    case OBOE_AUDIO_FORMAT_PCM32:
+    case OBOE_AUDIO_FORMAT_PCM_I32:
         androidFormat = AUDIO_FORMAT_PCM_32_BIT;
         break;
     default:
@@ -120,16 +153,16 @@
     oboe_audio_format_t oboeFormat = OBOE_AUDIO_FORMAT_INVALID;
     switch (androidFormat) {
     case AUDIO_FORMAT_PCM_16_BIT:
-        oboeFormat = OBOE_AUDIO_FORMAT_PCM16;
+        oboeFormat = OBOE_AUDIO_FORMAT_PCM_I16;
         break;
     case AUDIO_FORMAT_PCM_FLOAT:
         oboeFormat = OBOE_AUDIO_FORMAT_PCM_FLOAT;
         break;
     case AUDIO_FORMAT_PCM_32_BIT:
-        oboeFormat = OBOE_AUDIO_FORMAT_PCM32;
+        oboeFormat = OBOE_AUDIO_FORMAT_PCM_I32;
         break;
     case AUDIO_FORMAT_PCM_8_24_BIT:
-        oboeFormat = OBOE_AUDIO_FORMAT_PCM824;
+        oboeFormat = OBOE_AUDIO_FORMAT_PCM_I8_24;
         break;
     default:
         oboeFormat = OBOE_AUDIO_FORMAT_INVALID;
diff --git a/media/liboboe/src/utility/OboeUtilities.h b/media/liboboe/src/utility/OboeUtilities.h
index 974ccf6..4096e2a 100644
--- a/media/liboboe/src/utility/OboeUtilities.h
+++ b/media/liboboe/src/utility/OboeUtilities.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef UTILITY_OBOEUTILITIES_H
-#define UTILITY_OBOEUTILITIES_H
+#ifndef UTILITY_OBOE_UTILITIES_H
+#define UTILITY_OBOE_UTILITIES_H
 
 #include <stdint.h>
 #include <sys/types.h>
@@ -25,7 +25,15 @@
 
 #include "oboe/OboeDefinitions.h"
 
-oboe_result_t OboeConvert_androidToOboeError(android::status_t error);
+/**
+ * Convert an Oboe result into the closest matching Android status.
+ */
+android::status_t OboeConvert_oboeToAndroidStatus(oboe_result_t result);
+
+/**
+ * Convert an Android status into the closest matching Oboe result.
+ */
+oboe_result_t OboeConvert_androidToOboeResult(android::status_t status);
 
 void OboeConvert_floatToPcm16(const float *source, int32_t numSamples, int16_t *destination);
 
@@ -51,4 +59,4 @@
  */
 oboe_size_bytes_t OboeConvert_formatToSizeInBytes(oboe_audio_format_t format);
 
-#endif //UTILITY_OBOEUTILITIES_H
+#endif //UTILITY_OBOE_UTILITIES_H
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 */