Level measurement in offloaded visualizer

Add Peak and RMS measurement capabilities in the "offloaded" version
  of the visualizer effect.

Bug 8413913

Change-Id: I09a88f4cc791db6c68f0769dc23ced0d3aac955c
diff --git a/visualizer/offload_visualizer.c b/visualizer/offload_visualizer.c
index 75ce4a5..eb43558 100644
--- a/visualizer/offload_visualizer.c
+++ b/visualizer/offload_visualizer.c
@@ -17,6 +17,7 @@
 #define LOG_TAG "offload_visualizer"
 /*#define LOG_NDEBUG 0*/
 #include <assert.h>
+#include <math.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -80,6 +81,16 @@
 
 #define CAPTURE_BUF_SIZE 65536 /* "64k should be enough for everyone" */
 
+#define DISCARD_MEASUREMENTS_TIME_MS 2000 /* discard measurements older than this number of ms */
+
+/* maximum number of buffers for which we keep track of the measurements */
+#define MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS 25 /* note: buffer index is stored in uint8_t */
+
+typedef struct buffer_stats_s {
+    bool is_valid;
+    uint16_t peak_u16; /* the positive peak of the absolute value of the samples in a buffer */
+    float rms_squared; /* the average square of the samples in a buffer */
+} buffer_stats_t;
 
 typedef struct visualizer_context_s {
     effect_context_t common;
@@ -91,6 +102,12 @@
     uint32_t latency;
     struct timespec buffer_update_time;
     uint8_t capture_buf[CAPTURE_BUF_SIZE];
+    /* for measurements */
+    uint8_t channel_count; /* to avoid recomputing it every time a buffer is processed */
+    uint32_t meas_mode;
+    uint8_t meas_wndw_size_in_buffers;
+    uint8_t meas_buffer_idx;
+    buffer_stats_t past_meas[MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS];
 } visualizer_context_t;
 
 
@@ -507,6 +524,23 @@
  * Visualizer operations
  */
 
+uint32_t visualizer_get_delta_time_ms_from_updated_time(visualizer_context_t* visu_ctxt) {
+    uint32_t delta_ms = 0;
+    if (visu_ctxt->buffer_update_time.tv_sec != 0) {
+        struct timespec ts;
+        if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+            time_t secs = ts.tv_sec - visu_ctxt->buffer_update_time.tv_sec;
+            long nsec = ts.tv_nsec - visu_ctxt->buffer_update_time.tv_nsec;
+            if (nsec < 0) {
+                --secs;
+                nsec += 1000000000;
+            }
+            delta_ms = secs * 1000 + nsec / 1000000;
+        }
+    }
+    return delta_ms;
+}
+
 int visualizer_reset(effect_context_t *context)
 {
     visualizer_context_t * visu_ctxt = (visualizer_context_t *)context;
@@ -521,6 +555,8 @@
 
 int visualizer_init(effect_context_t *context)
 {
+    int32_t i;
+
     visualizer_context_t * visu_ctxt = (visualizer_context_t *)context;
 
     context->config.inputCfg.accessMode = EFFECT_BUFFER_ACCESS_READ;
@@ -543,6 +579,17 @@
     visu_ctxt->capture_size = VISUALIZER_CAPTURE_SIZE_MAX;
     visu_ctxt->scaling_mode = VISUALIZER_SCALING_MODE_NORMALIZED;
 
+    // measurement initialization
+    visu_ctxt->channel_count = popcount(context->config.inputCfg.channels);
+    visu_ctxt->meas_mode = MEASUREMENT_MODE_NONE;
+    visu_ctxt->meas_wndw_size_in_buffers = MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS;
+    visu_ctxt->meas_buffer_idx = 0;
+    for (i=0 ; i<visu_ctxt->meas_wndw_size_in_buffers ; i++) {
+        visu_ctxt->past_meas[i].is_valid = false;
+        visu_ctxt->past_meas[i].peak_u16 = 0;
+        visu_ctxt->past_meas[i].rms_squared = 0;
+    }
+
     set_config(context, &context->config);
 
     return 0;
@@ -571,6 +618,12 @@
         p->vsize = sizeof(uint32_t);
         *size += sizeof(uint32_t);
         break;
+    case VISUALIZER_PARAM_MEASUREMENT_MODE:
+        ALOGV("%s get meas_mode = %d", __func__, visu_ctxt->meas_mode);
+        *((uint32_t *)p->data + 1) = visu_ctxt->meas_mode;
+        p->vsize = sizeof(uint32_t);
+        *size += sizeof(uint32_t);
+        break;
     default:
         p->status = -EINVAL;
     }
@@ -598,6 +651,10 @@
          * visu_ctxt->latency = *((uint32_t *)p->data + 1); */
         ALOGV("%s set latency = %d", __func__, visu_ctxt->latency);
         break;
+    case VISUALIZER_PARAM_MEASUREMENT_MODE:
+        visu_ctxt->meas_mode = *((uint32_t *)p->data + 1);
+        ALOGV("%s set meas_mode = %d", __func__, visu_ctxt->meas_mode);
+        break;
     default:
         return -EINVAL;
     }
@@ -621,6 +678,30 @@
         return -EINVAL;
     }
 
