aaudio: lower latency using MMAP capture

MMAP can be enabled by setting system properties.

Bug: 38267780
Test: input_monitor.cpp
Change-Id: I5e86fd1d9baef4fe59837ccbca7971acbb54d8b5
Signed-off-by: Phil Burk <philburk@google.com>
diff --git a/services/oboeservice/AAudioEndpointManager.cpp b/services/oboeservice/AAudioEndpointManager.cpp
index 65b17bc..2cb0cba 100644
--- a/services/oboeservice/AAudioEndpointManager.cpp
+++ b/services/oboeservice/AAudioEndpointManager.cpp
@@ -21,10 +21,8 @@
 #include <assert.h>
 #include <map>
 #include <mutex>
-#include <utils/Singleton.h>
 
 #include "AAudioEndpointManager.h"
-#include "AAudioServiceEndpoint.h"
 
 using namespace android;
 using namespace aaudio;
@@ -55,32 +53,36 @@
             assert(false); // There are only two possible directions.
             break;
     }
-
-    // If we can't find an existing one then open one.
     ALOGD("AAudioEndpointManager::openEndpoint(), found %p", endpoint);
+
+    // If we can't find an existing one then open a new one.
     if (endpoint == nullptr) {
-        endpoint = new AAudioServiceEndpoint(audioService);
-        if (endpoint->open(deviceId, direction) != AAUDIO_OK) {
-            ALOGE("AAudioEndpointManager::findEndpoint(), open failed");
-            delete endpoint;
-            endpoint = nullptr;
-        } else {
-            switch(direction) {
-                case AAUDIO_DIRECTION_INPUT:
-                    mInputs[deviceId] = endpoint;
-                    break;
-                case AAUDIO_DIRECTION_OUTPUT:
-                    mOutputs[deviceId] = endpoint;
-                    break;
+        if (direction == AAUDIO_DIRECTION_INPUT) {
+            AAudioServiceEndpointCapture *capture = new AAudioServiceEndpointCapture(audioService);
+            if (capture->open(deviceId) != AAUDIO_OK) {
+                ALOGE("AAudioEndpointManager::openEndpoint(), open failed");
+                delete capture;
+            } else {
+                mInputs[deviceId] = capture;
+                endpoint = capture;
+            }
+        } else if (direction == AAUDIO_DIRECTION_OUTPUT) {
+            AAudioServiceEndpointPlay *player = new AAudioServiceEndpointPlay(audioService);
+            if (player->open(deviceId) != AAUDIO_OK) {
+                ALOGE("AAudioEndpointManager::openEndpoint(), open failed");
+                delete player;
+            } else {
+                mOutputs[deviceId] = player;
+                endpoint = player;
             }
         }
+
     }
 
     if (endpoint != nullptr) {
         // Increment the reference count under this lock.
         endpoint->setReferenceCount(endpoint->getReferenceCount() + 1);
     }
-
     return endpoint;
 }
 
@@ -105,6 +107,7 @@
                 mOutputs.erase(deviceId);
                 break;
         }
+
         serviceEndpoint->close();
         delete serviceEndpoint;
     }
diff --git a/services/oboeservice/AAudioEndpointManager.h b/services/oboeservice/AAudioEndpointManager.h
index bbcfc1d..db1103d 100644
--- a/services/oboeservice/AAudioEndpointManager.h
+++ b/services/oboeservice/AAudioEndpointManager.h
@@ -23,6 +23,8 @@
 
 #include "binding/AAudioServiceMessage.h"
 #include "AAudioServiceEndpoint.h"
+#include "AAudioServiceEndpointCapture.h"
+#include "AAudioServiceEndpointPlay.h"
 
 namespace aaudio {
 
@@ -49,10 +51,8 @@
 
     std::mutex    mLock;
 
-    // We need separate inputs and outputs because they may both have device==0.
-    // TODO review
-    std::map<int32_t, AAudioServiceEndpoint *> mInputs;
-    std::map<int32_t, AAudioServiceEndpoint *> mOutputs;
+    std::map<int32_t, AAudioServiceEndpointCapture *> mInputs;
+    std::map<int32_t, AAudioServiceEndpointPlay *> mOutputs;
 
 };
 
