Use libnbaio to avoid file I/O in audio callback
Bug: 7142834
Change-Id: I4f78f029ac6bfe27bea4dfe1f00812a2bdf8785d
diff --git a/tests/sandbox/Android.mk b/tests/sandbox/Android.mk
index 9145503..4425c2d 100644
--- a/tests/sandbox/Android.mk
+++ b/tests/sandbox/Android.mk
@@ -326,10 +326,11 @@
$(call include-path-for, audio-utils)
LOCAL_SRC_FILES:= \
- playbq.c
+ playbq.cpp
LOCAL_SHARED_LIBRARIES := \
libaudioutils \
+ libnbaio \
libutils \
libOpenSLES
diff --git a/tests/sandbox/playbq.c b/tests/sandbox/playbq.c
index e1a7639..9a33f4d 100644
--- a/tests/sandbox/playbq.c
+++ b/tests/sandbox/playbq.c
@@ -18,6 +18,7 @@
#include <assert.h>
#include <math.h>
+#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -31,6 +32,9 @@
#include <sndfile.h>
#endif
+#include <media/nbaio/MonoPipe.h>
+#include <media/nbaio/MonoPipeReader.h>
+
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
@@ -71,6 +75,10 @@
}
}
+static android::MonoPipeReader *pipeReader;
+static android::MonoPipe *pipeWriter;
+static unsigned underruns = 0;
+
// This callback is called each time a buffer finishes playing
static void callback(SLBufferQueueItf bufq, void *param)
@@ -78,11 +86,14 @@
assert(NULL == param);
if (!eof) {
short *buffer = &buffers[framesPerBuffer * sfinfo.channels * which];
- sf_count_t count;
- count = sf_readf_short(sndfile, buffer, (sf_count_t) framesPerBuffer);
+ ssize_t count = pipeReader->read(buffer, framesPerBuffer, (int64_t) -1);
+ // on underrun from pipe, substitute silence
if (0 >= count) {
- eof = SL_BOOLEAN_TRUE;
- } else {
+ memset(buffer, 0, framesPerBuffer * sfinfo.channels * sizeof(short));
+ count = framesPerBuffer;
+ ++underruns;
+ }
+ if (count > 0) {
SLuint32 nbytes = count * sfinfo.channels * sizeof(short);
if (byteOrder != nativeByteOrder) {
swab(buffer, buffer, nbytes);
@@ -99,6 +110,43 @@
}
}
+// This thread reads from a (slow) filesystem with unpredictable latency and writes to pipe
+
+static void *file_reader_loop(void *arg)
+{
+#define READ_FRAMES 256
+ short *temp = (short *) malloc(READ_FRAMES * sfinfo.channels * sizeof(short));
+ sf_count_t total = 0;
+ for (;;) {
+ sf_count_t count = sf_readf_short(sndfile, temp, (sf_count_t) READ_FRAMES);
+ if (0 >= count) {
+ eof = SL_BOOLEAN_TRUE;
+ break;
+ }
+ const short *ptr = temp;
+ while (count > 0) {
+ ssize_t actual = pipeWriter->write(ptr, (size_t) count);
+ if (actual < 0) {
+ break;
+ }
+ if ((sf_count_t) actual < count) {
+ usleep(10000);
+ }
+ ptr += actual * sfinfo.channels;
+ count -= actual;
+ total += actual;
+ }
+ // simulate occasional filesystem latency
+ if ((total & 0xFF00) == 0xFF00) {
+ usleep(100000);
+ }
+ }
+ free(temp);
+ return NULL;
+}
+
+// Main program
+
int main(int argc, char **argv)
{
// Determine the native byte order (SL_BYTEORDER_NATIVE not available until 1.1)
@@ -188,6 +236,9 @@
return EXIT_FAILURE;
}
+ // The sample rate is a lie, but it doesn't actually matter
+ const android::NBAIO_Format nbaio_format = android::Format_from_SR_C(44100, sfinfo.channels);
+
// verify the file format
switch (sfinfo.channels) {
case 1:
@@ -231,6 +282,8 @@
goto close_sndfile;
}
+ {
+
buffers = (short *) malloc(framesPerBuffer * sfinfo.channels * sizeof(short) * numBuffers);
// create engine
@@ -302,6 +355,8 @@
goto no_player;
}
+ {
+
// realize the player
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
@@ -400,6 +455,24 @@
result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, callback, NULL);
assert(SL_RESULT_SUCCESS == result);
+ pipeWriter = new android::MonoPipe(16384, nbaio_format, false /*writeCanBlock*/);
+ android::NBAIO_Format offer = nbaio_format;
+ size_t numCounterOffers = 0;
+ ssize_t neg = pipeWriter->negotiate(&offer, 1, NULL, numCounterOffers);
+ assert(0 == neg);
+ pipeReader = new android::MonoPipeReader(pipeWriter);
+ numCounterOffers = 0;
+ neg = pipeReader->negotiate(&offer, 1, NULL, numCounterOffers);
+ assert(0 == neg);
+
+ // create thread to read from file
+ pthread_t thread;
+ int ok = pthread_create(&thread, (const pthread_attr_t *) NULL, file_reader_loop, NULL);
+ assert(0 == ok);
+
+ // give thread a head start so that the pipe is initially filled
+ sleep(1);
+
// set the player's state to playing
result = (*playerPlay)->SetPlayState(playerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result);
@@ -455,6 +528,8 @@
// destroy audio player
(*playerObject)->Destroy(playerObject);
+ }
+
no_player:
// destroy output mix
@@ -463,6 +538,8 @@
// destroy engine
(*engineObject)->Destroy(engineObject);
+ }
+
close_sndfile:
(void) sf_close(sndfile);
diff --git a/tests/sandbox/playbq.cpp b/tests/sandbox/playbq.cpp
new file mode 120000
index 0000000..a438ce1
--- /dev/null
+++ b/tests/sandbox/playbq.cpp
@@ -0,0 +1 @@
+playbq.c
\ No newline at end of file