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();
/**