diff --git a/services/oboeservice/AAudioServiceEndpoint.cpp b/services/oboeservice/AAudioServiceEndpoint.cpp
index 57dc58b..e7d9e0d 100644
--- a/services/oboeservice/AAudioServiceEndpoint.cpp
+++ b/services/oboeservice/AAudioServiceEndpoint.cpp
@@ -44,43 +44,23 @@
 // This is the maximum size in frames. The effective size can be tuned smaller at runtime.
 #define DEFAULT_BUFFER_CAPACITY   (48 * 8)
 
-// The mStreamInternal will use a service interface that does not go through Binder.
-AAudioServiceEndpoint::AAudioServiceEndpoint(AAudioService &audioService)
-        : mStreamInternal(audioService, true)
-        {
-}
-
-AAudioServiceEndpoint::~AAudioServiceEndpoint() {
-}
-
 // Set up an EXCLUSIVE MMAP stream that will be shared.
-aaudio_result_t AAudioServiceEndpoint::open(int32_t deviceId, aaudio_direction_t direction) {
+aaudio_result_t AAudioServiceEndpoint::open(int32_t deviceId) {
+    mStreamInternal = getStreamInternal();
+
     AudioStreamBuilder builder;
     builder.setSharingMode(AAUDIO_SHARING_MODE_EXCLUSIVE);
     // Don't fall back to SHARED because that would cause recursion.
     builder.setSharingModeMatchRequired(true);
     builder.setDeviceId(deviceId);
-    builder.setDirection(direction);
+    builder.setDirection(getDirection());
     builder.setBufferCapacity(DEFAULT_BUFFER_CAPACITY);
 
-    aaudio_result_t result = mStreamInternal.open(builder);
-    if (result == AAUDIO_OK) {
-        mMixer.allocate(mStreamInternal.getSamplesPerFrame(), mStreamInternal.getFramesPerBurst());
-
-        int32_t burstsPerBuffer = AAudioProperty_getMixerBursts();
-        if (burstsPerBuffer == 0) {
-            mLatencyTuningEnabled = true;
-            burstsPerBuffer = 2;
-        }
-        ALOGD("AAudioServiceEndpoint(): burstsPerBuffer = %d", burstsPerBuffer);
-        int32_t desiredBufferSize = burstsPerBuffer * mStreamInternal.getFramesPerBurst();
-        mStreamInternal.setBufferSize(desiredBufferSize);
-    }
-    return result;
+    return getStreamInternal()->open(builder);
 }
 
 aaudio_result_t AAudioServiceEndpoint::close() {
-    return mStreamInternal.close();
+    return getStreamInternal()->close();
 }
 
 // TODO, maybe use an interface to reduce exposure
@@ -102,96 +82,51 @@
     std::lock_guard<std::mutex> lock(mLockStreams);
     mRunningStreams.push_back(sharedStream);
     if (mRunningStreams.size() == 1) {
-        startMixer_l();
+        startSharingThread_l();
     }
     return AAUDIO_OK;
 }
 
 aaudio_result_t AAudioServiceEndpoint::stopStream(AAudioServiceStreamShared *sharedStream) {
-    std::lock_guard<std::mutex> lock(mLockStreams);
-    mRunningStreams.erase(std::remove(mRunningStreams.begin(), mRunningStreams.end(), sharedStream),
-              mRunningStreams.end());
-    if (mRunningStreams.size() == 0) {
-        stopMixer_l();
+    int numRunningStreams = 0;
+    {
+        std::lock_guard<std::mutex> lock(mLockStreams);
+        mRunningStreams.erase(
+                std::remove(mRunningStreams.begin(), mRunningStreams.end(), sharedStream),
+                mRunningStreams.end());
+        numRunningStreams = mRunningStreams.size();
+    }
+    if (numRunningStreams == 0) {
+        // Don't call this under a lock because the callbackLoop also uses the lock.
+        stopSharingThread();
     }
     return AAUDIO_OK;
 }
 
-static void *aaudio_mixer_thread_proc(void *context) {
-    AAudioServiceEndpoint *stream = (AAudioServiceEndpoint *) context;
-    if (stream != NULL) {
-        return stream->callbackLoop();
+static void *aaudio_endpoint_thread_proc(void *context) {
+    AAudioServiceEndpoint *endpoint = (AAudioServiceEndpoint *) context;
+    if (endpoint != NULL) {
+        return endpoint->callbackLoop();
     } else {
         return NULL;
     }
 }
 
-// Render audio in the application callback and then write the data to the stream.
-void *AAudioServiceEndpoint::callbackLoop() {
-    ALOGD("AAudioServiceEndpoint(): callbackLoop() entering");
-    int32_t underflowCount = 0;
-
-    aaudio_result_t result = mStreamInternal.requestStart();
-
-    // result might be a frame count
-    while (mCallbackEnabled.load() && mStreamInternal.isPlaying() && (result >= 0)) {
-        // Mix data from each active stream.
-        {
-            mMixer.clear();
-            std::lock_guard<std::mutex> lock(mLockStreams);
-            for(AAudioServiceStreamShared *sharedStream : mRunningStreams) {
-                FifoBuffer *fifo = sharedStream->getDataFifoBuffer();
-                const float volume = 1.0f; // to match the perceived volume from AudioTrack
-                bool underflowed = mMixer.mix(fifo, volume);
-                underflowCount += underflowed ? 1 : 0;
-                // TODO log underflows in each stream
-                sharedStream->markTransferTime(AudioClock::getNanoseconds());
-            }
-        }
-
-        // Write audio data to stream using a blocking write.
-        int64_t timeoutNanos = calculateReasonableTimeout(mStreamInternal.getFramesPerBurst());
-        result = mStreamInternal.write(mMixer.getOutputBuffer(), getFramesPerBurst(), timeoutNanos);
-        if (result == AAUDIO_ERROR_DISCONNECTED) {
-            disconnectRegisteredStreams();
-            break;
-        } else if (result != getFramesPerBurst()) {
-            ALOGW("AAudioServiceEndpoint(): callbackLoop() wrote %d / %d",
-                  result, getFramesPerBurst());
-            break;
-        }
-    }
-
-    result = mStreamInternal.requestStop();
-
-    ALOGD("AAudioServiceEndpoint(): callbackLoop() exiting, %d underflows", underflowCount);
-    return NULL; // TODO review
-}
-
-aaudio_result_t AAudioServiceEndpoint::startMixer_l() {
+aaudio_result_t AAudioServiceEndpoint::startSharingThread_l() {
     // Launch the callback loop thread.
-    int64_t periodNanos = mStreamInternal.getFramesPerBurst()
+    int64_t periodNanos = getStreamInternal()->getFramesPerBurst()
                           * AAUDIO_NANOS_PER_SECOND
                           / getSampleRate();
     mCallbackEnabled.store(true);
-    return mStreamInternal.createThread(periodNanos, aaudio_mixer_thread_proc, this);
+    return getStreamInternal()->createThread(periodNanos, aaudio_endpoint_thread_proc, this);
 }
 
-aaudio_result_t AAudioServiceEndpoint::stopMixer_l() {
+aaudio_result_t AAudioServiceEndpoint::stopSharingThread() {
+    ALOGD("AAudioServiceEndpoint(): call joinThread()");
     mCallbackEnabled.store(false);
-    return mStreamInternal.joinThread(NULL, calculateReasonableTimeout(mStreamInternal.getFramesPerBurst()));
-}
-
-// TODO Call method in AudioStreamInternal when that callback CL is merged.
-int64_t AAudioServiceEndpoint::calculateReasonableTimeout(int32_t framesPerOperation) {
-
-    // Wait for at least a second or some number of callbacks to join the thread.
-    int64_t timeoutNanoseconds = (MIN_TIMEOUT_OPERATIONS * framesPerOperation * AAUDIO_NANOS_PER_SECOND)
-                                 / getSampleRate();
-    if (timeoutNanoseconds < MIN_TIMEOUT_NANOS) { // arbitrary number of seconds
-        timeoutNanoseconds = MIN_TIMEOUT_NANOS;
-    }
-    return timeoutNanoseconds;
+    aaudio_result_t result = getStreamInternal()->joinThread(NULL);
+    ALOGD("AAudioServiceEndpoint(): joinThread() returned %d", result);
+    return result;
 }
 
 void AAudioServiceEndpoint::disconnectRegisteredStreams() {
diff --git a/services/oboeservice/AAudioServiceEndpoint.h b/services/oboeservice/AAudioServiceEndpoint.h
index d0c2f53..50bf049 100644
--- a/services/oboeservice/AAudioServiceEndpoint.h
+++ b/services/oboeservice/AAudioServiceEndpoint.h
@@ -23,6 +23,7 @@
 #include <vector>
 
 #include "client/AudioStreamInternal.h"
+#include "client/AudioStreamInternalPlay.h"
 #include "binding/AAudioServiceMessage.h"
 #include "AAudioServiceStreamShared.h"
 #include "AAudioServiceStreamMMAP.h"
@@ -33,14 +34,13 @@
 
 class AAudioServiceEndpoint {
 public:
-    explicit AAudioServiceEndpoint(android::AAudioService &audioService);
-    virtual ~AAudioServiceEndpoint();
+    virtual ~AAudioServiceEndpoint() = default;
 
-    aaudio_result_t open(int32_t deviceId, aaudio_direction_t direction);
+    virtual aaudio_result_t open(int32_t deviceId);
 
-    int32_t getSampleRate() const { return mStreamInternal.getSampleRate(); }
-    int32_t getSamplesPerFrame() const { return mStreamInternal.getSamplesPerFrame();  }
-    int32_t getFramesPerBurst() const { return mStreamInternal.getFramesPerBurst();  }
+    int32_t getSampleRate() const { return mStreamInternal->getSampleRate(); }
+    int32_t getSamplesPerFrame() const { return mStreamInternal->getSamplesPerFrame();  }
+    int32_t getFramesPerBurst() const { return mStreamInternal->getFramesPerBurst();  }
 
     aaudio_result_t registerStream(AAudioServiceStreamShared *sharedStream);
     aaudio_result_t unregisterStream(AAudioServiceStreamShared *sharedStream);
@@ -48,13 +48,13 @@
     aaudio_result_t stopStream(AAudioServiceStreamShared *sharedStream);
     aaudio_result_t close();
 
-    int32_t getDeviceId() const { return mStreamInternal.getDeviceId(); }
+    int32_t getDeviceId() const { return mStreamInternal->getDeviceId(); }
 
-    aaudio_direction_t getDirection() const { return mStreamInternal.getDirection(); }
+    aaudio_direction_t getDirection() const { return mStreamInternal->getDirection(); }
 
     void disconnectRegisteredStreams();
 
-    void *callbackLoop();
+    virtual void *callbackLoop() = 0;
 
     // This should only be called from the AAudioEndpointManager under a mutex.
     int32_t getReferenceCount() const {
@@ -66,23 +66,21 @@
         mReferenceCount = count;
     }
 
-private:
-    aaudio_result_t startMixer_l();
-    aaudio_result_t stopMixer_l();
-
-    int64_t calculateReasonableTimeout(int32_t framesPerOperation);
-
-    AudioStreamInternal      mStreamInternal;
-    AAudioMixer              mMixer;
+    virtual AudioStreamInternal *getStreamInternal() = 0;
 
     std::atomic<bool>        mCallbackEnabled;
-    int32_t                  mReferenceCount = 0;
-    bool                     mLatencyTuningEnabled = false; // TODO implement tuning
 
     std::mutex               mLockStreams;
+
     std::vector<AAudioServiceStreamShared *> mRegisteredStreams;
     std::vector<AAudioServiceStreamShared *> mRunningStreams;
 
+private:
+    aaudio_result_t startSharingThread_l();
+    aaudio_result_t stopSharingThread();
+
+    AudioStreamInternal     *mStreamInternal = nullptr;
+    int32_t                  mReferenceCount = 0;
 };
 
 } /* namespace aaudio */
diff --git a/services/oboeservice/AAudioServiceEndpointCapture.cpp b/services/oboeservice/AAudioServiceEndpointCapture.cpp
new file mode 100644
index 0000000..b7b42b4
--- /dev/null
+++ b/services/oboeservice/AAudioServiceEndpointCapture.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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 "AAudioService"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <map>
+#include <mutex>
+#include <utils/Singleton.h>
+
+#include "AAudioEndpointManager.h"
+#include "AAudioServiceEndpoint.h"
+
+#include "core/AudioStreamBuilder.h"
+#include "AAudioServiceEndpoint.h"
+#include "AAudioServiceStreamShared.h"
+#include "AAudioServiceEndpointCapture.h"
+
+using namespace android;  // TODO just import names needed
+using namespace aaudio;   // TODO just import names needed
+
+AAudioServiceEndpointCapture::AAudioServiceEndpointCapture(AAudioService &audioService)
+        : mStreamInternalCapture(audioService, true) {
+}
+
+AAudioServiceEndpointCapture::~AAudioServiceEndpointCapture() {
+    delete mDistributionBuffer;
+}
+
+aaudio_result_t AAudioServiceEndpointCapture::open(int32_t deviceId) {
+    aaudio_result_t result = AAudioServiceEndpoint::open(deviceId);
+    if (result == AAUDIO_OK) {
+        delete mDistributionBuffer;
+        int distributionBufferSizeBytes = getStreamInternal()->getFramesPerBurst()
+                                          * getStreamInternal()->getBytesPerFrame();
+        mDistributionBuffer = new uint8_t[distributionBufferSizeBytes];
+    }
+    return result;
+}
+
+// Read data from the shared MMAP stream and then distribute it to the client streams.
+void *AAudioServiceEndpointCapture::callbackLoop() {
+    ALOGD("AAudioServiceEndpointCapture(): callbackLoop() entering");
+    int32_t underflowCount = 0;
+
+    aaudio_result_t result = getStreamInternal()->requestStart();
+
+    int64_t timeoutNanos = getStreamInternal()->calculateReasonableTimeout();
+
+    // result might be a frame count
+    while (mCallbackEnabled.load() && getStreamInternal()->isActive() && (result >= 0)) {
+        // Read audio data from stream using a blocking read.
+        result = getStreamInternal()->read(mDistributionBuffer, getFramesPerBurst(), timeoutNanos);
+        if (result == AAUDIO_ERROR_DISCONNECTED) {
+            disconnectRegisteredStreams();
+            break;
+        } else if (result != getFramesPerBurst()) {
+            ALOGW("AAudioServiceEndpointCapture(): callbackLoop() read %d / %d",
+                  result, getFramesPerBurst());
+            break;
+        }
+
+        // Distribute data to each active stream.
+        { // use lock guard
+            //ALOGD("AAudioServiceEndpointCapture(): try to lock()");
+            std::lock_guard <std::mutex> lock(mLockStreams);
+            //ALOGD("AAudioServiceEndpointCapture(): got lock()");
+            for (AAudioServiceStreamShared *sharedStream : mRunningStreams) {
+                FifoBuffer *fifo = sharedStream->getDataFifoBuffer();
+                if (fifo->getFifoControllerBase()->getEmptyFramesAvailable() <
+                    getFramesPerBurst()) {
+                    underflowCount++;
+                } else {
+                    fifo->write(mDistributionBuffer, getFramesPerBurst());
+                }
+                sharedStream->markTransferTime(AudioClock::getNanoseconds());
+            }
+        }
+    }
+
+    result = getStreamInternal()->requestStop();
+
+    ALOGD("AAudioServiceEndpointCapture(): callbackLoop() exiting, %d underflows", underflowCount);
+    return NULL; // TODO review
+}
diff --git a/services/oboeservice/AAudioServiceEndpointCapture.h b/services/oboeservice/AAudioServiceEndpointCapture.h
new file mode 100644
index 0000000..35857d1
--- /dev/null
+++ b/services/oboeservice/AAudioServiceEndpointCapture.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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 AAUDIO_SERVICE_ENDPOINT_CAPTURE_H
+#define AAUDIO_SERVICE_ENDPOINT_CAPTURE_H
+
+#include "client/AudioStreamInternal.h"
+#include "client/AudioStreamInternalCapture.h"
+
+namespace aaudio {
+
+class AAudioServiceEndpointCapture : public AAudioServiceEndpoint {
+public:
+    explicit AAudioServiceEndpointCapture(android::AAudioService &audioService);
+    virtual ~AAudioServiceEndpointCapture();
+
+    aaudio_result_t open(int32_t deviceId) override;
+
+    AudioStreamInternal *getStreamInternal() override {
+        return &mStreamInternalCapture;
+    }
+
+    void *callbackLoop() override;
+
+private:
+    AudioStreamInternalCapture  mStreamInternalCapture;
+    uint8_t                    *mDistributionBuffer = nullptr;
+};
+
+} /* namespace aaudio */
+
+#endif //AAUDIO_SERVICE_ENDPOINT_CAPTURE_H
diff --git a/services/oboeservice/AAudioServiceEndpointPlay.cpp b/services/oboeservice/AAudioServiceEndpointPlay.cpp
new file mode 100644
index 0000000..cc09cc3
--- /dev/null
+++ b/services/oboeservice/AAudioServiceEndpointPlay.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2017 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 "AAudioService"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <map>
+#include <mutex>
+#include <utils/Singleton.h>
+
+#include "AAudioEndpointManager.h"
+#include "AAudioServiceEndpoint.h"
+#include <algorithm>
+#include <mutex>
+#include <vector>
+
+#include "core/AudioStreamBuilder.h"
+#include "AAudioServiceEndpoint.h"
+#include "AAudioServiceStreamShared.h"
+#include "AAudioServiceEndpointPlay.h"
+
+using namespace android;  // TODO just import names needed
+using namespace aaudio;   // TODO just import names needed
+
+#define BURSTS_PER_BUFFER_DEFAULT   2
+
+AAudioServiceEndpointPlay::AAudioServiceEndpointPlay(AAudioService &audioService)
+        : mStreamInternalPlay(audioService, true) {
+}
+
+AAudioServiceEndpointPlay::~AAudioServiceEndpointPlay() {
+}
+
+aaudio_result_t AAudioServiceEndpointPlay::open(int32_t deviceId) {
+    aaudio_result_t result = AAudioServiceEndpoint::open(deviceId);
+    if (result == AAUDIO_OK) {
+        mMixer.allocate(getStreamInternal()->getSamplesPerFrame(),
+                        getStreamInternal()->getFramesPerBurst());
+
+        int32_t burstsPerBuffer = AAudioProperty_getMixerBursts();
+        if (burstsPerBuffer == 0) {
+            mLatencyTuningEnabled = true;
+            burstsPerBuffer = BURSTS_PER_BUFFER_DEFAULT;
+        }
+        ALOGD("AAudioServiceEndpoint(): burstsPerBuffer = %d", burstsPerBuffer);
+        int32_t desiredBufferSize = burstsPerBuffer * getStreamInternal()->getFramesPerBurst();
+        getStreamInternal()->setBufferSize(desiredBufferSize);
+    }
+    return result;
+}
+
+// Mix data from each application stream and write result to the shared MMAP stream.
+void *AAudioServiceEndpointPlay::callbackLoop() {
+    ALOGD("AAudioServiceEndpointPlay(): callbackLoop() entering");
+    int32_t underflowCount = 0;
+
+    aaudio_result_t result = getStreamInternal()->requestStart();
+
+    int64_t timeoutNanos = getStreamInternal()->calculateReasonableTimeout();
+
+    // result might be a frame count
+    while (mCallbackEnabled.load() && getStreamInternal()->isActive() && (result >= 0)) {
+        // Mix data from each active stream.
+        mMixer.clear();
+        { // use lock guard
+            std::lock_guard <std::mutex> lock(mLockStreams);
+            for (AAudioServiceStreamShared *sharedStream : mRunningStreams) {
+                FifoBuffer *fifo = sharedStream->getDataFifoBuffer();
+                float volume = 0.5; // TODO get from system
+                bool underflowed = mMixer.mix(fifo, volume);
+                underflowCount += underflowed ? 1 : 0;
+                // TODO log underflows in each stream
+                sharedStream->markTransferTime(AudioClock::getNanoseconds());
+            }
+        }
+
+        // Write mixer output to stream using a blocking write.
+        result = getStreamInternal()->write(mMixer.getOutputBuffer(),
+                                            getFramesPerBurst(), timeoutNanos);
+        if (result == AAUDIO_ERROR_DISCONNECTED) {
+            disconnectRegisteredStreams();
+            break;
+        } else if (result != getFramesPerBurst()) {
+            ALOGW("AAudioServiceEndpoint(): callbackLoop() wrote %d / %d",
+                  result, getFramesPerBurst());
+            break;
+        }
+    }
+
+    result = getStreamInternal()->requestStop();
+
+    ALOGD("AAudioServiceEndpointPlay(): callbackLoop() exiting, %d underflows", underflowCount);
+    return NULL; // TODO review
+}
diff --git a/services/oboeservice/AAudioServiceEndpointPlay.h b/services/oboeservice/AAudioServiceEndpointPlay.h
new file mode 100644
index 0000000..b977960
--- /dev/null
+++ b/services/oboeservice/AAudioServiceEndpointPlay.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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 AAUDIO_SERVICE_ENDPOINT_PLAY_H
+#define AAUDIO_SERVICE_ENDPOINT_PLAY_H
+
+#include <atomic>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+#include "client/AudioStreamInternal.h"
+#include "client/AudioStreamInternalPlay.h"
+#include "binding/AAudioServiceMessage.h"
+#include "AAudioServiceStreamShared.h"
+#include "AAudioServiceStreamMMAP.h"
+#include "AAudioMixer.h"
+#include "AAudioService.h"
+
+namespace aaudio {
+
+class AAudioServiceEndpointPlay : public AAudioServiceEndpoint {
+public:
+    explicit AAudioServiceEndpointPlay(android::AAudioService &audioService);
+    virtual ~AAudioServiceEndpointPlay();
+
+    aaudio_result_t open(int32_t deviceId) override;
+
+    AudioStreamInternal *getStreamInternal() override {
+        return &mStreamInternalPlay;
+    }
+
+    void *callbackLoop() override;
+
+private:
+    AudioStreamInternalPlay  mStreamInternalPlay; // for playing output of mixer
+    bool                     mLatencyTuningEnabled = false; // TODO implement tuning
+    AAudioMixer              mMixer;    //
+};
+
+} /* namespace aaudio */
+
+#endif //AAUDIO_SERVICE_ENDPOINT_PLAY_H
diff --git a/services/oboeservice/AAudioServiceStreamBase.cpp b/services/oboeservice/AAudioServiceStreamBase.cpp
index 8248f8b..f04bc86 100644
--- a/services/oboeservice/AAudioServiceStreamBase.cpp
+++ b/services/oboeservice/AAudioServiceStreamBase.cpp
@@ -162,11 +162,12 @@
 
 aaudio_result_t AAudioServiceStreamBase::sendCurrentTimestamp() {
     AAudioServiceMessage command;
-    //ALOGD("sendCurrentTimestamp() called");
     aaudio_result_t result = getFreeRunningPosition(&command.timestamp.position,
                                                     &command.timestamp.timestamp);
     if (result == AAUDIO_OK) {
-        //ALOGD("sendCurrentTimestamp(): position %d", (int) command.timestamp.position);
+    //    ALOGD("sendCurrentTimestamp(): position = %lld, nanos = %lld",
+    //          (long long) command.timestamp.position,
+    //          (long long) command.timestamp.timestamp);
         command.what = AAudioServiceMessage::code::TIMESTAMP;
         result = writeUpMessageQueue(&command);
     }
diff --git a/services/oboeservice/AAudioServiceStreamMMAP.cpp b/services/oboeservice/AAudioServiceStreamMMAP.cpp
index 78a1583..b9a5631 100644
--- a/services/oboeservice/AAudioServiceStreamMMAP.cpp
+++ b/services/oboeservice/AAudioServiceStreamMMAP.cpp
@@ -76,7 +76,7 @@
     const audio_attributes_t attributes = {
         .content_type = AUDIO_CONTENT_TYPE_MUSIC,
         .usage = AUDIO_USAGE_MEDIA,
-        .source = AUDIO_SOURCE_DEFAULT,
+        .source = AUDIO_SOURCE_VOICE_RECOGNITION,
         .flags = AUDIO_FLAG_LOW_LATENCY,
         .tags = ""
     };
@@ -91,8 +91,8 @@
     const AAudioStreamConfiguration &configurationInput = request.getConstantConfiguration();
     audio_port_handle_t deviceId = configurationInput.getDeviceId();
 
-    // ALOGI("open request dump()");
-    // request.dump();
+    ALOGI("open request dump()");
+    request.dump();
 
     mMmapClient.clientUid = request.getUserId();
     mMmapClient.clientPid = request.getProcessId();
@@ -171,7 +171,7 @@
                            : audio_channel_count_from_in_mask(config.channel_mask);
 
     mAudioDataFileDescriptor = mMmapBufferinfo.shared_memory_fd;
-    ALOGV("AAudioServiceStreamMMAP::open LEAK? mAudioDataFileDescriptor = %d\n",
+    ALOGD("AAudioServiceStreamMMAP::open LEAK? mAudioDataFileDescriptor = %d\n",
           mAudioDataFileDescriptor);
     mFramesPerBurst = mMmapBufferinfo.burst_size_frames;
     mCapacityInFrames = mMmapBufferinfo.buffer_size_frames;
@@ -205,16 +205,17 @@
     return AAUDIO_OK;
 }
 
-
 /**
  * Start the flow of data.
  */
 aaudio_result_t AAudioServiceStreamMMAP::start() {
     if (mMmapStream == nullptr) return AAUDIO_ERROR_NULL;
-    aaudio_result_t result = mMmapStream->start(mMmapClient, &mPortHandle);
-    if (result != AAUDIO_OK) {
-        ALOGE("AAudioServiceStreamMMAP::start() mMmapStream->start() returned %d", result);
+    aaudio_result_t result;
+    status_t status = mMmapStream->start(mMmapClient, &mPortHandle);
+    if (status != OK) {
+        ALOGE("AAudioServiceStreamMMAP::start() mMmapStream->start() returned %d", status);
         processError();
+        result = AAudioConvert_androidToAAudioResult(status);
     } else {
         result = AAudioServiceStreamBase::start();
     }
@@ -228,18 +229,18 @@
     if (mMmapStream == nullptr) return AAUDIO_ERROR_NULL;
 
     aaudio_result_t result1 = AAudioServiceStreamBase::pause();
-    aaudio_result_t result2 = mMmapStream->stop(mPortHandle);
+    status_t status = mMmapStream->stop(mPortHandle);
     mFramesRead.reset32();
-    return (result1 != AAUDIO_OK) ? result1 : result2;
+    return (result1 != AAUDIO_OK) ? result1 : AAudioConvert_androidToAAudioResult(status);
 }
 
 aaudio_result_t AAudioServiceStreamMMAP::stop() {
     if (mMmapStream == nullptr) return AAUDIO_ERROR_NULL;
 
     aaudio_result_t result1 = AAudioServiceStreamBase::stop();
-    aaudio_result_t result2 = mMmapStream->stop(mPortHandle);
+    aaudio_result_t status = mMmapStream->stop(mPortHandle);
     mFramesRead.reset32();
-    return (result1 != AAUDIO_OK) ? result1 : result2;
+    return (result1 != AAUDIO_OK) ? result1 :  AAudioConvert_androidToAAudioResult(status);
 }
 
 /**
diff --git a/services/oboeservice/AAudioServiceStreamShared.cpp b/services/oboeservice/AAudioServiceStreamShared.cpp
index 713d1f8..3bf7811 100644
--- a/services/oboeservice/AAudioServiceStreamShared.cpp
+++ b/services/oboeservice/AAudioServiceStreamShared.cpp
@@ -62,8 +62,8 @@
     ALOGD("AAudioServiceStreamShared::open(), direction = %d", direction);
     AAudioEndpointManager &mEndpointManager = AAudioEndpointManager::getInstance();
     mServiceEndpoint = mEndpointManager.openEndpoint(mAudioService, deviceId, direction);
-    ALOGD("AAudioServiceStreamShared::open(), mServiceEndPoint = %p", mServiceEndpoint);
     if (mServiceEndpoint == nullptr) {
+        ALOGE("AAudioServiceStreamShared::open(), mServiceEndPoint = %p", mServiceEndpoint);
         return AAUDIO_ERROR_UNAVAILABLE;
     }
 
@@ -134,7 +134,7 @@
     if (endpoint == nullptr) {
         return AAUDIO_ERROR_INVALID_STATE;
     }
-    // Add this stream to the mixer.
+    // For output streams, this will add the stream to the mixer.
     aaudio_result_t result = endpoint->startStream(this);
     if (result != AAUDIO_OK) {
         ALOGE("AAudioServiceStreamShared::start() mServiceEndpoint returned %d", result);
diff --git a/services/oboeservice/AAudioServiceStreamShared.h b/services/oboeservice/AAudioServiceStreamShared.h
index b981387..dfdbbb3 100644
--- a/services/oboeservice/AAudioServiceStreamShared.h
+++ b/services/oboeservice/AAudioServiceStreamShared.h
@@ -100,7 +100,7 @@
 private:
     android::AAudioService  &mAudioService;
     AAudioServiceEndpoint   *mServiceEndpoint = nullptr;
-    SharedRingBuffer        *mAudioDataQueue;
+    SharedRingBuffer        *mAudioDataQueue = nullptr;
 
     int64_t                  mMarkedPosition = 0;
     int64_t                  mMarkedTime = 0;
diff --git a/services/oboeservice/Android.mk b/services/oboeservice/Android.mk
index afb477e..b447725 100644
--- a/services/oboeservice/Android.mk
+++ b/services/oboeservice/Android.mk
@@ -29,6 +29,8 @@
     AAudioMixer.cpp \
     AAudioService.cpp \
     AAudioServiceEndpoint.cpp \
+    AAudioServiceEndpointCapture.cpp \
+    AAudioServiceEndpointPlay.cpp \
     AAudioServiceStreamBase.cpp \
     AAudioServiceStreamMMAP.cpp \
     AAudioServiceStreamShared.cpp \