OSDN Git Service

fifo: improve blocking and hysteresis
authorGlenn Kasten <gkasten@google.com>
Sun, 2 Oct 2016 20:00:19 +0000 (13:00 -0700)
committerGlenn Kasten <gkasten@google.com>
Wed, 12 Oct 2016 23:52:16 +0000 (16:52 -0700)
Work-in-progress to eventually allow the application to independently
specify whether or not each index (both front and rear) is also treated
as a futex, and if so whether it is single-process or multi-process.
This involves replacing mIsPrivate by an enum for futex types.
Still to do:
 - provide an API to configure this
 - better testing

Continued work on the setHysteresis() API.
Still to do:
 - better testing and debugging, especially for the read side.
 - do the right thing based on data currently in the buffer

Added available() API to return the number of frames that could
be obtain()ed.  For reader, this is the fill level.

Miscellaneous:
 - renamed sharedRear to writerRear
 - improved Doxygen comments
 - address code review comments by explaining why some paramters are
   passed by reference and some as pointers
 - updated tests
 - fixed bug: iovec was not being clear on error
 - changed default value of throttlesWriter to true

Test: tests/fifo_*
Change-Id: I0e19052abc90453ca48a0299d2c2de62468de6cb

audio_utils/fifo.cpp
audio_utils/include/audio_utils/fifo.h
audio_utils/tests/fifo_multiprocess.cpp
audio_utils/tests/fifo_tests.cpp
audio_utils/tests/fifo_threads.cpp

index f19f93b..31a0c62 100644 (file)
@@ -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>
 #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 @@ static int sys_futex(void *addr1, int op, int val1, struct timespec *timeout, vo
     (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 @@ int32_t audio_utils_fifo_base::diff(uint32_t rear, uint32_t front, size_t *lost)
 ////////////////////////////////////////////////////////////////////////////////
 
 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_provider::~audio_utils_fifo_provider()
 
 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 @@ ssize_t audio_utils_fifo_writer::write(const void *buffer, size_t count, struct
     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 @@ ssize_t audio_utils_fifo_writer::obtain(audio_utils_iovec iovec[2], size_t count
                     (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 @@ ssize_t audio_utils_fifo_writer::obtain(audio_utils_iovec iovec[2], size_t count
         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 @@ void audio_utils_fifo_writer::release(size_t count)
         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;
+                    }
+                    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;
                     }
-                    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 @@ ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count
         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 @@ void audio_utils_fifo_reader::release(size_t count)
     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;
+                    }
+                    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;
                     }
-                    mArmed = false;
                 }
+                break;
+            default:
+                LOG_ALWAYS_FATAL("mFifo.mThrottleFrontSync=%d", mFifo.mThrottleFrontSync);
+                break;
             }
         } else {
             mLocalFront = mFifo.sum(mLocalFront, count);
@@ -355,32 +494,56 @@ void audio_utils_fifo_reader::release(size_t count)
     }
 }
 
+// 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 @@ ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count
         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 @@ ssize_t audio_utils_fifo_reader::obtain(audio_utils_iovec iovec[2], size_t count
         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;
 }
index 53099b6..bb55ab4 100644 (file)
 #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 @@ struct audio_utils_fifo_index {
 
 public:
     audio_utils_fifo_index() : mIndex(0) { }
+    ~audio_utils_fifo_index() { }
 
 private:
     // Linux futex is 32 bits regardless of platform.
@@ -45,65 +48,93 @@ private:
     // 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
-
-    audio_utils_fifo_index&     mSharedRear;
-
-    // Pointer to the front index of at most one reader that throttles the writer,
-    // or NULL for no throttling
+    /** 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;
+
+    /**
+     * 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;
+
+    /** 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 @@ class audio_utils_fifo : audio_utils_fifo_base {
 
 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);
-
-/**
- * Construct a FIFO object: single-process.
- *  \param throttlesWriter Whether there is a reader that throttles the writer.
- */
+            audio_utils_fifo_index& writerRear, audio_utils_fifo_index *throttleFront = NULL);
+
+    /**
+     * 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 @@ private:
     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.
-
-// 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;
-
+    /**
+     * 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;
+
+    /**
+     * 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);
-
-    // TODO add error checks and getters
-    void setHighLevelTrigger(uint32_t level) { mHighLevelTrigger = level; }
-    void setEffectiveFrames(uint32_t effectiveFrames) { mEffectiveFrames = effectiveFrames; }
+    virtual ssize_t available();
+
+    /**
+     * 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 @@ private:
     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);
-
-    // Extended parameter list for reader only
+    virtual ssize_t available();
+
+    /**
+     * 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 @@ private:
     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
index 0965749..f9c72e2 100644 (file)
@@ -94,7 +94,18 @@ int main(int argc __unused, char **argv __unused)
                 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 @@ int main(int argc __unused, char **argv __unused)
         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 @@ int main(int argc __unused, char **argv __unused)
                     goto out;
                 }
                 break;
+            case -ETIMEDOUT:
+                printf("read timed out\n");
+                break;
             default:
                 printf("read unexpected actual = %zd\n", actual);
                 goto out;
index f4fc1d3..08c1e08 100644 (file)
@@ -34,6 +34,7 @@ int main(int argc, char **argv)
     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 @@ int main(int argc, char **argv)
         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 @@ int main(int argc, char **argv)
 
     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 @@ usage:
     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 @@ usage:
         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 @@ usage:
             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 @@ usage:
         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 @@ usage:
             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 @@ usage:
     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 @@ usage:
         return EXIT_FAILURE;
     }
     sf_close(sfout);
+    printf("done\n");
+
     return EXIT_SUCCESS;
 }
index 92a2eae..83a0cdf 100644 (file)
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <errno.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -36,15 +37,23 @@ void *input_routine(void *arg)
     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 @@ void *input_routine(void *arg)
     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 @@ int main(int argc, char **argv)
     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 @@ int main(int argc, char **argv)
     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);
             }