Merge "fifo: improve blocking and hysteresis"
diff --git a/audio_utils/fifo.cpp b/audio_utils/fifo.cpp
index f19f93b..31a0c62 100644
--- a/audio_utils/fifo.cpp
+++ b/audio_utils/fifo.cpp
@@ -22,7 +22,7 @@
 #include <stdlib.h>
 #include <string.h>
 
-// FIXME futex portion is not supported on Mac, should use the Mac alternative
+// FIXME futex portion is not supported on macOS, should use the macOS alternative
 #ifdef __linux__
 #include <linux/futex.h>
 #include <sys/syscall.h>
@@ -38,11 +38,33 @@
 #include <cutils/log.h>
 #include <utils/Errors.h>
 
+#ifdef __linux__
+#ifdef __ANDROID__
+// bionic for Android provides clock_nanosleep
+#else
+// bionic for desktop Linux omits clock_nanosleep
+int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request,
+        struct timespec *remain)
+{
+    return syscall(SYS_clock_nanosleep, clock_id, flags, request, remain);
+}
+#endif  // __ANDROID__
+#else   // __linux__
+// macOS doesn't have clock_nanosleep
+int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request,
+        struct timespec *remain)
+{
+    errno = ENOSYS;
+    return -1;
+}
+#endif  // __linux__
+
 static int sys_futex(void *addr1, int op, int val1, struct timespec *timeout, void *addr2, int val3)
 {
 #ifdef __linux__
     return syscall(SYS_futex, addr1, op, val1, timeout, addr2, val3);
-#else
+#else   // __linux__
+    // macOS doesn't have futex
     (void) addr1;
     (void) op;
     (void) val1;
@@ -51,16 +73,17 @@
     (void) val3;
     errno = ENOSYS;
     return -1;
-#endif
+#endif  // __linux__
 }
 
 audio_utils_fifo_base::audio_utils_fifo_base(uint32_t frameCount,
-        audio_utils_fifo_index& sharedRear, audio_utils_fifo_index *throttleFront)
+        audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront)
         __attribute__((no_sanitize("integer"))) :
     mFrameCount(frameCount), mFrameCountP2(roundup(frameCount)),
     mFudgeFactor(mFrameCountP2 - mFrameCount),
-    mIsPrivate(true),
-    mSharedRear(sharedRear), mThrottleFront(throttleFront)
+    // FIXME need an API to configure the sync types
+    mWriterRear(writerRear), mWriterRearSync(AUDIO_UTILS_FIFO_SYNC_SHARED),
+    mThrottleFront(throttleFront), mThrottleFrontSync(AUDIO_UTILS_FIFO_SYNC_SHARED)
 {
     // actual upper bound on frameCount will depend on the frame size
     LOG_ALWAYS_FATAL_IF(frameCount == 0 || frameCount > ((uint32_t) INT_MAX));
@@ -124,9 +147,9 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 audio_utils_fifo::audio_utils_fifo(uint32_t frameCount, uint32_t frameSize, void *buffer,
-        audio_utils_fifo_index& sharedRear, audio_utils_fifo_index *throttleFront)
+        audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront)
         __attribute__((no_sanitize("integer"))) :
-    audio_utils_fifo_base(frameCount, sharedRear, throttleFront),
+    audio_utils_fifo_base(frameCount, writerRear, throttleFront),
     mFrameSize(frameSize), mBuffer(buffer)
 {
     // maximum value of frameCount * frameSize is INT_MAX (2^31 - 1), not 2^31, because we need to
@@ -161,7 +184,8 @@
 
 audio_utils_fifo_writer::audio_utils_fifo_writer(audio_utils_fifo& fifo) :
     audio_utils_fifo_provider(), mFifo(fifo), mLocalRear(0),
-    mLowLevelArm(fifo.mFrameCount), mHighLevelTrigger(0), mArmed(false),
+    mLowLevelArm(fifo.mFrameCount), mHighLevelTrigger(0),
+    mArmed(true),   // because initial fill level of zero is < mLowLevelArm
     mEffectiveFrames(fifo.mFrameCount)
 {
 }
@@ -188,19 +212,27 @@
     return availToWrite;
 }
 
+// iovec == NULL is not part of the public API, but is used internally to mean don't set mObtained
 ssize_t audio_utils_fifo_writer::obtain(audio_utils_iovec iovec[2], size_t count,
         struct timespec *timeout)
         __attribute__((no_sanitize("integer")))
 {
+    int err = 0;
     size_t availToWrite;
     if (mFifo.mThrottleFront != NULL) {
         uint32_t front;
         for (;;) {
-            front = atomic_load_explicit(&mFifo.mThrottleFront->mIndex,
-                    std::memory_order_acquire);
-            int32_t filled = mFifo.diff(mLocalRear, front, NULL /*lost*/);
+            front = atomic_load_explicit(&mFifo.mThrottleFront->mIndex, std::memory_order_acquire);
+            int32_t filled = mFifo.diff(mLocalRear, front);
             if (filled < 0) {
-                mObtained = 0;
+                // on error, return an empty slice
+                if (iovec != NULL) {
+                    iovec[0].mOffset = 0;
+                    iovec[0].mLength = 0;
+                    iovec[1].mOffset = 0;
+                    iovec[1].mLength = 0;
+                    mObtained = 0;
+                }
                 return (ssize_t) filled;
             }
             availToWrite = mEffectiveFrames > (uint32_t) filled ?
@@ -210,18 +242,42 @@
                     (timeout->tv_sec == 0 && timeout->tv_nsec == 0)) {
                 break;
             }
-            int err = sys_futex(&mFifo.mThrottleFront->mIndex,
-                    mFifo.mIsPrivate ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT, front, timeout, NULL, 0);
-            if (err < 0) {
-                switch (errno) {
-                case EWOULDBLOCK:
-                case EINTR:
-                case ETIMEDOUT:
-                    break;
-                default:
-                    LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno);
-                    break;
+            // TODO add comments
+            // TODO abstract out switch and replace by general sync object
+            int op = FUTEX_WAIT;
+            switch (mFifo.mThrottleFrontSync) {
+            case AUDIO_UTILS_FIFO_SYNC_SLEEP:
+                err = clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, timeout, NULL /*remain*/);
+                if (err < 0) {
+                    LOG_ALWAYS_FATAL_IF(errno != EINTR, "unexpected err=%d errno=%d", err, errno);
+                    err = -errno;
+                } else {
+                    err = -ETIMEDOUT;
                 }
+                break;
+            case AUDIO_UTILS_FIFO_SYNC_PRIVATE:
+                op = FUTEX_WAIT_PRIVATE;
+                // fall through
+            case AUDIO_UTILS_FIFO_SYNC_SHARED:
+                if (timeout->tv_sec == LONG_MAX) {
+                    timeout = NULL;
+                }
+                err = sys_futex(&mFifo.mThrottleFront->mIndex, op, front, timeout, NULL, 0);
+                if (err < 0) {
+                    switch (errno) {
+                    case EINTR:
+                    case ETIMEDOUT:
+                        err = -errno;
+                        break;
+                    default:
+                        LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno);
+                        break;
+                    }
+                }
+                break;
+            default:
+                LOG_ALWAYS_FATAL("mFifo.mThrottleFrontSync=%d", mFifo.mThrottleFrontSync);
+                break;
             }
             timeout = NULL;
         }
@@ -237,12 +293,15 @@
         part1 = availToWrite;
     }
     size_t part2 = part1 > 0 ? availToWrite - part1 : 0;
