liboboe: oboe MMAP client interface

Bug: 33347409
Test: test_oboe_api
Change-Id: I2ff3e9b57c91839c6debe91903b3e4b92e82d681
Signed-off-by: Phil Burk <philburk@google.com>
diff --git a/media/liboboe/src/Android.mk b/media/liboboe/src/Android.mk
index 7b9a906..59edcb2 100644
--- a/media/liboboe/src/Android.mk
+++ b/media/liboboe/src/Android.mk
@@ -8,28 +8,49 @@
 LOCAL_MODULE := liboboe
 LOCAL_MODULE_TAGS := optional
 
+LIBOBOE_DIR := $(TOP)/frameworks/av/media/liboboe
+LIBOBOE_SRC_DIR := $(LIBOBOE_DIR)/src
+
 LOCAL_C_INCLUDES := \
     $(call include-path-for, audio-utils) \
     frameworks/native/include \
     system/core/base/include \
     frameworks/native/media/liboboe/include/include \
     frameworks/av/media/liboboe/include \
+    frameworks/native/include \
+    $(LOCAL_PATH) \
+    $(LOCAL_PATH)/binding \
+    $(LOCAL_PATH)/client \
     $(LOCAL_PATH)/core \
-    $(LOCAL_PATH)/utility \
-    $(LOCAL_PATH)/legacy
+    $(LOCAL_PATH)/fifo \
+    $(LOCAL_PATH)/legacy \
+    $(LOCAL_PATH)/utility
 
-LOCAL_SRC_FILES += core/AudioStream.cpp
-LOCAL_SRC_FILES += core/AudioStreamBuilder.cpp
-LOCAL_SRC_FILES += core/OboeAudio.cpp
-LOCAL_SRC_FILES += legacy/AudioStreamRecord.cpp
-LOCAL_SRC_FILES += legacy/AudioStreamTrack.cpp
-LOCAL_SRC_FILES += utility/HandleTracker.cpp
-LOCAL_SRC_FILES += utility/OboeUtilities.cpp
+LOCAL_SRC_FILES = \
+    core/AudioStream.cpp \
+    core/AudioStreamBuilder.cpp \
+    core/OboeAudio.cpp \
+    legacy/AudioStreamRecord.cpp \
+    legacy/AudioStreamTrack.cpp \
+    utility/HandleTracker.cpp \
+    utility/OboeUtilities.cpp \
+    fifo/FifoBuffer.cpp \
+    fifo/FifoControllerBase.cpp \
+    client/AudioEndpoint.cpp \
+    client/AudioStreamInternal.cpp \
+    client/IsochronousClockModel.cpp \
+    binding/SharedMemoryParcelable.cpp \
+    binding/SharedRegionParcelable.cpp \
+    binding/RingBufferParcelable.cpp \
+    binding/AudioEndpointParcelable.cpp \
+    binding/OboeStreamRequest.cpp \
+    binding/OboeStreamConfiguration.cpp \
+    binding/IOboeAudioService.cpp
 
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-unused-parameter -Wall -Werror
+
 # By default, all symbols are hidden.
-LOCAL_CFLAGS += -fvisibility=hidden
+# LOCAL_CFLAGS += -fvisibility=hidden
 # OBOE_API is used to explicitly export a function or a variable as a visible symbol.
 LOCAL_CFLAGS += -DOBOE_API='__attribute__((visibility("default")))'
 
@@ -47,24 +68,41 @@
     system/core/base/include \
     frameworks/native/media/liboboe/include/include \
     frameworks/av/media/liboboe/include \
+    $(LOCAL_PATH) \
+    $(LOCAL_PATH)/binding \
+    $(LOCAL_PATH)/client \
     $(LOCAL_PATH)/core \
-    $(LOCAL_PATH)/utility \
-    $(LOCAL_PATH)/legacy
+    $(LOCAL_PATH)/fifo \
+    $(LOCAL_PATH)/legacy \
+    $(LOCAL_PATH)/utility
 
-LOCAL_SRC_FILES += core/AudioStream.cpp
-LOCAL_SRC_FILES += core/AudioStreamBuilder.cpp
-LOCAL_SRC_FILES += core/OboeAudio.cpp
-LOCAL_SRC_FILES += legacy/AudioStreamRecord.cpp
-LOCAL_SRC_FILES += legacy/AudioStreamTrack.cpp
-LOCAL_SRC_FILES += utility/HandleTracker.cpp
-LOCAL_SRC_FILES += utility/OboeUtilities.cpp
+LOCAL_SRC_FILES = core/AudioStream.cpp \
+    core/AudioStreamBuilder.cpp \
+    core/OboeAudio.cpp \
+    legacy/AudioStreamRecord.cpp \
+    legacy/AudioStreamTrack.cpp \
+    utility/HandleTracker.cpp \
+    utility/OboeUtilities.cpp \
+    fifo/FifoBuffer.cpp \
+    fifo/FifoControllerBase.cpp \
+    client/AudioEndpoint.cpp \
+    client/AudioStreamInternal.cpp \
+    client/IsochronousClockModel.cpp \
+    binding/SharedMemoryParcelable.cpp \
+    binding/SharedRegionParcelable.cpp \
+    binding/RingBufferParcelable.cpp \
+    binding/AudioEndpointParcelable.cpp \
+    binding/OboeStreamRequest.cpp \
+    binding/OboeStreamConfiguration.cpp \
+    binding/IOboeAudioService.cpp
 
-LOCAL_CFLAGS += -Wno-unused-parameter
-LOCAL_CFLAGS += -Wall -Werror
+LOCAL_CFLAGS += -Wno-unused-parameter -Wall -Werror
+
 # By default, all symbols are hidden.
-LOCAL_CFLAGS += -fvisibility=hidden
+# LOCAL_CFLAGS += -fvisibility=hidden
 # OBOE_API is used to explicitly export a function or a variable as a visible symbol.
 LOCAL_CFLAGS += -DOBOE_API='__attribute__((visibility("default")))'
 
-LOCAL_SHARED_LIBRARIES := libaudioclient liblog libutils
+LOCAL_SHARED_LIBRARIES := libaudioclient liblog libcutils libutils libbinder
+
 include $(BUILD_SHARED_LIBRARY)
