NBAIO: re-implement NBAIO Pipe and MonoPipe using fifo
Also removed const from NBAIO_Sink::availableToWrite() because
at least one implementation can no longer implement the const-ness.
Test: normal mixer, tee sink, and remote submix still work
Change-Id: I8461177efdf53bba8295b147e97835b018804903
diff --git a/include/media/nbaio/AudioStreamOutSink.h b/include/media/nbaio/AudioStreamOutSink.h
index 56052a6..56a2a38 100644
--- a/include/media/nbaio/AudioStreamOutSink.h
+++ b/include/media/nbaio/AudioStreamOutSink.h
@@ -44,7 +44,7 @@
// This is an over-estimate, and could dupe the caller into making a blocking write()
// FIXME Use an audio HAL API to query the buffer emptying status when it's available.
- virtual ssize_t availableToWrite() const { return mStreamBufferSizeBytes / mFrameSize; }
+ virtual ssize_t availableToWrite() { return mStreamBufferSizeBytes / mFrameSize; }
virtual ssize_t write(const void *buffer, size_t count);
diff --git a/include/media/nbaio/LibsndfileSink.h b/include/media/nbaio/LibsndfileSink.h
index f5d53d5..97a57e0 100644
--- a/include/media/nbaio/LibsndfileSink.h
+++ b/include/media/nbaio/LibsndfileSink.h
@@ -41,7 +41,7 @@
//virtual size_t framesWritten() const;
//virtual size_t framesUnderrun() const;
//virtual size_t underruns() const;
- //virtual ssize_t availableToWrite() const;
+ //virtual ssize_t availableToWrite();
virtual ssize_t write(const void *buffer, size_t count);
//virtual ssize_t writeVia(writeVia_t via, size_t total, void *user, size_t block);
diff --git a/include/media/nbaio/MonoPipe.h b/include/media/nbaio/MonoPipe.h
index d2cd218..60ae92e 100644
--- a/include/media/nbaio/MonoPipe.h
+++ b/include/media/nbaio/MonoPipe.h
@@ -18,8 +18,9 @@
#define ANDROID_AUDIO_MONO_PIPE_H
#include <time.h>
-#include "NBAIO.h"
+#include <audio_utils/fifo.h>
#include <media/SingleStateQueue.h>
+#include "NBAIO.h"
namespace android {
@@ -55,7 +56,10 @@
//virtual int64_t framesUnderrun() const;
//virtual int64_t underruns() const;
- virtual ssize_t availableToWrite() const;
+ // returns n where 0 <= n <= mMaxFrames, or a negative status_t
+ // including the private status codes in NBAIO.h
+ virtual ssize_t availableToWrite();
+
virtual ssize_t write(const void *buffer, size_t count);
//virtual ssize_t writeVia(writeVia_t via, size_t total, void *user, size_t block);
@@ -80,16 +84,10 @@
status_t getTimestamp(ExtendedTimestamp ×tamp);
private:
- const size_t mReqFrames; // as requested in constructor, unrounded
- const size_t mMaxFrames; // always a power of 2
+ const size_t mMaxFrames; // as requested in constructor, rounded up to a power of 2
void * const mBuffer;
- // mFront and mRear will never be separated by more than mMaxFrames.
- // 32-bit overflow is possible if the pipe is active for a long time, but if that happens it's
- // safe because we "&" with (mMaxFrames-1) at end of computations to calculate a buffer index.
- volatile int32_t mFront; // written by reader with android_atomic_release_store,
- // read by writer with android_atomic_acquire_load
- volatile int32_t mRear; // written by writer with android_atomic_release_store,
- // read by reader with android_atomic_acquire_load
+ audio_utils_fifo mFifo;
+ audio_utils_fifo_writer mFifoWriter;
bool mWriteTsValid; // whether mWriteTs is valid
struct timespec mWriteTs; // time that the previous write() completed
size_t mSetpoint; // target value for pipe fill depth
diff --git a/include/media/nbaio/MonoPipeReader.h b/include/media/nbaio/MonoPipeReader.h
index b3c891d..0776ecd 100644
--- a/include/media/nbaio/MonoPipeReader.h
+++ b/include/media/nbaio/MonoPipeReader.h
@@ -27,7 +27,7 @@
public:
// Construct a MonoPipeReader and associate it with a MonoPipe;
- // any data already in the pipe is visible to this PipeReader.
+ // any data already in the pipe is visible to this MonoPipeReader.
// There can be only a single MonoPipeReader per MonoPipe.
// FIXME make this constructor a factory method of MonoPipe.
MonoPipeReader(MonoPipe* pipe);
@@ -59,6 +59,7 @@
private:
MonoPipe * const mPipe;
+ audio_utils_fifo_reader mFifoReader;
};
} // namespace android
diff --git a/include/media/nbaio/NBAIO.h b/include/media/nbaio/NBAIO.h
index 3fd97ac..d8b7343 100644
--- a/include/media/nbaio/NBAIO.h
+++ b/include/media/nbaio/NBAIO.h
@@ -164,7 +164,12 @@
// UNDERRUN write() has not been called frequently enough, or with enough frames to keep up.
// An underrun event is counted, and the caller should re-try this operation.
// WOULD_BLOCK Determining how many frames can be written without blocking would itself block.
- virtual ssize_t availableToWrite() const { return SSIZE_MAX; }
+ virtual ssize_t availableToWrite() {
+ if (!mNegotiated) {
+ return NEGOTIATE;
+ }
+ return SSIZE_MAX;
+ }
// Transfer data to sink from single input buffer. Implies a copy.
// Inputs:
diff --git a/include/media/nbaio/Pipe.h b/include/media/nbaio/Pipe.h
index cc95ff7..58b9750 100644
--- a/include/media/nbaio/Pipe.h
+++ b/include/media/nbaio/Pipe.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_AUDIO_PIPE_H
#define ANDROID_AUDIO_PIPE_H
+#include <audio_utils/fifo.h>
#include "NBAIO.h"
namespace android {
@@ -51,7 +52,7 @@
// The write side of a pipe permits overruns; flow control is the caller's responsibility.
// It doesn't return +infinity because that would guarantee an overrun.
- virtual ssize_t availableToWrite() const { return mMaxFrames; }
+ virtual ssize_t availableToWrite() { return mMaxFrames; }
virtual ssize_t write(const void *buffer, size_t count);
//virtual ssize_t writeVia(writeVia_t via, size_t total, void *user, size_t block);
@@ -59,7 +60,8 @@
private:
const size_t mMaxFrames; // always a power of 2
void * const mBuffer;
- volatile int32_t mRear; // written by android_atomic_release_store
+ audio_utils_fifo mFifo;
+ audio_utils_fifo_writer mFifoWriter;
volatile int32_t mReaders; // number of PipeReader clients currently attached to this Pipe
const bool mFreeBufferInDestructor;
};
diff --git a/include/media/nbaio/PipeReader.h b/include/media/nbaio/PipeReader.h
index 00c2b3c..70ecb34 100644
--- a/include/media/nbaio/PipeReader.h
+++ b/include/media/nbaio/PipeReader.h
@@ -57,7 +57,7 @@
private:
Pipe& mPipe;
- int32_t mFront; // follows behind mPipe.mRear
+ audio_utils_fifo_reader mFifoReader;
int64_t mFramesOverrun;
int64_t mOverruns;
};
diff --git a/media/libnbaio/Android.bp b/media/libnbaio/Android.bp
index 615b541..fd7af4f 100644
--- a/media/libnbaio/Android.bp
+++ b/media/libnbaio/Android.bp
@@ -2,16 +2,15 @@
name: "libnbaio",
srcs: [
"AudioBufferProviderSource.cpp",
- "AudioStreamOutSink.cpp",
"AudioStreamInSource.cpp",
- "NBAIO.cpp",
+ "AudioStreamOutSink.cpp",
"MonoPipe.cpp",
"MonoPipeReader.cpp",
+ "NBAIO.cpp",
+ "NBLog.cpp",
"Pipe.cpp",
"PipeReader.cpp",
"SourceAudioBufferProvider.cpp",
-
- "NBLog.cpp",
],
// libsndfile license is incompatible; uncomment to use for local debug only
@@ -33,4 +32,6 @@
"-Werror",
"-Wall",
],
+
+ include_dirs: ["system/media/audio_utils/include"],
}
diff --git a/media/libnbaio/MonoPipe.cpp b/media/libnbaio/MonoPipe.cpp
index 8d1cb0f..3c5df1a 100644
--- a/media/libnbaio/MonoPipe.cpp
+++ b/media/libnbaio/MonoPipe.cpp
@@ -19,7 +19,6 @@
#define LOG_TAG "MonoPipe"
//#define LOG_NDEBUG 0
-#include <cutils/atomic.h>
#include <cutils/compiler.h>
#include <utils/Log.h>
#include <utils/Trace.h>
@@ -32,11 +31,11 @@
MonoPipe::MonoPipe(size_t reqFrames, const NBAIO_Format& format, bool writeCanBlock) :
NBAIO_Sink(format),
- mReqFrames(reqFrames),
+ // TODO fifo now supports non-power-of-2 buffer sizes, so could remove the roundup
mMaxFrames(roundup(reqFrames)),
mBuffer(malloc(mMaxFrames * Format_frameSize(format))),
- mFront(0),
- mRear(0),
+ mFifo(mMaxFrames, Format_frameSize(format), mBuffer, true /*throttlesWriter*/),
+ mFifoWriter(mFifo),
mWriteTsValid(false),
// mWriteTs
mSetpoint((reqFrames * 11) / 16),
@@ -53,14 +52,14 @@
free(mBuffer);
}
-ssize_t MonoPipe::availableToWrite() const
+ssize_t MonoPipe::availableToWrite()
{
if (CC_UNLIKELY(!mNegotiated)) {
return NEGOTIATE;
}
- // uses mMaxFrames not mReqFrames, so allows "over-filling" the pipe beyond requested limit
- ssize_t ret = mMaxFrames - (mRear - android_atomic_acquire_load(&mFront));
- ALOG_ASSERT((0 <= ret) && (ret <= mMaxFrames));
+ // uses mMaxFrames not reqFrames, so allows "over-filling" the pipe beyond requested limit
+ ssize_t ret = mFifoWriter.available();
+ ALOG_ASSERT(ret <= mMaxFrames);
return ret;
}
@@ -71,38 +70,33 @@
}
size_t totalFramesWritten = 0;
while (count > 0) {
- // can't return a negative value, as we already checked for !mNegotiated
- size_t avail = availableToWrite();
- size_t written = avail;
- if (CC_LIKELY(written > count)) {
- written = count;
- }
- size_t rear = mRear & (mMaxFrames - 1);
- size_t part1 = mMaxFrames - rear;
- if (part1 > written) {
- part1 = written;
- }
- if (CC_LIKELY(part1 > 0)) {
- memcpy((char *) mBuffer + (rear * mFrameSize), buffer, part1 * mFrameSize);
- if (CC_UNLIKELY(rear + part1 == mMaxFrames)) {
- size_t part2 = written - part1;
- if (CC_LIKELY(part2 > 0)) {
- memcpy(mBuffer, (char *) buffer + (part1 * mFrameSize), part2 * mFrameSize);
- }
+ ssize_t actual = mFifoWriter.write(buffer, count);
+ ALOG_ASSERT(actual <= count);
+ if (actual < 0) {
+ if (totalFramesWritten == 0) {
+ return actual;
}
- android_atomic_release_store(written + mRear, &mRear);
- totalFramesWritten += written;
+ break;
}
+ size_t written = (size_t) actual;
+ totalFramesWritten += written;
if (!mWriteCanBlock || mIsShutdown) {
break;
}
count -= written;
buffer = (char *) buffer + (written * mFrameSize);
+ // TODO Replace this whole section by audio_util_fifo's setpoint feature.
// Simulate blocking I/O by sleeping at different rates, depending on a throttle.
// The throttle tries to keep the mean pipe depth near the setpoint, with a slight jitter.
uint32_t ns;
if (written > 0) {
- size_t filled = (mMaxFrames - avail) + written;
+ ssize_t avail = mFifoWriter.available();
+ ALOG_ASSERT(avail <= mMaxFrames);
+ if (avail < 0) {
+ // don't return avail as status, because totalFramesWritten > 0
+ break;
+ }
+ size_t filled = mMaxFrames - (size_t) avail;
// FIXME cache these values to avoid re-computation
if (filled <= mSetpoint / 2) {
// pipe is (nearly) empty, fill quickly
diff --git a/media/libnbaio/MonoPipeReader.cpp b/media/libnbaio/MonoPipeReader.cpp
index 01dc524..a9b4d18 100644
--- a/media/libnbaio/MonoPipeReader.cpp
+++ b/media/libnbaio/MonoPipeReader.cpp
@@ -25,7 +25,7 @@
MonoPipeReader::MonoPipeReader(MonoPipe* pipe) :
NBAIO_Source(pipe->mFormat),
- mPipe(pipe)
+ mPipe(pipe), mFifoReader(mPipe->mFifo, true /*throttlesWriter*/)
{
}
@@ -38,38 +38,21 @@
if (CC_UNLIKELY(!mNegotiated)) {
return NEGOTIATE;
}
- ssize_t ret = android_atomic_acquire_load(&mPipe->mRear) - mPipe->mFront;
- ALOG_ASSERT((0 <= ret) && ((size_t) ret <= mPipe->mMaxFrames));
+ ssize_t ret = mFifoReader.available();
+ ALOG_ASSERT(ret <= mPipe->mMaxFrames);
return ret;
}
ssize_t MonoPipeReader::read(void *buffer, size_t count)
{
// count == 0 is unlikely and not worth checking for explicitly; will be handled automatically
- ssize_t red = availableToRead();
- if (CC_UNLIKELY(red <= 0)) {
- return red;
+ ssize_t actual = mFifoReader.read(buffer, count);
+ ALOG_ASSERT(actual <= count);
+ if (CC_UNLIKELY(actual <= 0)) {
+ return actual;
}
- if (CC_LIKELY((size_t) red > count)) {
- red = count;
- }
- size_t front = mPipe->mFront & (mPipe->mMaxFrames - 1);
- size_t part1 = mPipe->mMaxFrames - front;
- if (part1 > (size_t) red) {
- part1 = red;
- }
- if (CC_LIKELY(part1 > 0)) {
- memcpy(buffer, (char *) mPipe->mBuffer + (front * mFrameSize), part1 * mFrameSize);
- if (CC_UNLIKELY(front + part1 == mPipe->mMaxFrames)) {
- size_t part2 = red - part1;
- if (CC_LIKELY(part2 > 0)) {
- memcpy((char *) buffer + (part1 * mFrameSize), mPipe->mBuffer, part2 * mFrameSize);
- }
- }
- android_atomic_release_store(red + mPipe->mFront, &mPipe->mFront);
- mFramesRead += red;
- }
- return red;
+ mFramesRead += (size_t) actual;
+ return actual;
}
void MonoPipeReader::onTimestamp(const ExtendedTimestamp ×tamp)
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index 4d14904..c728e3e 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -67,6 +67,7 @@
/*static*/
size_t NBLog::Timeline::sharedSize(size_t size)
{
+ // TODO fifo now supports non-power-of-2 buffer sizes, so could remove the roundup
return sizeof(Shared) + roundup(size);
}
diff --git a/media/libnbaio/Pipe.cpp b/media/libnbaio/Pipe.cpp
index 13f211d..39df3f4 100644
--- a/media/libnbaio/Pipe.cpp
+++ b/media/libnbaio/Pipe.cpp
@@ -27,9 +27,11 @@
Pipe::Pipe(size_t maxFrames, const NBAIO_Format& format, void *buffer) :
NBAIO_Sink(format),
+ // TODO fifo now supports non-power-of-2 buffer sizes, so could remove the roundup
mMaxFrames(roundup(maxFrames)),
mBuffer(buffer == NULL ? malloc(mMaxFrames * Format_frameSize(format)) : buffer),
- mRear(0),
+ mFifo(mMaxFrames, Format_frameSize(format), mBuffer, false /*throttlesWriter*/),
+ mFifoWriter(mFifo),
mReaders(0),
mFreeBufferInDestructor(buffer == NULL)
{
@@ -49,25 +51,13 @@
if (CC_UNLIKELY(!mNegotiated)) {
return NEGOTIATE;
}
- // write() is not multi-thread safe w.r.t. itself, so no mutex or atomic op needed to read mRear
- size_t rear = mRear & (mMaxFrames - 1);
- size_t written = mMaxFrames - rear;
- if (CC_LIKELY(written > count)) {
- written = count;
+ ssize_t actual = mFifoWriter.write(buffer, count);
+ ALOG_ASSERT(actual <= count);
+ if (actual <= 0) {
+ return actual;
}
- memcpy((char *) mBuffer + (rear * mFrameSize), buffer, written * mFrameSize);
- if (CC_UNLIKELY(rear + written == mMaxFrames)) {
- if (CC_UNLIKELY((count -= written) > rear)) {
- count = rear;
- }
- if (CC_LIKELY(count > 0)) {
- memcpy(mBuffer, (char *) buffer + (written * mFrameSize), count * mFrameSize);
- written += count;
- }
- }
- android_atomic_release_store(written + mRear, &mRear);
- mFramesWritten += written;
- return written;
+ mFramesWritten += (size_t) actual;
+ return actual;
}
} // namespace android
diff --git a/media/libnbaio/PipeReader.cpp b/media/libnbaio/PipeReader.cpp
index fdea68e..bd468a6 100644
--- a/media/libnbaio/PipeReader.cpp
+++ b/media/libnbaio/PipeReader.cpp
@@ -25,9 +25,7 @@
PipeReader::PipeReader(Pipe& pipe) :
NBAIO_Source(pipe.mFormat),
- mPipe(pipe),
- // any data already in the pipe is not visible to this PipeReader
- mFront(android_atomic_acquire_load(&pipe.mRear)),
+ mPipe(pipe), mFifoReader(mPipe.mFifo, false /*throttlesWriter*/),
mFramesOverrun(0),
mOverruns(0)
{
@@ -45,71 +43,54 @@
ALOG_ASSERT(readers > 0);
}
-__attribute__((no_sanitize("integer")))
ssize_t PipeReader::availableToRead()
{
if (CC_UNLIKELY(!mNegotiated)) {
return NEGOTIATE;
}
- int32_t rear = android_atomic_acquire_load(&mPipe.mRear);
- // read() is not multi-thread safe w.r.t. itself, so no mutex or atomic op needed to read mFront
- size_t avail = rear - mFront;
- if (CC_UNLIKELY(avail > mPipe.mMaxFrames)) {
- // Discard all data in pipe to avoid another overrun immediately
- mFront = rear;
- mFramesOverrun += avail;
+ size_t lost;
+ ssize_t avail = mFifoReader.available(&lost);
+ if (avail == -EOVERFLOW || lost > 0) {
+ mFramesOverrun += lost;
++mOverruns;
- return OVERRUN;
+ avail = OVERRUN;
}
return avail;
}
-__attribute__((no_sanitize("integer")))
ssize_t PipeReader::read(void *buffer, size_t count)
{
- ssize_t avail = availableToRead();
- if (CC_UNLIKELY(avail <= 0)) {
- return avail;
+ size_t lost;
+ ssize_t actual = mFifoReader.read(buffer, count, NULL /*timeout*/, &lost);
+ ALOG_ASSERT(actual <= count);
+ if (actual == -EOVERFLOW || lost > 0) {
+ mFramesOverrun += lost;
+ ++mOverruns;
+ actual = OVERRUN;
}
- // An overrun can occur from here on and be silently ignored,
- // but it will be caught at next read()
- if (CC_LIKELY(count > (size_t) avail)) {
- count = avail;
+ if (actual <= 0) {
+ return actual;
}
- size_t front = mFront & (mPipe.mMaxFrames - 1);
- size_t red = mPipe.mMaxFrames - front;
- if (CC_LIKELY(red > count)) {
- red = count;
- }
- // In particular, an overrun during the memcpy will result in reading corrupt data
- memcpy(buffer, (char *) mPipe.mBuffer + (front * mFrameSize), red * mFrameSize);
- // We could re-read the rear pointer here to detect the corruption, but why bother?
- if (CC_UNLIKELY(front + red == mPipe.mMaxFrames)) {
- if (CC_UNLIKELY((count -= red) > front)) {
- count = front;
- }
- if (CC_LIKELY(count > 0)) {
- memcpy((char *) buffer + (red * mFrameSize), mPipe.mBuffer, count * mFrameSize);
- red += count;
- }
- }
- mFront += red;
- mFramesRead += red;
- return red;
+ mFramesRead += (size_t) actual;
+ return actual;
}
-__attribute__((no_sanitize("integer")))
ssize_t PipeReader::flush()
{
if (CC_UNLIKELY(!mNegotiated)) {
return NEGOTIATE;
}
- const int32_t rear = android_atomic_acquire_load(&mPipe.mRear);
- const size_t flushed = rear - mFront;
- // We don't check if flushed > mPipe.mMaxFrames (an overrun occurred) as the
- // distinction is unimportant; all data is dropped.
- mFront = rear;
- mFramesRead += flushed; // we consider flushed frames as read.
+ size_t lost;
+ ssize_t flushed = mFifoReader.flush(&lost);
+ if (flushed == -EOVERFLOW || lost > 0) {
+ mFramesOverrun += lost;
+ ++mOverruns;
+ flushed = OVERRUN;
+ }
+ if (flushed <= 0) {
+ return flushed;
+ }
+ mFramesRead += (size_t) flushed; // we consider flushed frames as read, but not lost frames
return flushed;
}