-    iovec[0].mOffset = rearMasked;
-    iovec[0].mLength = part1;
-    iovec[1].mOffset = 0;
-    iovec[1].mLength = part2;
-    mObtained = availToWrite;
-    return availToWrite;
+    // return slice
+    if (iovec != NULL) {
+        iovec[0].mOffset = rearMasked;
+        iovec[0].mLength = part1;
+        iovec[1].mOffset = 0;
+        iovec[1].mLength = part2;
+        mObtained = availToWrite;
+    }
+    return availToWrite > 0 ? availToWrite : err;
 }
 
 void audio_utils_fifo_writer::release(size_t count)
@@ -253,40 +312,106 @@
         if (mFifo.mThrottleFront != NULL) {
             uint32_t front = atomic_load_explicit(&mFifo.mThrottleFront->mIndex,
                     std::memory_order_acquire);
-            int32_t filled = mFifo.diff(mLocalRear, front, NULL /*lost*/);
+            int32_t filled = mFifo.diff(mLocalRear, front);
             mLocalRear = mFifo.sum(mLocalRear, count);
-            atomic_store_explicit(&mFifo.mSharedRear.mIndex, mLocalRear,
+            atomic_store_explicit(&mFifo.mWriterRear.mIndex, mLocalRear,
                     std::memory_order_release);
-            if (filled >= 0) {
-                if (filled + count <= mLowLevelArm) {
-                    mArmed = true;
-                }
-                if (mArmed && filled + count >= mHighLevelTrigger) {
-                    int err = sys_futex(&mFifo.mSharedRear.mIndex,
-                            mFifo.mIsPrivate ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
-                            INT_MAX /*waiters*/, NULL, NULL, 0);
-                    // err is number of processes woken up
-                    if (err < 0) {
-                        LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d", __func__, err, errno);
+            // TODO add comments
+            int op = FUTEX_WAKE;
+            switch (mFifo.mWriterRearSync) {
+            case AUDIO_UTILS_FIFO_SYNC_SLEEP:
+                break;
+            case AUDIO_UTILS_FIFO_SYNC_PRIVATE:
+                op = FUTEX_WAKE_PRIVATE;
+                // fall through
+            case AUDIO_UTILS_FIFO_SYNC_SHARED:
+                if (filled >= 0) {
+                    if ((uint32_t) filled < mLowLevelArm) {
+                        mArmed = true;
                     }
-                    mArmed = false;
+                    if (mArmed && filled + count > mHighLevelTrigger) {
+                        int err = sys_futex(&mFifo.mWriterRear.mIndex,
+                                op, INT_MAX /*waiters*/, NULL, NULL, 0);
+                        // err is number of processes woken up
+                        if (err < 0) {
+                            LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d",
+                                    __func__, err, errno);
+                        }
+                        mArmed = false;
+                    }
                 }
+                break;
+            default:
+                LOG_ALWAYS_FATAL("mFifo.mWriterRearSync=%d", mFifo.mWriterRearSync);
+                break;
             }
         } else {
             mLocalRear = mFifo.sum(mLocalRear, count);
-            atomic_store_explicit(&mFifo.mSharedRear.mIndex, mLocalRear,
+            atomic_store_explicit(&mFifo.mWriterRear.mIndex, mLocalRear,
                     std::memory_order_release);
         }
         mObtained -= count;
     }
 }
 
+ssize_t audio_utils_fifo_writer::available()
+{
+    return obtain(NULL /*iovec*/, SIZE_MAX /*count*/, NULL /*timeout*/);
+}
+
+void audio_utils_fifo_writer::resize(uint32_t frameCount)
+{
+    // cap to range [0, mFifo.mFrameCount]
+    if (frameCount > mFifo.mFrameCount) {
+        frameCount = mFifo.mFrameCount;
+    }
+    // if we reduce the effective frame count, update hysteresis points to be within the new range
+    if (frameCount < mEffectiveFrames) {
+        if (mLowLevelArm > frameCount) {
+            mLowLevelArm = frameCount;
+        }
+        if (mHighLevelTrigger > frameCount) {
+            mHighLevelTrigger = frameCount;
+        }
+    }
+    mEffectiveFrames = frameCount;
+}
+
+uint32_t audio_utils_fifo_writer::getSize() const
+{
+    return mEffectiveFrames;
+}
+
+void audio_utils_fifo_writer::setHysteresis(uint32_t lowLevelArm, uint32_t highLevelTrigger)
+{
+    // cap to range [0, mEffectiveFrames]
+    if (lowLevelArm > mEffectiveFrames) {
+        lowLevelArm = mEffectiveFrames;
+    }
+    if (highLevelTrigger > mEffectiveFrames) {
+        highLevelTrigger = mEffectiveFrames;
+    }
+    // TODO this is overly conservative; it would be better to arm based on actual fill level
+    if (lowLevelArm > mLowLevelArm) {
+        mArmed = true;
+    }
+    mLowLevelArm = lowLevelArm;
+    mHighLevelTrigger = highLevelTrigger;
+}
+
+void audio_utils_fifo_writer::getHysteresis(uint32_t *lowLevelArm, uint32_t *highLevelTrigger) const
+{
+    *lowLevelArm = mLowLevelArm;
+    *highLevelTrigger = mHighLevelTrigger;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 audio_utils_fifo_reader::audio_utils_fifo_reader(audio_utils_fifo& fifo, bool throttlesWriter) :
     audio_utils_fifo_provider(), mFifo(fifo), mLocalFront(0),
     mThrottleFront(throttlesWriter ? mFifo.mThrottleFront : NULL),
-    mHighLevelArm(0), mLowLevelTrigger(mFifo.mFrameCount), mArmed(false)
+    mHighLevelArm(-1), mLowLevelTrigger(mFifo.mFrameCount),
+    mArmed(true)    // because initial fill level of zero is > mHighLevelArm
 {
 }
 
@@ -318,7 +443,7 @@
         struct timespec *timeout)
         __attribute__((no_sanitize("integer")))
 {
-    return obtain(iovec, count, timeout, NULL);
+    return obtain(iovec, count, timeout, NULL /*lost*/);
 }
 
 void audio_utils_fifo_reader::release(size_t count)
@@ -327,26 +452,40 @@
     if (count > 0) {
         LOG_ALWAYS_FATAL_IF(count > mObtained);
         if (mThrottleFront != NULL) {
-            uint32_t rear = atomic_load_explicit(&mFifo.mSharedRear.mIndex,
+            uint32_t rear = atomic_load_explicit(&mFifo.mWriterRear.mIndex,
                     std::memory_order_acquire);
-            int32_t filled = mFifo.diff(rear, mLocalFront, NULL /*lost*/);
+            int32_t filled = mFifo.diff(rear, mLocalFront);
             mLocalFront = mFifo.sum(mLocalFront, count);
             atomic_store_explicit(&mThrottleFront->mIndex, mLocalFront,
                     std::memory_order_release);
-            if (filled >= 0) {
-                if (filled - count >= mHighLevelArm) {
-                    mArmed = true;
-                }
-                if (mArmed && filled - count <= mLowLevelTrigger) {
-                    int err = sys_futex(&mFifo.mSharedRear.mIndex,
-                            mFifo.mIsPrivate ? FUTEX_WAKE_PRIVATE : FUTEX_WAKE,
-                            1 /*waiters*/, NULL, NULL, 0);
-                    // err is number of processes woken up
-                    if (err < 0 || err > 1) {
-                        LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d", __func__, err, errno);
+            // TODO add comments
+            int op = FUTEX_WAKE;
+            switch (mFifo.mThrottleFrontSync) {
+            case AUDIO_UTILS_FIFO_SYNC_SLEEP:
+                break;
+            case AUDIO_UTILS_FIFO_SYNC_PRIVATE:
+                op = FUTEX_WAKE_PRIVATE;
+                // fall through
+            case AUDIO_UTILS_FIFO_SYNC_SHARED:
+                if (filled >= 0) {
+                    if (filled > mHighLevelArm) {
+                        mArmed = true;
                     }
-                    mArmed = false;
+                    if (mArmed && filled - count < mLowLevelTrigger) {
+                        int err = sys_futex(&mThrottleFront->mIndex,
+                                op, 1 /*waiters*/, NULL, NULL, 0);
+                        // err is number of processes woken up
+                        if (err < 0 || err > 1) {
+                            LOG_ALWAYS_FATAL("%s: unexpected err=%d errno=%d",
+                                    __func__, err, errno);
+                        }
+                        mArmed = false;
+                    }
                 }
+                break;
+            default:
+                LOG_ALWAYS_FATAL("mFifo.mThrottleFrontSync=%d", mFifo.mThrottleFrontSync);
+                break;
             }
         } else {
             mLocalFront = mFifo.sum(mLocalFront, count);
@@ -355,32 +494,56 @@
     }
 }
 
+// iovec == NULL is not part of the public API, but is used internally to mean don't set mObtained
 ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count,
         struct timespec *timeout, size_t *lost)
         __attribute__((no_sanitize("integer")))
 {
+    int err = 0;
     uint32_t rear;
     for (;;) {
-        rear = atomic_load_explicit(&mFifo.mSharedRear.mIndex,
+        rear = atomic_load_explicit(&mFifo.mWriterRear.mIndex,
                 std::memory_order_acquire);
         // TODO pull out "count == 0"
         if (count == 0 || rear != mLocalFront || timeout == NULL ||
                 (timeout->tv_sec == 0 && timeout->tv_nsec == 0)) {
             break;
         }
-        int err = sys_futex(&mFifo.mSharedRear.mIndex,
-                mFifo.mIsPrivate ? FUTEX_WAIT_PRIVATE : FUTEX_WAIT,
-                rear, timeout, NULL, 0);
-        if (err < 0) {
-            switch (errno) {
-            case EWOULDBLOCK:
-            case EINTR:
-            case ETIMEDOUT:
-                break;
-            default:
-                LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno);
-                break;
+        // TODO add comments
+        int op = FUTEX_WAIT;
+        switch (mFifo.mWriterRearSync) {
+        case AUDIO_UTILS_FIFO_SYNC_SLEEP:
+            err = clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, timeout, NULL /*remain*/);
+            if (err < 0) {
+                LOG_ALWAYS_FATAL_IF(errno != EINTR, "unexpected err=%d errno=%d", err, errno);
+                err = -errno;
+            } else {
+                err = -ETIMEDOUT;
             }
+            break;
+        case AUDIO_UTILS_FIFO_SYNC_PRIVATE:
+            op = FUTEX_WAIT_PRIVATE;
+            // fall through
+        case AUDIO_UTILS_FIFO_SYNC_SHARED:
+            if (timeout->tv_sec == LONG_MAX) {
+                timeout = NULL;
+            }
+            err = sys_futex(&mFifo.mWriterRear.mIndex, op, rear, timeout, NULL, 0);
+            if (err < 0) {
+                switch (errno) {
+                case EINTR:
+                case ETIMEDOUT:
+                    err = -errno;
+                    break;
+                default:
+                    LOG_ALWAYS_FATAL("unexpected err=%d errno=%d", err, errno);
+                    break;
+                }
+            }
+            break;
+        default:
+            LOG_ALWAYS_FATAL("mFifo.mWriterRearSync=%d", mFifo.mWriterRearSync);
+            break;
         }
         timeout = NULL;
     }
@@ -389,7 +552,14 @@
         if (filled == -EOVERFLOW) {
             mLocalFront = rear;
         }
-        mObtained = 0;
+        // on error, return an empty slice
+        if (iovec != NULL) {
+            iovec[0].mOffset = 0;
+            iovec[0].mLength = 0;
+            iovec[1].mOffset = 0;
+            iovec[1].mLength = 0;
+            mObtained = 0;
+        }
         return (ssize_t) filled;
     }
     size_t availToRead = (size_t) filled;
@@ -402,10 +572,48 @@
         part1 = availToRead;
     }
     size_t part2 = part1 > 0 ? availToRead - part1 : 0;
-    iovec[0].mOffset = frontMasked;
-    iovec[0].mLength = part1;
-    iovec[1].mOffset = 0;
-    iovec[1].mLength = part2;
-    mObtained = availToRead;
-    return availToRead;
+    // return slice
+    if (iovec != NULL) {
+        iovec[0].mOffset = frontMasked;
+        iovec[0].mLength = part1;
+        iovec[1].mOffset = 0;
+        iovec[1].mLength = part2;
+        mObtained = availToRead;
+    }
+    return availToRead > 0 ? availToRead : err;
+}
+
+ssize_t audio_utils_fifo_reader::available()
+{
+    return available(NULL /*lost*/);
+}
+
+ssize_t audio_utils_fifo_reader::available(size_t *lost)
+{
+    return obtain(NULL /*iovec*/, SIZE_MAX /*count*/, NULL /*timeout*/, lost);
+}
+
+void audio_utils_fifo_reader::setHysteresis(int32_t highLevelArm, uint32_t lowLevelTrigger)
+{
+    // cap to range [0, mFifo.mFrameCount]
+    if (highLevelArm < 0) {
+        highLevelArm = -1;
+    } else if ((uint32_t) highLevelArm > mFifo.mFrameCount) {
+        highLevelArm = mFifo.mFrameCount;
+    }
+    if (lowLevelTrigger > mFifo.mFrameCount) {
+        lowLevelTrigger = mFifo.mFrameCount;
+    }
+    // TODO this is overly conservative; it would be better to arm based on actual fill level
+    if (highLevelArm < mHighLevelArm) {
+        mArmed = true;
+    }
+    mHighLevelArm = highLevelArm;
+    mLowLevelTrigger = lowLevelTrigger;
+}
+
+void audio_utils_fifo_reader::getHysteresis(int32_t *highLevelArm, uint32_t *lowLevelTrigger) const
+{
+    *highLevelArm = mHighLevelArm;
+    *lowLevelTrigger = mLowLevelTrigger;
 }
diff --git a/audio_utils/include/audio_utils/fifo.h b/audio_utils/include/audio_utils/fifo.h
index 53099b6..bb55ab4 100644
--- a/audio_utils/include/audio_utils/fifo.h
+++ b/audio_utils/include/audio_utils/fifo.h
@@ -24,9 +24,11 @@
 #error C API is no longer supported
 #endif
 
-/** An index that may optionally be placed in shared memory.
- *  Must be Plain Old Data (POD), so no virtual methods are allowed.
- *  If in shared memory, exactly one process must explicitly call the constructor via placement new.
+/**
+ * An index that may optionally be placed in shared memory.
+ * Must be Plain Old Data (POD), so no virtual methods are allowed.
+ * If in shared memory, exactly one process must explicitly call the constructor via placement new.
+ * \see #audio_utils_fifo_sync
  */
 struct audio_utils_fifo_index {
     friend class audio_utils_fifo_reader;
@@ -34,6 +36,7 @@
 
 public:
     audio_utils_fifo_index() : mIndex(0) { }
+    ~audio_utils_fifo_index() { }
 
 private:
     // Linux futex is 32 bits regardless of platform.
@@ -45,65 +48,93 @@
     // TODO Replace friend by setter and getter, and abstract the futex
 };
 
-// Base class for single writer, single-reader or multi-reader non-blocking FIFO.
-// The base class manipulates frame indices only, and has no knowledge of frame sizes or the buffer.
+/** Indicates whether an index is also used for synchronization. */
+enum audio_utils_fifo_sync {
+    /** Index is not also used for synchronization; timeouts are done via clock_nanosleep(). */
+    AUDIO_UTILS_FIFO_SYNC_SLEEP,
+    /** Index is also used for synchronization as futex, and is mapped by one process. */
+    AUDIO_UTILS_FIFO_SYNC_PRIVATE,
+    /** Index is also used for synchronization as futex, and is mapped by one or more processes. */
+    AUDIO_UTILS_FIFO_SYNC_SHARED,
+};
 
+/**
+ * Base class for single-writer, single-reader or multi-reader, optionally blocking FIFO.
+ * The base class manipulates frame indices only, and has no knowledge of frame sizes or the buffer.
+ * At most one reader, called the "throttling reader", can block the writer.
+ * The "fill level", or unread frame count, is defined with respect to the throttling reader.
+ */
 class audio_utils_fifo_base {
 
 protected:
 
-/* Construct FIFO base class
- *
- *  \param sharedRear  Writer's rear index in shared memory.
- *  \param throttleFront Pointer to the front index of at most one reader that throttles the
- *                       writer, or NULL for no throttling.
- */
-    audio_utils_fifo_base(uint32_t frameCount, audio_utils_fifo_index& sharedRear,
-            // TODO inconsistent & vs *
+    /**
+     * Construct FIFO base class
+     *
+     *  \param frameCount    Maximum usable frames to be stored in the FIFO > 0 && <= INT_MAX,
+     *                       aka "capacity".
+     *                       If release()s always use the same count, and the count is a divisor of
+     *                       (effective) \p frameCount, then the obtain()s won't ever be fragmented.
+     *  \param writerRear    Writer's rear index.  Passed by reference because it must be non-NULL.
+     *  \param throttleFront Pointer to the front index of at most one reader that throttles the
+     *                       writer, or NULL for no throttling.
+     */
+    audio_utils_fifo_base(uint32_t frameCount, audio_utils_fifo_index& writerRear,
             audio_utils_fifo_index *throttleFront = NULL);
     /*virtual*/ ~audio_utils_fifo_base();
 
-/** Return a new index as the sum of a validated index and a specified increment.
- *
- * \param index     Caller should supply a validated mFront or mRear.
- * \param increment Value to be added to the index <= mFrameCount.
- *
- * \return the sum of index plus increment.
- */
+    /** Return a new index as the sum of a validated index and a specified increment.
+     *
+     * \param index     Caller should supply a validated mFront or mRear.
+     * \param increment Value to be added to the index <= mFrameCount.
+     *
+     * \return The sum of index plus increment.
+     */
     uint32_t sum(uint32_t index, uint32_t increment);
 
-/** Return the difference between two indices: rear - front.
- *
- * \param rear     Caller should supply an unvalidated mRear.
- * \param front    Caller should supply an unvalidated mFront.
- * \param lost     If non-NULL, set to the approximate number of lost frames.
- *
- * \return the zero or positive difference <= mFrameCount, or a negative error code.
- */
-    int32_t diff(uint32_t rear, uint32_t front, size_t *lost);
+    /** Return the difference between two indices: rear - front.
+     *
+     * \param rear     Caller should supply an unvalidated mRear.
+     * \param front    Caller should supply an unvalidated mFront.
+     * \param lost     If non-NULL, set to the approximate number of lost frames.
+     *
+     * \return The zero or positive difference <= mFrameCount, or a negative error code.
+     */
+    int32_t diff(uint32_t rear, uint32_t front, size_t *lost = NULL);
 
     // These fields are const after initialization
-    const uint32_t mFrameCount;   // max number of significant frames to be stored in the FIFO > 0
-    const uint32_t mFrameCountP2; // roundup(mFrameCount)
-    const uint32_t mFudgeFactor;  // mFrameCountP2 - mFrameCount, the number of "wasted" frames
-                                  // after the end of mBuffer.  Only the indices are wasted, not any
-                                  // memory.
 
-    // TODO always true for now, will be extended later to support false
-    const bool mIsPrivate;        // whether reader and writer virtual address spaces are the same
+    /** Maximum usable frames to be stored in the FIFO > 0 && <= INT_MAX, aka "capacity" */
+    const uint32_t mFrameCount;
+    /** Equal to roundup(mFrameCount) */
+    const uint32_t mFrameCountP2;
 
-    audio_utils_fifo_index&     mSharedRear;
+    /**
+     * Equal to mFrameCountP2 - mFrameCount, the number of "wasted" frames after the end of mBuffer.
+     * Only the indices are wasted, not any memory.
+     */
+    const uint32_t mFudgeFactor;
 
-    // Pointer to the front index of at most one reader that throttles the writer,
-    // or NULL for no throttling
+    /** Reference to writer's rear index. */
+    audio_utils_fifo_index&     mWriterRear;
+    /** Indicates how synchronization is done for mWriterRear. */
+    audio_utils_fifo_sync       mWriterRearSync;
+
+    /**
+     * Pointer to the front index of at most one reader that throttles the writer,
+     * or NULL for no throttling.
+     */
     audio_utils_fifo_index*     mThrottleFront;
+    /** Indicates how synchronization is done for mThrottleFront. */
+    audio_utils_fifo_sync       mThrottleFrontSync;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
-// Same as above, but understands frame sizes and knows about the buffer but does not own it.
-// Writer and reader must be in same process.
-
+/**
+ * Same as audio_utils_fifo_base, but understands frame sizes and knows about the buffer but does
+ * not own it.
+ */
 class audio_utils_fifo : audio_utils_fifo_base {
 
     friend class audio_utils_fifo_reader;
@@ -111,28 +142,34 @@
 
 public:
 
-/**
- * Construct a FIFO object: multi-process.
- *
- *  \param frameCount  Max number of significant frames to be stored in the FIFO > 0.
- *                     If writes and reads always use the same count, and that count is a divisor of
- *                     frameCount, then the writes and reads will never do a partial transfer.
- *  \param frameSize   Size of each frame in bytes > 0, and frameSize * frameCount <= INT_MAX.
- *  \param buffer      Pointer to a caller-allocated buffer of frameCount frames.
- *  \param sharedRear  Writer's rear index in shared memory.
- *  \param throttleFront Pointer to the front index of at most one reader that throttles the
- *                       writer, or NULL for no throttling.
- */
+    /**
+     * Construct a FIFO object: multi-process.
+     *
+     *  \param frameCount  Maximum usable frames to be stored in the FIFO > 0 && <= INT_MAX,
+     *                     aka "capacity".
+     *                     If writes and reads always use the same count, and the count is a divisor
+     *                     of \p frameCount, then the writes and reads won't do a partial transfer.
+     *  \param frameSize   Size of each frame in bytes > 0, \p frameSize * \p frameCount <= INT_MAX.
+     *  \param buffer      Pointer to a caller-allocated buffer of \p frameCount frames.
+     *  \param writerRear  Writer's rear index.  Passed by reference because it must be non-NULL.
+     *  \param throttleFront Pointer to the front index of at most one reader that throttles the
+     *                       writer, or NULL for no throttling.
+     */
     audio_utils_fifo(uint32_t frameCount, uint32_t frameSize, void *buffer,
-            // TODO inconsistent & vs *
-            audio_utils_fifo_index& sharedRear, audio_utils_fifo_index *throttleFront = NULL);
+            audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront = NULL);
 
-/**
- * Construct a FIFO object: single-process.
- *  \param throttlesWriter Whether there is a reader that throttles the writer.
- */
+    /**
+     * Construct a FIFO object: single-process.
+     *  \param frameCount  Maximum usable frames to be stored in the FIFO > 0 && <= INT_MAX,
+     *                     aka "capacity".
+     *                     If writes and reads always use the same count, and the count is a divisor
+     *                     of \p frameCount, then the writes and reads won't do a partial transfer.
+     *  \param frameSize   Size of each frame in bytes > 0, \p frameSize * \p frameCount <= INT_MAX.
+     *  \param buffer      Pointer to a caller-allocated buffer of \p frameCount frames.
+     *  \param throttlesWriter Whether there is one reader that throttles the writer.
+     */
     audio_utils_fifo(uint32_t frameCount, uint32_t frameSize, void *buffer,
-            bool throttlesWriter = false);
+            bool throttlesWriter = true);
 
     /*virtual*/ ~audio_utils_fifo();
 
@@ -149,71 +186,182 @@
     audio_utils_fifo_index      mSingleProcessSharedFront;
 };
 
-// Describes one virtually contiguous fragment of a logically contiguous slice.
-// Compare to struct iovec for readv(2) and writev(2).
+/**
+ * Describes one virtually contiguous fragment of a logically contiguous slice.
+ * Compare to struct iovec for readv(2) and writev(2).
+ */
 struct audio_utils_iovec {
-    uint32_t    mOffset;    // in frames, relative to mBuffer, undefined if mLength == 0
-    uint32_t    mLength;    // in frames
+    /** Offset of fragment in frames, relative to mBuffer, undefined if mLength == 0 */
+    uint32_t    mOffset;
+    /** Length of fragment in frames, 0 means fragment is empty */
+    uint32_t    mLength;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
-// Based on frameworks/av/include/media/AudioBufferProvider.h
+/**
+ * Based on frameworks/av/include/media/AudioBufferProvider.h
+ */
 class audio_utils_fifo_provider {
 public:
     audio_utils_fifo_provider();
     virtual ~audio_utils_fifo_provider();
 
-// The count is the maximum number of desired frames, not the minimum number of desired frames.
-// See the high/low setpoints for something which is close to, but not the same as, a true minimum.
+    /**
+     * Obtain access to a logically contiguous slice of a stream, represented by \p iovec.
+     * For the reader(s), the slice is initialized and has read-only access.
+     * For the writer, the slice is uninitialized and has read/write access.
+     * It is permitted to call obtain() multiple times without an intervening release().
+     * Each call resets the notion of most recently obtained slice.
+     *
+     * \param iovec Non-NULL pointer to a pair of fragment descriptors.
+     *              On entry, the descriptors may be uninitialized.
+     *              On exit, the descriptors are initialized and refer to each of the two fragments.
+     *              iovec[0] describes the initial fragment of the slice, and
+     *              iovec[1] describes the remaining non-virtually-contiguous fragment.
+     *              Empty iovec[0] implies that iovec[1] is also empty.
+     * \param count The maximum number of frames to obtain.
+     *              See the high/low setpoints for something which is close to, but not the same as,
+     *              a minimum.
+     * \param timeout Indicates the maximum time to block for at least one frame.
+     *                NULL and {0, 0} both mean non-blocking.
+     *                Time is expressed as relative CLOCK_MONOTONIC.
+     *                As an optimization, if \p timeout->tv_sec is the maximum positive value for
+     *                time_t (LONG_MAX), then the implementation treats it as infinite timeout.
+     *
+     * \return Actual number of frames available, if greater than or equal to zero.
+     *         Guaranteed to be <= \p count.
+     *
+     *  \retval -EIO        corrupted indices, no recovery is possible
+     *  \retval -EOVERFLOW  reader is not keeping up with writer (reader only)
+     *  \retval -ETIMEDOUT  count is greater than zero, timeout is non-NULL and not {0, 0},
+     *                      timeout expired, and no frames were available after the timeout.
+     *  \retval -EINTR      count is greater than zero, timeout is non-NULL and not {0, 0}, timeout
+     *                      was interrupted by a signal, and no frames were available after signal.
+     *
+     * Applications should treat all of these as equivalent to zero available frames,
+     * except they convey extra information as to the cause.
+     */
+    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count,
+            struct timespec *timeout = NULL) = 0;
 
-// The timeout indicates the maximum time to wait for at least one frame, not for all frames.
-// NULL is equivalent to non-blocking.
-// FIXME specify timebase, relative/absolute etc
-
-// Error codes for ssize_t return value:
-//  -EIO        corrupted indices (reader or writer)
-//  -EOVERFLOW  reader is not keeping up with writer (reader only)
-    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count, struct timespec *timeout) = 0;
-
+    /**
+     * Release access to a portion of the most recently obtained slice.
+     * It is permitted to call release() multiple times without an intervening obtain().
+     *
+     * \param count Number of frames to release.  The total number of frames released must not
+     *              exceed the number of frames most recently obtained.
+     */
     virtual void release(size_t count) = 0;
 
+    /**
+     * Determine the number of frames that could be obtained or read/written without blocking.
+     * There's an inherent race condition: the value may soon be obsolete so shouldn't be trusted.
+     * available() may be called after obtain(), but doesn't affect the number of releasable frames.
+     *
+     * \return Number of available frames, if greater than or equal to zero.
+     *  \retval -EIO        corrupted indices, no recovery is possible
+     *  \retval -EOVERFLOW  reader is not keeping up with writer (reader only)
+     */
+    virtual ssize_t available() = 0;
+
 protected:
-    // Number of frames obtained at most recent obtain(), less number of frames released
+    /** Number of frames obtained at most recent obtain(), less total number of frames released. */
     uint32_t    mObtained;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
+/**
+ * Used to write to a FIFO.  There should be exactly one writer per FIFO.
+ * The writer is multi-thread safe with respect to reader(s),
+ * but not with respect to multiple threads calling the writer API.
+ */
 class audio_utils_fifo_writer : public audio_utils_fifo_provider {
 
 public:
-    // Single-process and multi-process use same constructor here, but different 'fifo' constructors
+    /**
+     * Single-process and multi-process use same constructor here,
+     * but different FIFO constructors.
+     *
+     * \param fifo Associated FIFO.  Passed by reference because it must be non-NULL.
+     */
     audio_utils_fifo_writer(audio_utils_fifo& fifo);
     virtual ~audio_utils_fifo_writer();
 
-/**
- * Write to FIFO.
- *
- * \param buffer    Pointer to source buffer containing 'count' frames of data.
- * \param count     Desired number of frames to write.
- * \param timeout   NULL and zero fields are both non-blocking.
- *
- * \return actual number of frames written <= count.
- *
- * The actual transfer count may be zero if the FIFO is full,
- * or partial if the FIFO was almost full.
- * A negative return value indicates an error.
- */
+    /**
+     * Write to FIFO.  Resets the number of releasable frames to zero.
+     *
+     * \param buffer  Pointer to source buffer containing \p count frames of data.
+     * \param count   Desired number of frames to write.
+     * \param timeout Indicates the maximum time to block for at least one frame.
+     *                NULL and {0, 0} both mean non-blocking.
+     *                Time is expressed as relative CLOCK_MONOTONIC.
+     *                As an optimization, if \p timeout->tv_sec is the maximum positive value for
+     *                time_t (LONG_MAX), then the implementation treats it as infinite timeout.
+     *
+     * \return Actual number of frames written, if greater than or equal to zero.
+     *         Guaranteed to be <= \p count.
+     *         The actual transfer count may be zero if the FIFO is full,
+     *         or partial if the FIFO was almost full.
+     *  \retval -EIO       corrupted indices, no recovery is possible
+     *  \retval -ETIMEDOUT count is greater than zero, timeout is non-NULL and not {0, 0},
+     *                     timeout expired, and no frames were available after the timeout.
+     *  \retval -EINTR     count is greater than zero, timeout is non-NULL and not {0, 0}, timeout
+     *                     was interrupted by a signal, and no frames were available after signal.
+     */
     ssize_t write(const void *buffer, size_t count, struct timespec *timeout = NULL);
 
     // Implement audio_utils_fifo_provider
-    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count, struct timespec *timeout);
+    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count,
+            struct timespec *timeout = NULL);
     virtual void release(size_t count);
+    virtual ssize_t available();
 
-    // TODO add error checks and getters
-    void setHighLevelTrigger(uint32_t level) { mHighLevelTrigger = level; }
-    void setEffectiveFrames(uint32_t effectiveFrames) { mEffectiveFrames = effectiveFrames; }
+    /**
+     * Set the current effective buffer size.
+     * Any filled frames already written or released to the buffer are unaltered, and pending
+     * releasable frames from obtain() may be release()ed.  However subsequent write() and obtain()
+     * will be limited such that the total filled frame count is <= the effective buffer size.
+     * The default effective buffer size is mFifo.mFrameCount.
+     * Reducing the effective buffer size may update the hysteresis levels; see getHysteresis().
+     *
+     * \param frameCount    effective buffer size in frames. Capped to range [0, mFifo.mFrameCount].
+     */
+    void resize(uint32_t frameCount);
+
+    /**
+     * Get the current effective buffer size.
+     *
+     * \return effective buffer size in frames
+     */
+    uint32_t getSize() const;
+
+    /**
+     * Set the hysteresis levels for the writer to wake blocked readers.
+     * A non-empty write() or release() will wake readers
+     * only if the fill level was < \p lowLevelArm before the write() or release(),
+     * and then the fill level became > \p highLevelTrigger afterwards.
+     * The default value for \p lowLevelArm is mFifo.mFrameCount, which means always armed.
+     * The default value for \p highLevelTrigger is zero,
+     * which means every write() or release() will wake the readers.
+     * For hysteresis, \p lowLevelArm must be <= \p highLevelTrigger + 1.
+     * Increasing \p lowLevelArm will arm for wakeup, regardless of the current fill level.
+     *
+     * \param lowLevelArm       Arm for wakeup when fill level < this value.
+     *                          Capped to range [0, effective buffer size].
+     * \param highLevelTrigger  Trigger wakeup when armed and fill level > this value.
+     *                          Capped to range [0, effective buffer size].
+     */
+    void setHysteresis(uint32_t lowLevelArm, uint32_t highLevelTrigger);
+
+    /**
+     * Get the hysteresis levels for waking readers.
+     *
+     * \param lowLevelArm       Set to the current low level arm value in frames.
+     * \param highLevelTrigger  Set to the current high level trigger value in frames.
+     */
+    void getHysteresis(uint32_t *lowLevelArm, uint32_t *highLevelTrigger) const;
 
 private:
     audio_utils_fifo&   mFifo;
@@ -222,45 +370,121 @@
     uint32_t    mLocalRear; // frame index of next frame slot available to write, or write index
 
     // TODO needs a state transition diagram for threshold and arming process
-    uint32_t    mLowLevelArm;       // arm if filled <= threshold
-    uint32_t    mHighLevelTrigger;  // trigger reader if armed and filled >= threshold
-    bool        mArmed;
+    // TODO make a separate class and associate with the synchronization object
+    uint32_t    mLowLevelArm;       // arm if filled < arm level before release()
+    uint32_t    mHighLevelTrigger;  // trigger if armed and filled > trigger level after release()
+    bool        mArmed;             // whether currently armed
 
     uint32_t    mEffectiveFrames;   // current effective buffer size, <= mFifo.mFrameCount
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
+/**
+ * Used to read from a FIFO.  There can be one or more readers per FIFO,
+ * and at most one of those readers can throttle the writer.
+ * All other readers must keep up with the writer or they will lose frames.
+ * Each reader is multi-thread safe with respect to the writer and any other readers,
+ * but not with respect to multiple threads calling the reader API.
+ */
 class audio_utils_fifo_reader : public audio_utils_fifo_provider {
 
 public:
-    // At most one reader can specify throttlesWriter == true
+    /**
+     * Single-process and multi-process use same constructor here,
+     * but different FIFO constructors.
+     *
+     * \param fifo Associated FIFO.  Passed by reference because it must be non-NULL.
+     * \param throttlesWriter Whether this reader throttles the writer.
+     *                        At most one reader can specify throttlesWriter == true.
+     */
     audio_utils_fifo_reader(audio_utils_fifo& fifo, bool throttlesWriter = true);
     virtual ~audio_utils_fifo_reader();
 
-/** Read from FIFO.
- *
- * \param buffer    Pointer to destination buffer to be filled with up to 'count' frames of data.
- * \param count     Desired number of frames to read.
- * \param timeout   NULL and zero fields are both non-blocking.
- * \param lost      If non-NULL, set to the approximate number of lost frames before re-sync.
- *
- * \return actual number of frames read <= count.
- *
- * The actual transfer count may be zero if the FIFO is empty,
- * or partial if the FIFO was almost empty.
- * A negative return value indicates an error.
- */
+    /**
+     * Read from FIFO.  Resets the number of releasable frames to zero.
+     *
+     * \param buffer  Pointer to destination buffer to be filled with up to \p count frames of data.
+     * \param count   Desired number of frames to read.
+     * \param timeout Indicates the maximum time to block for at least one frame.
+     *                NULL and {0, 0} both mean non-blocking.
+     *                Time is expressed as relative CLOCK_MONOTONIC.
+     *                As an optimization, if \p timeout->tv_sec is the maximum positive value for
+     *                time_t (LONG_MAX), then the implementation treats it as infinite timeout.
+     * \param lost    If non-NULL, updated to the approximate number of lost frames before re-sync.
+     *
+     * \return Actual number of frames read, if greater than or equal to zero.
+     *         Guaranteed to be <= \p count.
+     *         The actual transfer count may be zero if the FIFO is empty,
+     *         or partial if the FIFO was almost empty.
+     *  \retval -EIO        corrupted indices, no recovery is possible
+     *  \retval -EOVERFLOW  reader is not keeping up with writer
+     *  \retval -ETIMEDOUT  count is greater than zero, timeout is non-NULL and not {0, 0},
+     *                      timeout expired, and no frames were available after the timeout.
+     *  \retval -EINTR      count is greater than zero, timeout is non-NULL and not {0, 0}, timeout
+     *                      was interrupted by a signal, and no frames were available after signal.
+     */
     ssize_t read(void *buffer, size_t count, struct timespec *timeout = NULL, size_t *lost = NULL);
 
     // Implement audio_utils_fifo_provider
-    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count, struct timespec *timeout);
+    virtual ssize_t obtain(audio_utils_iovec iovec[2], size_t count,
+            struct timespec *timeout = NULL);
     virtual void release(size_t count);
+    virtual ssize_t available();
 
-    // Extended parameter list for reader only
+    /**
+     * Same as audio_utils_fifo_provider::obtain, except has an additional parameter \p lost.
+     *
+     * \param iovec   See audio_utils_fifo_provider::obtain.
+     * \param count   See audio_utils_fifo_provider::obtain.
+     * \param timeout See audio_utils_fifo_provider::obtain.
+     * \param lost    If non-NULL, updated to the approximate number of lost frames before re-sync.
+     * \return See audio_utils_fifo_provider::obtain for 'Returns' and 'Return values'.
+     */
     ssize_t obtain(audio_utils_iovec iovec[2], size_t count, struct timespec *timeout,
             size_t *lost);
 
+    /**
+     * Determine the number of frames that could be obtained or read without blocking.
+     * There's an inherent race condition: the value may soon be obsolete so shouldn't be trusted.
+     * available() may be called after obtain(), but doesn't affect the number of releasable frames.
+     *
+     * \param lost    If non-NULL, updated to the approximate number of lost frames before re-sync.
+     *
+     * \return Number of available frames, if greater than or equal to zero.
+     *  \retval -EIO        corrupted indices, no recovery is possible
+     *  \retval -EOVERFLOW  reader is not keeping up with writer
+     */
+    ssize_t available(size_t *lost);
+
+    /**
+     * Set the hysteresis levels for a throttling reader to wake a blocked writer.
+     * A non-empty read() or release() by a throttling reader will wake the writer
+     * only if the fill level was > \p highLevelArm before the read() or release(),
+     * and then the fill level became < \p lowLevelTrigger afterwards.
+     * The default value for \p highLevelArm is -1, which means always armed.
+     * The default value for \p lowLevelTrigger is mFifo.mFrameCount,
+     * which means every read() or release() will wake the writer.
+     * For hysteresis, \p highLevelArm must be >= \p lowLevelTrigger - 1.
+     * Decreasing \p highLevelArm will arm for wakeup, regardless of the current fill level.
+     * Note that the throttling reader is not directly aware of the writer's effective buffer size,
+     * so any change in effective buffer size must be communicated indirectly.
+     *
+     * \param highLevelArm      Arm for wakeup when fill level > this value.
+     *                          Capped to range [-1, mFifo.mFrameCount].
+     * \param lowLevelTrigger   Trigger wakeup when armed and fill level < this value.
+     *                          Capped to range [0, mFifo.mFrameCount].
+     */
+    void setHysteresis(int32_t highLevelArm, uint32_t lowLevelTrigger);
+
+    /**
+     * Get the hysteresis levels for waking readers.
+     *
+     * \param highLevelArm      Set to the current high level arm value in frames.
+     * \param lowLevelTrigger   Set to the current low level trigger value in frames.
+     */
+    void getHysteresis(int32_t *highLevelArm, uint32_t *lowLevelTrigger) const;
+
 private:
     audio_utils_fifo&   mFifo;
 
@@ -268,13 +492,13 @@
     uint32_t     mLocalFront;   // frame index of first frame slot available to read, or read index
 
     // Points to shared front index if this reader throttles writer, or NULL if we don't throttle
+    // FIXME consider making it a boolean
     audio_utils_fifo_index*     mThrottleFront;
 
-    // TODO not used yet
-    uint32_t    mHighLevelArm;      // arm if filled >= threshold
-    uint32_t    mLowLevelTrigger;   // trigger writer if armed and filled <= threshold
-    bool        mArmed;
-
+    // TODO not used yet, needs state transition diagram
+    int32_t     mHighLevelArm;      // arm if filled > arm level before release()
+    uint32_t    mLowLevelTrigger;   // trigger if armed and filled < trigger level after release()
+    bool        mArmed;             // whether currently armed
 };
 
 #endif  // !ANDROID_AUDIO_FIFO_H
diff --git a/audio_utils/tests/fifo_multiprocess.cpp b/audio_utils/tests/fifo_multiprocess.cpp
index 0965749..f9c72e2 100644
--- a/audio_utils/tests/fifo_multiprocess.cpp
+++ b/audio_utils/tests/fifo_multiprocess.cpp
@@ -94,7 +94,18 @@
                 printf("wrote unexpected actual = %zd\n", actual);
                 break;
             }
-            sleep(1);
+            // TODO needs a lot of work
+            switch (value) {
+            case 10:
+                sleep(2);
+                break;
+            case 14:
+                sleep(4);
+                break;
+            default:
+                usleep(500000);
+                break;
+            }
         }
 
         (void) close(frontFd);
