Integrate with aaudio_loopback. Eventually this could be used in the CTS Verifier for latency testing. Pass std:string back from analyze(). General cleanup. Test: adb shell aaudio_loopback Test: check the latency.msec value Change-Id: Ibf7128d78d47d91e0bf314344ca450d7f70b3cebgugelfrei
parent
9c7e2a51e2
commit
62aa0219c2
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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_GLITCH_ANALYZER_H
|
||||
#define ANALYZER_GLITCH_ANALYZER_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "LatencyAnalyzer.h"
|
||||
#include "PseudoRandom.h"
|
||||
|
||||
/**
|
||||
* Output a steady sine wave 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() const {
|
||||
return mState;
|
||||
}
|
||||
|
||||
double getPeakAmplitude() const {
|
||||
return mPeakFollower.getLevel();
|
||||
}
|
||||
|
||||
double getTolerance() {
|
||||
return mTolerance;
|
||||
}
|
||||
|
||||
void setTolerance(double tolerance) {
|
||||
mTolerance = tolerance;
|
||||
mScaledTolerance = mMagnitude * mTolerance;
|
||||
}
|
||||
|
||||
void setMagnitude(double magnitude) {
|
||||
mMagnitude = magnitude;
|
||||
mScaledTolerance = mMagnitude * mTolerance;
|
||||
}
|
||||
|
||||
int32_t getGlitchCount() const {
|
||||
return mGlitchCount;
|
||||
}
|
||||
|
||||
int32_t getStateFrameCount(int state) const {
|
||||
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_SNR_DB) {
|
||||
ALOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
|
||||
MIN_SNR_DB);
|
||||
setResult(ERROR_VOLUME_TOO_LOW);
|
||||
}
|
||||
return signalToNoiseDB;
|
||||
}
|
||||
}
|
||||
|
||||
std::string analyze() override {
|
||||
std::stringstream report;
|
||||
report << "GlitchAnalyzer ------------------\n";
|
||||
report << LOOPBACK_RESULT_TAG "peak.amplitude = " << std::setw(8)
|
||||
<< getPeakAmplitude() << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "sine.magnitude = " << std::setw(8)
|
||||
<< mMagnitude << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "rms.noise = " << std::setw(8)
|
||||
<< mMeanSquareNoise << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "signal.to.noise.db = " << std::setw(8)
|
||||
<< getSignalToNoiseDB() << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "frames.accumulated = " << std::setw(8)
|
||||
<< mFramesAccumulated << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "sine.period = " << std::setw(8)
|
||||
<< mSinePeriod << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "test.state = " << std::setw(8)
|
||||
<< mState << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "frame.count = " << std::setw(8)
|
||||
<< mFrameCounter << "\n";
|
||||
// Did we ever get a lock?
|
||||
bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
|
||||
if (!gotLock) {
|
||||
report << "ERROR - failed to lock on reference sine tone.\n";
|
||||
setResult(ERROR_NO_LOCK);
|
||||
} else {
|
||||
// Only print if meaningful.
|
||||
report << LOOPBACK_RESULT_TAG "glitch.count = " << std::setw(8)
|
||||
<< mGlitchCount << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "max.glitch = " << std::setw(8)
|
||||
<< mMaxGlitchDelta << "\n";
|
||||
if (mGlitchCount > 0) {
|
||||
report << "ERROR - number of glitches > 0\n";
|
||||
setResult(ERROR_GLITCHES);
|
||||
}
|
||||
}
|
||||
return report.str();
|
||||
}
|
||||
|
||||
void printStatus() override {
|
||||
ALOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
|
||||
}
|
||||
/**
|
||||
* Calculate the magnitude of the component of the input signal
|
||||
* that matches the analysis frequency.
|
||||
* Also calculate the phase that we can use to create a
|
||||
* signal that matches that component.
|
||||
* The phase will be between -PI and +PI.
|
||||
*/
|
||||
double calculateMagnitude(double *phasePtr = nullptr) {
|
||||
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 != nullptr) {
|
||||
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 to test the detector!
|
||||
if (mForceGlitchDuration > 0) {
|
||||
if (mForceGlitchCounter == 0) {
|
||||
ALOGE("%s: force a glitch!!", __func__);
|
||||
mForceGlitchCounter = getSampleRate();
|
||||
} else if (mForceGlitchCounter <= mForceGlitchDuration) {
|
||||
// Force an abrupt offset.
|
||||
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;
|
||||
//ALOGD("%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));
|
||||
// ALOGD("%s() mag = %f, offset = %f, prev = %f",
|
||||
// __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset);
|
||||
if (mMagnitude > mThreshold) {
|
||||
if (abs(phaseOffset) < kMaxPhaseError) {
|
||||
mState = STATE_LOCKED;
|
||||
// ALOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
|
||||
}
|
||||
// Adjust mInputPhase to match measured phase
|
||||
mInputPhase += phaseOffset;
|
||||
}
|
||||
resetAccumulator();
|
||||
}
|
||||
incrementInputPhase();
|
||||
break;
|
||||
|
||||
case STATE_LOCKED: {
|
||||
// Predict next sine value
|
||||
double predicted = sinf(mInputPhase) * mMagnitude;
|
||||
double diff = predicted - sample;
|
||||
double 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();
|
||||
ALOGD("phase glitch detected, phaseOffset = %g", phaseOffset);
|
||||
} else if (mMagnitude < mThreshold) {
|
||||
result = ERROR_GLITCHES;
|
||||
onGlitchStart();
|
||||
ALOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
|
||||
}
|
||||
}
|
||||
}
|
||||
incrementInputPhase();
|
||||
} break;
|
||||
|
||||
case STATE_GLITCHING: {
|
||||
// Predict next sine value
|
||||
mGlitchLength++;
|
||||
double predicted = sinf(mInputPhase) * mMagnitude;
|
||||
double diff = predicted - sample;
|
||||
double 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);
|
||||
// ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut, mPhaseIncrement);
|
||||
}
|
||||
frameData[0] = output;
|
||||
for (int i = 1; i < channelCount; i++) {
|
||||
frameData[i] = 0.0f;
|
||||
}
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
void onGlitchStart() {
|
||||
mGlitchCount++;
|
||||
// ALOGD("%5d: STARTED a glitch # %d", mFrameCounter, mGlitchCount);
|
||||
mState = STATE_GLITCHING;
|
||||
mGlitchLength = 1;
|
||||
mNonGlitchCount = 0;
|
||||
}
|
||||
|
||||
void onGlitchEnd() {
|
||||
// ALOGD("%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() {
|
||||
// ALOGD("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 prepareToTest() override {
|
||||
LoopbackProcessor::prepareToTest();
|
||||
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_SNR_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;
|
||||
double mMaxGlitchDelta = 0.0;
|
||||
int32_t mGlitchCount = 0;
|
||||
int32_t mNonGlitchCount = 0;
|
||||
int32_t mGlitchLength = 0;
|
||||
// This is used for processing every frame so we cache it here.
|
||||
double mScaledTolerance = 0.0;
|
||||
int mDownCounter = IDLE_FRAME_COUNT;
|
||||
int32_t mFrameCounter = 0;
|
||||
double 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 //ANALYZER_GLITCH_ANALYZER_H
|
@ -0,0 +1,606 @@
|
||||
/*
|
||||
* 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 <iomanip>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "PeakDetector.h"
|
||||
#include "PseudoRandom.h"
|
||||
#include "RandomPulseGenerator.h"
|
||||
|
||||
// This is used when the code is in Oboe.
|
||||
#ifndef ALOGD
|
||||
#define ALOGD printf
|
||||
#define ALOGE printf
|
||||
#define ALOGW printf
|
||||
#endif
|
||||
|
||||
#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;
|
||||
|
||||
struct LatencyReport {
|
||||
int32_t latencyInFrames = 0.0;
|
||||
double confidence = 0.0;
|
||||
|
||||
void reset() {
|
||||
latencyInFrames = 0;
|
||||
confidence = 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
// 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 = 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:
|
||||
|
||||
void allocate(int maxFrames) {
|
||||
mData = std::make_unique<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;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
mFrameCounter = 0;
|
||||
}
|
||||
int32_t size() const {
|
||||
return mFrameCounter;
|
||||
}
|
||||
|
||||
bool isFull() const {
|
||||
return mFrameCounter >= mMaxFrames;
|
||||
}
|
||||
|
||||
float *getData() const {
|
||||
return mData.get();
|
||||
}
|
||||
|
||||
void setSampleRate(int32_t sampleRate) {
|
||||
mSampleRate = sampleRate;
|
||||
}
|
||||
|
||||
int32_t getSampleRate() const {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Square the samples so they are all positive and so the peaks are emphasized.
|
||||
*/
|
||||
void square() {
|
||||
float *x = mData.get();
|
||||
for (int i = 0; i < mFrameCounter; i++) {
|
||||
x[i] *= x[i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
std::unique_ptr<float[]> mData;
|
||||
int32_t mFrameCounter = 0;
|
||||
int32_t mMaxFrames = 0;
|
||||
int32_t mSampleRate = kDefaultSampleRate; // common default
|
||||
};
|
||||
|
||||
static int measureLatencyFromPulse(AudioRecording &recorded,
|
||||
AudioRecording &pulse,
|
||||
LatencyReport *report) {
|
||||
|
||||
report->latencyInFrames = 0;
|
||||
report->confidence = 0.0;
|
||||
|
||||
int numCorrelations = recorded.size() - pulse.size();
|
||||
if (numCorrelations < 10) {
|
||||
ALOGE("%s() recording too small = %d frames\n", __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) {
|
||||
ALOGE("%s() no signal for correlation\n", __func__);
|
||||
return -2;
|
||||
}
|
||||
|
||||
report->latencyInFrames = peakIndex;
|
||||
report->confidence = peakCorrelation;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
class LoopbackProcessor {
|
||||
public:
|
||||
virtual ~LoopbackProcessor() = default;
|
||||
|
||||
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 prepareToTest() {
|
||||
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 std::string 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() const {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
int32_t getResetCount() const {
|
||||
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() const = 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;
|
||||
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;
|
||||
|
||||
mState = STATE_MEASURE_BACKGROUND;
|
||||
mAudioRecording.clear();
|
||||
mLatencyReport.reset();
|
||||
}
|
||||
|
||||
bool hasEnoughData() {
|
||||
return mAudioRecording.isFull();
|
||||
}
|
||||
|
||||
bool isDone() override {
|
||||
return mState == STATE_DONE;
|
||||
}
|
||||
|
||||
int32_t getProgress() const override {
|
||||
return mAudioRecording.size();
|
||||
}
|
||||
|
||||
std::string analyze() override {
|
||||
std::stringstream report;
|
||||
report << "PulseLatencyAnalyzer ---------------\n";
|
||||
report << LOOPBACK_RESULT_TAG "test.state = "
|
||||
<< std::setw(8) << mState << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "test.state.name = "
|
||||
<< convertStateToText(mState) << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "background.rms = "
|
||||
<< std::setw(8) << mBackgroundRMS << "\n";
|
||||
|
||||
int32_t newResult = RESULT_OK;
|
||||
if (mState != STATE_GOT_DATA) {
|
||||
report << "WARNING - Bad state. Check volume on device.\n";
|
||||
// setResult(ERROR_INVALID_STATE);
|
||||
} else {
|
||||
float gain = mAudioRecording.normalize(1.0f);
|
||||
measureLatencyFromPulse(mAudioRecording,
|
||||
mPulse,
|
||||
&mLatencyReport);
|
||||
|
||||
if (mLatencyReport.confidence < kMinimumConfidence) {
|
||||
report << " ERROR - confidence too low!";
|
||||
newResult = ERROR_CONFIDENCE;
|
||||
} else {
|
||||
mSignalRMS = calculateRootMeanSquare(
|
||||
&mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
|
||||
/ gain;
|
||||
}
|
||||
double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
|
||||
/ getSampleRate();
|
||||
report << LOOPBACK_RESULT_TAG "latency.frames = " << std::setw(8)
|
||||
<< mLatencyReport.latencyInFrames << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "latency.msec = " << std::setw(8)
|
||||
<< latencyMillis << "\n";
|
||||
report << LOOPBACK_RESULT_TAG "latency.confidence = " << std::setw(8)
|
||||
<< mLatencyReport.confidence << "\n";
|
||||
}
|
||||
mState = STATE_DONE;
|
||||
if (getResult() == RESULT_OK) {
|
||||
setResult(newResult);
|
||||
}
|
||||
|
||||
return report.str();
|
||||
}
|
||||
|
||||
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 {
|
||||
ALOGD("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;
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_IN_PULSE:
|
||||
// Record input until the mAudioRecording is full.
|
||||
mAudioRecording.write(frameData, channelCount, 1);
|
||||
if (hasEnoughData()) {
|
||||
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) {
|
||||
switch (state) {
|
||||
case STATE_MEASURE_BACKGROUND:
|
||||
return "INIT";
|
||||
case STATE_IN_PULSE:
|
||||
return "PULSE";
|
||||
case STATE_GOT_DATA:
|
||||
return "GOT_DATA";
|
||||
case STATE_DONE:
|
||||
return "DONE";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
double mBackgroundSumSquare = 0.0;
|
||||
int32_t mBackgroundSumCount = 0;
|
||||
double mBackgroundRMS = 0.0;
|
||||
double mSignalRMS = 0.0;
|
||||
int32_t mFramesToRecord = 0;
|
||||
|
||||
AudioRecording mAudioRecording; // contains only the input after starting the pulse
|
||||
LatencyReport mLatencyReport;
|
||||
};
|
||||
|
||||
#endif // ANALYZER_LATENCY_ANALYZER_H
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2019 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_MANCHESTER_ENCODER_H
|
||||
#define ANALYZER_MANCHESTER_ENCODER_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* Encode bytes using Manchester Coding scheme.
|
||||
*
|
||||
* Manchester Code is self clocking.
|
||||
* There is a transition in the middle of every bit.
|
||||
* Zero is high then low.
|
||||
* One is low then high.
|
||||
*
|
||||
* This avoids having long DC sections that would droop when
|
||||
* passed though analog circuits with AC coupling.
|
||||
*
|
||||
* IEEE 802.3 compatible.
|
||||
*/
|
||||
|
||||
class ManchesterEncoder {
|
||||
public:
|
||||
ManchesterEncoder(int samplesPerPulse)
|
||||
: mSamplesPerPulse(samplesPerPulse)
|
||||
, mSamplesPerPulseHalf(samplesPerPulse / 2)
|
||||
, mCursor(samplesPerPulse) {
|
||||
}
|
||||
|
||||
virtual ~ManchesterEncoder() = default;
|
||||
|
||||
/**
|
||||
* This will be called when the next byte is needed.
|
||||
* @return
|
||||
*/
|
||||
virtual uint8_t onNextByte() = 0;
|
||||
|
||||
/**
|
||||
* Generate the next floating point sample.
|
||||
* @return
|
||||
*/
|
||||
virtual float nextFloat() {
|
||||
advanceSample();
|
||||
if (mCurrentBit) {
|
||||
return (mCursor < mSamplesPerPulseHalf) ? -1.0f : 1.0f; // one
|
||||
} else {
|
||||
return (mCursor < mSamplesPerPulseHalf) ? 1.0f : -1.0f; // zero
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* This will be called when a new bit is ready to be encoded.
|
||||
* It can be used to prepare the encoded samples.
|
||||
* @param current
|
||||
*/
|
||||
virtual void onNextBit(bool /* current */) {};
|
||||
|
||||
void advanceSample() {
|
||||
// Are we ready for a new bit?
|
||||
if (++mCursor >= mSamplesPerPulse) {
|
||||
mCursor = 0;
|
||||
if (mBitsLeft == 0) {
|
||||
mCurrentByte = onNextByte();
|
||||
mBitsLeft = 8;
|
||||
}
|
||||
--mBitsLeft;
|
||||
mCurrentBit = (mCurrentByte >> mBitsLeft) & 1;
|
||||
onNextBit(mCurrentBit);
|
||||
}
|
||||
}
|
||||
|
||||
bool getCurrentBit() {
|
||||
return mCurrentBit;
|
||||
}
|
||||
|
||||
const int mSamplesPerPulse;
|
||||
const int mSamplesPerPulseHalf;
|
||||
int mCursor;
|
||||
int mBitsLeft = 0;
|
||||
uint8_t mCurrentByte = 0;
|
||||
bool mCurrentBit = false;
|
||||
};
|
||||
#endif //ANALYZER_MANCHESTER_ENCODER_H
|
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2015 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_PEAK_DETECTOR_H
|
||||
#define ANALYZER_PEAK_DETECTOR_H
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* Measure a peak envelope by rising with the peaks,
|
||||
* and decaying exponentially after each peak.
|
||||
* The absolute value of the input signal is used.
|
||||
*/
|
||||
class PeakDetector {
|
||||
public:
|
||||
|
||||
void reset() {
|
||||
mLevel = 0.0;
|
||||
}
|
||||
|
||||
double process(double input) {
|
||||
mLevel *= mDecay; // exponential decay
|
||||
input = fabs(input);
|
||||
// never fall below the input signal
|
||||
if (input > mLevel) {
|
||||
mLevel = input;
|
||||
}
|
||||
return mLevel;
|
||||
}
|
||||
|
||||
double getLevel() const {
|
||||
return mLevel;
|
||||
}
|
||||
|
||||
double getDecay() const {
|
||||
return mDecay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply the level by this amount on every iteration.
|
||||
* This provides an exponential decay curve.
|
||||
* A value just under 1.0 is best, for example, 0.99;
|
||||
* @param decay scale level for each input
|
||||
*/
|
||||
void setDecay(double decay) {
|
||||
mDecay = decay;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double kDefaultDecay = 0.99f;
|
||||
|
||||
double mLevel = 0.0;
|
||||
double mDecay = kDefaultDecay;
|
||||
};
|
||||
#endif //ANALYZER_PEAK_DETECTOR_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(int64_t seed = 99887766)
|
||||
: 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
|
||||
* with known real-time performance.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
#endif //ANALYZER_PSEUDORANDOM_H
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2015 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_RANDOM_PULSE_GENERATOR_H
|
||||
#define ANALYZER_RANDOM_PULSE_GENERATOR_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "RoundedManchesterEncoder.h"
|
||||
|
||||
/**
|
||||
* Encode random ones and zeros using Manchester Code per IEEE 802.3.
|
||||
*/
|
||||
class RandomPulseGenerator : public RoundedManchesterEncoder {
|
||||
public:
|
||||
RandomPulseGenerator(int samplesPerPulse)
|
||||
: RoundedManchesterEncoder(samplesPerPulse) {
|
||||
}
|
||||
|
||||
virtual ~RandomPulseGenerator() = default;
|
||||
|
||||
/**
|
||||
* This will be called when the next byte is needed.
|
||||
* @return random byte
|
||||
*/
|
||||
uint8_t onNextByte() override {
|
||||
return static_cast<uint8_t>(rand());
|
||||
}
|
||||
};
|
||||
|
||||
#endif //ANALYZER_RANDOM_PULSE_GENERATOR_H
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2019 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_ROUNDED_MANCHESTER_ENCODER_H
|
||||
#define ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
|
||||
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
#include "ManchesterEncoder.h"
|
||||
|
||||
/**
|
||||
* Encode bytes using Manchester Code.
|
||||
* Round the edges using a half cosine to reduce ringing caused by a hard edge.
|
||||
*/
|
||||
|
||||
class RoundedManchesterEncoder : public ManchesterEncoder {
|
||||
public:
|
||||
RoundedManchesterEncoder(int samplesPerPulse)
|
||||
: ManchesterEncoder(samplesPerPulse) {
|
||||
int rampSize = samplesPerPulse / 4;
|
||||
mZeroAfterZero = std::make_unique<float[]>(samplesPerPulse);
|
||||
mZeroAfterOne = std::make_unique<float[]>(samplesPerPulse);
|
||||
|
||||
int sampleIndex = 0;
|
||||
for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
|
||||
float phase = (rampIndex + 1) * M_PI / rampSize;
|
||||
float sample = -cosf(phase);
|
||||
mZeroAfterZero[sampleIndex] = sample;
|
||||
mZeroAfterOne[sampleIndex] = 1.0f;
|
||||
sampleIndex++;
|
||||
}
|
||||
for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
|
||||
mZeroAfterZero[sampleIndex] = 1.0f;
|
||||
mZeroAfterOne[sampleIndex] = 1.0f;
|
||||
sampleIndex++;
|
||||
}
|
||||
for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
|
||||
float phase = (rampIndex + 1) * M_PI / rampSize;
|
||||
float sample = cosf(phase);
|
||||
mZeroAfterZero[sampleIndex] = sample;
|
||||
mZeroAfterOne[sampleIndex] = sample;
|
||||
sampleIndex++;
|
||||
}
|
||||
for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
|
||||
mZeroAfterZero[sampleIndex] = -1.0f;
|
||||
mZeroAfterOne[sampleIndex] = -1.0f;
|
||||
sampleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void onNextBit(bool current) override {
|
||||
// Do we need to use the rounded edge?
|
||||
mCurrentSamples = (current ^ mPreviousBit)
|
||||
? mZeroAfterOne.get()
|
||||
: mZeroAfterZero.get();
|
||||
mPreviousBit = current;
|
||||
}
|
||||
|
||||
float nextFloat() override {
|
||||
advanceSample();
|
||||
float output = mCurrentSamples[mCursor];
|
||||
if (getCurrentBit()) output = -output;
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
bool mPreviousBit = false;
|
||||
float *mCurrentSamples = nullptr;
|
||||
std::unique_ptr<float[]> mZeroAfterZero;
|
||||
std::unique_ptr<float[]> mZeroAfterOne;
|
||||
};
|
||||
|
||||
#endif //ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
|
Loading…
Reference in new issue