aaudio examples: handle disconnect in write_sine_callback

Automatically reopen the stream if we get disconnected.
Add a futex based signaling class so that the error callback can
immediately inform a waiting thread.

This can be used to prevent the system from playing an unpleasant sound
after the stream is disconnected.

Bug: 63342351
Test: plug and unplug headphones while running write_sine_callback
Change-Id: I3ea4fb24106156a29d2f302f3eb614f25f1b758a
diff --git a/media/libaaudio/examples/utils/AAudioExampleUtils.h b/media/libaaudio/examples/utils/AAudioExampleUtils.h
index 6cbcc58..9ef62c9 100644
--- a/media/libaaudio/examples/utils/AAudioExampleUtils.h
+++ b/media/libaaudio/examples/utils/AAudioExampleUtils.h
@@ -17,9 +17,14 @@
 #ifndef AAUDIO_EXAMPLE_UTILS_H
 #define AAUDIO_EXAMPLE_UTILS_H
 
-#include <unistd.h>
+#include <atomic>
+#include <linux/futex.h>
 #include <sched.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
 #include <aaudio/AAudio.h>
+#include <utils/Errors.h>
 
 #define NANOS_PER_MICROSECOND ((int64_t)1000)
 #define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
@@ -40,6 +45,12 @@
     return modeText;
 }
 
+static void convertNanosecondsToTimespec(int64_t nanoseconds, struct timespec *time) {
+    time->tv_sec = nanoseconds / NANOS_PER_SECOND;
+    // Calculate the fractional nanoseconds. Avoids expensive % operation.
+    time->tv_nsec = nanoseconds - (time->tv_sec * NANOS_PER_SECOND);
+}
+
 static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
     struct timespec time;
     int result = clock_gettime(clockId, &time);
@@ -79,4 +90,77 @@
     return latencyMillis;
 }
 
+// ================================================================================
+// These Futex calls are common online examples.
+static android::status_t sys_futex(void *addr1, int op, int val1,
+                      struct timespec *timeout, void *addr2, int val3) {
+    android::status_t result = (android::status_t) syscall(SYS_futex, addr1,
+                                                           op, val1, timeout,
+                                                           addr2, val3);
+    return (result == 0) ? 0 : -errno;
+}
+
+static android::status_t futex_wake(void *addr, int numWake) {
+    // Use _PRIVATE because we are just using the futex in one process.
+    return sys_futex(addr, FUTEX_WAKE_PRIVATE, numWake, NULL, NULL, 0);
+}
+
+static android::status_t futex_wait(void *addr, int current, struct timespec *time) {
+    // Use _PRIVATE because we are just using the futex in one process.
+    return sys_futex(addr, FUTEX_WAIT_PRIVATE, current, time, NULL, 0);
+}
+
+// TODO better name?
+/**
+ * The WakeUp class is used to send a wakeup signal to one or more sleeping threads.
+ */
+class WakeUp {
+public:
+    WakeUp() : mValue(0) {}
+    explicit WakeUp(int32_t value) : mValue(value) {}
+
+    /**
+     * Wait until the internal value no longer matches the given value.
+     * Note that this code uses a futex, which is subject to spurious wake-ups.
+     * So check to make sure that the desired condition has been met.
+     *
+     * @return zero if the value changes or various negative errors including
+     *    -ETIMEDOUT if a timeout occurs,
+     *    or -EINTR if interrupted by a signal,
+     *    or -EAGAIN or -EWOULDBLOCK if the internal value does not match the specified value
+     */
+    android::status_t wait(int32_t value, int64_t timeoutNanoseconds) {
+        struct timespec time;
+        convertNanosecondsToTimespec(timeoutNanoseconds, &time);
+        return futex_wait(&mValue, value, &time);
+    }
+
+    /**
+     * Increment value and wake up any threads that need to be woken.
+     *
+     * @return number of waiters woken up
+     */
+    android::status_t wake() {
+        ++mValue;
+        return futex_wake(&mValue, INT_MAX);
+    }
+
+    /**
+     * Set value and wake up any threads that need to be woken.
+     *
+     * @return number of waiters woken up
+     */
+    android::status_t wake(int32_t value) {
+        mValue.store(value);
+        return futex_wake(&mValue, INT_MAX);
+    }
+
+    int32_t get() {
+        return mValue.load();
+    }
+
+private:
+    std::atomic<int32_t>   mValue;
+};
+
 #endif // AAUDIO_EXAMPLE_UTILS_H