@@ -129,7 +140,8 @@
         printf("reader prot read rear ok=%d\n", ok);
         // The pagesize * 4 offset confirms that we don't assume identical mapping in both processes
         rearIndex = (audio_utils_fifo_index *) mmap((char *) rearIndex + pageSize * 4,
-                sizeof(audio_utils_fifo_index), PROT_READ, MAP_SHARED | MAP_FIXED, rearFd, (off_t) 0);
+                sizeof(audio_utils_fifo_index), PROT_READ, MAP_SHARED | MAP_FIXED, rearFd,
+                (off_t) 0);
         printf("reader rearIndex=%p\n", rearIndex);
 
         // Similarly for the data
@@ -162,6 +174,9 @@
                     goto out;
                 }
                 break;
+            case -ETIMEDOUT:
+                printf("read timed out\n");
+                break;
             default:
                 printf("read unexpected actual = %zd\n", actual);
                 goto out;
diff --git a/audio_utils/tests/fifo_tests.cpp b/audio_utils/tests/fifo_tests.cpp
index f4fc1d3..08c1e08 100644
--- a/audio_utils/tests/fifo_tests.cpp
+++ b/audio_utils/tests/fifo_tests.cpp
@@ -34,6 +34,7 @@
     size_t maxFramesPerRead = 0;
     size_t maxFramesPerWrite = 0;
     bool readerThrottlesWriter = true;
