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);
}