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