+    // perform measurements if needed
+    if (visu_ctxt->meas_mode & MEASUREMENT_MODE_PEAK_RMS) {
+        // find the peak and RMS squared for the new buffer
+        uint32_t inIdx;
+        int16_t max_sample = 0;
+        float rms_squared_acc = 0;
+        for (inIdx = 0 ; inIdx < inBuffer->frameCount * visu_ctxt->channel_count ; inIdx++) {
+            if (inBuffer->s16[inIdx] > max_sample) {
+                max_sample = inBuffer->s16[inIdx];
+            } else if (-inBuffer->s16[inIdx] > max_sample) {
+                max_sample = -inBuffer->s16[inIdx];
+            }
+            rms_squared_acc += (inBuffer->s16[inIdx] * inBuffer->s16[inIdx]);
+        }
+        // store the measurement
+        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].peak_u16 = (uint16_t)max_sample;
+        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].rms_squared =
+                rms_squared_acc / (inBuffer->frameCount * visu_ctxt->channel_count);
+        visu_ctxt->past_meas[visu_ctxt->meas_buffer_idx].is_valid = true;
+        if (++visu_ctxt->meas_buffer_idx >= visu_ctxt->meas_wndw_size_in_buffers) {
+            visu_ctxt->meas_buffer_idx = 0;
+        }
+    }
+
     /* all code below assumes stereo 16 bit PCM output and input */
     int32_t shift;
 
@@ -700,23 +781,12 @@
 
         if (context->state == EFFECT_STATE_ACTIVE) {
             int32_t latency_ms = visu_ctxt->latency;
-            uint32_t delta_ms = 0;
-            if (visu_ctxt->buffer_update_time.tv_sec != 0) {
-                struct timespec ts;
-                if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
-                    time_t secs = ts.tv_sec - visu_ctxt->buffer_update_time.tv_sec;
-                    long nsec = ts.tv_nsec - visu_ctxt->buffer_update_time.tv_nsec;
-                    if (nsec < 0) {
-                        --secs;
-                        nsec += 1000000000;
-                    }
-                    delta_ms = secs * 1000 + nsec / 1000000;
-                    latency_ms -= delta_ms;
-                    if (latency_ms < 0)
-                        latency_ms = 0;
-                }
+            const uint32_t delta_ms = visualizer_get_delta_time_ms_from_updated_time(visu_ctxt);
+            latency_ms -= delta_ms;
+            if (latency_ms < 0) {
+                latency_ms = 0;
             }
-            uint32_t delta_smp = context->config.inputCfg.samplingRate * latency_ms / 1000;
+            const uint32_t delta_smp = context->config.inputCfg.samplingRate * latency_ms / 1000;
 
             int32_t capture_point = visu_ctxt->capture_idx - visu_ctxt->capture_size - delta_smp;
             int32_t capture_size = visu_ctxt->capture_size;
@@ -753,6 +823,56 @@
         }
         break;
 
+    case VISUALIZER_CMD_MEASURE: {
+        uint16_t peak_u16 = 0;
+        float sum_rms_squared = 0.0f;
+        uint8_t nb_valid_meas = 0;
+        /* reset measurements if last measurement was too long ago (which implies stored
+         * measurements aren't relevant anymore and shouldn't bias the new one) */
+        const int32_t delay_ms = visualizer_get_delta_time_ms_from_updated_time(visu_ctxt);
+        if (delay_ms > DISCARD_MEASUREMENTS_TIME_MS) {
+            uint32_t i;
+            ALOGV("Discarding measurements, last measurement is %dms old", delay_ms);
+            for (i=0 ; i<visu_ctxt->meas_wndw_size_in_buffers ; i++) {
+                visu_ctxt->past_meas[i].is_valid = false;
+                visu_ctxt->past_meas[i].peak_u16 = 0;
+                visu_ctxt->past_meas[i].rms_squared = 0;
+            }
+            visu_ctxt->meas_buffer_idx = 0;
+        } else {
+            /* only use actual measurements, otherwise the first RMS measure happening before
+             * MEASUREMENT_WINDOW_MAX_SIZE_IN_BUFFERS have been played will always be artificially
+             * low */
+            uint32_t i;
+            for (i=0 ; i < visu_ctxt->meas_wndw_size_in_buffers ; i++) {
+                if (visu_ctxt->past_meas[i].is_valid) {
+                    if (visu_ctxt->past_meas[i].peak_u16 > peak_u16) {
+                        peak_u16 = visu_ctxt->past_meas[i].peak_u16;
+                    }
+                    sum_rms_squared += visu_ctxt->past_meas[i].rms_squared;
+                    nb_valid_meas++;
+                }
+            }
+        }
+        float rms = nb_valid_meas == 0 ? 0.0f : sqrtf(sum_rms_squared / nb_valid_meas);
+        int32_t* p_int_reply_data = (int32_t*)pReplyData;
+        /* convert from I16 sample values to mB and write results */
+        if (rms < 0.000016f) {
+            p_int_reply_data[MEASUREMENT_IDX_RMS] = -9600; //-96dB
+        } else {
+            p_int_reply_data[MEASUREMENT_IDX_RMS] = (int32_t) (2000 * log10(rms / 32767.0f));
+        }
+        if (peak_u16 == 0) {
+            p_int_reply_data[MEASUREMENT_IDX_PEAK] = -9600; //-96dB
+        } else {
+            p_int_reply_data[MEASUREMENT_IDX_PEAK] = (int32_t) (2000 * log10(peak_u16 / 32767.0f));
+        }
+        ALOGV("VISUALIZER_CMD_MEASURE peak=%d (%dmB), rms=%.1f (%dmB)",
+                peak_u16, p_int_reply_data[MEASUREMENT_IDX_PEAK],
+                rms, p_int_reply_data[MEASUREMENT_IDX_RMS]);
+        }
+        break;
+
     default:
         ALOGW("%s invalid command %d", __func__, cmdCode);
         return -EINVAL;