oboe: add calculateLatencyMillis()

Based on getTimestamp().

Fixes #69
diff --git a/include/oboe/AudioStream.h b/include/oboe/AudioStream.h
index db171cd..2a825f1 100644
--- a/include/oboe/AudioStream.h
+++ b/include/oboe/AudioStream.h
@@ -158,9 +158,30 @@
 
     virtual int64_t getFramesRead() const { return mFramesRead; }
 
+    /**
+     * Calculate the latency of a stream based on getTimestamp().
+     *
+     * Latency is the time it takes for a given frame to travel from the
+     * app to the edge of the device or vice versa.
+     *
+     * Note that the latency of an OUTPUT stream will increase when you write data to it
+     * and then decrease over time.
+     *
+     * The latency of an INPUT stream will decrease when you read data from it
+     * and then increase over time.
+     *
+     * The latency of an OUTPUT stream is generally higher than the INPUT latency
+     * because an tries to keep the OUTPUT buffer full and the INPUT buffer empty.
+     *
+     * @return The latency in millisecondssec and Result::OK, or a negative error.
+     */
+    virtual ErrorOrValue<double> calculateLatencyMillis() {
+        return ErrorOrValue<double>(Result::ErrorUnimplemented);
+    }
+
     virtual Result getTimestamp(clockid_t clockId,
-                                       int64_t *framePosition,
-                                       int64_t *timeNanoseconds) {
+                                int64_t *framePosition,
+                                int64_t *timeNanoseconds) {
         return Result::ErrorUnimplemented;
     }
 
@@ -173,7 +194,7 @@
      * @param buffer The address of the first sample.
      * @param numFrames Number of frames to write. Only complete frames will be written.
      * @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion.
-     * @return The number of frames actually written or a negative error.
+     * @return The number of frames actually written and Result::OK, or a negative error.
      */
     virtual ErrorOrValue<int32_t> write(const void *buffer,
                              int32_t numFrames,
diff --git a/src/aaudio/AudioStreamAAudio.cpp b/src/aaudio/AudioStreamAAudio.cpp
index 0bc6588..6d4b7c0 100644
--- a/src/aaudio/AudioStreamAAudio.cpp
+++ b/src/aaudio/AudioStreamAAudio.cpp
@@ -390,4 +390,46 @@
     }
 }
 
+ErrorOrValue<double> AudioStreamAAudio::calculateLatencyMillis() {
+    AAudioStream *stream = mAAudioStream.load();
+    if (stream != nullptr) {
+        bool isOutput = (getDirection() == oboe::Direction::Output);
+
+        // Get the time that a known audio frame was presented.
+        int64_t hardwareFrameIndex;
+        int64_t hardwareFrameHardwareTime;
+        auto result = getTimestamp(CLOCK_MONOTONIC,
+                                   &hardwareFrameIndex,
+                                   &hardwareFrameHardwareTime);
+        if (result != oboe::Result::OK) {
+            return ErrorOrValue<double>(static_cast<Result>(result));
+        }
+
+        // Get counter closest to the app.
+        int64_t appFrameIndex = isOutput ? getFramesWritten() : getFramesRead();
+
+        // Assume that the next frame will be processed at the current time
+        using namespace std::chrono;
+        int64_t appFrameAppTime =
+                duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
+
+        // Calculate the number of frames between app and hardware
+        int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
+
+        // Calculate the time which the next frame will be or was presented
+        int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / getSampleRate();
+        int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
+
+        // The current latency is the difference in time between when the current frame is at
+        // the app and when it is at the hardware.
+        int64_t latencyNanos = (isOutput)
+                               ? (appFrameHardwareTime - appFrameAppTime) // hardware is later
+                               : (appFrameAppTime - appFrameHardwareTime); // hardware is earlier
+        double latencyMillis = (double) latencyNanos / kNanosPerMillisecond;
+        return ErrorOrValue<double>(latencyMillis);
+    } else {
+        return ErrorOrValue<double>(Result::ErrorNull);
+    }
+}
+
 } // namespace oboe
diff --git a/src/aaudio/AudioStreamAAudio.h b/src/aaudio/AudioStreamAAudio.h
index 61b4928..928e0fb 100644
--- a/src/aaudio/AudioStreamAAudio.h
+++ b/src/aaudio/AudioStreamAAudio.h
@@ -76,6 +76,8 @@
     int64_t getFramesRead() const override;
     int64_t getFramesWritten() const override;
 
+    ErrorOrValue<double> calculateLatencyMillis() override;
+
     Result waitForStateChange(StreamState currentState,
                               StreamState *nextState,
                               int64_t timeoutNanoseconds) override;
@@ -86,7 +88,6 @@
 
     StreamState getState() override;
 
-
     AudioApi getAudioApi() const override {
         return AudioApi::AAudio;
     }