OboeTester: factor out analyzer
So we can use it in CTS or other projects.
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h
index b323ffb..ee12fc1 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexAnalyzer.h
@@ -22,7 +22,7 @@
#include "oboe/Oboe.h"
#include "FullDuplexStream.h"
-#include "LatencyAnalyzer.h"
+#include "analyzer/LatencyAnalyzer.h"
#include "MultiChannelRecording.h"
class FullDuplexAnalyzer : public FullDuplexStream {
diff --git a/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h
index 68e3688..1046e04 100644
--- a/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h
+++ b/apps/OboeTester/app/src/main/cpp/FullDuplexGlitches.h
@@ -22,6 +22,7 @@
#include "oboe/Oboe.h"
#include "FullDuplexAnalyzer.h"
+#include "analyzer/GlitchAnalyzer.h"
class FullDuplexGlitches : public FullDuplexAnalyzer {
public:
diff --git a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
index 4bc6a80..42ffbfb 100644
--- a/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
+++ b/apps/OboeTester/app/src/main/cpp/InputStreamCallbackAnalyzer.h
@@ -24,7 +24,7 @@
// TODO #include "flowgraph/FlowGraph.h"
#include "oboe/Oboe.h"
#include "MultiChannelRecording.h"
-#include "PeakDetector.h"
+#include "analyzer/PeakDetector.h"
constexpr int kMaxInputChannels = 8;
diff --git a/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h b/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h
deleted file mode 100644
index 343629a..0000000
--- a/apps/OboeTester/app/src/main/cpp/LatencyAnalyzer.h
+++ /dev/null
@@ -1,1126 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
-/**
- * Tools for measuring latency and for detecting glitches.
- * These classes are pure math and can be used with any audio system.
- */
-
-#ifndef OBOETESTER_LATENCY_ANALYSER_H
-#define OBOETESTER_LATENCY_ANALYSER_H
-
-#include <algorithm>
-#include <assert.h>
-#include <cctype>
-#include <math.h>
-#include <memory>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <vector>
-
-#include "RandomPulseGenerator.h"
-
-#define LOOPBACK_RESULT_TAG "RESULT: "
-
-constexpr int32_t kDefaultSampleRate = 48000;
-constexpr int32_t kMillisPerSecond = 1000;
-
-constexpr int32_t kMaxLatencyMillis = 700; // arbitrary and generous
-constexpr double kMinimumConfidence = 0.2;
-
-class PseudoRandom {
-public:
- PseudoRandom() {}
- PseudoRandom(int64_t seed)
- : mSeed(seed)
- {}
-
- /**
- * Returns the next random double from -1.0 to 1.0
- *
- * @return value from -1.0 to 1.0
- */
- double nextRandomDouble() {
- return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
- }
-
- /** Calculate random 32 bit number using linear-congruential method.
- */
- int32_t nextRandomInteger() {
-#if __has_builtin(__builtin_mul_overflow) && __has_builtin(__builtin_add_overflow)
- int64_t prod;
- // Use values for 64-bit sequence from MMIX by Donald Knuth.
- __builtin_mul_overflow(mSeed, (int64_t)6364136223846793005, &prod);
- __builtin_add_overflow(prod, (int64_t)1442695040888963407, &mSeed);
-#else
- mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
-#endif
- return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
- }
-
-private:
- int64_t mSeed = 99887766;
-};
-
-class PeakFollower {
-public:
- float process(float input) {
- mPrevious *= kDecayCoefficient;
- const float positive = abs(input);
- if (positive > mPrevious) {
- mPrevious = positive;
- }
- return mPrevious;
- }
-
- float get() {
- return mPrevious;
- }
-
-private:
- static constexpr float kDecayCoefficient = 0.99f;
- float mPrevious = 0.0f;
-};
-
-typedef struct LatencyReport_s {
- int32_t latencyInFrames = 0.0;
- double confidence = 0.0;
-
- void reset() {
- latencyInFrames = 0;
- confidence = 0.0;
- }
-} LatencyReport;
-
-// Calculate a normalized cross correlation.
-static double calculateNormalizedCorrelation(const float *a,
- const float *b,
- int windowSize)
-{
- double correlation = 0.0;
- double sumProducts = 0.0;
- double sumSquares = 0.0;
-
- // Correlate a against b.
- for (int i = 0; i < windowSize; i++) {
- float s1 = a[i];
- float s2 = b[i];
- // Use a normalized cross-correlation.
- sumProducts += s1 * s2;
- sumSquares += ((s1 * s1) + (s2 * s2));
- }
-
- if (sumSquares >= 1.0e-9) {
- correlation = (float) (2.0 * sumProducts / sumSquares);
- }
- return correlation;
-}
-
-static double calculateRootMeanSquare(float *data, int32_t numSamples) {
- double sum = 0.0;
- for (int32_t i = 0; i < numSamples; i++) {
- float sample = data[i];
- sum += sample * sample;
- }
- return sqrt(sum / numSamples);
-}
-
-/**
- * Monophonic recording with processing.
- */
-class AudioRecording
-{
-public:
- AudioRecording() {
- }
- ~AudioRecording() {
- delete[] mData;
- }
-
- void allocate(int maxFrames) {
- delete[] mData;
- mData = new float[maxFrames];
- mMaxFrames = maxFrames;
- }
-
- // Write SHORT data from the first channel.
- int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
- // stop at end of buffer
- if ((mFrameCounter + numFrames) > mMaxFrames) {
- numFrames = mMaxFrames - mFrameCounter;
- }
- for (int i = 0; i < numFrames; i++) {
- mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
- }
- return numFrames;
- }
-
- // Write FLOAT data from the first channel.
- int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
- // stop at end of buffer
- if ((mFrameCounter + numFrames) > mMaxFrames) {
- numFrames = mMaxFrames - mFrameCounter;
- }
- for (int i = 0; i < numFrames; i++) {
- mData[mFrameCounter++] = inputData[i * inputChannelCount];
- }
- return numFrames;
- }
-
- // Write FLOAT data from the first channel.
- int32_t write(float sample) {
- // stop at end of buffer
- if (mFrameCounter < mMaxFrames) {
- mData[mFrameCounter++] = sample;
- }
- return 1;
- }
-
- void clear() {
- mFrameCounter = 0;
- }
- int32_t size() {
- return mFrameCounter;
- }
-
- bool isFull() {
- return mFrameCounter >= mMaxFrames;
- }
-
- float *getData() {
- return mData;
- }
-
- void setSampleRate(int32_t sampleRate) {
- mSampleRate = sampleRate;
- }
-
- int32_t getSampleRate() {
- return mSampleRate;
- }
-
-/*
- int save(const char *fileName, bool writeShorts = true) {
- SNDFILE *sndFile = nullptr;
- int written = 0;
- SF_INFO info = {
- .frames = mFrameCounter,
- .samplerate = mSampleRate,
- .channels = 1,
- .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
- };
-
- sndFile = sf_open(fileName, SFM_WRITE, &info);
- if (sndFile == nullptr) {
- LOGD("AudioRecording::save(%s) failed to open file\n", fileName);
- return -errno;
- }
-
- written = sf_writef_float(sndFile, mData, mFrameCounter);
-
- sf_close(sndFile);
- return written;
- }
-
- int load(const char *fileName) {
- SNDFILE *sndFile = nullptr;
- SF_INFO info;
-
- sndFile = sf_open(fileName, SFM_READ, &info);
- if (sndFile == nullptr) {
- LOGD("AudioRecording::load(%s) failed to open file\n", fileName);
- return -errno;
- }
-
- assert(info.channels == 1);
- assert(info.format == SF_FORMAT_FLOAT);
-
- setSampleRate(info.samplerate);
- allocate(info.frames);
- mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
-
- sf_close(sndFile);
- return mFrameCounter;
- }
-*/
- /**
- * Square the samples so they are all positive and so the peaks are emphasized.
- */
- void square() {
- for (int i = 0; i < mFrameCounter; i++) {
- const float sample = mData[i];
- mData[i] = sample * sample;
- }
- }
-
- /**
- * Amplify a signal so that the peak matches the specified target.
- *
- * @param target final max value
- * @return gain applied to signal
- */
- float normalize(float target) {
- float maxValue = 1.0e-9f;
- for (int i = 0; i < mFrameCounter; i++) {
- maxValue = std::max(maxValue, abs(mData[i]));
- }
- float gain = target / maxValue;
- for (int i = 0; i < mFrameCounter; i++) {
- mData[i] *= gain;
- }
- return gain;
- }
-
-private:
- float *mData = nullptr;
- int32_t mFrameCounter = 0;
- int32_t mMaxFrames = 0;
- int32_t mSampleRate = kDefaultSampleRate; // common default
-};
-
-static int measureLatencyFromPulse(AudioRecording &recorded,
- AudioRecording &pulse,
- int32_t framesPerEncodedBit,
- LatencyReport *report) {
-
- report->latencyInFrames = 0;
- report->confidence = 0.0;
-
- int numCorrelations = recorded.size() - pulse.size();
- if (numCorrelations < 10) {
- LOGE("%s() recording too small = %d frames", __func__, recorded.size());
- return -1;
- }
- std::unique_ptr<float[]> correlations= std::make_unique<float[]>(numCorrelations);
-
- // Correlate pulse against the recorded data.
- for (int i = 0; i < numCorrelations; i++) {
- float correlation = (float) calculateNormalizedCorrelation(&recorded.getData()[i],
- &pulse.getData()[0],
- pulse.size());
- correlations[i] = correlation;
- }
-
- // Find highest peak in correlation array.
- float peakCorrelation = 0.0;
- int peakIndex = -1;
- for (int i = 0; i < numCorrelations; i++) {
- float value = abs(correlations[i]);
- if (value > peakCorrelation) {
- peakCorrelation = value;
- peakIndex = i;
- }
- }
- if (peakIndex < 0) {
- LOGE("%s() no signal for correlation", __func__);
- return -2;
- }
-
- report->latencyInFrames = peakIndex;
- report->confidence = peakCorrelation;
-
- return 0;
-}
-
-// ====================================================================================
-class LoopbackProcessor {
-public:
- virtual ~LoopbackProcessor() = default;
-
- // Note that these values must match the switch in RoundTripLatencyActivity.h
- enum result_code {
- RESULT_OK = 0,
- ERROR_NOISY = -99,
- ERROR_VOLUME_TOO_LOW,
- ERROR_VOLUME_TOO_HIGH,
- ERROR_CONFIDENCE,
- ERROR_INVALID_STATE,
- ERROR_GLITCHES,
- ERROR_NO_LOCK
- };
-
- virtual void onStartTest() {
- reset();
- }
-
- virtual void reset() {
- mResult = 0;
- mResetCount++;
- }
-
- virtual result_code processInputFrame(float *frameData, int channelCount) = 0;
- virtual result_code processOutputFrame(float *frameData, int channelCount) = 0;
-
- void process(float *inputData, int inputChannelCount, int numInputFrames,
- float *outputData, int outputChannelCount, int numOutputFrames) {
- int numBoth = std::min(numInputFrames, numOutputFrames);
- // Process one frame at a time.
- for (int i = 0; i < numBoth; i++) {
- processInputFrame(inputData, inputChannelCount);
- inputData += inputChannelCount;
- processOutputFrame(outputData, outputChannelCount);
- outputData += outputChannelCount;
- }
- // If there is more input than output.
- for (int i = numBoth; i < numInputFrames; i++) {
- processInputFrame(inputData, inputChannelCount);
- inputData += inputChannelCount;
- }
- // If there is more output than input.
- for (int i = numBoth; i < numOutputFrames; i++) {
- processOutputFrame(outputData, outputChannelCount);
- outputData += outputChannelCount;
- }
- }
-
- virtual void analyze() = 0;
-
- virtual void printStatus() {};
-
- int32_t getResult() {
- return mResult;
- }
-
- void setResult(int32_t result) {
- mResult = result;
- }
-
- virtual bool isDone() {
- return false;
- }
-
- virtual int save(const char *fileName) {
- (void) fileName;
- return -1;
- }
-
- virtual int load(const char *fileName) {
- (void) fileName;
- return -1;
- }
-
- virtual void setSampleRate(int32_t sampleRate) {
- mSampleRate = sampleRate;
- }
-
- int32_t getSampleRate() {
- return mSampleRate;
- }
-
- int32_t getResetCount() {
- return mResetCount;
- }
-
- /** Called when not enough input frames could be read after synchronization.
- */
- virtual void onInsufficientRead() {
- reset();
- }
-
-protected:
- int32_t mResetCount = 0;
-
-private:
- int32_t mSampleRate = kDefaultSampleRate;
- int32_t mResult = 0;
-};
-
-/*
-class PeakAnalyzer {
-public:
- float process(float input) {
- float output = mPrevious * mDecay;
- if (input > output) {
- output = input;
- }
- mPrevious = output;
- return output;
- }
-
-private:
- float mDecay = 0.99f;
- float mPrevious = 0.0f;
-};
-*/
-
-class LatencyAnalyzer : public LoopbackProcessor {
-public:
-
- LatencyAnalyzer() : LoopbackProcessor() {}
- virtual ~LatencyAnalyzer() = default;
-
- virtual int32_t getProgress() = 0;
-
- virtual int getState() = 0;
-
- // @return latency in frames
- virtual int32_t getMeasuredLatency() = 0;
-
- virtual double getMeasuredConfidence() = 0;
-
- virtual double getBackgroundRMS() = 0;
-
- virtual double getSignalRMS() = 0;
-
-};
-
-// ====================================================================================
-/**
- * Measure latency given a loopback stream data.
- * Use an encoded bit train as the sound source because it
- * has an unambiguous correlation value.
- * Uses a state machine to cycle through various stages.
- *
- */
-class PulseLatencyAnalyzer : public LatencyAnalyzer {
-public:
-
- PulseLatencyAnalyzer() : LatencyAnalyzer() {
- int32_t maxLatencyFrames = getSampleRate() * kMaxLatencyMillis / kMillisPerSecond;
- int32_t numPulseBits = getSampleRate() * kPulseLengthMillis
- / (kFramesPerEncodedBit * kMillisPerSecond);
- int32_t pulseLength = numPulseBits * kFramesPerEncodedBit;
- mFramesToRecord = pulseLength + maxLatencyFrames;
- LOGD("PulseLatencyAnalyzer: allocate recording with %d frames", mFramesToRecord);
- mAudioRecording.allocate(mFramesToRecord);
- mAudioRecording.setSampleRate(getSampleRate());
- generateRandomPulse(pulseLength);
- }
-
- void generateRandomPulse(int32_t pulseLength) {
- mPulse.allocate(pulseLength);
- RandomPulseGenerator pulser(kFramesPerEncodedBit);
- for (int i = 0; i < pulseLength; i++) {
- mPulse.write(pulser.nextFloat());
- }
- }
-
- int getState() override {
- return mState;
- }
-
- void setSampleRate(int32_t sampleRate) override {
- LoopbackProcessor::setSampleRate(sampleRate);
- mAudioRecording.setSampleRate(sampleRate);
- }
-
- void reset() override {
- LoopbackProcessor::reset();
- mDownCounter = getSampleRate() / 2;
- mLoopCounter = 0;
-
- mPulseCursor = 0;
- mBackgroundSumSquare = 0.0f;
- mBackgroundSumCount = 0;
- mBackgroundRMS = 0.0f;
- mSignalRMS = 0.0f;
-
- LOGD("state reset to STATE_MEASURE_BACKGROUND");
- mState = STATE_MEASURE_BACKGROUND;
- mAudioRecording.clear();
- mLatencyReport.reset();
- }
-
- bool hasEnoughData() {
- return mAudioRecording.isFull();
- }
-
- bool isDone() override {
- return mState == STATE_DONE;
- }
-
- int32_t getProgress() override {
- return mAudioRecording.size();
- }
-
- void analyze() override {
- LOGD("PulseLatencyAnalyzer ---------------");
- LOGD(LOOPBACK_RESULT_TAG "test.state = %8d", mState);
- LOGD(LOOPBACK_RESULT_TAG "test.state.name = %8s", convertStateToText(mState));
- LOGD(LOOPBACK_RESULT_TAG "background.rms = %8f", mBackgroundRMS);
-
- int32_t newResult = RESULT_OK;
- if (mState != STATE_GOT_DATA) {
- LOGD("WARNING - Bad state. Check volume on device.");
- // setResult(ERROR_INVALID_STATE);
- } else {
- LOGD("Please wait several seconds for cross-correlation to complete.");
- float gain = mAudioRecording.normalize(1.0f);
- measureLatencyFromPulse(mAudioRecording,
- mPulse,
- kFramesPerEncodedBit,
- &mLatencyReport);
-
- if (mLatencyReport.confidence < kMinimumConfidence) {
- LOGD(" ERROR - confidence too low!");
- newResult = ERROR_CONFIDENCE;
- } else {
- mSignalRMS = calculateRootMeanSquare(
- &mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
- / gain;
- }
-#if OBOE_ENABLE_LOGGING
- double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
- / getSampleRate();
-#endif
- LOGD(LOOPBACK_RESULT_TAG "latency.frames = %8d",
- mLatencyReport.latencyInFrames);
- LOGD(LOOPBACK_RESULT_TAG "latency.msec = %8.2f",
- latencyMillis);
- LOGD(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f",
- mLatencyReport.confidence);
- }
- mState = STATE_DONE;
- if (getResult() == RESULT_OK) {
- setResult(newResult);
- }
- }
-
- int32_t getMeasuredLatency() override {
- return mLatencyReport.latencyInFrames;
- }
-
- double getMeasuredConfidence() override {
- return mLatencyReport.confidence;
- }
-
- double getBackgroundRMS() override {
- return mBackgroundRMS;
- }
-
- double getSignalRMS() override {
- return mSignalRMS;
- }
-
- void printStatus() override {
- LOGD("st = %d", mState);
- }
-
- result_code processInputFrame(float *frameData, int channelCount) override {
- echo_state nextState = mState;
- mLoopCounter++;
-
- switch (mState) {
- case STATE_MEASURE_BACKGROUND:
- // Measure background RMS on channel 0
- mBackgroundSumSquare += frameData[0] * frameData[0];
- mBackgroundSumCount++;
- mDownCounter--;
- if (mDownCounter <= 0) {
- mBackgroundRMS = sqrtf(mBackgroundSumSquare / mBackgroundSumCount);
- nextState = STATE_IN_PULSE;
- mPulseCursor = 0;
- LOGD("LatencyAnalyzer state => STATE_SENDING_PULSE");
- }
- break;
-
- case STATE_IN_PULSE:
- // Record input until the mAudioRecording is full.
- mAudioRecording.write(frameData, channelCount, 1);
- if (hasEnoughData()) {
- LOGD("LatencyAnalyzer state => STATE_GOT_DATA");
- nextState = STATE_GOT_DATA;
- }
- break;
-
- case STATE_GOT_DATA:
- case STATE_DONE:
- default:
- break;
- }
-
- mState = nextState;
- return RESULT_OK;
- }
-
- result_code processOutputFrame(float *frameData, int channelCount) override {
- switch (mState) {
- case STATE_IN_PULSE:
- if (mPulseCursor < mPulse.size()) {
- float pulseSample = mPulse.getData()[mPulseCursor++];
- for (int i = 0; i < channelCount; i++) {
- frameData[i] = pulseSample;
- }
- } else {
- for (int i = 0; i < channelCount; i++) {
- frameData[i] = 0;
- }
- }
- break;
-
- case STATE_MEASURE_BACKGROUND:
- case STATE_GOT_DATA:
- case STATE_DONE:
- default:
- for (int i = 0; i < channelCount; i++) {
- frameData[i] = 0.0f; // silence
- }
- break;
- }
-
- return RESULT_OK;
- }
-
-private:
-
- enum echo_state {
- STATE_MEASURE_BACKGROUND,
- STATE_IN_PULSE,
- STATE_GOT_DATA, // must match RoundTripLatencyActivity.java
- STATE_DONE,
- };
-
- const char *convertStateToText(echo_state state) {
- const char *result = "Unknown";
- switch(state) {
- case STATE_MEASURE_BACKGROUND:
- result = "INIT";
- break;
- case STATE_IN_PULSE:
- result = "PULSE";
- break;
- case STATE_GOT_DATA:
- result = "GOT_DATA";
- break;
- case STATE_DONE:
- result = "DONE";
- break;
- }
- return result;
- }
-
- int32_t mDownCounter = 500;
- int32_t mLoopCounter = 0;
- echo_state mState = STATE_MEASURE_BACKGROUND;
-
- static constexpr int32_t kFramesPerEncodedBit = 8; // multiple of 2
- static constexpr int32_t kPulseLengthMillis = 500;
-
- AudioRecording mPulse;
- int32_t mPulseCursor = 0;
-
- float mBackgroundSumSquare = 0.0f;
- int32_t mBackgroundSumCount = 0;
- float mBackgroundRMS = 0.0f;
- float mSignalRMS = 0.0f;
- int32_t mFramesToRecord = 0;
-
- AudioRecording mAudioRecording; // contains only the input after starting the pulse
- LatencyReport mLatencyReport;
-};
-
-// ====================================================================================
-/**
- * Output a steady sinewave and analyze the return signal.
- *
- * Use a cosine transform to measure the predicted magnitude and relative phase of the
- * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
- */
-class GlitchAnalyzer : public LoopbackProcessor {
-public:
-
- int32_t getState() {
- return mState;
- }
-
- float getPeakAmplitude() {
- return mPeakFollower.get();
- }
-
- float getTolerance() {
- return mTolerance;
- }
-
- void setTolerance(float tolerance) {
- mTolerance = tolerance;
- mScaledTolerance = mMagnitude * mTolerance;
- }
-
- void setMagnitude(double magnitude) {
- mMagnitude = magnitude;
- mScaledTolerance = mMagnitude * mTolerance;
- }
-
- int32_t getGlitchCount() {
- return mGlitchCount;
- }
-
- int32_t getStateFrameCount(int state) {
- return mStateFrameCounters[state];
- }
-
- double getSignalToNoiseDB() {
- static const double threshold = 1.0e-14;
- if (mMeanSquareSignal < threshold || mMeanSquareNoise < threshold) {
- return 0.0;
- } else {
- double signalToNoise = mMeanSquareSignal / mMeanSquareNoise; // power ratio
- double signalToNoiseDB = 10.0 * log(signalToNoise);
- if (signalToNoiseDB < MIN_SNRATIO_DB) {
- LOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
- MIN_SNRATIO_DB);
- setResult(ERROR_VOLUME_TOO_LOW);
- }
- return signalToNoiseDB;
- }
- }
-
- void analyze() override {
- LOGD("GlitchAnalyzer ------------------");
- LOGD(LOOPBACK_RESULT_TAG "peak.amplitude = %8f", getPeakAmplitude());
- LOGD(LOOPBACK_RESULT_TAG "sine.magnitude = %8f", mMagnitude);
- LOGD(LOOPBACK_RESULT_TAG "rms.noise = %8f", mMeanSquareNoise);
- LOGD(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f", getSignalToNoiseDB());
- LOGD(LOOPBACK_RESULT_TAG "frames.accumulated = %8d", mFramesAccumulated);
- LOGD(LOOPBACK_RESULT_TAG "sine.period = %8d", mSinePeriod);
- LOGD(LOOPBACK_RESULT_TAG "test.state = %8d", mState);
- LOGD(LOOPBACK_RESULT_TAG "frame.count = %8d", mFrameCounter);
- // Did we ever get a lock?
- bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
- if (!gotLock) {
- LOGD("ERROR - failed to lock on reference sine tone");
- setResult(ERROR_NO_LOCK);
- } else {
- // Only print if meaningful.
- LOGD(LOOPBACK_RESULT_TAG "glitch.count = %8d", mGlitchCount);
- LOGD(LOOPBACK_RESULT_TAG "max.glitch = %8f", mMaxGlitchDelta);
- if (mGlitchCount > 0) {
- LOGD("ERROR - number of glitches > 0");
- setResult(ERROR_GLITCHES);
- }
- }
- }
-
- void printStatus() override {
- LOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
- }
-
- double calculateMagnitude(double *phasePtr = NULL) {
- if (mFramesAccumulated == 0) {
- return 0.0;
- }
- double sinMean = mSinAccumulator / mFramesAccumulated;
- double cosMean = mCosAccumulator / mFramesAccumulated;
- double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
- if( phasePtr != NULL )
- {
- double phase = M_PI_2 - atan2( sinMean, cosMean );
- *phasePtr = phase;
- }
- return magnitude;
- }
-
- /**
- * @param frameData contains microphone data with sine signal feedback
- * @param channelCount
- */
- result_code processInputFrame(float *frameData, int channelCount) override {
- result_code result = RESULT_OK;
-
- float sample = frameData[0];
- float peak = mPeakFollower.process(sample);
-
- // Force a periodic glitch!
- if (mForceGlitchDuration > 0) {
- if (mForceGlitchCounter == 0) {
- LOGE("%s: force a glitch!!", __func__);
- mForceGlitchCounter = getSampleRate();
- } else if (mForceGlitchCounter <= mForceGlitchDuration) {
- sample += (sample > 0.0) ? -0.5f : 0.5f;
- }
- --mForceGlitchCounter;
- }
-
- mStateFrameCounters[mState]++; // count how many frames we are in each state
-
- switch (mState) {
- case STATE_IDLE:
- mDownCounter--;
- if (mDownCounter <= 0) {
- mState = STATE_IMMUNE;
- mDownCounter = IMMUNE_FRAME_COUNT;
- mInputPhase = 0.0; // prevent spike at start
- mOutputPhase = 0.0;
- }
- break;
-
- case STATE_IMMUNE:
- mDownCounter--;
- if (mDownCounter <= 0) {
- mState = STATE_WAITING_FOR_SIGNAL;
- }
- break;
-
- case STATE_WAITING_FOR_SIGNAL:
- if (peak > mThreshold) {
- mState = STATE_WAITING_FOR_LOCK;
- //LOGD("%5d: switch to STATE_WAITING_FOR_LOCK", mFrameCounter);
- resetAccumulator();
- }
- break;
-
- case STATE_WAITING_FOR_LOCK:
- mSinAccumulator += sample * sinf(mInputPhase);
- mCosAccumulator += sample * cosf(mInputPhase);
- mFramesAccumulated++;
- // Must be a multiple of the period or the calculation will not be accurate.
- if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
- double phaseOffset = 0.0;
- setMagnitude(calculateMagnitude(&phaseOffset));
-// LOGD("%s() mag = %f, offset = %f, prev = %f",
-// __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset);
- if (mMagnitude > mThreshold) {
- if (abs(phaseOffset) < kMaxPhaseError) {
- mState = STATE_LOCKED;
-// LOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
- }
- // Adjust mInputPhase to match measured phase
- mInputPhase += phaseOffset;
- }
- resetAccumulator();
- }
- incrementInputPhase();
- break;
-
- case STATE_LOCKED: {
- // Predict next sine value
- float predicted = sinf(mInputPhase) * mMagnitude;
- float diff = predicted - sample;
- float absDiff = fabs(diff);
- mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
- if (absDiff > mScaledTolerance) {
- result = ERROR_GLITCHES;
- onGlitchStart();
-// LOGI("diff glitch detected, absDiff = %g", absDiff);
- } else {
- mSumSquareSignal += predicted * predicted;
- mSumSquareNoise += diff * diff;
- // Track incoming signal and slowly adjust magnitude to account
- // for drift in the DRC or AGC.
- mSinAccumulator += sample * sinf(mInputPhase);
- mCosAccumulator += sample * cosf(mInputPhase);
- mFramesAccumulated++;
- // Must be a multiple of the period or the calculation will not be accurate.
- if (mFramesAccumulated == mSinePeriod) {
- const double coefficient = 0.1;
- double phaseOffset = 0.0;
- double magnitude = calculateMagnitude(&phaseOffset);
- // One pole averaging filter.
- setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
-
- mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod;
- mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod;
- resetAccumulator();
-
- if (abs(phaseOffset) > kMaxPhaseError) {
- result = ERROR_GLITCHES;
- onGlitchStart();
- LOGD("phase glitch detected, phaseOffset = %g", phaseOffset);
- } else if (mMagnitude < mThreshold) {
- result = ERROR_GLITCHES;
- onGlitchStart();
- LOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
- }
- }
- }
- incrementInputPhase();
- } break;
-
- case STATE_GLITCHING: {
- // Predict next sine value
- mGlitchLength++;
- float predicted = sinf(mInputPhase) * mMagnitude;
- float diff = predicted - sample;
- float absDiff = fabs(diff);
- mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
- if (absDiff < mScaledTolerance) { // close enough?
- // If we get a full sine period of non-glitch samples in a row then consider the glitch over.
- // We don't want to just consider a zero crossing the end of a glitch.
- if (mNonGlitchCount++ > mSinePeriod) {
- onGlitchEnd();
- }
- } else {
- mNonGlitchCount = 0;
- if (mGlitchLength > (4 * mSinePeriod)) {
- relock();
- }
- }
- incrementInputPhase();
- } break;
-
- case NUM_STATES: // not a real state
- break;
- }
-
- mFrameCounter++;
-
- return result;
- }
-
- // advance and wrap phase
- void incrementInputPhase() {
- mInputPhase += mPhaseIncrement;
- if (mInputPhase > M_PI) {
- mInputPhase -= (2.0 * M_PI);
- }
- }
-
- // advance and wrap phase
- void incrementOutputPhase() {
- mOutputPhase += mPhaseIncrement;
- if (mOutputPhase > M_PI) {
- mOutputPhase -= (2.0 * M_PI);
- }
- }
-
- /**
- * @param frameData upon return, contains the reference sine wave
- * @param channelCount
- */
- result_code processOutputFrame(float *frameData, int channelCount) override {
- float output = 0.0f;
- // Output sine wave so we can measure it.
- if (mState != STATE_IDLE) {
- float sinOut = sinf(mOutputPhase);
- incrementOutputPhase();
- output = (sinOut * mOutputAmplitude)
- + (mWhiteNoise.nextRandomDouble() * kNoiseAmplitude);
- // LOGD("%5d: sin(%f) = %f, %f", i, mPhase, sinOut, mPhaseIncrement);
- }
- frameData[0] = output;
- for (int i = 1; i < channelCount; i++) {
- frameData[i] = 0.0f;
- }
- return RESULT_OK;
- }
-
- void onGlitchStart() {
- mGlitchCount++;
-// LOGD("%5d: STARTED a glitch # %d", mFrameCounter, mGlitchCount);
- mState = STATE_GLITCHING;
- mGlitchLength = 1;
- mNonGlitchCount = 0;
- }
-
- void onGlitchEnd() {
-// LOGD("%5d: ENDED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
- mState = STATE_LOCKED;
- resetAccumulator();
- }
-
- // reset the sine wave detector
- void resetAccumulator() {
- mFramesAccumulated = 0;
- mSinAccumulator = 0.0;
- mCosAccumulator = 0.0;
- mSumSquareSignal = 0.0;
- mSumSquareNoise = 0.0;
- }
-
- void relock() {
-// LOGD("relock: %d because of a very long %d glitch", mFrameCounter, mGlitchLength);
- mState = STATE_WAITING_FOR_LOCK;
- resetAccumulator();
- }
-
- void reset() override {
- LoopbackProcessor::reset();
- mState = STATE_IDLE;
- mDownCounter = IDLE_FRAME_COUNT;
- resetAccumulator();
- }
-
- void onStartTest() override {
- LoopbackProcessor::onStartTest();
- mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
- mOutputPhase = 0.0f;
- mInverseSinePeriod = 1.0 / mSinePeriod;
- mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
- mGlitchCount = 0;
- mMaxGlitchDelta = 0.0;
- for (int i = 0; i < NUM_STATES; i++) {
- mStateFrameCounters[i] = 0;
- }
- }
-
-private:
-
- // These must match the values in GlitchActivity.java
- enum sine_state_t {
- STATE_IDLE, // beginning
- STATE_IMMUNE, // ignoring input, waiting fo HW to settle
- STATE_WAITING_FOR_SIGNAL, // looking for a loud signal
- STATE_WAITING_FOR_LOCK, // trying to lock onto the phase of the sine
- STATE_LOCKED, // locked on the sine wave, looking for glitches
- STATE_GLITCHING, // locked on the sine wave but glitching
- NUM_STATES
- };
-
- enum constants {
- // Arbitrary durations, assuming 48000 Hz
- IDLE_FRAME_COUNT = 48 * 100,
- IMMUNE_FRAME_COUNT = 48 * 100,
- PERIODS_NEEDED_FOR_LOCK = 8,
- MIN_SNRATIO_DB = 65
- };
-
- static constexpr float kNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
- static constexpr int kTargetGlitchFrequency = 607;
- static constexpr double kMaxPhaseError = M_PI * 0.05;
-
- float mTolerance = 0.10; // scaled from 0.0 to 1.0
- double mThreshold = 0.005;
- int mSinePeriod = 1; // this will be set before use
- double mInverseSinePeriod = 1.0;
-
- int32_t mStateFrameCounters[NUM_STATES];
-
- double mPhaseIncrement = 0.0;
- double mInputPhase = 0.0;
- double mOutputPhase = 0.0;
- double mMagnitude = 0.0;
- int32_t mFramesAccumulated = 0;
- double mSinAccumulator = 0.0;
- double mCosAccumulator = 0.0;
- float mMaxGlitchDelta = 0.0f;
- int32_t mGlitchCount = 0;
- int32_t mNonGlitchCount = 0;
- int32_t mGlitchLength = 0;
- float mScaledTolerance = 0.0;
- int mDownCounter = IDLE_FRAME_COUNT;
- int32_t mFrameCounter = 0;
- float mOutputAmplitude = 0.75;
-
- int32_t mForceGlitchDuration = 0; // if > 0 then force a glitch for debugging
- int32_t mForceGlitchCounter = 4 * 48000; // count down and trigger at zero
-
- // measure background noise continuously as a deviation from the expected signal
- double mSumSquareSignal = 0.0;
- double mSumSquareNoise = 0.0;
- double mMeanSquareSignal = 0.0;
- double mMeanSquareNoise = 0.0;
-
- PeakFollower mPeakFollower;
-
- PseudoRandom mWhiteNoise;
-
- sine_state_t mState = STATE_IDLE;
-};
-
-#undef LOOPBACK_RESULT_TAG
-
-#endif // OBOETESTER_LATENCY_ANALYSER_H
diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h
new file mode 100644
index 0000000..1cf218c
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2017 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 OBOETESTER_GLITCHANALYZER_H
+#define OBOETESTER_GLITCHANALYZER_H
+
+#include <cctype>
+
+#include "PseudoRandom.h"
+#include "LatencyAnalyzer.h"
+
+/**
+ * Output a steady sinewave and analyze the return signal.
+ *
+ * Use a cosine transform to measure the predicted magnitude and relative phase of the
+ * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
+ */
+class GlitchAnalyzer : public LoopbackProcessor {
+public:
+
+ int32_t getState() {
+ return mState;
+ }
+
+ float getPeakAmplitude() {
+ return mPeakFollower.getLevel();
+ }
+
+ float getTolerance() {
+ return mTolerance;
+ }
+
+ void setTolerance(float tolerance) {
+ mTolerance = tolerance;
+ mScaledTolerance = mMagnitude * mTolerance;
+ }
+
+ void setMagnitude(double magnitude) {
+ mMagnitude = magnitude;
+ mScaledTolerance = mMagnitude * mTolerance;
+ }
+
+ int32_t getGlitchCount() {
+ return mGlitchCount;
+ }
+
+ int32_t getStateFrameCount(int state) {
+ return mStateFrameCounters[state];
+ }
+
+ double getSignalToNoiseDB() {
+ static const double threshold = 1.0e-14;
+ if (mMeanSquareSignal < threshold || mMeanSquareNoise < threshold) {
+ return 0.0;
+ } else {
+ double signalToNoise = mMeanSquareSignal / mMeanSquareNoise; // power ratio
+ double signalToNoiseDB = 10.0 * log(signalToNoise);
+ if (signalToNoiseDB < MIN_SNRATIO_DB) {
+ LOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
+ MIN_SNRATIO_DB);
+ setResult(ERROR_VOLUME_TOO_LOW);
+ }
+ return signalToNoiseDB;
+ }
+ }
+
+ void analyze() override {
+ LOGD("GlitchAnalyzer ------------------");
+ LOGD(LOOPBACK_RESULT_TAG "peak.amplitude = %8f", getPeakAmplitude());
+ LOGD(LOOPBACK_RESULT_TAG "sine.magnitude = %8f", mMagnitude);
+ LOGD(LOOPBACK_RESULT_TAG "rms.noise = %8f", mMeanSquareNoise);
+ LOGD(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f", getSignalToNoiseDB());
+ LOGD(LOOPBACK_RESULT_TAG "frames.accumulated = %8d", mFramesAccumulated);
+ LOGD(LOOPBACK_RESULT_TAG "sine.period = %8d", mSinePeriod);
+ LOGD(LOOPBACK_RESULT_TAG "test.state = %8d", mState);
+ LOGD(LOOPBACK_RESULT_TAG "frame.count = %8d", mFrameCounter);
+ // Did we ever get a lock?
+ bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
+ if (!gotLock) {
+ LOGD("ERROR - failed to lock on reference sine tone");
+ setResult(ERROR_NO_LOCK);
+ } else {
+ // Only print if meaningful.
+ LOGD(LOOPBACK_RESULT_TAG "glitch.count = %8d", mGlitchCount);
+ LOGD(LOOPBACK_RESULT_TAG "max.glitch = %8f", mMaxGlitchDelta);
+ if (mGlitchCount > 0) {
+ LOGD("ERROR - number of glitches > 0");
+ setResult(ERROR_GLITCHES);
+ }
+ }
+ }
+
+ void printStatus() override {
+ LOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
+ }
+
+ double calculateMagnitude(double *phasePtr = NULL) {
+ if (mFramesAccumulated == 0) {
+ return 0.0;
+ }
+ double sinMean = mSinAccumulator / mFramesAccumulated;
+ double cosMean = mCosAccumulator / mFramesAccumulated;
+ double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
+ if( phasePtr != NULL )
+ {
+ double phase = M_PI_2 - atan2( sinMean, cosMean );
+ *phasePtr = phase;
+ }
+ return magnitude;
+ }
+
+ /**
+ * @param frameData contains microphone data with sine signal feedback
+ * @param channelCount
+ */
+ result_code processInputFrame(float *frameData, int channelCount) override {
+ result_code result = RESULT_OK;
+
+ float sample = frameData[0];
+ float peak = mPeakFollower.process(sample);
+
+ // Force a periodic glitch!
+ if (mForceGlitchDuration > 0) {
+ if (mForceGlitchCounter == 0) {
+ LOGE("%s: force a glitch!!", __func__);
+ mForceGlitchCounter = getSampleRate();
+ } else if (mForceGlitchCounter <= mForceGlitchDuration) {
+ sample += (sample > 0.0) ? -0.5f : 0.5f;
+ }
+ --mForceGlitchCounter;
+ }
+
+ mStateFrameCounters[mState]++; // count how many frames we are in each state
+
+ switch (mState) {
+ case STATE_IDLE:
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mState = STATE_IMMUNE;
+ mDownCounter = IMMUNE_FRAME_COUNT;
+ mInputPhase = 0.0; // prevent spike at start
+ mOutputPhase = 0.0;
+ }
+ break;
+
+ case STATE_IMMUNE:
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mState = STATE_WAITING_FOR_SIGNAL;
+ }
+ break;
+
+ case STATE_WAITING_FOR_SIGNAL:
+ if (peak > mThreshold) {
+ mState = STATE_WAITING_FOR_LOCK;
+ //LOGD("%5d: switch to STATE_WAITING_FOR_LOCK", mFrameCounter);
+ resetAccumulator();
+ }
+ break;
+
+ case STATE_WAITING_FOR_LOCK:
+ mSinAccumulator += sample * sinf(mInputPhase);
+ mCosAccumulator += sample * cosf(mInputPhase);
+ mFramesAccumulated++;
+ // Must be a multiple of the period or the calculation will not be accurate.
+ if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
+ double phaseOffset = 0.0;
+ setMagnitude(calculateMagnitude(&phaseOffset));
+// LOGD("%s() mag = %f, offset = %f, prev = %f",
+// __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset);
+ if (mMagnitude > mThreshold) {
+ if (abs(phaseOffset) < kMaxPhaseError) {
+ mState = STATE_LOCKED;
+// LOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
+ }
+ // Adjust mInputPhase to match measured phase
+ mInputPhase += phaseOffset;
+ }
+ resetAccumulator();
+ }
+ incrementInputPhase();
+ break;
+
+ case STATE_LOCKED: {
+ // Predict next sine value
+ float predicted = sinf(mInputPhase) * mMagnitude;
+ float diff = predicted - sample;
+ float absDiff = fabs(diff);
+ mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+ if (absDiff > mScaledTolerance) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+// LOGI("diff glitch detected, absDiff = %g", absDiff);
+ } else {
+ mSumSquareSignal += predicted * predicted;
+ mSumSquareNoise += diff * diff;
+ // Track incoming signal and slowly adjust magnitude to account
+ // for drift in the DRC or AGC.
+ mSinAccumulator += sample * sinf(mInputPhase);
+ mCosAccumulator += sample * cosf(mInputPhase);
+ mFramesAccumulated++;
+ // Must be a multiple of the period or the calculation will not be accurate.
+ if (mFramesAccumulated == mSinePeriod) {
+ const double coefficient = 0.1;
+ double phaseOffset = 0.0;
+ double magnitude = calculateMagnitude(&phaseOffset);
+ // One pole averaging filter.
+ setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
+
+ mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod;
+ mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod;
+ resetAccumulator();
+
+ if (abs(phaseOffset) > kMaxPhaseError) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+ LOGD("phase glitch detected, phaseOffset = %g", phaseOffset);
+ } else if (mMagnitude < mThreshold) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+ LOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
+ }
+ }
+ }
+ incrementInputPhase();
+ } break;
+
+ case STATE_GLITCHING: {
+ // Predict next sine value
+ mGlitchLength++;
+ float predicted = sinf(mInputPhase) * mMagnitude;
+ float diff = predicted - sample;
+ float absDiff = fabs(diff);
+ mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+ if (absDiff < mScaledTolerance) { // close enough?
+ // If we get a full sine period of non-glitch samples in a row then consider the glitch over.
+ // We don't want to just consider a zero crossing the end of a glitch.
+ if (mNonGlitchCount++ > mSinePeriod) {
+ onGlitchEnd();
+ }
+ } else {
+ mNonGlitchCount = 0;
+ if (mGlitchLength > (4 * mSinePeriod)) {
+ relock();
+ }
+ }
+ incrementInputPhase();
+ } break;
+
+ case NUM_STATES: // not a real state
+ break;
+ }
+
+ mFrameCounter++;
+
+ return result;
+ }
+
+ // advance and wrap phase
+ void incrementInputPhase() {
+ mInputPhase += mPhaseIncrement;
+ if (mInputPhase > M_PI) {
+ mInputPhase -= (2.0 * M_PI);
+ }
+ }
+
+ // advance and wrap phase
+ void incrementOutputPhase() {
+ mOutputPhase += mPhaseIncrement;
+ if (mOutputPhase > M_PI) {
+ mOutputPhase -= (2.0 * M_PI);
+ }
+ }
+
+ /**
+ * @param frameData upon return, contains the reference sine wave
+ * @param channelCount
+ */
+ result_code processOutputFrame(float *frameData, int channelCount) override {
+ float output = 0.0f;
+ // Output sine wave so we can measure it.
+ if (mState != STATE_IDLE) {
+ float sinOut = sinf(mOutputPhase);
+ incrementOutputPhase();
+ output = (sinOut * mOutputAmplitude)
+ + (mWhiteNoise.nextRandomDouble() * kNoiseAmplitude);
+ // LOGD("%5d: sin(%f) = %f, %f", i, mPhase, sinOut, mPhaseIncrement);
+ }
+ frameData[0] = output;
+ for (int i = 1; i < channelCount; i++) {
+ frameData[i] = 0.0f;
+ }
+ return RESULT_OK;
+ }
+
+ void onGlitchStart() {
+ mGlitchCount++;
+// LOGD("%5d: STARTED a glitch # %d", mFrameCounter, mGlitchCount);
+ mState = STATE_GLITCHING;
+ mGlitchLength = 1;
+ mNonGlitchCount = 0;
+ }
+
+ void onGlitchEnd() {
+// LOGD("%5d: ENDED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
+ mState = STATE_LOCKED;
+ resetAccumulator();
+ }
+
+ // reset the sine wave detector
+ void resetAccumulator() {
+ mFramesAccumulated = 0;
+ mSinAccumulator = 0.0;
+ mCosAccumulator = 0.0;
+ mSumSquareSignal = 0.0;
+ mSumSquareNoise = 0.0;
+ }
+
+ void relock() {
+// LOGD("relock: %d because of a very long %d glitch", mFrameCounter, mGlitchLength);
+ mState = STATE_WAITING_FOR_LOCK;
+ resetAccumulator();
+ }
+
+ void reset() override {
+ LoopbackProcessor::reset();
+ mState = STATE_IDLE;
+ mDownCounter = IDLE_FRAME_COUNT;
+ resetAccumulator();
+ }
+
+ void onStartTest() override {
+ LoopbackProcessor::onStartTest();
+ mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
+ mOutputPhase = 0.0f;
+ mInverseSinePeriod = 1.0 / mSinePeriod;
+ mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
+ mGlitchCount = 0;
+ mMaxGlitchDelta = 0.0;
+ for (int i = 0; i < NUM_STATES; i++) {
+ mStateFrameCounters[i] = 0;
+ }
+ }
+
+private:
+
+ // These must match the values in GlitchActivity.java
+ enum sine_state_t {
+ STATE_IDLE, // beginning
+ STATE_IMMUNE, // ignoring input, waiting fo HW to settle
+ STATE_WAITING_FOR_SIGNAL, // looking for a loud signal
+ STATE_WAITING_FOR_LOCK, // trying to lock onto the phase of the sine
+ STATE_LOCKED, // locked on the sine wave, looking for glitches
+ STATE_GLITCHING, // locked on the sine wave but glitching
+ NUM_STATES
+ };
+
+ enum constants {
+ // Arbitrary durations, assuming 48000 Hz
+ IDLE_FRAME_COUNT = 48 * 100,
+ IMMUNE_FRAME_COUNT = 48 * 100,
+ PERIODS_NEEDED_FOR_LOCK = 8,
+ MIN_SNRATIO_DB = 65
+ };
+
+ static constexpr float kNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
+ static constexpr int kTargetGlitchFrequency = 607;
+ static constexpr double kMaxPhaseError = M_PI * 0.05;
+
+ float mTolerance = 0.10; // scaled from 0.0 to 1.0
+ double mThreshold = 0.005;
+ int mSinePeriod = 1; // this will be set before use
+ double mInverseSinePeriod = 1.0;
+
+ int32_t mStateFrameCounters[NUM_STATES];
+
+ double mPhaseIncrement = 0.0;
+ double mInputPhase = 0.0;
+ double mOutputPhase = 0.0;
+ double mMagnitude = 0.0;
+ int32_t mFramesAccumulated = 0;
+ double mSinAccumulator = 0.0;
+ double mCosAccumulator = 0.0;
+ float mMaxGlitchDelta = 0.0f;
+ int32_t mGlitchCount = 0;
+ int32_t mNonGlitchCount = 0;
+ int32_t mGlitchLength = 0;
+ float mScaledTolerance = 0.0;
+ int mDownCounter = IDLE_FRAME_COUNT;
+ int32_t mFrameCounter = 0;
+ float mOutputAmplitude = 0.75;
+
+ int32_t mForceGlitchDuration = 0; // if > 0 then force a glitch for debugging
+ int32_t mForceGlitchCounter = 4 * 48000; // count down and trigger at zero
+
+ // measure background noise continuously as a deviation from the expected signal
+ double mSumSquareSignal = 0.0;
+ double mSumSquareNoise = 0.0;
+ double mMeanSquareSignal = 0.0;
+ double mMeanSquareNoise = 0.0;
+
+ PeakDetector mPeakFollower;
+
+ PseudoRandom mWhiteNoise;
+
+ sine_state_t mState = STATE_IDLE;
+};
+
+
+#endif //OBOETESTER_GLITCHANALYZER_H
diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h
new file mode 100644
index 0000000..d0d1aa8
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/LatencyAnalyzer.h
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+/**
+ * Tools for measuring latency and for detecting glitches.
+ * These classes are pure math and can be used with any audio system.
+ */
+
+#ifndef ANALYZER_LATENCY_ANALYZER_H
+#define ANALYZER_LATENCY_ANALYZER_H
+
+#include <algorithm>
+#include <assert.h>
+#include <cctype>
+#include <math.h>
+#include <memory>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vector>
+
+#include "RandomPulseGenerator.h"
+#include "PeakDetector.h"
+#include "PseudoRandom.h"
+
+#define LOOPBACK_RESULT_TAG "RESULT: "
+
+static constexpr int32_t kDefaultSampleRate = 48000;
+static constexpr int32_t kMillisPerSecond = 1000;
+static constexpr int32_t kMaxLatencyMillis = 700; // arbitrary and generous
+static constexpr double kMinimumConfidence = 0.2;
+
+typedef struct LatencyReport_s {
+ int32_t latencyInFrames = 0.0;
+ double confidence = 0.0;
+
+ void reset() {
+ latencyInFrames = 0;
+ confidence = 0.0;
+ }
+} LatencyReport;
+
+// Calculate a normalized cross correlation.
+static double calculateNormalizedCorrelation(const float *a,
+ const float *b,
+ int windowSize)
+{
+ double correlation = 0.0;
+ double sumProducts = 0.0;
+ double sumSquares = 0.0;
+
+ // Correlate a against b.
+ for (int i = 0; i < windowSize; i++) {
+ float s1 = a[i];
+ float s2 = b[i];
+ // Use a normalized cross-correlation.
+ sumProducts += s1 * s2;
+ sumSquares += ((s1 * s1) + (s2 * s2));
+ }
+
+ if (sumSquares >= 1.0e-9) {
+ correlation = (float) (2.0 * sumProducts / sumSquares);
+ }
+ return correlation;
+}
+
+static double calculateRootMeanSquare(float *data, int32_t numSamples) {
+ double sum = 0.0;
+ for (int32_t i = 0; i < numSamples; i++) {
+ float sample = data[i];
+ sum += sample * sample;
+ }
+ return sqrt(sum / numSamples);
+}
+
+/**
+ * Monophonic recording with processing.
+ */
+class AudioRecording
+{
+public:
+ AudioRecording() {
+ }
+ ~AudioRecording() {
+ delete[] mData;
+ }
+
+ void allocate(int maxFrames) {
+ delete[] mData;
+ mData = new float[maxFrames];
+ mMaxFrames = maxFrames;
+ }
+
+ // Write SHORT data from the first channel.
+ int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
+ // stop at end of buffer
+ if ((mFrameCounter + numFrames) > mMaxFrames) {
+ numFrames = mMaxFrames - mFrameCounter;
+ }
+ for (int i = 0; i < numFrames; i++) {
+ mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
+ }
+ return numFrames;
+ }
+
+ // Write FLOAT data from the first channel.
+ int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
+ // stop at end of buffer
+ if ((mFrameCounter + numFrames) > mMaxFrames) {
+ numFrames = mMaxFrames - mFrameCounter;
+ }
+ for (int i = 0; i < numFrames; i++) {
+ mData[mFrameCounter++] = inputData[i * inputChannelCount];
+ }
+ return numFrames;
+ }
+
+ // Write FLOAT data from the first channel.
+ int32_t write(float sample) {
+ // stop at end of buffer
+ if (mFrameCounter < mMaxFrames) {
+ mData[mFrameCounter++] = sample;
+ }
+ return 1;
+ }
+
+ void clear() {
+ mFrameCounter = 0;
+ }
+ int32_t size() {
+ return mFrameCounter;
+ }
+
+ bool isFull() {
+ return mFrameCounter >= mMaxFrames;
+ }
+
+ float *getData() {
+ return mData;
+ }
+
+ void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
+ }
+
+ int32_t getSampleRate() {
+ return mSampleRate;
+ }
+
+ /**
+ * Square the samples so they are all positive and so the peaks are emphasized.
+ */
+ void square() {
+ for (int i = 0; i < mFrameCounter; i++) {
+ const float sample = mData[i];
+ mData[i] = sample * sample;
+ }
+ }
+
+ /**
+ * Amplify a signal so that the peak matches the specified target.
+ *
+ * @param target final max value
+ * @return gain applied to signal
+ */
+ float normalize(float target) {
+ float maxValue = 1.0e-9f;
+ for (int i = 0; i < mFrameCounter; i++) {
+ maxValue = std::max(maxValue, abs(mData[i]));
+ }
+ float gain = target / maxValue;
+ for (int i = 0; i < mFrameCounter; i++) {
+ mData[i] *= gain;
+ }
+ return gain;
+ }
+
+private:
+ float *mData = nullptr;
+ int32_t mFrameCounter = 0;
+ int32_t mMaxFrames = 0;
+ int32_t mSampleRate = kDefaultSampleRate; // common default
+};
+
+static int measureLatencyFromPulse(AudioRecording &recorded,
+ AudioRecording &pulse,
+ int32_t framesPerEncodedBit,
+ LatencyReport *report) {
+
+ report->latencyInFrames = 0;
+ report->confidence = 0.0;
+
+ int numCorrelations = recorded.size() - pulse.size();
+ if (numCorrelations < 10) {
+ LOGE("%s() recording too small = %d frames", __func__, recorded.size());
+ return -1;
+ }
+ std::unique_ptr<float[]> correlations= std::make_unique<float[]>(numCorrelations);
+
+ // Correlate pulse against the recorded data.
+ for (int i = 0; i < numCorrelations; i++) {
+ float correlation = (float) calculateNormalizedCorrelation(&recorded.getData()[i],
+ &pulse.getData()[0],
+ pulse.size());
+ correlations[i] = correlation;
+ }
+
+ // Find highest peak in correlation array.
+ float peakCorrelation = 0.0;
+ int peakIndex = -1;
+ for (int i = 0; i < numCorrelations; i++) {
+ float value = abs(correlations[i]);
+ if (value > peakCorrelation) {
+ peakCorrelation = value;
+ peakIndex = i;
+ }
+ }
+ if (peakIndex < 0) {
+ LOGE("%s() no signal for correlation", __func__);
+ return -2;
+ }
+
+ report->latencyInFrames = peakIndex;
+ report->confidence = peakCorrelation;
+
+ return 0;
+}
+
+// ====================================================================================
+class LoopbackProcessor {
+public:
+ virtual ~LoopbackProcessor() = default;
+
+ // Note that these values must match the switch in RoundTripLatencyActivity.h
+ enum result_code {
+ RESULT_OK = 0,
+ ERROR_NOISY = -99,
+ ERROR_VOLUME_TOO_LOW,
+ ERROR_VOLUME_TOO_HIGH,
+ ERROR_CONFIDENCE,
+ ERROR_INVALID_STATE,
+ ERROR_GLITCHES,
+ ERROR_NO_LOCK
+ };
+
+ virtual void onStartTest() {
+ reset();
+ }
+
+ virtual void reset() {
+ mResult = 0;
+ mResetCount++;
+ }
+
+ virtual result_code processInputFrame(float *frameData, int channelCount) = 0;
+ virtual result_code processOutputFrame(float *frameData, int channelCount) = 0;
+
+ void process(float *inputData, int inputChannelCount, int numInputFrames,
+ float *outputData, int outputChannelCount, int numOutputFrames) {
+ int numBoth = std::min(numInputFrames, numOutputFrames);
+ // Process one frame at a time.
+ for (int i = 0; i < numBoth; i++) {
+ processInputFrame(inputData, inputChannelCount);
+ inputData += inputChannelCount;
+ processOutputFrame(outputData, outputChannelCount);
+ outputData += outputChannelCount;
+ }
+ // If there is more input than output.
+ for (int i = numBoth; i < numInputFrames; i++) {
+ processInputFrame(inputData, inputChannelCount);
+ inputData += inputChannelCount;
+ }
+ // If there is more output than input.
+ for (int i = numBoth; i < numOutputFrames; i++) {
+ processOutputFrame(outputData, outputChannelCount);
+ outputData += outputChannelCount;
+ }
+ }
+
+ virtual void analyze() = 0;
+
+ virtual void printStatus() {};
+
+ int32_t getResult() {
+ return mResult;
+ }
+
+ void setResult(int32_t result) {
+ mResult = result;
+ }
+
+ virtual bool isDone() {
+ return false;
+ }
+
+ virtual int save(const char *fileName) {
+ (void) fileName;
+ return -1;
+ }
+
+ virtual int load(const char *fileName) {
+ (void) fileName;
+ return -1;
+ }
+
+ virtual void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
+ }
+
+ int32_t getSampleRate() {
+ return mSampleRate;
+ }
+
+ int32_t getResetCount() {
+ return mResetCount;
+ }
+
+ /** Called when not enough input frames could be read after synchronization.
+ */
+ virtual void onInsufficientRead() {
+ reset();
+ }
+
+protected:
+ int32_t mResetCount = 0;
+
+private:
+ int32_t mSampleRate = kDefaultSampleRate;
+ int32_t mResult = 0;
+};
+
+class LatencyAnalyzer : public LoopbackProcessor {
+public:
+
+ LatencyAnalyzer() : LoopbackProcessor() {}
+ virtual ~LatencyAnalyzer() = default;
+
+ virtual int32_t getProgress() = 0;
+
+ virtual int getState() = 0;
+
+ // @return latency in frames
+ virtual int32_t getMeasuredLatency() = 0;
+
+ virtual double getMeasuredConfidence() = 0;
+
+ virtual double getBackgroundRMS() = 0;
+
+ virtual double getSignalRMS() = 0;
+
+};
+
+// ====================================================================================
+/**
+ * Measure latency given a loopback stream data.
+ * Use an encoded bit train as the sound source because it
+ * has an unambiguous correlation value.
+ * Uses a state machine to cycle through various stages.
+ *
+ */
+class PulseLatencyAnalyzer : public LatencyAnalyzer {
+public:
+
+ PulseLatencyAnalyzer() : LatencyAnalyzer() {
+ int32_t maxLatencyFrames = getSampleRate() * kMaxLatencyMillis / kMillisPerSecond;
+ int32_t numPulseBits = getSampleRate() * kPulseLengthMillis
+ / (kFramesPerEncodedBit * kMillisPerSecond);
+ int32_t pulseLength = numPulseBits * kFramesPerEncodedBit;
+ mFramesToRecord = pulseLength + maxLatencyFrames;
+ LOGD("PulseLatencyAnalyzer: allocate recording with %d frames", mFramesToRecord);
+ mAudioRecording.allocate(mFramesToRecord);
+ mAudioRecording.setSampleRate(getSampleRate());
+ generateRandomPulse(pulseLength);
+ }
+
+ void generateRandomPulse(int32_t pulseLength) {
+ mPulse.allocate(pulseLength);
+ RandomPulseGenerator pulser(kFramesPerEncodedBit);
+ for (int i = 0; i < pulseLength; i++) {
+ mPulse.write(pulser.nextFloat());
+ }
+ }
+
+ int getState() override {
+ return mState;
+ }
+
+ void setSampleRate(int32_t sampleRate) override {
+ LoopbackProcessor::setSampleRate(sampleRate);
+ mAudioRecording.setSampleRate(sampleRate);
+ }
+
+ void reset() override {
+ LoopbackProcessor::reset();
+ mDownCounter = getSampleRate() / 2;
+ mLoopCounter = 0;
+
+ mPulseCursor = 0;
+ mBackgroundSumSquare = 0.0f;
+ mBackgroundSumCount = 0;
+ mBackgroundRMS = 0.0f;
+ mSignalRMS = 0.0f;
+
+ LOGD("state reset to STATE_MEASURE_BACKGROUND");
+ mState = STATE_MEASURE_BACKGROUND;
+ mAudioRecording.clear();
+ mLatencyReport.reset();
+ }
+
+ bool hasEnoughData() {
+ return mAudioRecording.isFull();
+ }
+
+ bool isDone() override {
+ return mState == STATE_DONE;
+ }
+
+ int32_t getProgress() override {
+ return mAudioRecording.size();
+ }
+
+ void analyze() override {
+ LOGD("PulseLatencyAnalyzer ---------------");
+ LOGD(LOOPBACK_RESULT_TAG "test.state = %8d", mState);
+ LOGD(LOOPBACK_RESULT_TAG "test.state.name = %8s", convertStateToText(mState));
+ LOGD(LOOPBACK_RESULT_TAG "background.rms = %8f", mBackgroundRMS);
+
+ int32_t newResult = RESULT_OK;
+ if (mState != STATE_GOT_DATA) {
+ LOGD("WARNING - Bad state. Check volume on device.");
+ // setResult(ERROR_INVALID_STATE);
+ } else {
+ LOGD("Please wait several seconds for cross-correlation to complete.");
+ float gain = mAudioRecording.normalize(1.0f);
+ measureLatencyFromPulse(mAudioRecording,
+ mPulse,
+ kFramesPerEncodedBit,
+ &mLatencyReport);
+
+ if (mLatencyReport.confidence < kMinimumConfidence) {
+ LOGD(" ERROR - confidence too low!");
+ newResult = ERROR_CONFIDENCE;
+ } else {
+ mSignalRMS = calculateRootMeanSquare(
+ &mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
+ / gain;
+ }
+#if OBOE_ENABLE_LOGGING
+ double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
+ / getSampleRate();
+#endif
+ LOGD(LOOPBACK_RESULT_TAG "latency.frames = %8d",
+ mLatencyReport.latencyInFrames);
+ LOGD(LOOPBACK_RESULT_TAG "latency.msec = %8.2f",
+ latencyMillis);
+ LOGD(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f",
+ mLatencyReport.confidence);
+ }
+ mState = STATE_DONE;
+ if (getResult() == RESULT_OK) {
+ setResult(newResult);
+ }
+ }
+
+ int32_t getMeasuredLatency() override {
+ return mLatencyReport.latencyInFrames;
+ }
+
+ double getMeasuredConfidence() override {
+ return mLatencyReport.confidence;
+ }
+
+ double getBackgroundRMS() override {
+ return mBackgroundRMS;
+ }
+
+ double getSignalRMS() override {
+ return mSignalRMS;
+ }
+
+ void printStatus() override {
+ LOGD("st = %d", mState);
+ }
+
+ result_code processInputFrame(float *frameData, int channelCount) override {
+ echo_state nextState = mState;
+ mLoopCounter++;
+
+ switch (mState) {
+ case STATE_MEASURE_BACKGROUND:
+ // Measure background RMS on channel 0
+ mBackgroundSumSquare += frameData[0] * frameData[0];
+ mBackgroundSumCount++;
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mBackgroundRMS = sqrtf(mBackgroundSumSquare / mBackgroundSumCount);
+ nextState = STATE_IN_PULSE;
+ mPulseCursor = 0;
+ LOGD("LatencyAnalyzer state => STATE_SENDING_PULSE");
+ }
+ break;
+
+ case STATE_IN_PULSE:
+ // Record input until the mAudioRecording is full.
+ mAudioRecording.write(frameData, channelCount, 1);
+ if (hasEnoughData()) {
+ LOGD("LatencyAnalyzer state => STATE_GOT_DATA");
+ nextState = STATE_GOT_DATA;
+ }
+ break;
+
+ case STATE_GOT_DATA:
+ case STATE_DONE:
+ default:
+ break;
+ }
+
+ mState = nextState;
+ return RESULT_OK;
+ }
+
+ result_code processOutputFrame(float *frameData, int channelCount) override {
+ switch (mState) {
+ case STATE_IN_PULSE:
+ if (mPulseCursor < mPulse.size()) {
+ float pulseSample = mPulse.getData()[mPulseCursor++];
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = pulseSample;
+ }
+ } else {
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = 0;
+ }
+ }
+ break;
+
+ case STATE_MEASURE_BACKGROUND:
+ case STATE_GOT_DATA:
+ case STATE_DONE:
+ default:
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = 0.0f; // silence
+ }
+ break;
+ }
+
+ return RESULT_OK;
+ }
+
+private:
+
+ enum echo_state {
+ STATE_MEASURE_BACKGROUND,
+ STATE_IN_PULSE,
+ STATE_GOT_DATA, // must match RoundTripLatencyActivity.java
+ STATE_DONE,
+ };
+
+ const char *convertStateToText(echo_state state) {
+ const char *result = "Unknown";
+ switch(state) {
+ case STATE_MEASURE_BACKGROUND:
+ result = "INIT";
+ break;
+ case STATE_IN_PULSE:
+ result = "PULSE";
+ break;
+ case STATE_GOT_DATA:
+ result = "GOT_DATA";
+ break;
+ case STATE_DONE:
+ result = "DONE";
+ break;
+ }
+ return result;
+ }
+
+ int32_t mDownCounter = 500;
+ int32_t mLoopCounter = 0;
+ echo_state mState = STATE_MEASURE_BACKGROUND;
+
+ static constexpr int32_t kFramesPerEncodedBit = 8; // multiple of 2
+ static constexpr int32_t kPulseLengthMillis = 500;
+
+ AudioRecording mPulse;
+ int32_t mPulseCursor = 0;
+
+ float mBackgroundSumSquare = 0.0f;
+ int32_t mBackgroundSumCount = 0;
+ float mBackgroundRMS = 0.0f;
+ float mSignalRMS = 0.0f;
+ int32_t mFramesToRecord = 0;
+
+ AudioRecording mAudioRecording; // contains only the input after starting the pulse
+ LatencyReport mLatencyReport;
+};
+
+#endif // ANALYZER_LATENCY_ANALYZER_H
diff --git a/apps/OboeTester/app/src/main/cpp/util/ManchesterEncoder.h b/apps/OboeTester/app/src/main/cpp/analyzer/ManchesterEncoder.h
similarity index 95%
rename from apps/OboeTester/app/src/main/cpp/util/ManchesterEncoder.h
rename to apps/OboeTester/app/src/main/cpp/analyzer/ManchesterEncoder.h
index 1ffd1b1..b3d12b3 100644
--- a/apps/OboeTester/app/src/main/cpp/util/ManchesterEncoder.h
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/ManchesterEncoder.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef UTIL_MANCHESTER_ENCODER_H
-#define UTIL_MANCHESTER_ENCODER_H
+#ifndef ANALYZER_MANCHESTER_ENCODER_H
+#define ANALYZER_MANCHESTER_ENCODER_H
#include <cstdint>
@@ -93,4 +93,4 @@
uint8_t mCurrentByte = 0;
bool mCurrentBit = false;
};
-#endif //UTIL_MANCHESTER_ENCODER_H
+#endif //ANALYZER_MANCHESTER_ENCODER_H
diff --git a/apps/OboeTester/app/src/main/cpp/PeakDetector.h b/apps/OboeTester/app/src/main/cpp/analyzer/PeakDetector.h
similarity index 86%
rename from apps/OboeTester/app/src/main/cpp/PeakDetector.h
rename to apps/OboeTester/app/src/main/cpp/analyzer/PeakDetector.h
index 5e4b92c..9139e42 100644
--- a/apps/OboeTester/app/src/main/cpp/PeakDetector.h
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/PeakDetector.h
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-#ifndef OBOE_TESTER_PEAK_DETECTOR_H
-#define OBOE_TESTER_PEAK_DETECTOR_H
+#ifndef ANALYZER_PEAK_DETECTOR_H
+#define ANALYZER_PEAK_DETECTOR_H
#include <math.h>
-constexpr double kDefaultDecay = 0.9999; //
-
class PeakDetector {
public:
@@ -42,7 +40,9 @@
}
private:
+ static constexpr float kDefaultDecay = 0.99f;
+
double mLevel = 0.0;
double mDecay = kDefaultDecay;
};
-#endif //OBOE_TESTER_PEAK_DETECTOR_H
+#endif //ANALYZER_PEAK_DETECTOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/PseudoRandom.h b/apps/OboeTester/app/src/main/cpp/analyzer/PseudoRandom.h
new file mode 100644
index 0000000..4aedbe0
--- /dev/null
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/PseudoRandom.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 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 ANALYZER_PSEUDORANDOM_H
+#define ANALYZER_PSEUDORANDOM_H
+
+#include <cctype>
+
+class PseudoRandom {
+public:
+ PseudoRandom() {}
+ PseudoRandom(int64_t seed)
+ : mSeed(seed)
+ {}
+
+ /**
+ * Returns the next random double from -1.0 to 1.0
+ *
+ * @return value from -1.0 to 1.0
+ */
+ double nextRandomDouble() {
+ return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
+ }
+
+ /** Calculate random 32 bit number using linear-congruential method.
+ */
+ int32_t nextRandomInteger() {
+#if __has_builtin(__builtin_mul_overflow) && __has_builtin(__builtin_add_overflow)
+ int64_t prod;
+ // Use values for 64-bit sequence from MMIX by Donald Knuth.
+ __builtin_mul_overflow(mSeed, (int64_t)6364136223846793005, &prod);
+ __builtin_add_overflow(prod, (int64_t)1442695040888963407, &mSeed);
+#else
+ mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
+#endif
+ return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
+ }
+
+private:
+ int64_t mSeed = 99887766;
+};
+
+#endif //ANALYZER_PSEUDORANDOM_H
diff --git a/apps/OboeTester/app/src/main/cpp/RandomPulseGenerator.h b/apps/OboeTester/app/src/main/cpp/analyzer/RandomPulseGenerator.h
similarity index 85%
rename from apps/OboeTester/app/src/main/cpp/RandomPulseGenerator.h
rename to apps/OboeTester/app/src/main/cpp/analyzer/RandomPulseGenerator.h
index d861289..f0623cc 100644
--- a/apps/OboeTester/app/src/main/cpp/RandomPulseGenerator.h
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/RandomPulseGenerator.h
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-#ifndef OBOETESTER_RANDOM_PULSE_GENERATOR_H
-#define OBOETESTER_RANDOM_PULSE_GENERATOR_H
+#ifndef ANALYZER_RANDOM_PULSE_GENERATOR_H
+#define ANALYZER_RANDOM_PULSE_GENERATOR_H
#include <stdlib.h>
-#include "util/RoundedManchesterEncoder.h"
+#include "RoundedManchesterEncoder.h"
/**
* Encode random ones and zeros using Manchester Code per IEEE 802.3.
@@ -38,4 +38,4 @@
}
};
-#endif //OBOETESTER_RANDOM_PULSE_GENERATOR_H
+#endif //ANALYZER_RANDOM_PULSE_GENERATOR_H
diff --git a/apps/OboeTester/app/src/main/cpp/util/RoundedManchesterEncoder.h b/apps/OboeTester/app/src/main/cpp/analyzer/RoundedManchesterEncoder.h
similarity index 94%
rename from apps/OboeTester/app/src/main/cpp/util/RoundedManchesterEncoder.h
rename to apps/OboeTester/app/src/main/cpp/analyzer/RoundedManchesterEncoder.h
index 92967c5..b1ba949 100644
--- a/apps/OboeTester/app/src/main/cpp/util/RoundedManchesterEncoder.h
+++ b/apps/OboeTester/app/src/main/cpp/analyzer/RoundedManchesterEncoder.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef UTIL_ROUNDED_MANCHESTER_ENCODER_H
-#define UTIL_ROUNDED_MANCHESTER_ENCODER_H
+#ifndef ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
+#define ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
#include <math.h>
#include <memory.h>
@@ -86,4 +86,4 @@
std::unique_ptr<float[]> mZeroAfterOne;
};
-#endif //UTIL_ROUNDED_MANCHESTER_ENCODER_H
+#endif //ANALYZER_ROUNDED_MANCHESTER_ENCODER_H