Oboe Examples: write_sine_threaded

These will eventually be moved to a separate repo.

Bug: 33347409
Test: these are examples that also serve as tests

Change-Id: I4c2330bb2490f55305cebcd9d3cbdfa26ee837a1
Signed-off-by: Phil Burk <philburk@google.com>
diff --git a/media/liboboe/examples/Android.mk b/media/liboboe/examples/Android.mk
new file mode 100644
index 0000000..5053e7d
--- /dev/null
+++ b/media/liboboe/examples/Android.mk
@@ -0,0 +1 @@
+include $(call all-subdir-makefiles)
diff --git a/media/liboboe/examples/write_sine/Android.mk b/media/liboboe/examples/write_sine/Android.mk
new file mode 100644
index 0000000..b56328b
--- /dev/null
+++ b/media/liboboe/examples/write_sine/Android.mk
@@ -0,0 +1,6 @@
+# include $(call all-subdir-makefiles)
+
+# Just include static/ for now.
+LOCAL_PATH := $(call my-dir)
+#include $(LOCAL_PATH)/jni/Android.mk
+include $(LOCAL_PATH)/static/Android.mk
diff --git a/media/liboboe/examples/write_sine/README.md b/media/liboboe/examples/write_sine/README.md
new file mode 100644
index 0000000..9f7ee87
--- /dev/null
+++ b/media/liboboe/examples/write_sine/README.md
@@ -0,0 +1,7 @@
+# cd to this directory
+mkdir -p jni/include/oboe
+ln -s $PLATFORM/frameworks/av/media/liboboe/include/oboe/*.h jni/include/oboe
+ln -s $PLATFORM/out/target/product/$TARGET_PRODUCT/symbols/out/soong/ndk/platforms/android-current/arch-arm64/usr/lib/liboboe.so jni
+$NDK/ndk-build
+adb push libs/arm64-v8a/write_sine_threaded /data
+adb shell /data/write_sine_threaded
diff --git a/media/liboboe/examples/write_sine/jni/Android.mk b/media/liboboe/examples/write_sine/jni/Android.mk
new file mode 100644
index 0000000..51a5a85
--- /dev/null
+++ b/media/liboboe/examples/write_sine/jni/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+        libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine_threaded.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+        libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_threaded_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := liboboe_prebuilt
+LOCAL_SRC_FILES := liboboe.so
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
+include $(PREBUILT_SHARED_LIBRARY)
diff --git a/media/liboboe/examples/write_sine/jni/Application.mk b/media/liboboe/examples/write_sine/jni/Application.mk
new file mode 100644
index 0000000..e74475c
--- /dev/null
+++ b/media/liboboe/examples/write_sine/jni/Application.mk
@@ -0,0 +1,3 @@
+# TODO remove then when we support other architectures
+APP_ABI := arm64-v8a
+APP_CPPFLAGS += -std=c++11
diff --git a/media/liboboe/examples/write_sine/src/SineGenerator.h b/media/liboboe/examples/write_sine/src/SineGenerator.h
new file mode 100644
index 0000000..ade7527
--- /dev/null
+++ b/media/liboboe/examples/write_sine/src/SineGenerator.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SINE_GENERATOR_H
+#define SINE_GENERATOR_H
+
+#include <math.h>
+
+class SineGenerator
+{
+public:
+    SineGenerator() {}
+    virtual ~SineGenerator() = default;
+
+    void setup(double frequency, double frameRate) {
+        mFrameRate = frameRate;
+        mPhaseIncrement = frequency * M_PI * 2 / frameRate;
+    }
+
+    void setSweep(double frequencyLow, double frequencyHigh, double seconds) {
+        mPhaseIncrementLow = frequencyLow * M_PI * 2 / mFrameRate;
+        mPhaseIncrementHigh = frequencyHigh * M_PI * 2 / mFrameRate;
+
+        double numFrames = seconds * mFrameRate;
+        mUpScaler = pow((frequencyHigh / frequencyLow), (1.0 / numFrames));
+        mDownScaler = 1.0 / mUpScaler;
+        mGoingUp = true;
+        mSweeping = true;
+    }
+
+    void render(int16_t *buffer, int32_t channelStride, int32_t numFrames) {
+        int sampleIndex = 0;
+        for (int i = 0; i < numFrames; i++) {
+            buffer[sampleIndex] = (int16_t) (32767 * sin(mPhase) * mAmplitude);
+            sampleIndex += channelStride;
+            advancePhase();
+        }
+    }
+    void render(float *buffer, int32_t channelStride, int32_t numFrames) {
+        int sampleIndex = 0;
+        for (int i = 0; i < numFrames; i++) {
+            buffer[sampleIndex] = sin(mPhase) * mAmplitude;
+            sampleIndex += channelStride;
+            advancePhase();
+        }
+    }
+
+private:
+    void advancePhase() {
+        mPhase += mPhaseIncrement;
+        if (mPhase > M_PI * 2) {
+            mPhase -= M_PI * 2;
+        }
+        if (mSweeping) {
+            if (mGoingUp) {
+                mPhaseIncrement *= mUpScaler;
+                if (mPhaseIncrement > mPhaseIncrementHigh) {
+                    mGoingUp = false;
+                }
+            } else {
+                mPhaseIncrement *= mDownScaler;
+                if (mPhaseIncrement < mPhaseIncrementLow) {
+                    mGoingUp = true;
+                }
+            }
+        }
+    }
+
+    double mAmplitude = 0.01;
+    double mPhase = 0.0;
+    double mPhaseIncrement = 440 * M_PI * 2 / 48000;
+    double mFrameRate = 48000;
+    double mPhaseIncrementLow;
+    double mPhaseIncrementHigh;
+    double mUpScaler = 1.0;
+    double mDownScaler = 1.0;
+    bool   mGoingUp = false;
+    bool   mSweeping = false;
+};
+
+#endif /* SINE_GENERATOR_H */
+
diff --git a/media/liboboe/examples/write_sine/src/write_sine.cpp b/media/liboboe/examples/write_sine/src/write_sine.cpp
new file mode 100644
index 0000000..084665c
--- /dev/null
+++ b/media/liboboe/examples/write_sine/src/write_sine.cpp
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Play sine waves using Oboe.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <oboe/OboeDefinitions.h>
+#include <oboe/OboeAudio.h>
+#include "SineGenerator.h"
+
+#define SAMPLE_RATE   48000
+#define NUM_SECONDS   10
+
+static const char *getSharingModeText(oboe_sharing_mode_t mode) {
+    const char *modeText = "unknown";
+    switch (mode) {
+    case OBOE_SHARING_MODE_EXCLUSIVE:
+        modeText = "EXCLUSIVE";
+        break;
+    case OBOE_SHARING_MODE_LEGACY:
+        modeText = "LEGACY";
+        break;
+    case OBOE_SHARING_MODE_SHARED:
+        modeText = "SHARED";
+        break;
+    case OBOE_SHARING_MODE_PUBLIC_MIX:
+        modeText = "PUBLIC_MIX";
+        break;
+    default:
+        break;
+    }
+    return modeText;
+}
+
+int main(int argc, char **argv)
+{
+    (void)argc; // unused
+
+    oboe_result_t result = OBOE_OK;
+
+    const int requestedSamplesPerFrame = 2;
+    int actualSamplesPerFrame = 0;
+    const int requestedSampleRate = SAMPLE_RATE;
+    int actualSampleRate = 0;
+    const oboe_audio_format_t requestedDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+    oboe_audio_format_t actualDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+
+    const oboe_sharing_mode_t requestedSharingMode = OBOE_SHARING_MODE_EXCLUSIVE;
+    //const oboe_sharing_mode_t requestedSharingMode = OBOE_SHARING_MODE_LEGACY;
+    oboe_sharing_mode_t actualSharingMode = OBOE_SHARING_MODE_LEGACY;
+
+    OboeStreamBuilder oboeBuilder = OBOE_STREAM_BUILDER_NONE;
+    OboeStream oboeStream = OBOE_STREAM_NONE;
+    oboe_stream_state_t state = OBOE_STREAM_STATE_UNINITIALIZED;
+    oboe_size_frames_t framesPerBurst = 0;
+    oboe_size_frames_t framesToPlay = 0;
+    oboe_size_frames_t framesLeft = 0;
+    int32_t xRunCount = 0;
+    int16_t *data = nullptr;
+
+    SineGenerator sineOsc1;
+    SineGenerator sineOsc2;
+
+    // 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 wave using Oboe\n", argv[0]);
+
+    // Use an OboeStreamBuilder to contain requested parameters.
+    result = Oboe_createStreamBuilder(&oboeBuilder);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+
+    // Request stream properties.
+    result = OboeStreamBuilder_setSampleRate(oboeBuilder, requestedSampleRate);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+    result = OboeStreamBuilder_setSamplesPerFrame(oboeBuilder, requestedSamplesPerFrame);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+    result = OboeStreamBuilder_setFormat(oboeBuilder, requestedDataFormat);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+    result = OboeStreamBuilder_setSharingMode(oboeBuilder, requestedSharingMode);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+
+    // Create an OboeStream using the Builder.
+    result = OboeStreamBuilder_openStream(oboeBuilder, &oboeStream);
+    printf("oboeStream 0x%08x\n", oboeStream);
+    if (result != OBOE_OK) {
+        goto finish;
+    }
+
+    result = OboeStream_getState(oboeStream, &state);
+    printf("after open, state = %s\n", Oboe_convertStreamStateToText(state));
+
+    // Check to see what kind of stream we actually got.
+    result = OboeStream_getSampleRate(oboeStream, &actualSampleRate);
+    printf("SampleRate: requested = %d, actual = %d\n", requestedSampleRate, actualSampleRate);
+
+    sineOsc1.setup(440.0, actualSampleRate);
+    sineOsc2.setup(660.0, actualSampleRate);
+
+    result = OboeStream_getSamplesPerFrame(oboeStream, &actualSamplesPerFrame);
+    printf("SamplesPerFrame: requested = %d, actual = %d\n",
+            requestedSamplesPerFrame, actualSamplesPerFrame);
+
+    result = OboeStream_getSharingMode(oboeStream, &actualSharingMode);
+    printf("SharingMode: requested = %s, actual = %s\n",
+            getSharingModeText(requestedSharingMode),
+            getSharingModeText(actualSharingMode));
+
+    // This is the number of frames that are read in one chunk by a DMA controller
+    // or a DSP or a mixer.
+    result = OboeStream_getFramesPerBurst(oboeStream, &framesPerBurst);
+    printf("DataFormat: original framesPerBurst = %d\n",framesPerBurst);
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR - OboeStream_getFramesPerBurst() returned %d\n", result);
+        goto finish;
+    }
+    // Some DMA might use very short bursts of 16 frames. We don't need to write such small
+    // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
+    while (framesPerBurst < 48) {
+        framesPerBurst *= 2;
+    }
+    printf("DataFormat: final framesPerBurst = %d\n",framesPerBurst);
+
+    OboeStream_getFormat(oboeStream, &actualDataFormat);
+    printf("DataFormat: requested = %d, actual = %d\n", requestedDataFormat, actualDataFormat);
+    // TODO handle other data formats
+
+    // Allocate a buffer for the audio data.
+    data = new int16_t[framesPerBurst * actualSamplesPerFrame];
+    if (data == nullptr) {
+        fprintf(stderr, "ERROR - could not allocate data buffer\n");
+        result = OBOE_ERROR_NO_MEMORY;
+        goto finish;
+    }
+
+    // Start the stream.
+    printf("call OboeStream_requestStart()\n");
+    result = OboeStream_requestStart(oboeStream);
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR - OboeStream_requestStart() returned %d\n", result);
+        goto finish;
+    }
+
+    result = OboeStream_getState(oboeStream, &state);
+    printf("after start, state = %s\n", Oboe_convertStreamStateToText(state));
+
+    // Play for a while.
+    framesToPlay = actualSampleRate * NUM_SECONDS;
+    framesLeft = framesToPlay;
+    while (framesLeft > 0) {
+        // Render sine waves to left and right channels.
+        sineOsc1.render(&data[0], actualSamplesPerFrame, framesPerBurst);
+        if (actualSamplesPerFrame > 1) {
+            sineOsc2.render(&data[1], actualSamplesPerFrame, framesPerBurst);
+        }
+
+        // Write audio data to the stream.
+        oboe_nanoseconds_t timeoutNanos = 100 * OBOE_NANOS_PER_MILLISECOND;
+        int minFrames = (framesToPlay < framesPerBurst) ? framesToPlay : framesPerBurst;
+        int actual = OboeStream_write(oboeStream, data, minFrames, timeoutNanos);
+        if (actual < 0) {
+            fprintf(stderr, "ERROR - OboeStream_write() returned %zd\n", actual);
+            goto finish;
+        } else if (actual == 0) {
+            fprintf(stderr, "WARNING - OboeStream_write() returned %zd\n", actual);
+            goto finish;
+        }
+        framesLeft -= actual;
+    }
+
+    result = OboeStream_getXRunCount(oboeStream, &xRunCount);
+    printf("OboeStream_getXRunCount %d\n", xRunCount);
+
+finish:
+    delete[] data;
+    OboeStream_close(oboeStream);
+    OboeStreamBuilder_delete(oboeBuilder);
+    printf("exiting - Oboe result = %d = %s\n", result, Oboe_convertResultToText(result));
+    return (result != OBOE_OK) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
diff --git a/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp b/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp
new file mode 100644
index 0000000..aedcc6e
--- /dev/null
+++ b/media/liboboe/examples/write_sine/src/write_sine_threaded.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Play sine waves using an Oboe background thread.
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <oboe/OboeDefinitions.h>
+#include <oboe/OboeAudio.h>
+#include "SineGenerator.h"
+
+#define NUM_SECONDS   10
+#define SHARING_MODE  OBOE_SHARING_MODE_EXCLUSIVE
+//#define SHARING_MODE  OBOE_SHARING_MODE_LEGACY
+
+// Prototype for a callback.
+typedef int audio_callback_proc_t(float *outputBuffer,
+                                     oboe_size_frames_t numFrames,
+                                     void *userContext);
+
+static void *SimpleOboePlayerThreadProc(void *arg);
+
+/**
+ * Simple wrapper for Oboe that opens a default stream and then calls
+ * a callback function to fill the output buffers.
+ */
+class SimpleOboePlayer {
+public:
+    SimpleOboePlayer() {}
+    virtual ~SimpleOboePlayer() {
+        close();
+    };
+
+    void setSharingMode(oboe_sharing_mode_t requestedSharingMode) {
+        mRequestedSharingMode = requestedSharingMode;
+    }
+
+    /** Also known as "sample rate"
+     */
+    int32_t getFramesPerSecond() {
+        return mFramesPerSecond;
+    }
+
+    int32_t getSamplesPerFrame() {
+        return mSamplesPerFrame;
+    }
+
+    /**
+     * Open a stream
+     */
+    oboe_result_t open(audio_callback_proc_t *proc, void *userContext) {
+        mCallbackProc = proc;
+        mUserContext = userContext;
+        oboe_result_t result = OBOE_OK;
+
+        // Use an OboeStreamBuilder to contain requested parameters.
+        result = Oboe_createStreamBuilder(&mBuilder);
+        if (result != OBOE_OK) return result;
+
+        result = OboeStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
+        if (result != OBOE_OK) goto finish1;
+
+        // Open an OboeStream using the Builder.
+        result = OboeStreamBuilder_openStream(mBuilder, &mStream);
+        if (result != OBOE_OK) goto finish1;
+
+        // Check to see what kind of stream we actually got.
+        result = OboeStream_getSampleRate(mStream, &mFramesPerSecond);
+        printf("open() mFramesPerSecond = %d\n", mFramesPerSecond);
+        if (result != OBOE_OK) goto finish2;
+        result = OboeStream_getSamplesPerFrame(mStream, &mSamplesPerFrame);
+        printf("open() mSamplesPerFrame = %d\n", mSamplesPerFrame);
+        if (result != OBOE_OK) goto finish2;
+
+        // This is the number of frames that are read in one chunk by a DMA controller
+        // or a DSP or a mixer.
+        result = OboeStream_getFramesPerBurst(mStream, &mFramesPerBurst);
+        if (result != OBOE_OK) goto finish2;
+        // Some DMA might use very short bursts. We don't need to write such small
+        // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
+        while (mFramesPerBurst < 48) {
+            mFramesPerBurst *= 2;
+        }
+        printf("DataFormat: final framesPerBurst = %d\n",mFramesPerBurst);
+
+        result = OboeStream_getFormat(mStream, &mDataFormat);
+        if (result != OBOE_OK) {
+            fprintf(stderr, "ERROR - OboeStream_getFormat() returned %d\n", result);
+            goto finish2;
+        }
+
+        // Allocate a buffer for the audio data.
+        mOutputBuffer = new float[mFramesPerBurst * mSamplesPerFrame];
+        if (mOutputBuffer == nullptr) {
+            fprintf(stderr, "ERROR - could not allocate data buffer\n");
+            result = OBOE_ERROR_NO_MEMORY;
+        }
+
+        // If needed allocate a buffer for converting float to int16_t.
+        if (mDataFormat == OBOE_AUDIO_FORMAT_PCM16) {
+            mConversionBuffer = new int16_t[mFramesPerBurst * mSamplesPerFrame];
+            if (mConversionBuffer == nullptr) {
+                fprintf(stderr, "ERROR - could not allocate conversion buffer\n");
+                result = OBOE_ERROR_NO_MEMORY;
+            }
+        }
+        return result;
+
+     finish2:
+        OboeStream_close(mStream);
+        mStream = OBOE_HANDLE_INVALID;
+     finish1:
+        OboeStreamBuilder_delete(mBuilder);
+        mBuilder = OBOE_HANDLE_INVALID;
+        return result;
+    }
+
+    oboe_result_t close() {
+        stop();
+        OboeStream_close(mStream);
+        mStream = OBOE_HANDLE_INVALID;
+        OboeStreamBuilder_delete(mBuilder);
+        mBuilder = OBOE_HANDLE_INVALID;
+        delete mOutputBuffer;
+        mOutputBuffer = nullptr;
+        delete mConversionBuffer;
+        mConversionBuffer = nullptr;
+        return OBOE_OK;
+    }
+
+    // Start a thread that will call the callback proc.
+    oboe_result_t start() {
+        mEnabled = true;
+        oboe_nanoseconds_t nanosPerBurst = mFramesPerBurst * OBOE_NANOS_PER_SECOND
+                                           / mFramesPerSecond;
+        return OboeStream_createThread(mStream, nanosPerBurst,
+                                       SimpleOboePlayerThreadProc,
+                                       this);
+    }
+
+    // Tell the thread to stop.
+    oboe_result_t stop() {
+        mEnabled = false;
+        return OboeStream_joinThread(mStream, nullptr, 2 * OBOE_NANOS_PER_SECOND);
+    }
+
+    oboe_result_t callbackLoop() {
+        int32_t framesWritten = 0;
+        int32_t xRunCount = 0;
+        oboe_result_t result = OBOE_OK;
+
+        result = OboeStream_requestStart(mStream);
+        if (result != OBOE_OK) {
+            fprintf(stderr, "ERROR - OboeStream_requestStart() returned %d\n", result);
+            return result;
+        }
+
+        // Give up after several burst periods have passed.
+        const int burstsPerTimeout = 8;
+        oboe_nanoseconds_t nanosPerTimeout =
+                        burstsPerTimeout * mFramesPerBurst * OBOE_NANOS_PER_SECOND
+                        / mFramesPerSecond;
+
+        while (mEnabled && result >= 0) {
+            // Call application's callback function to fill the buffer.
+            if (mCallbackProc(mOutputBuffer, mFramesPerBurst, mUserContext)) {
+                mEnabled = false;
+            }
+            // if needed, convert from float to int16_t PCM
+            if (mConversionBuffer != nullptr) {
+                int32_t numSamples = mFramesPerBurst * mSamplesPerFrame;
+                for (int i = 0; i < numSamples; i++) {
+                    mConversionBuffer[i] = (int16_t)(32767.0 * mOutputBuffer[i]);
+                }
+                // Write the application data to stream.
+                result = OboeStream_write(mStream, mConversionBuffer, mFramesPerBurst, nanosPerTimeout);
+            } else {
+                // Write the application data to stream.
+                result = OboeStream_write(mStream, mOutputBuffer, mFramesPerBurst, nanosPerTimeout);
+            }
+            framesWritten += result;
+            if (result < 0) {
+                fprintf(stderr, "ERROR - OboeStream_write() returned %zd\n", result);
+            }
+        }
+
+        result = OboeStream_getXRunCount(mStream, &xRunCount);
+        printf("OboeStream_getXRunCount %d\n", xRunCount);
+
+        result = OboeStream_requestStop(mStream);
+        if (result != OBOE_OK) {
+            fprintf(stderr, "ERROR - OboeStream_requestStart() returned %d\n", result);
+            return result;
+        }
+
+        return result;
+    }
+
+private:
+    OboeStreamBuilder   mBuilder = OBOE_HANDLE_INVALID;
+    OboeStream          mStream = OBOE_HANDLE_INVALID;
+    float            *  mOutputBuffer = nullptr;
+    int16_t          *  mConversionBuffer = nullptr;
+
+    audio_callback_proc_t * mCallbackProc = nullptr;
+    void             *  mUserContext = nullptr;
+    oboe_sharing_mode_t mRequestedSharingMode = SHARING_MODE;
+    int32_t             mSamplesPerFrame = 0;
+    int32_t             mFramesPerSecond = 0;
+    oboe_size_frames_t  mFramesPerBurst = 0;
+    oboe_audio_format_t mDataFormat = OBOE_AUDIO_FORMAT_PCM16;
+
+    volatile bool       mEnabled = false; // used to request that callback exit its loop
+};
+
+static void *SimpleOboePlayerThreadProc(void *arg) {
+    SimpleOboePlayer *player = (SimpleOboePlayer *) arg;
+    player->callbackLoop();
+    return nullptr;
+}
+
+// Application data that gets passed to the callback.
+typedef struct SineThreadedData_s {
+    SineGenerator  sineOsc1;
+    SineGenerator  sineOsc2;
+    int32_t        samplesPerFrame = 0;
+} SineThreadedData_t;
+
+// Callback function that fills the audio output buffer.
+int MyCallbackProc(float *outputBuffer, int32_t numFrames, void *userContext) {
+    SineThreadedData_t *data = (SineThreadedData_t *) userContext;
+    // Render sine waves to left and right channels.
+    data->sineOsc1.render(&outputBuffer[0], data->samplesPerFrame, numFrames);
+    if (data->samplesPerFrame > 1) {
+        data->sineOsc2.render(&outputBuffer[1], data->samplesPerFrame, numFrames);
+    }
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    (void)argc; // unused
+    SimpleOboePlayer player;
+    SineThreadedData_t myData;
+    oboe_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 wave using an Oboe Thread\n", argv[0]);
+
+    result = player.open(MyCallbackProc, &myData);
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR -  player.open() returned %d\n", result);
+        goto error;
+    }
+    printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
+    printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+    myData.sineOsc1.setup(440.0, 48000);
+    myData.sineOsc1.setSweep(300.0, 2000.0, 5.0);
+    myData.sineOsc2.setup(660.0, 48000);
+    myData.sineOsc2.setSweep(400.0, 3000.0, 7.0);
+    myData.samplesPerFrame = player.getSamplesPerFrame();
+
+    result = player.start();
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR -  player.start() returned %d\n", result);
+        goto error;
+    }
+
+    printf("Sleep for %d seconds while audio plays in a background thread.\n", NUM_SECONDS);
+    {
+        // FIXME sleep is not an NDK API
+        // sleep(NUM_SECONDS);
+        const struct timespec request = { .tv_sec = NUM_SECONDS, .tv_nsec = 0 };
+        (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+    }
+    printf("Woke up now.\n");
+
+    result = player.stop();
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR -  player.stop() returned %d\n", result);
+        goto error;
+    }
+    result = player.close();
+    if (result != OBOE_OK) {
+        fprintf(stderr, "ERROR -  player.close() returned %d\n", result);
+        goto error;
+    }
+
+    printf("SUCCESS\n");
+    return EXIT_SUCCESS;
+error:
+    player.close();
+    printf("exiting - Oboe result = %d = %s\n", result, Oboe_convertResultToText(result));
+    return EXIT_FAILURE;
+}
+
diff --git a/media/liboboe/examples/write_sine/static/Android.mk b/media/liboboe/examples/write_sine/static/Android.mk
new file mode 100644
index 0000000..7c8d17c
--- /dev/null
+++ b/media/liboboe/examples/write_sine/static/Android.mk
@@ -0,0 +1,34 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := examples
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+# TODO reorganize folders to avoid using ../
+LOCAL_SRC_FILES:= ../src/write_sine.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+                          libbinder libcutils libutils \
+                          libaudioclient liblog libtinyalsa
+LOCAL_STATIC_LIBRARIES := liboboe
+
+LOCAL_MODULE := write_sine
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    $(call include-path-for, audio-utils) \
+    frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= ../src/write_sine_threaded.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+                          libbinder libcutils libutils \
+                          libaudioclient liblog libtinyalsa
+LOCAL_STATIC_LIBRARIES := liboboe
+
+LOCAL_MODULE := write_sine_threaded
+include $(BUILD_EXECUTABLE)
diff --git a/media/liboboe/examples/write_sine/static/README.md b/media/liboboe/examples/write_sine/static/README.md
new file mode 100644
index 0000000..768f4cb
--- /dev/null
+++ b/media/liboboe/examples/write_sine/static/README.md
@@ -0,0 +1,2 @@
+Makefile for building simple command line examples.
+They link with Oboe as a static library.