#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;
(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));
////////////////////////////////////////////////////////////////////////////////
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
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)
{
}
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 ?
(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;
}
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)
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
{
}
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)
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);
}
}
+// 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;
}
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;
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;
}
#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;
public:
audio_utils_fifo_index() : mIndex(0) { }
+ ~audio_utils_fifo_index() { }
private:
// Linux futex is 32 bits regardless of platform.
// 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;
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();
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;
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;
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