Home | History | Annotate | Download | only in loopback
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package org.drrickorang.loopback;
     18 
     19 import android.util.Log;
     20 
     21 
     22 /**
     23  * This class is used to automatically the audio performance according to recorder/player buffer
     24  * period.
     25  */
     26 
     27 public class PerformanceMeasurement {
     28     public static final String TAG = "PerformanceMeasurement";
     29 
     30     // this is used to enlarge the benchmark, so that it can be displayed with better accuracy on
     31     // the dashboard
     32     private static final int mMultiplicationFactor = 10000;
     33 
     34     private int   mExpectedBufferPeriodMs;
     35     private int[] mBufferData;
     36     private int   mTotalOccurrence;
     37 
     38     // used to determine buffer sizes mismatch
     39     private static final double mPercentOccurrenceThreshold = 0.95;
     40     // used to count the number of outliers
     41     private static final int    mOutliersThreshold = 3;
     42 
     43 
     44     /**
     45      * Note: if mBufferSize * Constant.MILLIS_PER_SECOND / mSamplingRate == Integer is satisfied,
     46      * the measurement will be more accurate, but this is not necessary.
     47      */
     48     public PerformanceMeasurement(int expectedBufferPeriod, int[] bufferData) {
     49         mBufferData = bufferData;
     50 
     51         mTotalOccurrence = 0;
     52         for (int i = 0; i < mBufferData.length; i++) {
     53             mTotalOccurrence += mBufferData[i];
     54         }
     55 
     56         mExpectedBufferPeriodMs = expectedBufferPeriod;
     57     }
     58 
     59 
     60     /**
     61      * Measure the performance according to the collected buffer period.
     62      * First, determine if there is a buffer sizes mismatch. If there is, then the performance
     63      * measurement should be disregarded since it won't be accurate. If there isn't a mismatch,
     64      * then a benchmark and a count on outliers can be produced as a measurement of performance.
     65      * The benchmark should be as small as possible, so is the number of outliers.
     66      * Note: This is a wrapper method that calls different methods and prints their results. It is
     67      * also possible to call individual method to obtain specific result.
     68      * Note: Should try to compare the number of outliers with the number of glitches and see if
     69      * they match.
     70      */
     71     public void measurePerformance() {
     72         // calculate standard deviation and mean of mBufferData
     73         double mean = computeMean(mBufferData);
     74         double standardDeviation = computeStandardDeviation(mBufferData, mean);
     75         log("mean before discarding 99% data: " + mean);
     76         log("standard deviation before discarding 99% data: " + standardDeviation);
     77         log("stdev/mean before discarding 99% data: " + (standardDeviation / mean));
     78 
     79         // calculate standard deviation and mean of dataAfterDiscard
     80         int[] dataAfterDiscard = computeDataAfterDiscard(mBufferData);
     81         double meanAfterDiscard = computeMean(dataAfterDiscard);
     82         double standardDeviationAfterDiscard = computeStandardDeviation(dataAfterDiscard,
     83                                                                         meanAfterDiscard);
     84         log("mean after discarding 99% data: " + meanAfterDiscard);
     85         log("standard deviation after discarding 99% data: " + standardDeviationAfterDiscard);
     86         log("stdev/mean after discarding 99% data: " + (standardDeviationAfterDiscard /
     87                                                         meanAfterDiscard));
     88         log("percent difference between two means: " + (Math.abs(meanAfterDiscard - mean) / mean));
     89 
     90         // determine if there's a buffer sizes mismatch
     91         boolean isBufferSizesMismatch =
     92                 percentBufferPeriodsAtExpected() > mPercentOccurrenceThreshold;
     93 
     94         // compute benchmark and count the number of outliers
     95         double benchmark = computeWeightedBenchmark();
     96         int outliers = countOutliers();
     97 
     98         log("total occurrence: " + mTotalOccurrence);
     99         log("buffer size mismatch: " + isBufferSizesMismatch);
    100         log("benchmark: " + benchmark);
    101         log("number of outliers: " + outliers);
    102         log("expected buffer period: " + mExpectedBufferPeriodMs + " ms");
    103         int maxPeriod = (mBufferData.length - 1);
    104         log("max buffer period: " + maxPeriod + " ms");
    105     }
    106 
    107 
    108     /**
    109      * Determine percent of Buffer Period Callbacks that occurred at the expected time
    110      * Returns a value between 0 and 1
    111      */
    112     public float percentBufferPeriodsAtExpected() {
    113         int occurrenceNearExpectedBufferPeriod = 0;
    114         // indicate how many buckets around mExpectedBufferPeriod do we want to add to the count
    115         int acceptableOffset = 2;
    116         int start = Math.max(0, mExpectedBufferPeriodMs - acceptableOffset);
    117         int end = Math.min(mBufferData.length - 1, mExpectedBufferPeriodMs + acceptableOffset);
    118         // include the next bucket too because the period is rounded up
    119         for (int i = start; i <= end; i++) {
    120             occurrenceNearExpectedBufferPeriod += mBufferData[i];
    121         }
    122         return ((float) occurrenceNearExpectedBufferPeriod) / mTotalOccurrence;
    123     }
    124 
    125 
    126     /**
    127      * Compute a benchmark using the following formula:
    128      * (1/totalOccurrence) sum_i(|i - expectedBufferPeriod|^2 * occurrence_i / expectedBufferPeriod)
    129      * , for i < expectedBufferPeriod * mOutliersThreshold
    130      * Also, the benchmark is additionally multiplied by mMultiplicationFactor. This is not in the
    131      * original formula, and it is used only because the original benchmark will be too small to
    132      * be displayed accurately on the dashboard.
    133      */
    134     public double computeWeightedBenchmark() {
    135         double weightedCount = 0;
    136         double weight;
    137         double benchmark;
    138 
    139         // don't count mExpectedBufferPeriodMs + 1 towards benchmark, cause this beam may be large
    140         // due to rounding issue (all results are rounded up when collecting buffer period.)
    141         int threshold = Math.min(mBufferData.length, mExpectedBufferPeriodMs * mOutliersThreshold);
    142         for (int i = 0; i < threshold; i++) {
    143             if (mBufferData[i] != 0 && (i != mExpectedBufferPeriodMs + 1)) {
    144                 weight = Math.abs(i - mExpectedBufferPeriodMs);
    145                 weight *= weight;   // squared
    146                 weightedCount += weight * mBufferData[i];
    147             }
    148         }
    149         weightedCount /= mExpectedBufferPeriodMs;
    150 
    151         benchmark = (weightedCount / mTotalOccurrence) * mMultiplicationFactor;
    152         return benchmark;
    153     }
    154 
    155 
    156     /**
    157      * All occurrence that happens after (mExpectedBufferPeriodMs * mOutliersThreshold) ms, will
    158      * be considered as outliers.
    159      */
    160     public int countOutliers() {
    161         int outliersThresholdInMs = mExpectedBufferPeriodMs * mOutliersThreshold;
    162         int outliersCount = 0;
    163         for (int i = outliersThresholdInMs; i < mBufferData.length; i++) {
    164             outliersCount += mBufferData[i];
    165         }
    166         return outliersCount;
    167     }
    168 
    169 
    170     /**
    171      * Output an array that has discarded 99 % of the data in the middle. In this array,
    172      * data[i] = x means there are x occurrences of value i.
    173      */
    174     private int[] computeDataAfterDiscard(int[] data) {
    175         // calculate the total amount of data
    176         int totalCount = 0;
    177         int length = data.length;
    178         for (int i = 0; i < length; i++) {
    179             totalCount += data[i];
    180         }
    181 
    182         // we only want to keep a certain percent of data at the bottom and top
    183         final double percent = 0.005;
    184         int bar = (int) (totalCount * percent);
    185         if (bar == 0) { // at least keep the lowest and highest data
    186             bar = 1;
    187         }
    188         int count = 0;
    189         int[] dataAfterDiscard = new int[length];
    190 
    191         // for bottom data
    192         for (int i = 0; i < length; i++) {
    193             if (count > bar) {
    194                 break;
    195             } else if (count + data[i] > bar) {
    196                 dataAfterDiscard[i] += bar - count;
    197                 break;
    198             } else {
    199                 dataAfterDiscard[i] += data[i];
    200                 count += data[i];
    201             }
    202         }
    203 
    204         // for top data
    205         count = 0;
    206         for (int i = length - 1; i >= 0; i--) {
    207             if (count > bar) {
    208                 break;
    209             } else if (count + data[i] > bar) {
    210                 dataAfterDiscard[i] += bar - count;
    211                 break;
    212             } else {
    213                 dataAfterDiscard[i] += data[i];
    214                 count += data[i];
    215             }
    216         }
    217 
    218         return dataAfterDiscard;
    219     }
    220 
    221 
    222     /**
    223      * Calculate the mean of int array "data". In this array, data[i] = x means there are
    224      * x occurrences of value i.
    225      */
    226     private double computeMean(int[] data) {
    227         int count = 0;
    228         int sum = 0;
    229         for (int i = 0; i < data.length; i++) {
    230             count += data[i];
    231             sum += data[i] * i;
    232         }
    233 
    234         double mean;
    235         if (count != 0) {
    236             mean = (double) sum / count;
    237         } else {
    238             mean = 0;
    239             log("zero count!");
    240         }
    241 
    242         return mean;
    243     }
    244 
    245 
    246     /**
    247      * Calculate the standard deviation of int array "data". In this array, data[i] = x means
    248      * there are x occurrences of value i.
    249      */
    250     private double computeStandardDeviation(int[] data, double mean) {
    251         double sumDeviation = 0;
    252         int count = 0;
    253         double standardDeviation;
    254 
    255         for (int i = 0; i < data.length; i++) {
    256             if (data[i] != 0) {
    257                 count += data[i];
    258                 sumDeviation += (i - mean) * (i - mean) * data[i];
    259             }
    260         }
    261 
    262         standardDeviation = Math.sqrt(sumDeviation / (count - 1));
    263         return standardDeviation;
    264     }
    265 
    266 
    267     private static void log(String msg) {
    268         Log.v(TAG, msg);
    269     }
    270 
    271 }
    272