+    bool verbose = false;
     int i;
     for (i = 1; i < argc; i++) {
         char *arg = argv[i];
@@ -49,6 +50,9 @@
         case 't':   // disable throttling of writer by reader
             readerThrottlesWriter = false;
             break;
+        case 'v':   // enable verbose logging
+            verbose = true;
+            break;
         case 'w':   // maximum frame count per write to FIFO
             maxFramesPerWrite = atoi(&arg[2]);
             break;
@@ -69,7 +73,7 @@
 
     if (argc - i != 2) {
 usage:
-        fprintf(stderr, "usage: %s [-f#] [-r#] [-t] [-w#] in.wav out.wav\n", argv[0]);
+        fprintf(stderr, "usage: %s [-f#] [-r#] [-t] [-v] [-w#] in.wav out.wav\n", argv[0]);
         return EXIT_FAILURE;
     }
     char *inputFile = argv[i];
@@ -105,7 +109,7 @@
     size_t framesWritten = 0;
     size_t framesRead = 0;
     short *fifoBuffer = new short[frameCount * sfinfoin.channels];
-    audio_utils_fifo fifo(frameCount, frameSize, fifoBuffer);
+    audio_utils_fifo fifo(frameCount, frameSize, fifoBuffer, readerThrottlesWriter);
     audio_utils_fifo_writer fifoWriter(fifo);
     audio_utils_fifo_reader fifoReader(fifo, readerThrottlesWriter);
     int fifoWriteCount = 0, fifoReadCount = 0;
@@ -123,7 +127,9 @@
         framesToWrite = rand() % (framesToWrite + 1);
         ssize_t actualWritten = fifoWriter.write(
                 &inputBuffer[framesWritten * sfinfoin.channels], framesToWrite);
-        //printf("wrote %d out of %d\n", (int) actualWritten, (int) framesToWrite);
+        if (verbose) {
+            printf("wrote %d out of %d\n", (int) actualWritten, (int) framesToWrite);
+        }
         if (actualWritten < 0 || (size_t) actualWritten > framesToWrite) {
             fprintf(stderr, "write to FIFO failed\n");
             break;
@@ -137,6 +143,9 @@
             fifoWriteCount++;
         }
         fifoFillLevel += actualWritten;
+        if (verbose) {
+            printf("fill level after write %d\n", fifoFillLevel);
+        }
         if (fifoFillLevel > maxFillLevel) {
             maxFillLevel = fifoFillLevel;
             if (maxFillLevel > (int) frameCount) {
@@ -153,7 +162,9 @@
         framesToRead = rand() % (framesToRead + 1);
         ssize_t actualRead = fifoReader.read(
                 &outputBuffer[framesRead * sfinfoin.channels], framesToRead);
-        //printf("read %d out of %d\n", (int) actualRead, (int) framesToRead);
+        if (verbose) {
+            printf("read %d out of %d\n", (int) actualRead, (int) framesToRead);
+        }
         if (actualRead < 0 || (size_t) actualRead > framesToRead) {
             switch (actualRead) {
             case -EIO:
@@ -188,6 +199,9 @@
             fifoReadCount++;
         }
         fifoFillLevel -= actualRead;
+        if (verbose) {
+            printf("fill level after read %d\n", fifoFillLevel);
+        }
         if (fifoFillLevel < minFillLevel) {
             minFillLevel = fifoFillLevel;
             if (minFillLevel < 0) {
@@ -204,6 +218,7 @@
     printf("FIFO non-empty writes: %d, non-empty reads: %d\n", fifoWriteCount, fifoReadCount);
     printf("fill=%d, min=%d, max=%d\n", fifoFillLevel, minFillLevel, maxFillLevel);
 
+    printf("writing output\n");
     SF_INFO sfinfoout;
     memset(&sfinfoout, 0, sizeof(sfinfoout));
     sfinfoout.samplerate = sfinfoin.samplerate;
@@ -224,5 +239,7 @@
         return EXIT_FAILURE;
     }
     sf_close(sfout);
+    printf("done\n");
+
     return EXIT_SUCCESS;
 }
diff --git a/audio_utils/tests/fifo_threads.cpp b/audio_utils/tests/fifo_threads.cpp
index 92a2eae..83a0cdf 100644
--- a/audio_utils/tests/fifo_threads.cpp
+++ b/audio_utils/tests/fifo_threads.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <errno.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -36,15 +37,23 @@
     Context *context = (Context *) arg;
     for (;;) {
         struct timespec timeout;
-        timeout.tv_sec = 3;
+        timeout.tv_sec = 30;
         timeout.tv_nsec = 0;
         char buffer[4];
         ssize_t actual = context->mInputReader->read(buffer, sizeof(buffer), &timeout);
-        if (actual == 0) {
+        // TODO this test is unreadable
+        if (actual > 0) {
+            if ((size_t) actual > sizeof(buffer)) {
+                printf("input.read actual = %d\n", (int) actual);
+                abort();
+            }
+            ssize_t actual2 = context->mTransferWriter->write(buffer, actual, &timeout);
+            if (actual2 != actual) {
+                printf("transfer.write(%d) = %d\n", (int) actual, (int) actual2);
+            }
+            //sleep(10);
+        } else if (actual == -ETIMEDOUT) {
             (void) write(1, "t", 1);
-        } else if (actual > 0) {
-            actual = context->mTransferWriter->write(buffer, actual, &timeout);
-            //printf("transfer.write actual = %d\n", (int) actual);
         } else {
             printf("input.read actual = %d\n", (int) actual);
         }
@@ -52,20 +61,32 @@
     return NULL;
 }
 
+volatile bool outputPaused = false;
+
 void *output_routine(void *arg)
 {
     Context *context = (Context *) arg;
     for (;;) {
+        if (outputPaused) {
+            sleep(1);
+            continue;
+        }
         struct timespec timeout;
-        timeout.tv_sec = 5;
+        timeout.tv_sec = 60;
         timeout.tv_nsec = 0;
         char buffer[4];
         ssize_t actual = context->mTransferReader->read(buffer, sizeof(buffer), &timeout);
-        if (actual == 0) {
+        if (actual > 0) {
+            if ((size_t) actual > sizeof(buffer)) {
+                printf("transfer.read actual = %d\n", (int) actual);
+                abort();
+            }
+            ssize_t actual2 = context->mOutputWriter->write(buffer, actual, NULL /*timeout*/);
+            if (actual2 != actual) {
+                printf("output.write(%d) = %d\n", (int) actual, (int) actual2);
+            }
+        } else if (actual == -ETIMEDOUT) {
             (void) write(1, "T", 1);
-        } else if (actual > 0) {
-            actual = context->mOutputWriter->write(buffer, actual, &timeout);
-            //printf("output.write actual = %d\n", (int) actual);
         } else {
             printf("transfer.read actual = %d\n", (int) actual);
         }
@@ -79,21 +100,24 @@
     argc = argc + 0;
     argv = &argv[0];
 
-    char inputBuffer[64];
-    audio_utils_fifo inputFifo(sizeof(inputBuffer) /*frameCount*/, 1 /*frameSize*/, inputBuffer);
+    char inputBuffer[16];
+    audio_utils_fifo inputFifo(sizeof(inputBuffer) /*frameCount*/, 1 /*frameSize*/, inputBuffer,
+            true /*throttlesWriter*/);
     audio_utils_fifo_writer inputWriter(inputFifo);
-    audio_utils_fifo_reader inputReader(inputFifo, true /*readerThrottlesWriter*/);
-    inputWriter.setHighLevelTrigger(3);
+    audio_utils_fifo_reader inputReader(inputFifo, true /*throttlesWriter*/);
+    //inputWriter.setHysteresis(sizeof(inputBuffer) * 1/4, sizeof(inputBuffer) * 3/4);
 
     char transferBuffer[64];
     audio_utils_fifo transferFifo(sizeof(transferBuffer) /*frameCount*/, 1 /*frameSize*/,
-            transferBuffer);
+            transferBuffer, true /*throttlesWriter*/);
     audio_utils_fifo_writer transferWriter(transferFifo);
-    audio_utils_fifo_reader transferReader(transferFifo, true /*readerThrottlesWriter*/);
-    transferWriter.setEffectiveFrames(2);
+    audio_utils_fifo_reader transferReader(transferFifo, true /*throttlesWriter*/);
+    transferReader.setHysteresis(sizeof(transferBuffer) * 3/4, sizeof(transferBuffer) * 1/4);
+    //transferWriter.setEffective(8);
 
     char outputBuffer[64];
-    audio_utils_fifo outputFifo(sizeof(outputBuffer) /*frameCount*/, 1 /*frameSize*/, outputBuffer);
+    audio_utils_fifo outputFifo(sizeof(outputBuffer) /*frameCount*/, 1 /*frameSize*/, outputBuffer,
+            true /*throttlesWriter*/);
     audio_utils_fifo_writer outputWriter(outputFifo);
     audio_utils_fifo_reader outputReader(outputFifo, true /*readerThrottlesWriter*/);
 
@@ -114,24 +138,25 @@
     ok = ok + 0;
 
     for (;;) {
-        char buffer[1];
-        struct timespec timeout;
-        timeout.tv_sec = 0;
-        timeout.tv_nsec = 0;
-        ssize_t actual = outputReader.read(buffer, sizeof(buffer), &timeout);
-        if (actual == 1) {
-            printf("%c", buffer[0]);
+        char buffer[4];
+        ssize_t actual = outputReader.read(buffer, sizeof(buffer), NULL /*timeout*/);
+        if (actual > 0) {
+            printf("%.*s", (int) actual, buffer);
             fflush(stdout);
         } else if (actual != 0) {
             printf("outputReader.read actual = %d\n", (int) actual);
         }
         if (kbhit()) {
             int ch = getch();
-            if (ch <= 0 || ch == 3) {
+            if (ch <= 0 || ch == '\003' /*control-C*/) {
                 break;
             }
+            if (ch == 'p')
+                outputPaused = true;
+            else if (ch == 'p')
+                outputPaused = false;
             buffer[0] = ch;
-            actual = inputWriter.write(buffer, sizeof(buffer), &timeout);
+            actual = inputWriter.write(buffer, 1, NULL /*timeout*/);
             if (actual != 1) {
                 printf("inputWriter.write actual = %d\n", (int) actual);
             }