diff --git a/media/libaaudio/examples/utils/AAudioSimplePlayer.h b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
index cc0cb34..d2e7f23 100644
--- a/media/libaaudio/examples/utils/AAudioSimplePlayer.h
+++ b/media/libaaudio/examples/utils/AAudioSimplePlayer.h
@@ -23,6 +23,7 @@
 #include <sched.h>
 
 #include <aaudio/AAudio.h>
+#include <atomic>
 #include "AAudioArgsParser.h"
 #include "SineGenerator.h"
 
@@ -219,18 +220,26 @@
     AAudioStream             *mStream = nullptr;
     aaudio_sharing_mode_t     mRequestedSharingMode = SHARING_MODE;
     aaudio_performance_mode_t mRequestedPerformanceMode = PERFORMANCE_MODE;
+
 };
 
 typedef struct SineThreadedData_s {
+
     SineGenerator  sineOsc1;
     SineGenerator  sineOsc2;
     int64_t        framesTotal = 0;
     int64_t        nextFrameToGlitch = FORCED_UNDERRUN_PERIOD_FRAMES;
     int32_t        minNumFrames = INT32_MAX;
     int32_t        maxNumFrames = 0;
-    int            scheduler;
+
+    int            scheduler = 0;
     bool           schedulerChecked = false;
     bool           forceUnderruns = false;
+
+    AAudioSimplePlayer simplePlayer;
+    int32_t            callbackCount = 0;
+    WakeUp             waker{AAUDIO_OK};
+
 } SineThreadedData_t;
 
 // Callback function that fills the audio output buffer.
@@ -247,6 +256,7 @@
         return AAUDIO_CALLBACK_RESULT_STOP;
     }
     SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+    sineData->callbackCount++;
 
     sineData->framesTotal += numFrames;
 
@@ -304,9 +314,16 @@
 void SimplePlayerErrorCallbackProc(
         AAudioStream *stream __unused,
         void *userData __unused,
-        aaudio_result_t error)
-{
-    printf("Error Callback, error: %d\n",(int)error);
+        aaudio_result_t error) {
+    // should not happen but just in case...
+    if (userData == nullptr) {
+        printf("ERROR - MyPlayerErrorCallbackProc needs userData\n");
+        return;
+    }
+    SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+    android::status_t ret = sineData->waker.wake(error);
+    printf("Error Callback, error: %d, futex wake returns %d\n", error, ret);
 }
 
+
 #endif //AAUDIO_SIMPLE_PLAYER_H
diff --git a/media/libaaudio/examples/utils/SineGenerator.h b/media/libaaudio/examples/utils/SineGenerator.h
index 64b772d..a755582 100644
--- a/media/libaaudio/examples/utils/SineGenerator.h
+++ b/media/libaaudio/examples/utils/SineGenerator.h
@@ -58,6 +58,13 @@
         }
     }
 