diff --git a/media/liboboe/src/binding/AudioEndpointParcelable.cpp b/media/liboboe/src/binding/AudioEndpointParcelable.cpp
new file mode 100644
index 0000000..096a819
--- /dev/null
+++ b/media/liboboe/src/binding/AudioEndpointParcelable.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <sys/mman.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+
+#include "binding/OboeServiceDefinitions.h"
+#include "binding/RingBufferParcelable.h"
+#include "binding/AudioEndpointParcelable.h"
+
+using android::NO_ERROR;
+using android::status_t;
+using android::Parcel;
+using android::Parcelable;
+
+using namespace oboe;
+
+/**
+ * Container for information about the message queues plus
+ * general stream information needed by Oboe clients.
+ * It contains no addresses, just sizes, offsets and file descriptors for
+ * shared memory that can be passed through Binder.
+ */
+AudioEndpointParcelable::AudioEndpointParcelable() {}
+
+AudioEndpointParcelable::~AudioEndpointParcelable() {}
+
+/**
+ * Add the file descriptor to the table.
+ * @return index in table or negative error
+ */
+int32_t AudioEndpointParcelable::addFileDescriptor(int fd, int32_t sizeInBytes) {
+    if (mNumSharedMemories >= MAX_SHARED_MEMORIES) {
+        return OBOE_ERROR_OUT_OF_RANGE;
+    }
+    int32_t index = mNumSharedMemories++;
+    mSharedMemories[index].setup(fd, sizeInBytes);
+    return index;
+}
+
+/**
+ * The read and write must be symmetric.
+ */
+status_t AudioEndpointParcelable::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mNumSharedMemories);
+    for (int i = 0; i < mNumSharedMemories; i++) {
+        mSharedMemories[i].writeToParcel(parcel);
+    }
+    mUpMessageQueueParcelable.writeToParcel(parcel);
+    mDownMessageQueueParcelable.writeToParcel(parcel);
+    mUpDataQueueParcelable.writeToParcel(parcel);
+    mDownDataQueueParcelable.writeToParcel(parcel);
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t AudioEndpointParcelable::readFromParcel(const Parcel* parcel) {
+    parcel->readInt32(&mNumSharedMemories);
+    for (int i = 0; i < mNumSharedMemories; i++) {
+        mSharedMemories[i].readFromParcel(parcel);
+    }
+    mUpMessageQueueParcelable.readFromParcel(parcel);
+    mDownMessageQueueParcelable.readFromParcel(parcel);
+    mUpDataQueueParcelable.readFromParcel(parcel);
+    mDownDataQueueParcelable.readFromParcel(parcel);
+    return NO_ERROR; // TODO check for errors above
+}
+
+oboe_result_t AudioEndpointParcelable::resolve(EndpointDescriptor *descriptor) {
+    // TODO error check
+    mUpMessageQueueParcelable.resolve(mSharedMemories, &descriptor->upMessageQueueDescriptor);
+    mDownMessageQueueParcelable.resolve(mSharedMemories,
+                                        &descriptor->downMessageQueueDescriptor);
+    mUpDataQueueParcelable.resolve(mSharedMemories, &descriptor->upDataQueueDescriptor);
+    mDownDataQueueParcelable.resolve(mSharedMemories, &descriptor->downDataQueueDescriptor);
+    return OBOE_OK;
+}
+
+oboe_result_t AudioEndpointParcelable::validate() {
+    oboe_result_t result;
+    if (mNumSharedMemories < 0 || mNumSharedMemories >= MAX_SHARED_MEMORIES) {
+        ALOGE("AudioEndpointParcelable invalid mNumSharedMemories = %d", mNumSharedMemories);
+        return OBOE_ERROR_INTERNAL;
+    }
+    for (int i = 0; i < mNumSharedMemories; i++) {
+        result = mSharedMemories[i].validate();
+        if (result != OBOE_OK) {
+            return result;
+        }
+    }
+    if ((result = mUpMessageQueueParcelable.validate()) != OBOE_OK) {
+        ALOGE("AudioEndpointParcelable invalid mUpMessageQueueParcelable = %d", result);
+        return result;
+    }
+    if ((result = mDownMessageQueueParcelable.validate()) != OBOE_OK) {
+        ALOGE("AudioEndpointParcelable invalid mDownMessageQueueParcelable = %d", result);
+        return result;
+    }
+    if ((result = mUpDataQueueParcelable.validate()) != OBOE_OK) {
+        ALOGE("AudioEndpointParcelable invalid mUpDataQueueParcelable = %d", result);
+        return result;
+    }
+    if ((result = mDownDataQueueParcelable.validate()) != OBOE_OK) {
+        ALOGE("AudioEndpointParcelable invalid mDownDataQueueParcelable = %d", result);
+        return result;
+    }
+    return OBOE_OK;
+}
+
+void AudioEndpointParcelable::dump() {
+    ALOGD("AudioEndpointParcelable ======================================= BEGIN");
+    ALOGD("AudioEndpointParcelable mNumSharedMemories = %d", mNumSharedMemories);
+    for (int i = 0; i < mNumSharedMemories; i++) {
+        mSharedMemories[i].dump();
+    }
+    ALOGD("AudioEndpointParcelable mUpMessageQueueParcelable =========");
+    mUpMessageQueueParcelable.dump();
+    ALOGD("AudioEndpointParcelable mDownMessageQueueParcelable =======");
+    mDownMessageQueueParcelable.dump();
+    ALOGD("AudioEndpointParcelable mUpDataQueueParcelable ============");
+    mUpDataQueueParcelable.dump();
+    ALOGD("AudioEndpointParcelable mDownDataQueueParcelable ==========");
+    mDownDataQueueParcelable.dump();
+    ALOGD("AudioEndpointParcelable ======================================= END");
+}
+
diff --git a/media/liboboe/src/binding/IOboeAudioService.cpp b/media/liboboe/src/binding/IOboeAudioService.cpp
new file mode 100644
index 0000000..a3437b2
--- /dev/null
+++ b/media/liboboe/src/binding/IOboeAudioService.cpp
@@ -0,0 +1,296 @@
+/*
+ * 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.
+ */
+
+#include <oboe/OboeDefinitions.h>
+
+#include "binding/AudioEndpointParcelable.h"
+#include "binding/OboeStreamRequest.h"
+#include "binding/OboeStreamConfiguration.h"
+#include "binding/IOboeAudioService.h"
+
+namespace android {
+
+/**
+ * This is used by the Oboe Client to talk to the Oboe Service.
+ *
+ * The order of parameters in the Parcels must match with code in OboeAudioService.cpp.
+ */
+class BpOboeAudioService : public BpInterface<IOboeAudioService>
+{
+public:
+    explicit BpOboeAudioService(const sp<IBinder>& impl)
+        : BpInterface<IOboeAudioService>(impl)
+    {
+    }
+
+    virtual oboe_handle_t openStream(oboe::OboeStreamRequest &request,
+                                     oboe::OboeStreamConfiguration &configuration) override {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        request.writeToParcel(&data);
+        status_t err = remote()->transact(OPEN_STREAM, data, &reply);
+        if (err != NO_ERROR) {
+            return OBOE_ERROR_INTERNAL; // TODO consider another error
+        }
+        // parse reply
+        oboe_handle_t stream;
+        reply.readInt32(&stream);
+        configuration.readFromParcel(&reply);
+        return stream;
+    }
+
+    virtual oboe_result_t closeStream(int32_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
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        return res;
+    }
+
+    virtual oboe_result_t getStreamDescription(oboe_handle_t streamHandle,
+                                               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
+        }
+        // parse reply
+        parcelable.readFromParcel(&reply);
+        parcelable.dump();
+        oboe_result_t result = parcelable.validate();
+        if (result != OBOE_OK) {
+            return result;
+        }
+        reply.readInt32(&result);
+        return result;
+    }
+
+    // TODO should we wait for a reply?
+    virtual oboe_result_t startStream(oboe_handle_t streamHandle) override {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        data.writeInt32(streamHandle);
+        status_t err = remote()->transact(START_STREAM, data, &reply);
+        if (err != NO_ERROR) {
+            return OBOE_ERROR_INTERNAL; // TODO consider another error
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        return res;
+    }
+
+    virtual oboe_result_t pauseStream(oboe_handle_t streamHandle) override {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        data.writeInt32(streamHandle);
+        status_t err = remote()->transact(PAUSE_STREAM, data, &reply);
+        if (err != NO_ERROR) {
+            return OBOE_ERROR_INTERNAL; // TODO consider another error
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        return res;
+    }
+
+    virtual oboe_result_t flushStream(oboe_handle_t streamHandle) override {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        data.writeInt32(streamHandle);
+        status_t err = remote()->transact(FLUSH_STREAM, data, &reply);
+        if (err != NO_ERROR) {
+            return OBOE_ERROR_INTERNAL; // TODO consider another error
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        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 {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        data.writeInt32(streamHandle);
+        data.writeInt32((int32_t) clientThreadId);
+        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
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        return res;
+    }
+
+    virtual oboe_result_t unregisterAudioThread(oboe_handle_t streamHandle, pid_t clientThreadId)
+    override {
+        Parcel data, reply;
+        // send command
+        data.writeInterfaceToken(IOboeAudioService::getInterfaceDescriptor());
+        data.writeInt32(streamHandle);
+        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
+        }
+        // parse reply
+        oboe_result_t res;
+        reply.readInt32(&res);
+        return res;
+    }
+
+};
+
+// Implement an interface to the service.
+// This is here so that you don't have to link with liboboe static library.
+IMPLEMENT_META_INTERFACE(OboeAudioService, "IOboeAudioService");
+
+// The order of parameters in the Parcels must match with code in BpOboeAudioService
+
+status_t BnOboeAudioService::onTransact(uint32_t code, const Parcel& data,
+                                        Parcel* reply, uint32_t flags) {
+    OboeStream stream;
+    OboeStreamRequest request;
+    OboeStreamConfiguration configuration;
+    pid_t pid;
+    oboe_nanoseconds_t nanoseconds;
+    oboe_result_t result;
+    ALOGV("BnOboeAudioService::onTransact(%i) %i", code, flags);
+    data.checkInterface(this);
+
+    switch(code) {
+        case OPEN_STREAM: {
+            request.readFromParcel(&data);
+            stream = openStream(request, configuration);
+            ALOGD("BnOboeAudioService::onTransact OPEN_STREAM 0x%08X", stream);
+            reply->writeInt32(stream);
+            configuration.writeToParcel(reply);
+            return NO_ERROR;
+        } break;
+
+        case CLOSE_STREAM: {
+            data.readInt32(&stream);
+            ALOGD("BnOboeAudioService::onTransact CLOSE_STREAM 0x%08X", stream);
+            result = closeStream(stream);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case GET_STREAM_DESCRIPTION: {
+            data.readInt32(&stream);
+            ALOGD("BnOboeAudioService::onTransact GET_STREAM_DESCRIPTION 0x%08X", stream);
+            oboe::AudioEndpointParcelable parcelable;
+            result = getStreamDescription(stream, parcelable);
+            if (result != OBOE_OK) {
+                return -1; // FIXME
+            }
+            parcelable.dump();
+            result = parcelable.validate();
+            if (result != OBOE_OK) {
+                return -1; // FIXME
+            }
+            parcelable.writeToParcel(reply);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case START_STREAM: {
+            data.readInt32(&stream);
+            result = startStream(stream);
+            ALOGD("BnOboeAudioService::onTransact START_STREAM 0x%08X, result = %d",
+                    stream, result);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case PAUSE_STREAM: {
+            data.readInt32(&stream);
+            result = pauseStream(stream);
+            ALOGD("BnOboeAudioService::onTransact PAUSE_STREAM 0x%08X, result = %d",
+                    stream, result);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case FLUSH_STREAM: {
+            data.readInt32(&stream);
+            result = flushStream(stream);
+            ALOGD("BnOboeAudioService::onTransact FLUSH_STREAM 0x%08X, result = %d",
+                    stream, result);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case REGISTER_AUDIO_THREAD: {
+            data.readInt32(&stream);
+            data.readInt32(&pid);
+            data.readInt64(&nanoseconds);
+            result = registerAudioThread(stream, pid, nanoseconds);
+            ALOGD("BnOboeAudioService::onTransact REGISTER_AUDIO_THREAD 0x%08X, result = %d",
+                    stream, result);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        } break;
+
+        case UNREGISTER_AUDIO_THREAD: {
+            data.readInt32(&stream);
+            data.readInt32(&pid);
+            result = unregisterAudioThread(stream, pid);
+            ALOGD("BnOboeAudioService::onTransact UNREGISTER_AUDIO_THREAD 0x%08X, result = %d",
+                    stream, result);
+            reply->writeInt32(result);
+            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);
+    }
+}
+
+} /* namespace android */
diff --git a/media/liboboe/src/binding/OboeServiceDefinitions.h b/media/liboboe/src/binding/OboeServiceDefinitions.h
index 958f7a7..ad00fe2 100644
--- a/media/liboboe/src/binding/OboeServiceDefinitions.h
+++ b/media/liboboe/src/binding/OboeServiceDefinitions.h
@@ -27,6 +27,22 @@
 using android::NO_ERROR;
 using android::IBinder;
 
+namespace android {
+
+enum oboe_commands_t {
+    OPEN_STREAM = IBinder::FIRST_CALL_TRANSACTION,
+    CLOSE_STREAM,
+    GET_STREAM_DESCRIPTION,
+    START_STREAM,
+    PAUSE_STREAM,
+    FLUSH_STREAM,
+    REGISTER_AUDIO_THREAD,
+    UNREGISTER_AUDIO_THREAD,
+    TICKLE
+};
+
+} // namespace android
+
 namespace oboe {
 
 enum oboe_commands_t {
diff --git a/media/liboboe/src/binding/OboeServiceMessage.h b/media/liboboe/src/binding/OboeServiceMessage.h
new file mode 100644
index 0000000..aa13571
--- /dev/null
+++ b/media/liboboe/src/binding/OboeServiceMessage.h
@@ -0,0 +1,65 @@
+/*
+ * 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_OBOE_SERVICE_MESSAGE_H
+#define OBOE_OBOE_SERVICE_MESSAGE_H
+
+#include <stdint.h>
+
+#include <oboe/OboeDefinitions.h>
+
+namespace oboe {
+
+// TODO move this an "include" folder for the service.
+
+struct OboeMessageTimestamp {
+    oboe_position_frames_t position;
+    int64_t                deviceOffset; // add to client position to get device position
+    oboe_nanoseconds_t     timestamp;
+};
+
+typedef enum oboe_service_event_e : uint32_t {
+    OBOE_SERVICE_EVENT_STARTED,
+    OBOE_SERVICE_EVENT_PAUSED,
+    OBOE_SERVICE_EVENT_FLUSHED,
+    OBOE_SERVICE_EVENT_CLOSED,
+    OBOE_SERVICE_EVENT_DISCONNECTED
+} oboe_service_event_t;
+
+struct OboeMessageEvent {
+    oboe_service_event_t event;
+    int32_t data1;
+    int64_t data2;
+};
+
+typedef struct OboeServiceMessage_s {
+    enum class code : uint32_t {
+        NOTHING,
+        TIMESTAMP,
+        EVENT,
+    };
+
+    code what;
+    union {
+        OboeMessageTimestamp timestamp;
+        OboeMessageEvent event;
+    };
+} OboeServiceMessage;
+
+
+} /* namespace oboe */
+
+#endif //OBOE_OBOE_SERVICE_MESSAGE_H
diff --git a/media/liboboe/src/binding/OboeStreamConfiguration.cpp b/media/liboboe/src/binding/OboeStreamConfiguration.cpp
new file mode 100644
index 0000000..4b8b5b2
--- /dev/null
+++ b/media/liboboe/src/binding/OboeStreamConfiguration.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <sys/mman.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+
+#include <oboe/OboeDefinitions.h>
+
+#include "binding/OboeStreamConfiguration.h"
+
+using android::NO_ERROR;
+using android::status_t;
+using android::Parcel;
+using android::Parcelable;
+
+using namespace oboe;
+
+OboeStreamConfiguration::OboeStreamConfiguration() {}
+OboeStreamConfiguration::~OboeStreamConfiguration() {}
+
+status_t OboeStreamConfiguration::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mDeviceId);
+    parcel->writeInt32(mSampleRate);
+    parcel->writeInt32(mSamplesPerFrame);
+    parcel->writeInt32((int32_t) mAudioFormat);
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t OboeStreamConfiguration::readFromParcel(const Parcel* parcel) {
+    int32_t temp;
+    parcel->readInt32(&mDeviceId);
+    parcel->readInt32(&mSampleRate);
+    parcel->readInt32(&mSamplesPerFrame);
+    parcel->readInt32(&temp);
+    mAudioFormat = (oboe_audio_format_t) temp;
+    return NO_ERROR; // TODO check for errors above
+}
+
+oboe_result_t OboeStreamConfiguration::validate() {
+    // Validate results of the open.
+    if (mSampleRate < 0 || mSampleRate >= 8 * 48000) { // TODO review limits
+        ALOGE("OboeStreamConfiguration.validate(): invalid sampleRate = %d", mSampleRate);
+        return OBOE_ERROR_INTERNAL;
+    }
+
+    if (mSamplesPerFrame < 1 || mSamplesPerFrame >= 32) { // TODO review limits
+        ALOGE("OboeStreamConfiguration.validate() invalid samplesPerFrame = %d", mSamplesPerFrame);
+        return OBOE_ERROR_INTERNAL;
+    }
+
+    switch (mAudioFormat) {
+    case OBOE_AUDIO_FORMAT_PCM16:
+    case OBOE_AUDIO_FORMAT_PCM_FLOAT:
+    case OBOE_AUDIO_FORMAT_PCM824:
+    case OBOE_AUDIO_FORMAT_PCM32:
+        break;
+    default:
+        ALOGE("OboeStreamConfiguration.validate() invalid audioFormat = %d", mAudioFormat);
+        return OBOE_ERROR_INTERNAL;
+    }
+    return OBOE_OK;
+}
+
+void OboeStreamConfiguration::dump() {
+    ALOGD("OboeStreamConfiguration mSampleRate = %d -----", mSampleRate);
+    ALOGD("OboeStreamConfiguration mSamplesPerFrame = %d", mSamplesPerFrame);
+    ALOGD("OboeStreamConfiguration mAudioFormat = %d", (int)mAudioFormat);
+}
diff --git a/media/liboboe/src/binding/OboeStreamRequest.cpp b/media/liboboe/src/binding/OboeStreamRequest.cpp
new file mode 100644
index 0000000..5d521d0
--- /dev/null
+++ b/media/liboboe/src/binding/OboeStreamRequest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <sys/mman.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+
+#include <oboe/OboeDefinitions.h>
+
+#include "binding/OboeStreamConfiguration.h"
+#include "binding/OboeStreamRequest.h"
+
+using android::NO_ERROR;
+using android::status_t;
+using android::Parcel;
+using android::Parcelable;
+
+using namespace oboe;
+
+OboeStreamRequest::OboeStreamRequest()
+    : mConfiguration()
+    {}
+
+OboeStreamRequest::~OboeStreamRequest() {}
+
+status_t OboeStreamRequest::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32((int32_t) mUserId);
+    parcel->writeInt32((int32_t) mProcessId);
+    mConfiguration.writeToParcel(parcel);
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t OboeStreamRequest::readFromParcel(const Parcel* parcel) {
+    int32_t temp;
+    parcel->readInt32(&temp);
+    mUserId = (uid_t) temp;
+    parcel->readInt32(&temp);
+    mProcessId = (pid_t) temp;
+    mConfiguration.readFromParcel(parcel);
+    return NO_ERROR; // TODO check for errors above
+}
+
+oboe_result_t OboeStreamRequest::validate() {
+    return mConfiguration.validate();
+}
+
+void OboeStreamRequest::dump() {
+    ALOGD("OboeStreamRequest mUserId = %d -----", mUserId);
+    ALOGD("OboeStreamRequest mProcessId = %d", mProcessId);
+    mConfiguration.dump();
+}
diff --git a/media/liboboe/src/binding/RingBufferParcelable.cpp b/media/liboboe/src/binding/RingBufferParcelable.cpp
new file mode 100644
index 0000000..f097655
--- /dev/null
+++ b/media/liboboe/src/binding/RingBufferParcelable.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <binder/Parcelable.h>
+
+#include "binding/OboeServiceDefinitions.h"
+#include "binding/SharedRegionParcelable.h"
+#include "binding/RingBufferParcelable.h"
+
+using namespace oboe;
+
+RingBufferParcelable::RingBufferParcelable() {}
+RingBufferParcelable::~RingBufferParcelable() {}
+
+// TODO This assumes that all three use the same SharedMemoryParcelable
+void RingBufferParcelable::setupMemory(int32_t sharedMemoryIndex,
+                 int32_t dataMemoryOffset,
+                 int32_t dataSizeInBytes,
+                 int32_t readCounterOffset,
+                 int32_t writeCounterOffset,
+                 int32_t counterSizeBytes) {
+    mReadCounterParcelable.setup(sharedMemoryIndex, readCounterOffset, counterSizeBytes);
+    mWriteCounterParcelable.setup(sharedMemoryIndex, writeCounterOffset, counterSizeBytes);
+    mDataParcelable.setup(sharedMemoryIndex, dataMemoryOffset, dataSizeInBytes);
+}
+
+void RingBufferParcelable::setupMemory(int32_t sharedMemoryIndex,
+                 int32_t dataMemoryOffset,
+                 int32_t dataSizeInBytes) {
+    mReadCounterParcelable.setup(sharedMemoryIndex, 0, 0);
+    mWriteCounterParcelable.setup(sharedMemoryIndex, 0, 0);
+    mDataParcelable.setup(sharedMemoryIndex, dataMemoryOffset, dataSizeInBytes);
+}
+
+int32_t RingBufferParcelable::getBytesPerFrame() {
+    return mBytesPerFrame;
+}
+
+void RingBufferParcelable::setBytesPerFrame(int32_t bytesPerFrame) {
+    mBytesPerFrame = bytesPerFrame;
+}
+
+int32_t RingBufferParcelable::getFramesPerBurst() {
+    return mFramesPerBurst;
+}
+
+void RingBufferParcelable::setFramesPerBurst(int32_t framesPerBurst) {
+    mFramesPerBurst = framesPerBurst;
+}
+
+int32_t RingBufferParcelable::getCapacityInFrames() {
+    return mCapacityInFrames;
+}
+
+void RingBufferParcelable::setCapacityInFrames(int32_t capacityInFrames) {
+    mCapacityInFrames = capacityInFrames;
+}
+
+/**
+ * The read and write must be symmetric.
+ */
+status_t RingBufferParcelable::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mCapacityInFrames);
+    if (mCapacityInFrames > 0) {
+        parcel->writeInt32(mBytesPerFrame);
+        parcel->writeInt32(mFramesPerBurst);
+        parcel->writeInt32(mFlags);
+        mReadCounterParcelable.writeToParcel(parcel);
+        mWriteCounterParcelable.writeToParcel(parcel);
+        mDataParcelable.writeToParcel(parcel);
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t RingBufferParcelable::readFromParcel(const Parcel* parcel) {
+    parcel->readInt32(&mCapacityInFrames);
+    if (mCapacityInFrames > 0) {
+        parcel->readInt32(&mBytesPerFrame);
+        parcel->readInt32(&mFramesPerBurst);
+        parcel->readInt32((int32_t *)&mFlags);
+        mReadCounterParcelable.readFromParcel(parcel);
+        mWriteCounterParcelable.readFromParcel(parcel);
+        mDataParcelable.readFromParcel(parcel);
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+oboe_result_t RingBufferParcelable::resolve(SharedMemoryParcelable *memoryParcels, RingBufferDescriptor *descriptor) {
+    oboe_result_t result;
+
+    result = mReadCounterParcelable.resolve(memoryParcels,
+                                            (void **) &descriptor->readCounterAddress);
+    if (result != OBOE_OK) {
+        return result;
+    }
+
+    result = mWriteCounterParcelable.resolve(memoryParcels,
+                                             (void **) &descriptor->writeCounterAddress);
+    if (result != OBOE_OK) {
+        return result;
+    }
+
+    result = mDataParcelable.resolve(memoryParcels, (void **) &descriptor->dataAddress);
+    if (result != OBOE_OK) {
+        return result;
+    }
+
+    descriptor->bytesPerFrame = mBytesPerFrame;
+    descriptor->framesPerBurst = mFramesPerBurst;
+    descriptor->capacityInFrames = mCapacityInFrames;
+    descriptor->flags = mFlags;
+    return OBOE_OK;
+}
+
+oboe_result_t RingBufferParcelable::validate() {
+    oboe_result_t result;
+    if (mCapacityInFrames < 0 || mCapacityInFrames >= 32 * 1024) {
+        ALOGE("RingBufferParcelable invalid mCapacityInFrames = %d", mCapacityInFrames);
+        return OBOE_ERROR_INTERNAL;
+    }
+    if (mBytesPerFrame < 0 || mBytesPerFrame >= 256) {
+        ALOGE("RingBufferParcelable invalid mBytesPerFrame = %d", mBytesPerFrame);
+        return OBOE_ERROR_INTERNAL;
+    }
+    if (mFramesPerBurst < 0 || mFramesPerBurst >= 1024) {
+        ALOGE("RingBufferParcelable invalid mFramesPerBurst = %d", mFramesPerBurst);
+        return OBOE_ERROR_INTERNAL;
+    }
+    if ((result = mReadCounterParcelable.validate()) != OBOE_OK) {
+        ALOGE("RingBufferParcelable invalid mReadCounterParcelable = %d", result);
+        return result;
+    }
+    if ((result = mWriteCounterParcelable.validate()) != OBOE_OK) {
+        ALOGE("RingBufferParcelable invalid mWriteCounterParcelable = %d", result);
+        return result;
+    }
+    if ((result = mDataParcelable.validate()) != OBOE_OK) {
+        ALOGE("RingBufferParcelable invalid mDataParcelable = %d", result);
+        return result;
+    }
+    return OBOE_OK;
+}
+
+
+void RingBufferParcelable::dump() {
+    ALOGD("RingBufferParcelable mCapacityInFrames = %d ---------", mCapacityInFrames);
+    if (mCapacityInFrames > 0) {
+        ALOGD("RingBufferParcelable mBytesPerFrame = %d", mBytesPerFrame);
+        ALOGD("RingBufferParcelable mFramesPerBurst = %d", mFramesPerBurst);
+        ALOGD("RingBufferParcelable mFlags = %u", mFlags);
+        mReadCounterParcelable.dump();
+        mWriteCounterParcelable.dump();
+        mDataParcelable.dump();
+    }
+}
diff --git a/media/liboboe/src/binding/SharedMemoryParcelable.cpp b/media/liboboe/src/binding/SharedMemoryParcelable.cpp
new file mode 100644
index 0000000..5b739c0
--- /dev/null
+++ b/media/liboboe/src/binding/SharedMemoryParcelable.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <sys/mman.h>
+#include <oboe/OboeDefinitions.h>
+
+#include <binder/Parcelable.h>
+
+#include "binding/SharedMemoryParcelable.h"
+
+using android::NO_ERROR;
+using android::status_t;
+using android::Parcel;
+using android::Parcelable;
+
+using namespace oboe;
+
+SharedMemoryParcelable::SharedMemoryParcelable() {}
+SharedMemoryParcelable::~SharedMemoryParcelable() {};
+
+void SharedMemoryParcelable::setup(int fd, int32_t sizeInBytes) {
+    mFd = fd;
+    mSizeInBytes = sizeInBytes;
+}
+
+status_t SharedMemoryParcelable::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mSizeInBytes);
+    if (mSizeInBytes > 0) {
+        parcel->writeDupFileDescriptor(mFd);
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t SharedMemoryParcelable::readFromParcel(const Parcel* parcel) {
+    parcel->readInt32(&mSizeInBytes);
+    if (mSizeInBytes > 0) {
+        mFd = dup(parcel->readFileDescriptor());
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+// TODO Add code to unmmap()
+
+oboe_result_t SharedMemoryParcelable::resolve(int32_t offsetInBytes, int32_t sizeInBytes,
+                                              void **regionAddressPtr) {
+    if (offsetInBytes < 0) {
+        ALOGE("SharedMemoryParcelable illegal offsetInBytes = %d", offsetInBytes);
+        return OBOE_ERROR_OUT_OF_RANGE;
+    } else if ((offsetInBytes + sizeInBytes) > mSizeInBytes) {
+        ALOGE("SharedMemoryParcelable out of range, offsetInBytes = %d, "
+              "sizeInBytes = %d, mSizeInBytes = %d",
+              offsetInBytes, sizeInBytes, mSizeInBytes);
+        return OBOE_ERROR_OUT_OF_RANGE;
+    }
+    if (mResolvedAddress == nullptr) {
+        mResolvedAddress = (uint8_t *) mmap(0, mSizeInBytes, PROT_READ|PROT_WRITE,
+                                          MAP_SHARED, mFd, 0);
+        if (mResolvedAddress == nullptr) {
+            ALOGE("SharedMemoryParcelable mmap failed for fd = %d", mFd);
+            return OBOE_ERROR_INTERNAL;
+        }
+    }
+    *regionAddressPtr = mResolvedAddress + offsetInBytes;
+    ALOGD("SharedMemoryParcelable mResolvedAddress = %p", mResolvedAddress);
+    ALOGD("SharedMemoryParcelable offset by %d, *regionAddressPtr = %p",
+          offsetInBytes, *regionAddressPtr);
+    return OBOE_OK;
+}
+
+int32_t SharedMemoryParcelable::getSizeInBytes() {
+    return mSizeInBytes;
+}
+
+oboe_result_t SharedMemoryParcelable::validate() {
+    if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE) {
+        ALOGE("SharedMemoryParcelable invalid mSizeInBytes = %d", mSizeInBytes);
+        return OBOE_ERROR_INTERNAL;
+    }
+    if (mSizeInBytes > 0) {
+        if (mFd == -1) {
+            ALOGE("SharedMemoryParcelable uninitialized mFd = %d", mFd);
+            return OBOE_ERROR_INTERNAL;
+        }
+    }
+    return OBOE_OK;
+}
+
+void SharedMemoryParcelable::dump() {
+    ALOGD("SharedMemoryParcelable mFd = %d", mFd);
+    ALOGD("SharedMemoryParcelable mSizeInBytes = %d", mSizeInBytes);
+    ALOGD("SharedMemoryParcelable mResolvedAddress = %p", mResolvedAddress);
+}
diff --git a/media/liboboe/src/binding/SharedRegionParcelable.cpp b/media/liboboe/src/binding/SharedRegionParcelable.cpp
new file mode 100644
index 0000000..86ce8f3
--- /dev/null
+++ b/media/liboboe/src/binding/SharedRegionParcelable.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright 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.
+ */
+
+#include <stdint.h>
+
+#include <sys/mman.h>
+#include <binder/Parcelable.h>
+
+#include <oboe/OboeDefinitions.h>
+
+#include "binding/SharedMemoryParcelable.h"
+#include "binding/SharedRegionParcelable.h"
+
+using android::NO_ERROR;
+using android::status_t;
+using android::Parcel;
+using android::Parcelable;
+
+using namespace oboe;
+
+SharedRegionParcelable::SharedRegionParcelable() {}
+SharedRegionParcelable::~SharedRegionParcelable() {}
+
+void SharedRegionParcelable::setup(int32_t sharedMemoryIndex,
+                                   int32_t offsetInBytes,
+                                   int32_t sizeInBytes) {
+    mSharedMemoryIndex = sharedMemoryIndex;
+    mOffsetInBytes = offsetInBytes;
+    mSizeInBytes = sizeInBytes;
+}
+
+status_t SharedRegionParcelable::writeToParcel(Parcel* parcel) const {
+    parcel->writeInt32(mSizeInBytes);
+    if (mSizeInBytes > 0) {
+        parcel->writeInt32(mSharedMemoryIndex);
+        parcel->writeInt32(mOffsetInBytes);
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+status_t SharedRegionParcelable::readFromParcel(const Parcel* parcel) {
+    parcel->readInt32(&mSizeInBytes);
+    if (mSizeInBytes > 0) {
+        parcel->readInt32(&mSharedMemoryIndex);
+        parcel->readInt32(&mOffsetInBytes);
+    }
+    return NO_ERROR; // TODO check for errors above
+}
+
+oboe_result_t SharedRegionParcelable::resolve(SharedMemoryParcelable *memoryParcels,
+                                              void **regionAddressPtr) {
+    if (mSizeInBytes == 0) {
+        *regionAddressPtr = nullptr;
+        return OBOE_OK;
+    }
+    if (mSharedMemoryIndex < 0) {
+        ALOGE("SharedRegionParcelable invalid mSharedMemoryIndex = %d", mSharedMemoryIndex);
+        return OBOE_ERROR_INTERNAL;
+    }
+    SharedMemoryParcelable *memoryParcel = &memoryParcels[mSharedMemoryIndex];
+    return memoryParcel->resolve(mOffsetInBytes, mSizeInBytes, regionAddressPtr);
+}
+
+oboe_result_t SharedRegionParcelable::validate() {
+    if (mSizeInBytes < 0 || mSizeInBytes >= MAX_MMAP_SIZE) {
+        ALOGE("SharedRegionParcelable invalid mSizeInBytes = %d", mSizeInBytes);
+        return OBOE_ERROR_INTERNAL;
+    }
+    if (mSizeInBytes > 0) {
+        if (mOffsetInBytes < 0 || mOffsetInBytes >= MAX_MMAP_OFFSET) {
+            ALOGE("SharedRegionParcelable invalid mOffsetInBytes = %d", mOffsetInBytes);
+            return OBOE_ERROR_INTERNAL;
+        }
+        if (mSharedMemoryIndex < 0 || mSharedMemoryIndex >= MAX_SHARED_MEMORIES) {
+            ALOGE("SharedRegionParcelable invalid mSharedMemoryIndex = %d", mSharedMemoryIndex);
+            return OBOE_ERROR_INTERNAL;
+        }
+    }
+    return OBOE_OK;
+}
+
+void SharedRegionParcelable::dump() {
+    ALOGD("SharedRegionParcelable mSizeInBytes = %d -----", mSizeInBytes);
+    if (mSizeInBytes > 0) {
+        ALOGD("SharedRegionParcelable mSharedMemoryIndex = %d", mSharedMemoryIndex);
+        ALOGD("SharedRegionParcelable mOffsetInBytes = %d", mOffsetInBytes);
+    }
+}
diff --git a/media/liboboe/src/client/AudioEndpoint.cpp b/media/liboboe/src/client/AudioEndpoint.cpp
new file mode 100644
index 0000000..160c37e
--- /dev/null
+++ b/media/liboboe/src/client/AudioEndpoint.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "OboeAudio"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <cassert>
+#include <oboe/OboeDefinitions.h>
+
+#include "AudioEndpointParcelable.h"
+#include "AudioEndpoint.h"
+#include "OboeServiceMessage.h"
+
+using namespace android;
+using namespace oboe;
+
+AudioEndpoint::AudioEndpoint()
+    : mOutputFreeRunning(false)
+    , mDataReadCounter(0)
+    , mDataWriteCounter(0)
+{
+}
+
+AudioEndpoint::~AudioEndpoint()
+{
+}
+
+static void AudioEndpoint_validateQueueDescriptor(const char *type,
+                                                  const RingBufferDescriptor *descriptor) {
+    assert(descriptor->capacityInFrames > 0);
+    assert(descriptor->bytesPerFrame > 1);
+    assert(descriptor->dataAddress != nullptr);
+    ALOGD("AudioEndpoint_validateQueueDescriptor %s, dataAddress at %p ====================",
+          type,
+          descriptor->dataAddress);
+    ALOGD("AudioEndpoint_validateQueueDescriptor  readCounter at %p, writeCounter at %p",
+          descriptor->readCounterAddress,
+          descriptor->writeCounterAddress);
+
+    // Try to READ from the data area.
+    uint8_t value = descriptor->dataAddress[0];
+    ALOGD("AudioEndpoint_validateQueueDescriptor() dataAddress[0] = %d, then try to write",
+        (int) value);
+    // Try to WRITE to the data area.
+    descriptor->dataAddress[0] = value;
+    ALOGD("AudioEndpoint_validateQueueDescriptor() wrote successfully");
+
+    if (descriptor->readCounterAddress) {
+        fifo_counter_t counter = *descriptor->readCounterAddress;
+        ALOGD("AudioEndpoint_validateQueueDescriptor() *readCounterAddress = %d, now write",
+              (int) counter);
+        *descriptor->readCounterAddress = counter;
+        ALOGD("AudioEndpoint_validateQueueDescriptor() wrote readCounterAddress successfully");
+    }
+    if (descriptor->writeCounterAddress) {
+        fifo_counter_t counter = *descriptor->writeCounterAddress;
+        ALOGD("AudioEndpoint_validateQueueDescriptor() *writeCounterAddress = %d, now write",
+              (int) counter);
+        *descriptor->writeCounterAddress = counter;
+        ALOGD("AudioEndpoint_validateQueueDescriptor() wrote writeCounterAddress successfully");
+    }
+}
+
+void AudioEndpoint_validateDescriptor(const EndpointDescriptor *pEndpointDescriptor) {
+    AudioEndpoint_validateQueueDescriptor("msg", &pEndpointDescriptor->upMessageQueueDescriptor);
+    AudioEndpoint_validateQueueDescriptor("data", &pEndpointDescriptor->downDataQueueDescriptor);
+}
+
+oboe_result_t AudioEndpoint::configure(const EndpointDescriptor *pEndpointDescriptor)
+{
+    oboe_result_t result = OBOE_OK;
+    AudioEndpoint_validateDescriptor(pEndpointDescriptor); // FIXME remove after debugging
+
+    const RingBufferDescriptor *descriptor = &pEndpointDescriptor->upMessageQueueDescriptor;
+    assert(descriptor->bytesPerFrame == sizeof(OboeServiceMessage));
+    assert(descriptor->readCounterAddress != nullptr);
+    assert(descriptor->writeCounterAddress != nullptr);
+    mUpCommandQueue = new FifoBuffer(
+            descriptor->bytesPerFrame,
+            descriptor->capacityInFrames,
+            descriptor->readCounterAddress,
+            descriptor->writeCounterAddress,
+            descriptor->dataAddress
+    );
+    /* TODO mDownCommandQueue
+    if (descriptor->capacityInFrames > 0) {
+        descriptor = &pEndpointDescriptor->downMessageQueueDescriptor;
+        mDownCommandQueue = new FifoBuffer(
+                descriptor->capacityInFrames,
+                descriptor->bytesPerFrame,
+                descriptor->readCounterAddress,
+                descriptor->writeCounterAddress,
+                descriptor->dataAddress
+        );
+    }
+     */
+    descriptor = &pEndpointDescriptor->downDataQueueDescriptor;
+    assert(descriptor->capacityInFrames > 0);
+    assert(descriptor->bytesPerFrame > 1);
+    assert(descriptor->bytesPerFrame < 4 * 16); // FIXME just for initial debugging
+    assert(descriptor->framesPerBurst > 0);
+    assert(descriptor->framesPerBurst < 8 * 1024); // FIXME just for initial debugging
+    assert(descriptor->dataAddress != nullptr);
+    ALOGD("AudioEndpoint::configure() data framesPerBurst = %d", descriptor->framesPerBurst);
+    ALOGD("AudioEndpoint::configure() data readCounterAddress = %p", descriptor->readCounterAddress);
+    mOutputFreeRunning = descriptor->readCounterAddress == nullptr;
+    ALOGD("AudioEndpoint::configure() mOutputFreeRunning = %d", mOutputFreeRunning ? 1 : 0);
+    int64_t *readCounterAddress = (descriptor->readCounterAddress == nullptr)
+                                  ? &mDataReadCounter
+                                  : descriptor->readCounterAddress;
+    int64_t *writeCounterAddress = (descriptor->writeCounterAddress == nullptr)
+                                  ? &mDataWriteCounter
+                                  : descriptor->writeCounterAddress;
+    mDownDataQueue = new FifoBuffer(
+            descriptor->bytesPerFrame,
+            descriptor->capacityInFrames,
+            readCounterAddress,
+            writeCounterAddress,
+            descriptor->dataAddress
+    );
+    uint32_t threshold = descriptor->capacityInFrames / 2;
+    mDownDataQueue->setThreshold(threshold);
+    return result;
+}
+
+oboe_result_t AudioEndpoint::readUpCommand(OboeServiceMessage *commandPtr)
+{
+    return mUpCommandQueue->read(commandPtr, 1);
+}
+
+oboe_result_t AudioEndpoint::writeDataNow(const void *buffer, int32_t numFrames)
+{
+    return mDownDataQueue->write(buffer, numFrames);
+}
+
+void AudioEndpoint::setDownDataReadCounter(fifo_counter_t framesRead)
+{
+    mDownDataQueue->setReadCounter(framesRead);
+}
+
+fifo_counter_t AudioEndpoint::getDownDataReadCounter()
+{
+    return mDownDataQueue->getReadCounter();
+}
+
+void AudioEndpoint::setDownDataWriteCounter(fifo_counter_t framesRead)
+{
+    mDownDataQueue->setWriteCounter(framesRead);
+}
+
+fifo_counter_t AudioEndpoint::getDownDataWriteCounter()
+{
+    return mDownDataQueue->getWriteCounter();
+}
+
+oboe_size_frames_t AudioEndpoint::setBufferSizeInFrames(oboe_size_frames_t requestedFrames,
+                                            oboe_size_frames_t *actualFrames)
+{
+    if (requestedFrames < ENDPOINT_DATA_QUEUE_SIZE_MIN) {
+        requestedFrames = ENDPOINT_DATA_QUEUE_SIZE_MIN;
+    }
+    mDownDataQueue->setThreshold(requestedFrames);
+    *actualFrames = mDownDataQueue->getThreshold();
+    return OBOE_OK;
+}
+
+int32_t AudioEndpoint::getBufferSizeInFrames() const
+{
+    return mDownDataQueue->getThreshold();
+}
+
+int32_t AudioEndpoint::getBufferCapacityInFrames() const
+{
+    return (int32_t)mDownDataQueue->getBufferCapacityInFrames();
+}
+
+int32_t AudioEndpoint::getFullFramesAvailable()
+{
+    return mDownDataQueue->getFifoControllerBase()->getFullFramesAvailable();
+}
diff --git a/media/liboboe/src/client/AudioEndpoint.h b/media/liboboe/src/client/AudioEndpoint.h
new file mode 100644
index 0000000..6ae8b72
--- /dev/null
+++ b/media/liboboe/src/client/AudioEndpoint.h
@@ -0,0 +1,92 @@
+/*
+ * 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_AUDIO_ENDPOINT_H
+#define OBOE_AUDIO_ENDPOINT_H
+
+#include <oboe/OboeAudio.h>
+
+#include "OboeServiceMessage.h"
+#include "AudioEndpointParcelable.h"
+#include "fifo/FifoBuffer.h"
+
+namespace oboe {
+
+#define ENDPOINT_DATA_QUEUE_SIZE_MIN   64
+
+/**
+ * A sink for audio.
+ * Used by the client code.
+ */
+class AudioEndpoint {
+
+public:
+    AudioEndpoint();
+    virtual ~AudioEndpoint();
+
+    /**
+     * Configure based on the EndPointDescriptor_t.
+     */
+    oboe_result_t configure(const EndpointDescriptor *pEndpointDescriptor);
+
+    /**
+     * Read from a command passed up from the Server.
+     * @return 1 if command received, 0 for no command, or negative error.
+     */
+    oboe_result_t readUpCommand(OboeServiceMessage *commandPtr);
+
+    /**
+     * Non-blocking write.
+     * @return framesWritten or a negative error code.
+     */
+    oboe_result_t writeDataNow(const void *buffer, int32_t numFrames);
+
+    /**
+     * Set the read index in the downData queue.
+     * This is needed if the reader is not updating the index itself.
+     */
+    void setDownDataReadCounter(fifo_counter_t framesRead);
+    fifo_counter_t getDownDataReadCounter();
+
+    void setDownDataWriteCounter(fifo_counter_t framesWritten);
+    fifo_counter_t getDownDataWriteCounter();
+
+    /**
+     * The result is not valid until after configure() is called.
+     *
+     * @return true if the output buffer read position is not updated, eg. DMA
+     */
+    bool isOutputFreeRunning() const { return mOutputFreeRunning; }
+
+    int32_t setBufferSizeInFrames(oboe_size_frames_t requestedFrames,
+                                  oboe_size_frames_t *actualFrames);
+    oboe_size_frames_t getBufferSizeInFrames() const;
+
+    oboe_size_frames_t getBufferCapacityInFrames() const;
+
+    oboe_size_frames_t getFullFramesAvailable();
+
+private:
+    FifoBuffer   * mUpCommandQueue;
+    FifoBuffer   * mDownDataQueue;
+    bool           mOutputFreeRunning;
+    fifo_counter_t mDataReadCounter; // only used if free-running
+    fifo_counter_t mDataWriteCounter; // only used if free-running
+};
+
+} // namespace oboe
+
+#endif //OBOE_AUDIO_ENDPOINT_H
diff --git a/media/liboboe/src/client/AudioStreamInternal.cpp b/media/liboboe/src/client/AudioStreamInternal.cpp
new file mode 100644
index 0000000..0d169e1
--- /dev/null
+++ b/media/liboboe/src/client/AudioStreamInternal.cpp
@@ -0,0 +1,528 @@
+/*
+ * 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 "OboeAudio"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <assert.h>
+
+#include <binder/IServiceManager.h>
+
+#include <oboe/OboeAudio.h>
+
+#include "AudioClock.h"
+#include "AudioEndpointParcelable.h"
+#include "binding/OboeStreamRequest.h"
+#include "binding/OboeStreamConfiguration.h"
+#include "binding/IOboeAudioService.h"
+#include "binding/OboeServiceMessage.h"
+
+#include "AudioStreamInternal.h"
+
+#define LOG_TIMESTAMPS   0
+
+using android::String16;
+using android::IServiceManager;
+using android::defaultServiceManager;
+using android::interface_cast;
+
+using namespace oboe;
+
+// 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;
+}
+
+AudioStreamInternal::AudioStreamInternal()
+        : AudioStream()
+        , mClockModel()
+        , mAudioEndpoint()
+        , 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() {
+}
+
+oboe_result_t AudioStreamInternal::open(const AudioStreamBuilder &builder) {
+
+    oboe_result_t result = OBOE_OK;
+    OboeStreamRequest request;
+    OboeStreamConfiguration configuration;
+
+    result = AudioStream::open(builder);
+    if (result < 0) {
+        return result;
+    }
+
+    // Build the request.
+    request.setUserId(getuid());
+    request.setProcessId(getpid());
+    request.getConfiguration().setDeviceId(getDeviceId());
+    request.getConfiguration().setSampleRate(getSampleRate());
+    request.getConfiguration().setSamplesPerFrame(getSamplesPerFrame());
+    request.getConfiguration().setAudioFormat(getFormat());
+    request.dump();
+
+    mServiceStreamHandle = mService->openStream(request, configuration);
+    ALOGD("AudioStreamInternal.open(): openStream returned mServiceStreamHandle = 0x%08X",
+         (unsigned int)mServiceStreamHandle);
+    if (mServiceStreamHandle < 0) {
+        result = mServiceStreamHandle;
+        ALOGE("AudioStreamInternal.open(): acquireRealtimeStream oboe_result_t = 0x%08X", result);
+    } else {
+        result = configuration.validate();
+        if (result != OBOE_OK) {
+            close();
+            return result;
+        }
+        // Save results of the open.
+        setSampleRate(configuration.getSampleRate());
+        setSamplesPerFrame(configuration.getSamplesPerFrame());
+        setFormat(configuration.getAudioFormat());
+
+        oboe::AudioEndpointParcelable parcelable;
+        result = mService->getStreamDescription(mServiceStreamHandle, parcelable);
+        if (result != OBOE_OK) {
+            ALOGE("AudioStreamInternal.open(): getStreamDescriptor returns %d", result);
+            mService->closeStream(mServiceStreamHandle);
+            return result;
+        }
+        // resolve parcelable into a descriptor
+        parcelable.resolve(&mEndpointDescriptor);
+
+        // Configure endpoint based on descriptor.
+        mAudioEndpoint.configure(&mEndpointDescriptor);
+
+
+        mFramesPerBurst = mEndpointDescriptor.downDataQueueDescriptor.framesPerBurst;
+        assert(mFramesPerBurst >= 16);
+        assert(mEndpointDescriptor.downDataQueueDescriptor.capacityInFrames < 10 * 1024);
+
+        mClockModel.setSampleRate(getSampleRate());
+        mClockModel.setFramesPerBurst(mFramesPerBurst);
+
+        setState(OBOE_STREAM_STATE_OPEN);
+    }
+    return result;
+}
+
+oboe_result_t AudioStreamInternal::close() {
+    ALOGD("AudioStreamInternal.close(): mServiceStreamHandle = 0x%08X", mServiceStreamHandle);
+    if (mServiceStreamHandle != OBOE_HANDLE_INVALID) {
+        mService->closeStream(mServiceStreamHandle);
+        mServiceStreamHandle = OBOE_HANDLE_INVALID;
+        return OBOE_OK;
+    } else {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+}
+
+oboe_result_t AudioStreamInternal::requestStart()
+{
+    oboe_nanoseconds_t startTime;
+    ALOGD("AudioStreamInternal(): start()");
+    if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    startTime = Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC);
+    mClockModel.start(startTime);
+    processTimestamp(0, startTime);
+    setState(OBOE_STREAM_STATE_STARTING);
+    return mService->startStream(mServiceStreamHandle);
+}
+
+oboe_result_t AudioStreamInternal::requestPause()
+{
+    ALOGD("AudioStreamInternal(): pause()");
+    if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    mClockModel.stop(Oboe_getNanoseconds(OBOE_CLOCK_MONOTONIC));
+    setState(OBOE_STREAM_STATE_PAUSING);
+    return mService->pauseStream(mServiceStreamHandle);
+}
+
+oboe_result_t AudioStreamInternal::requestFlush() {
+    ALOGD("AudioStreamInternal(): flush()");
+    if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    setState(OBOE_STREAM_STATE_FLUSHING);
+    return mService->flushStream(mServiceStreamHandle);
+}
+
+void AudioStreamInternal::onFlushFromServer() {
+    ALOGD("AudioStreamInternal(): onFlushFromServer()");
+    oboe_position_frames_t readCounter = mAudioEndpoint.getDownDataReadCounter();
+    oboe_position_frames_t writeCounter = mAudioEndpoint.getDownDataWriteCounter();
+    // Bump offset so caller does not see the retrograde motion in getFramesRead().
+    oboe_position_frames_t framesFlushed = writeCounter - readCounter;
+    mFramesOffsetFromService += framesFlushed;
+    // Flush written frames by forcing writeCounter to readCounter.
+    // This is because we cannot move the read counter in the hardware.
+    mAudioEndpoint.setDownDataWriteCounter(readCounter);
+}
+
+oboe_result_t AudioStreamInternal::requestStop()
+{
+    // TODO better implementation of requestStop()
+    oboe_result_t result = requestPause();
+    if (result == OBOE_OK) {
+        oboe_stream_state_t state;
+        result = waitForStateChange(OBOE_STREAM_STATE_PAUSING,
+                                    &state,
+                                    500 * OBOE_NANOS_PER_MILLISECOND);// TODO temporary code
+        if (result == OBOE_OK) {
+            result = requestFlush();
+        }
+    }
+    return result;
+}
+
+oboe_result_t AudioStreamInternal::registerThread() {
+    ALOGD("AudioStreamInternal(): registerThread()");
+    if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    return mService->registerAudioThread(mServiceStreamHandle,
+                                         gettid(),
+                                         getPeriodNanoseconds());
+}
+
+oboe_result_t AudioStreamInternal::unregisterThread() {
+    ALOGD("AudioStreamInternal(): unregisterThread()");
+    if (mServiceStreamHandle == OBOE_HANDLE_INVALID) {
+        return OBOE_ERROR_INVALID_STATE;
+    }
+    return mService->unregisterAudioThread(mServiceStreamHandle, gettid());
+}
+
+// TODO use oboe_clockid_t all the way down to AudioClock
+oboe_result_t AudioStreamInternal::getTimestamp(clockid_t clockId,
+                           oboe_position_frames_t *framePosition,
+                           oboe_nanoseconds_t *timeNanoseconds) {
+// TODO implement using real HAL
+    oboe_nanoseconds_t time = AudioClock::getNanoseconds();
+    *framePosition = mClockModel.convertTimeToPosition(time);
+    *timeNanoseconds = time + (10 * OBOE_NANOS_PER_MILLISECOND); // Fake hardware delay
+    return OBOE_OK;
+}
+
+oboe_result_t AudioStreamInternal::updateState() {
+    return processCommands();
+}
+
+#if LOG_TIMESTAMPS
+static void AudioStreamInternal_LogTimestamp(OboeServiceMessage &command) {
+    static int64_t oldPosition = 0;
+    static oboe_nanoseconds_t oldTime = 0;
+    int64_t framePosition = command.timestamp.position;
+    oboe_nanoseconds_t nanoTime = command.timestamp.timestamp;
+    ALOGD("AudioStreamInternal() timestamp says framePosition = %08lld at nanoTime %llu",
+         (long long) framePosition,
+         (long long) nanoTime);
+    int64_t nanosDelta = nanoTime - oldTime;
+    if (nanosDelta > 0 && oldTime > 0) {
+        int64_t framesDelta = framePosition - oldPosition;
+        int64_t rate = (framesDelta * OBOE_NANOS_PER_SECOND) / nanosDelta;
+        ALOGD("AudioStreamInternal() - framesDelta = %08lld", (long long) framesDelta);
+        ALOGD("AudioStreamInternal() - nanosDelta = %08lld", (long long) nanosDelta);
+        ALOGD("AudioStreamInternal() - measured rate = %llu", (unsigned long long) rate);
+    }
+    oldPosition = framePosition;
+    oldTime = nanoTime;
+}
+#endif
+
+oboe_result_t AudioStreamInternal::onTimestampFromServer(OboeServiceMessage *message) {
+    oboe_position_frames_t framePosition = 0;
+#if LOG_TIMESTAMPS
+    AudioStreamInternal_LogTimestamp(command);
+#endif
+    framePosition = message->timestamp.position;
+    processTimestamp(framePosition, message->timestamp.timestamp);
+    return OBOE_OK;
+}
+
+oboe_result_t AudioStreamInternal::onEventFromServer(OboeServiceMessage *message) {
+    oboe_result_t result = OBOE_OK;
+    ALOGD("processCommands() got event %d", message->event.event);
+    switch (message->event.event) {
+        case OBOE_SERVICE_EVENT_STARTED:
+            ALOGD("processCommands() got OBOE_SERVICE_EVENT_STARTED");
+            setState(OBOE_STREAM_STATE_STARTED);
+            break;
+        case OBOE_SERVICE_EVENT_PAUSED:
+            ALOGD("processCommands() got OBOE_SERVICE_EVENT_PAUSED");
+            setState(OBOE_STREAM_STATE_PAUSED);
+            break;
+        case OBOE_SERVICE_EVENT_FLUSHED:
+            ALOGD("processCommands() got OBOE_SERVICE_EVENT_FLUSHED");
+            setState(OBOE_STREAM_STATE_FLUSHED);
+            onFlushFromServer();
+            break;
+        case OBOE_SERVICE_EVENT_CLOSED:
+            ALOGD("processCommands() got OBOE_SERVICE_EVENT_CLOSED");
+            setState(OBOE_STREAM_STATE_CLOSED);
+            break;
+        case OBOE_SERVICE_EVENT_DISCONNECTED:
+            result = OBOE_ERROR_DISCONNECTED;
+            ALOGW("WARNING - processCommands() OBOE_SERVICE_EVENT_DISCONNECTED");
+            break;
+        default:
+            ALOGW("WARNING - processCommands() Unrecognized event = %d",
+                 (int) message->event.event);
+            break;
+    }
+    return result;
+}
+
+// Process all the commands coming from the server.
+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) {
+            break; // no command this time, no problem
+        }
+        switch (message.what) {
+        case OboeServiceMessage::code::TIMESTAMP:
+            result = onTimestampFromServer(&message);
+            break;
+
+        case OboeServiceMessage::code::EVENT:
+            result = onEventFromServer(&message);
+            break;
+
+        default:
+            ALOGW("WARNING - AudioStreamInternal::processCommands() Unrecognized what = %d",
+                 (int) message.what);
+            result = OBOE_ERROR_UNEXPECTED_VALUE;
+            break;
+        }
+    }
+    return result;
+}
+
+// Write the data, block if needed and timeoutMillis > 0
+oboe_result_t AudioStreamInternal::write(const void *buffer, int32_t numFrames,
+                                         oboe_nanoseconds_t timeoutNanoseconds)
+{
+    oboe_result_t result = OBOE_OK;
+    uint8_t* source = (uint8_t*)buffer;
+    oboe_nanoseconds_t currentTimeNanos = AudioClock::getNanoseconds();
+    oboe_nanoseconds_t deadlineNanos = currentTimeNanos + timeoutNanoseconds;
+    int32_t framesLeft = numFrames;
+//    ALOGD("AudioStreamInternal::write(%p, %d) at time %08llu , mState = %d ------------------",
+//         buffer, numFrames, (unsigned long long) currentTimeNanos, mState);
+
+    // Write until all the data has been written or until a timeout occurs.
+    while (framesLeft > 0) {
+        // The call to writeNow() will not block. It will just write as much as it can.
+        oboe_nanoseconds_t wakeTimeNanos = 0;
+        oboe_result_t framesWritten = writeNow(source, framesLeft,
+                                               currentTimeNanos, &wakeTimeNanos);
+//        ALOGD("AudioStreamInternal::write() writeNow() framesLeft = %d --> framesWritten = %d", framesLeft, framesWritten);
+        if (framesWritten < 0) {
+            result = framesWritten;
+            break;
+        }
+        framesLeft -= (int32_t) framesWritten;
+        source += framesWritten * getBytesPerFrame();
+
+        // Should we block?
+        if (timeoutNanoseconds == 0) {
+            break; // don't block
+        } else if (framesLeft > 0) {
+            //ALOGD("AudioStreamInternal:: original wakeTimeNanos %lld", (long long) wakeTimeNanos);
+            // clip the wake time to something reasonable
+            if (wakeTimeNanos < currentTimeNanos) {
+                wakeTimeNanos = currentTimeNanos;
+            }
+            if (wakeTimeNanos > deadlineNanos) {
+                // If we time out, just return the framesWritten so far.
+                ALOGE("AudioStreamInternal::write(): timed out after %lld nanos", (long long) timeoutNanoseconds);
+                break;
+            }
+
+            //ALOGD("AudioStreamInternal:: sleep until %lld, dur = %lld", (long long) wakeTimeNanos,
+            //        (long long) (wakeTimeNanos - currentTimeNanos));
+            AudioClock::sleepForNanos(wakeTimeNanos - currentTimeNanos);
+            currentTimeNanos = AudioClock::getNanoseconds();
+        }
+    }
+
+    // return error or framesWritten
+    return (result < 0) ? result : numFrames - framesLeft;
+}
+
+// Write as much data as we can without blocking.
+oboe_result_t AudioStreamInternal::writeNow(const void *buffer, int32_t numFrames,
+                                         oboe_nanoseconds_t currentNanoTime, oboe_nanoseconds_t *wakeTimePtr) {
+    {
+        oboe_result_t result = processCommands();
+        if (result != OBOE_OK) {
+            return result;
+        }
+    }
+
+    if (mAudioEndpoint.isOutputFreeRunning()) {
+        // Update data queue based on the timing model.
+        int64_t estimatedReadCounter = mClockModel.convertTimeToPosition(currentNanoTime);
+        mAudioEndpoint.setDownDataReadCounter(estimatedReadCounter);
+        // If the read index passed the write index then consider it an underrun.
+        if (mAudioEndpoint.getFullFramesAvailable() < 0) {
+            mXRunCount++;
+        }
+    }
+    // TODO else query from endpoint cuz set by actual reader, maybe
+
+    // Write some data to the buffer.
+    int32_t framesWritten = mAudioEndpoint.writeDataNow(buffer, numFrames);
+    if (framesWritten > 0) {
+        incrementFramesWritten(framesWritten);
+    }
+    //ALOGD("AudioStreamInternal::writeNow() - tried to write %d frames, wrote %d",
+    //    numFrames, framesWritten);
+
+    // Calculate an ideal time to wake up.
+    if (wakeTimePtr != nullptr && framesWritten >= 0) {
+        // By default wake up a few milliseconds from now.  // TODO review
+        oboe_nanoseconds_t wakeTime = currentNanoTime + (2 * OBOE_NANOS_PER_MILLISECOND);
+        switch (getState()) {
+            case OBOE_STREAM_STATE_OPEN:
+            case OBOE_STREAM_STATE_STARTING:
+                if (framesWritten != 0) {
+                    // Don't wait to write more data. Just prime the buffer.
+                    wakeTime = currentNanoTime;
+                }
+                break;
+            case OBOE_STREAM_STATE_STARTED:   // When do we expect the next read burst to occur?
+                {
+                    uint32_t burstSize = mFramesPerBurst;
+                    if (burstSize < 32) {
+                        burstSize = 32; // TODO review
+                    }
+
+                    uint64_t nextReadPosition = mAudioEndpoint.getDownDataReadCounter() + burstSize;
+                    wakeTime = mClockModel.convertPositionToTime(nextReadPosition);
+                }
+                break;
+            default:
+                break;
+        }
+        *wakeTimePtr = wakeTime;
+
+    }
+//    ALOGD("AudioStreamInternal::writeNow finished: now = %llu, read# = %llu, wrote# = %llu",
+//         (unsigned long long)currentNanoTime,
+//         (unsigned long long)mAudioEndpoint.getDownDataReadCounter(),
+//         (unsigned long long)mAudioEndpoint.getDownDataWriteCounter());
+    return framesWritten;
+}
+
+oboe_result_t AudioStreamInternal::waitForStateChange(oboe_stream_state_t currentState,
+                                                      oboe_stream_state_t *nextState,
+                                                      oboe_nanoseconds_t timeoutNanoseconds)
+
+{
+    oboe_result_t result = processCommands();
+//    ALOGD("AudioStreamInternal::waitForStateChange() - processCommands() returned %d", result);
+    if (result != OBOE_OK) {
+        return result;
+    }
+    // TODO replace this polling with a timed sleep on a futex on the message queue
+    int32_t durationNanos = 5 * OBOE_NANOS_PER_MILLISECOND;
+    oboe_stream_state_t state = getState();
+//    ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
+    while (state == currentState && timeoutNanoseconds > 0) {
+        // TODO use futex from service message queue
+        if (durationNanos > timeoutNanoseconds) {
+            durationNanos = timeoutNanoseconds;
+        }
+        AudioClock::sleepForNanos(durationNanos);
+        timeoutNanoseconds -= durationNanos;
+
+        result = processCommands();
+        if (result != OBOE_OK) {
+            return result;
+        }
+
+        state = getState();
+//        ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
+    }
+    if (nextState != nullptr) {
+        *nextState = state;
+    }
+    return (state == currentState) ? OBOE_ERROR_TIMEOUT : OBOE_OK;
+}
+
+
+void AudioStreamInternal::processTimestamp(uint64_t position, oboe_nanoseconds_t time) {
+    mClockModel.processTimestamp( position, time);
+}
+
+oboe_result_t AudioStreamInternal::setBufferSize(oboe_size_frames_t requestedFrames,
+                                        oboe_size_frames_t *actualFrames) {
+    return mAudioEndpoint.setBufferSizeInFrames(requestedFrames, actualFrames);
+}
+
+oboe_size_frames_t AudioStreamInternal::getBufferSize() const
+{
+    return mAudioEndpoint.getBufferSizeInFrames();
+}
+
+oboe_size_frames_t AudioStreamInternal::getBufferCapacity() const
+{
+    return mAudioEndpoint.getBufferCapacityInFrames();
+}
+
+oboe_size_frames_t AudioStreamInternal::getFramesPerBurst() const
+{
+    return mEndpointDescriptor.downDataQueueDescriptor.framesPerBurst;
+}
+
+oboe_position_frames_t AudioStreamInternal::getFramesRead()
+{
+    oboe_position_frames_t framesRead =
+            mClockModel.convertTimeToPosition(AudioClock::getNanoseconds())
+            + mFramesOffsetFromService;
+    // Prevent retrograde motion.
+    if (framesRead < mLastFramesRead) {
+        framesRead = mLastFramesRead;
+    } else {
+        mLastFramesRead = framesRead;
+    }
+    ALOGD("AudioStreamInternal::getFramesRead() returns %lld", (long long)framesRead);
+    return framesRead;
+}
+
+// TODO implement getTimestamp
diff --git a/media/liboboe/src/client/AudioStreamInternal.h b/media/liboboe/src/client/AudioStreamInternal.h
new file mode 100644
index 0000000..6f37761
--- /dev/null
+++ b/media/liboboe/src/client/AudioStreamInternal.h
@@ -0,0 +1,129 @@
+/*
+ * 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_AUDIOSTREAMINTERNAL_H
+#define OBOE_AUDIOSTREAMINTERNAL_H
+
+#include <stdint.h>
+#include <oboe/OboeAudio.h>
+
+#include "binding/IOboeAudioService.h"
+#include "binding/AudioEndpointParcelable.h"
+#include "client/IsochronousClockModel.h"
+#include "client/AudioEndpoint.h"
+#include "core/AudioStream.h"
+
+using android::sp;
+using android::IOboeAudioService;
+
+namespace oboe {
+
+// A stream that talks to the OboeService or directly to a HAL.
+class AudioStreamInternal : public AudioStream {
+
+public:
+    AudioStreamInternal();
+    virtual ~AudioStreamInternal();
+
+    // =========== Begin ABSTRACT methods ===========================
+    virtual oboe_result_t requestStart() override;
+
+    virtual oboe_result_t requestPause() override;
+
+    virtual oboe_result_t requestFlush() override;
+
+    virtual oboe_result_t requestStop() override;
+
+    // TODO use oboe_clockid_t all the way down to AudioClock
+    virtual oboe_result_t getTimestamp(clockid_t clockId,
+                                       oboe_position_frames_t *framePosition,
+                                       oboe_nanoseconds_t *timeNanoseconds) override;
+
+
+    virtual oboe_result_t updateState() override;
+    // =========== End ABSTRACT methods ===========================
+
+    virtual oboe_result_t open(const AudioStreamBuilder &builder) override;
+
+    virtual oboe_result_t close() override;
+
+    virtual oboe_result_t write(const void *buffer,
+                             int32_t numFrames,
+                             oboe_nanoseconds_t timeoutNanoseconds) override;
+
+    virtual oboe_result_t waitForStateChange(oboe_stream_state_t currentState,
+                                          oboe_stream_state_t *nextState,
+                                          oboe_nanoseconds_t timeoutNanoseconds) override;
+
+    virtual oboe_result_t setBufferSize(oboe_size_frames_t requestedFrames,
+                                        oboe_size_frames_t *actualFrames) override;
+
+    virtual oboe_size_frames_t getBufferSize() const override;
+
+    virtual oboe_size_frames_t getBufferCapacity() const override;
+
+    virtual oboe_size_frames_t getFramesPerBurst() const override;
+
+    virtual oboe_position_frames_t getFramesRead() override;
+
+    virtual int32_t getXRunCount() const override {
+        return mXRunCount;
+    }
+
+    virtual oboe_result_t registerThread() override;
+
+    virtual oboe_result_t unregisterThread() override;
+
+protected:
+
+    oboe_result_t processCommands();
+
+/**
+ * Low level write that will not block. It will just write as much as it can.
+ *
+ * It passed back a recommended time to wake up if wakeTimePtr is not NULL.
+ *
+ * @return the number of frames written or a negative error code.
+ */
+    virtual oboe_result_t writeNow(const void *buffer,
+                                int32_t numFrames,
+                                oboe_nanoseconds_t currentTimeNanos,
+                                oboe_nanoseconds_t *wakeTimePtr);
+
+    void onFlushFromServer();
+
+    oboe_result_t onEventFromServer(OboeServiceMessage *message);
+
+    oboe_result_t onTimestampFromServer(OboeServiceMessage *message);
+
+private:
+    IsochronousClockModel    mClockModel;
+    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;
+    oboe_size_frames_t       mFramesPerBurst;
+    int32_t                  mXRunCount = 0;
+
+    void processTimestamp(uint64_t position, oboe_nanoseconds_t time);
+};
+
+} /* namespace oboe */
+
+#endif //OBOE_AUDIOSTREAMINTERNAL_H
diff --git a/media/liboboe/src/client/IsochronousClockModel.cpp b/media/liboboe/src/client/IsochronousClockModel.cpp
new file mode 100644
index 0000000..b8e5538
--- /dev/null
+++ b/media/liboboe/src/client/IsochronousClockModel.cpp
@@ -0,0 +1,177 @@
+/*
+ * 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 "OboeAudio"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <oboe/OboeDefinitions.h>
+
+#include "IsochronousClockModel.h"
+
+#define MIN_LATENESS_NANOS (10 * OBOE_NANOS_PER_MICROSECOND)
+
+using namespace android;
+using namespace oboe;
+
+IsochronousClockModel::IsochronousClockModel()
+        : mSampleRate(48000)
+        , mFramesPerBurst(64)
+        , mMaxLatenessInNanos(0)
+        , mMarkerFramePosition(0)
+        , mMarkerNanoTime(0)
+        , mState(STATE_STOPPED)
+{
+}
+
+IsochronousClockModel::~IsochronousClockModel() {
+}
+
+void IsochronousClockModel::start(oboe_nanoseconds_t nanoTime)
+{
+    mMarkerNanoTime = nanoTime;
+    mState = STATE_STARTING;
+}
+
+void IsochronousClockModel::stop(oboe_nanoseconds_t nanoTime)
+{
+    mMarkerNanoTime = nanoTime;
+    mMarkerFramePosition = convertTimeToPosition(nanoTime); // TODO should we do this?
+    mState = STATE_STOPPED;
+}
+
+void IsochronousClockModel::processTimestamp(oboe_position_frames_t framePosition,
+                                             oboe_nanoseconds_t nanoTime) {
+    int64_t framesDelta = framePosition - mMarkerFramePosition;
+    int64_t nanosDelta = nanoTime - mMarkerNanoTime;
+    if (nanosDelta < 1000) {
+        return;
+    }
+
+//    ALOGI("processTimestamp() - mMarkerFramePosition = %lld at mMarkerNanoTime %llu",
+//         (long long)mMarkerFramePosition,
+//         (long long)mMarkerNanoTime);
+//    ALOGI("processTimestamp() - framePosition = %lld at nanoTime %llu",
+//         (long long)framePosition,
+//         (long long)nanoTime);
+
+    int64_t expectedNanosDelta = convertDeltaPositionToTime(framesDelta);
+//    ALOGI("processTimestamp() - expectedNanosDelta = %lld, nanosDelta = %llu",
+//         (long long)expectedNanosDelta,
+//         (long long)nanosDelta);
+
+//    ALOGI("processTimestamp() - mSampleRate = %d", mSampleRate);
+//    ALOGI("processTimestamp() - mState = %d", mState);
+    switch (mState) {
+    case STATE_STOPPED:
+        break;
+    case STATE_STARTING:
+        mMarkerFramePosition = framePosition;
+        mMarkerNanoTime = nanoTime;
+        mState = STATE_SYNCING;
+        break;
+    case STATE_SYNCING:
+        // This will handle a burst of rapid consumption in the beginning.
+        if (nanosDelta < expectedNanosDelta) {
+            mMarkerFramePosition = framePosition;
+            mMarkerNanoTime = nanoTime;
+        } else {
+            ALOGI("processTimestamp() - advance to STATE_RUNNING");
+            mState = STATE_RUNNING;
+        }
+        break;
+    case STATE_RUNNING:
+        if (nanosDelta < expectedNanosDelta) {
+            // Earlier than expected timestamp.
+            // This data is probably more accurate so use it.
+            // or we may be drifting due to a slow HW clock.
+            mMarkerFramePosition = framePosition;
+            mMarkerNanoTime = nanoTime;
+            ALOGI("processTimestamp() - STATE_RUNNING - %d < %d micros - EARLY",
+                 (int) (nanosDelta / 1000), (int)(expectedNanosDelta / 1000));
+        } else if (nanosDelta > (expectedNanosDelta + mMaxLatenessInNanos)) {
+            // Later than expected timestamp.
+            mMarkerFramePosition = framePosition;
+            mMarkerNanoTime = nanoTime - mMaxLatenessInNanos;
+            ALOGI("processTimestamp() - STATE_RUNNING - %d > %d + %d micros - LATE",
+                 (int) (nanosDelta / 1000), (int)(expectedNanosDelta / 1000),
+                 (int) (mMaxLatenessInNanos / 1000));
+        }
+        break;
+    default:
+        break;
+    }
+    ++mTimestampCount;
+}
+
+void IsochronousClockModel::setSampleRate(int32_t sampleRate) {
+    mSampleRate = sampleRate;
+    update();
+}
+
+void IsochronousClockModel::setFramesPerBurst(int32_t framesPerBurst) {
+    mFramesPerBurst = framesPerBurst;
+    update();
+}
+
+void IsochronousClockModel::update() {
+    int64_t nanosLate = convertDeltaPositionToTime(mFramesPerBurst); // uses mSampleRate
+    mMaxLatenessInNanos = (nanosLate > MIN_LATENESS_NANOS) ? nanosLate : MIN_LATENESS_NANOS;
+}
+
+oboe_nanoseconds_t IsochronousClockModel::convertDeltaPositionToTime(
+        oboe_position_frames_t framesDelta) const {
+    return (OBOE_NANOS_PER_SECOND * framesDelta) / mSampleRate;
+}
+
+int64_t IsochronousClockModel::convertDeltaTimeToPosition(oboe_nanoseconds_t nanosDelta) const {
+    return (mSampleRate * nanosDelta) / OBOE_NANOS_PER_SECOND;
+}
+
+oboe_nanoseconds_t IsochronousClockModel::convertPositionToTime(
+        oboe_position_frames_t framePosition) const {
+    if (mState == STATE_STOPPED) {
+        return mMarkerNanoTime;
+    }
+    oboe_position_frames_t nextBurstIndex = (framePosition + mFramesPerBurst - 1) / mFramesPerBurst;
+    oboe_position_frames_t nextBurstPosition = mFramesPerBurst * nextBurstIndex;
+    oboe_position_frames_t framesDelta = nextBurstPosition - mMarkerFramePosition;
+    oboe_nanoseconds_t nanosDelta = convertDeltaPositionToTime(framesDelta);
+    oboe_nanoseconds_t time = (oboe_nanoseconds_t) (mMarkerNanoTime + nanosDelta);
+//    ALOGI("IsochronousClockModel::convertPositionToTime: pos = %llu --> time = %llu",
+//         (unsigned long long)framePosition,
+//         (unsigned long long)time);
+    return time;
+}
+
+oboe_position_frames_t IsochronousClockModel::convertTimeToPosition(
+        oboe_nanoseconds_t nanoTime) const {
+    if (mState == STATE_STOPPED) {
+        return mMarkerFramePosition;
+    }
+    oboe_nanoseconds_t nanosDelta = nanoTime - mMarkerNanoTime;
+    oboe_position_frames_t framesDelta = convertDeltaTimeToPosition(nanosDelta);
+    oboe_position_frames_t nextBurstPosition = mMarkerFramePosition + framesDelta;
+    oboe_position_frames_t nextBurstIndex = nextBurstPosition / mFramesPerBurst;
+    oboe_position_frames_t position = nextBurstIndex * mFramesPerBurst;
+//    ALOGI("IsochronousClockModel::convertTimeToPosition: time = %llu --> pos = %llu",
+//         (unsigned long long)nanoTime,
+//         (unsigned long long)position);
+//    ALOGI("IsochronousClockModel::convertTimeToPosition: framesDelta = %llu, mFramesPerBurst = %d",
+//         (long long) framesDelta, mFramesPerBurst);
+    return position;
+}
diff --git a/media/liboboe/src/client/IsochronousClockModel.h b/media/liboboe/src/client/IsochronousClockModel.h
new file mode 100644
index 0000000..97be325
--- /dev/null
+++ b/media/liboboe/src/client/IsochronousClockModel.h
@@ -0,0 +1,111 @@
+/*
+ * 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_ISOCHRONOUSCLOCKMODEL_H
+#define OBOE_ISOCHRONOUSCLOCKMODEL_H
+
+#include <stdint.h>
+#include <oboe/OboeAudio.h>
+
+namespace oboe {
+
+/**
+ * Model an isochronous data stream using occasional timestamps as input.
+ * This can be used to predict the position of the stream at a given time.
+ *
+ * This class is not thread safe and should only be called from one thread.
+ */
+class IsochronousClockModel {
+
+public:
+    IsochronousClockModel();
+    virtual ~IsochronousClockModel();
+
+    void start(oboe_nanoseconds_t nanoTime);
+    void stop(oboe_nanoseconds_t nanoTime);
+
+    void processTimestamp(oboe_position_frames_t framePosition, oboe_nanoseconds_t nanoTime);
+
+    /**
+     * @param sampleRate rate of the stream in frames per second
+     */
+    void setSampleRate(oboe_sample_rate_t sampleRate);
+
+    oboe_sample_rate_t getSampleRate() const {
+        return mSampleRate;
+    }
+
+    /**
+     * This must be set accurately in order to track the isochronous stream.
+     *
+     * @param framesPerBurst number of frames that stream advance at one time.
+     */
+    void setFramesPerBurst(oboe_size_frames_t framesPerBurst);
+
+    oboe_size_frames_t getFramesPerBurst() const {
+        return mFramesPerBurst;
+    }
+
+    /**
+     * Calculate an estimated time when the stream will be at that position.
+     *
+     * @param framePosition position of the stream in frames
+     * @return time in nanoseconds
+     */
+    oboe_nanoseconds_t convertPositionToTime(oboe_position_frames_t framePosition) const;
+
+    /**
+     * Calculate an estimated position where the stream will be at the specified time.
+     *
+     * @param nanoTime time of interest
+     * @return position in frames
+     */
+    oboe_position_frames_t convertTimeToPosition(oboe_nanoseconds_t nanoTime) const;
+
+    /**
+     * @param framesDelta difference in frames
+     * @return duration in nanoseconds
+     */
+    oboe_nanoseconds_t convertDeltaPositionToTime(oboe_position_frames_t framesDelta) const;
+
+    /**
+     * @param nanosDelta duration in nanoseconds
+     * @return frames that stream will advance in that time
+     */
+    oboe_position_frames_t convertDeltaTimeToPosition(oboe_nanoseconds_t nanosDelta) const;
+
+private:
+    enum clock_model_state_t {
+        STATE_STOPPED,
+        STATE_STARTING,
+        STATE_SYNCING,
+        STATE_RUNNING
+    };
+
+    oboe_sample_rate_t     mSampleRate;
+    oboe_size_frames_t     mFramesPerBurst;
+    int32_t                mMaxLatenessInNanos;
+    oboe_position_frames_t mMarkerFramePosition;
+    oboe_nanoseconds_t     mMarkerNanoTime;
+    int32_t                mTimestampCount;
+    clock_model_state_t     mState;
+
+    void update();
+};
+
+} /* namespace oboe */
+
+#endif //OBOE_ISOCHRONOUSCLOCKMODEL_H
diff --git a/media/liboboe/src/utility/HandleTracker.h b/media/liboboe/src/utility/HandleTracker.h
index 37dbac8..4c08321 100644
--- a/media/liboboe/src/utility/HandleTracker.h
+++ b/media/liboboe/src/utility/HandleTracker.h
@@ -41,7 +41,7 @@
     /**
      * @param maxHandles cannot exceed HANDLE_TRACKER_MAX_HANDLES
      */
-    HandleTracker(uint32_t maxHandles);
+    HandleTracker(uint32_t maxHandles = 256);
     virtual ~HandleTracker();
 
     /**