+    void setAmplitude(double amplitude) {
+        mAmplitude = amplitude;
+    }
+    double getAmplitude() const {
+        return mAmplitude;
+    }
+
 private:
     void advancePhase() {
         mPhase += mPhaseIncrement;
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
index 071ca87..2280b72 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -15,6 +15,7 @@
  */
 
 // Play sine waves using an AAudio callback.
+// If a disconnection occurs then reopen the stream on the new device.
 
 #include <assert.h>
 #include <unistd.h>
@@ -22,33 +23,32 @@
 #include <sched.h>
 #include <stdio.h>
 #include <math.h>
+#include <string.h>
 #include <time.h>
 #include <aaudio/AAudio.h>
 #include "AAudioExampleUtils.h"
 #include "AAudioSimplePlayer.h"
 #include "../../utils/AAudioSimplePlayer.h"
 
-int main(int argc, const char **argv)
+/**
+ * Open stream, play some sine waves, then close the stream.
+ *
+ * @param argParser
+ * @return AAUDIO_OK or negative error code
+ */
+static aaudio_result_t testOpenPlayClose(AAudioArgsParser &argParser)
 {
-    AAudioArgsParser   argParser;
-    AAudioSimplePlayer player;
     SineThreadedData_t myData;
-    aaudio_result_t result;
-    int32_t actualSampleRate;
+    AAudioSimplePlayer &player = myData.simplePlayer;
+    aaudio_result_t    result = AAUDIO_OK;
+    bool               disconnected = false;
+    int64_t            startedAtNanos;
 
-    // Make printf print immediately so that debug info is not stuck
-    // in a buffer if we hang or crash.
-    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
-
-    printf("%s - Play a sine sweep using an AAudio callback V0.1.3\n", argv[0]);
-
+    printf("----------------------- run complete test --------------------------\n");
     myData.schedulerChecked = false;
+    myData.callbackCount = 0;
     myData.forceUnderruns = false; // set true to test AAudioStream_getXRunCount()
 
-    if (argParser.parseArgs(argc, argv)) {
-        return EXIT_FAILURE;
-    }
-
     result = player.open(argParser,
                          SimplePlayerDataCallbackProc, SimplePlayerErrorCallbackProc, &myData);
     if (result != AAUDIO_OK) {
@@ -58,13 +58,19 @@
 
     argParser.compareWithStream(player.getStream());
 
-    actualSampleRate = player.getSampleRate();
-    myData.sineOsc1.setup(440.0, actualSampleRate);
-    myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
-    myData.sineOsc2.setup(660.0, actualSampleRate);
-    myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+    // Setup sine wave generators.
+    {
+        int32_t actualSampleRate = player.getSampleRate();
+        myData.sineOsc1.setup(440.0, actualSampleRate);
+        myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
+        myData.sineOsc1.setAmplitude(0.2);
+        myData.sineOsc2.setup(660.0, actualSampleRate);
+        myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+        myData.sineOsc2.setAmplitude(0.2);
+    }
 
 #if 0
+    //  writes not allowed for callback streams
     result = player.prime(); // FIXME crashes AudioTrack.cpp
     if (result != AAUDIO_OK) {
         fprintf(stderr, "ERROR - player.prime() returned %d\n", result);
@@ -78,34 +84,32 @@
         goto error;
     }
 
+    // Play a sine wave in the background.
     printf("Sleep for %d seconds while audio plays in a callback thread.\n",
            argParser.getDurationSeconds());
+    startedAtNanos = getNanoseconds(CLOCK_MONOTONIC);
     for (int second = 0; second < argParser.getDurationSeconds(); second++)
     {
-        const struct timespec request = { .tv_sec = 1, .tv_nsec = 0 };
-        (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
-
-        aaudio_stream_state_t state;
-        result = AAudioStream_waitForStateChange(player.getStream(),
-                                                 AAUDIO_STREAM_STATE_CLOSED,
-                                                 &state,
-                                                 0);
+        // Sleep a while. Wake up early if there is an error, for example a DISCONNECT.
+        long ret = myData.waker.wait(AAUDIO_OK, NANOS_PER_SECOND);
+        int64_t millis = (getNanoseconds(CLOCK_MONOTONIC) - startedAtNanos) / NANOS_PER_MILLISECOND;
+        result = myData.waker.get();
+        printf("wait() returns %ld, aaudio_result = %d, at %6d millis"
+               ", second = %d, framesWritten = %8d, underruns = %d\n",
+               ret, result, (int) millis,
+               second,
+               (int) AAudioStream_getFramesWritten(player.getStream()),
+               (int) AAudioStream_getXRunCount(player.getStream()));
         if (result != AAUDIO_OK) {
-            fprintf(stderr, "ERROR - AAudioStream_waitForStateChange() returned %d\n", result);
-            goto error;
-        }
-        if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) {
-            printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state));
+            if (result == AAUDIO_ERROR_DISCONNECTED) {
+                disconnected = true;
+            }
             break;
         }
-        printf("framesWritten = %d, underruns = %d\n",
-               (int) AAudioStream_getFramesWritten(player.getStream()),
-               (int) AAudioStream_getXRunCount(player.getStream())
-        );
     }
-    printf("Woke up now.\n");
+    printf("AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
 
-    printf("call stop()\n");
+    printf("call stop() callback # = %d\n", myData.callbackCount);
     result = player.stop();
     if (result != AAUDIO_OK) {
         goto error;
@@ -126,10 +130,28 @@
     printf("max numFrames = %8d\n", (int) myData.maxNumFrames);
 
     printf("SUCCESS\n");
-    return EXIT_SUCCESS;
 error:
     player.close();
-    printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
-    return EXIT_FAILURE;
+    return disconnected ? AAUDIO_ERROR_DISCONNECTED : result;
 }
 
+int main(int argc, const char **argv)
+{
+    AAudioArgsParser   argParser;
+    aaudio_result_t    result;
+
+    // Make printf print immediately so that debug info is not stuck
+    // in a buffer if we hang or crash.
+    setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+    printf("%s - Play a sine sweep using an AAudio callback V0.1.2\n", argv[0]);
+
+    if (argParser.parseArgs(argc, argv)) {
+        return EXIT_FAILURE;
+    }
+
+    // Keep looping until we can complete the test without disconnecting.
+    while((result = testOpenPlayClose(argParser)) == AAUDIO_ERROR_DISCONNECTED);
+
+    return (result) ? EXIT_FAILURE : EXIT_SUCCESS;
+}