Skip to content

Commit 7d4dc30

Browse files
authored
Fix TestDataPathsActivity jitter calculation (#2349)
1. Move jitter calculation from TestDataPathsActivity.java to DataPathAnalyzer.cpp to calcuate scores on each frame instead of in a UI event. 2. Add kMinSmoothedMagnitude to align CTSV data paths jitter analyzer. 3. Fix sinf to sin for precision.
1 parent f0207e7 commit 7d4dc30

6 files changed

Lines changed: 77 additions & 53 deletions

File tree

apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ class BaseSineAnalyzer : public LoopbackProcessor {
130130
switch (mSignalType) {
131131
case Chirp: {
132132
if (mFrameCounter < getSampleRate() * kChirpDurationSeconds) {
133-
float sinOut = sinf(mOutputPhase);
133+
double sinOut = sin(mOutputPhase);
134134
// Simple linear chirp from kChirpStartFrequency to mChirpEndFrequencyActual
135135
// in kChirpDurationSeconds seconds.
136136
double freq = kChirpStartFrequency
@@ -157,7 +157,7 @@ class BaseSineAnalyzer : public LoopbackProcessor {
157157
}
158158
case Sine:
159159
default: {
160-
float sinOut = sinf(mOutputPhase);
160+
double sinOut = sin(mOutputPhase);
161161
incrementOutputPhase();
162162
output = (sinOut * mOutputAmplitude)
163163
+ (mWhiteNoise.nextRandomDouble() * getNoiseAmplitude());
@@ -214,8 +214,8 @@ class BaseSineAnalyzer : public LoopbackProcessor {
214214
*/
215215
bool transformSample(float sample) {
216216
// Compare incoming signal with the reference input sine wave.
217-
mSinAccumulator += static_cast<double>(sample) * sinf(mInputPhase);
218-
mCosAccumulator += static_cast<double>(sample) * cosf(mInputPhase);
217+
mSinAccumulator += static_cast<double>(sample) * sin(mInputPhase);
218+
mCosAccumulator += static_cast<double>(sample) * cos(mInputPhase);
219219
incrementInputPhase();
220220

221221
mFramesAccumulated++;
@@ -224,7 +224,8 @@ class BaseSineAnalyzer : public LoopbackProcessor {
224224
const double coefficient = 0.1;
225225
double magnitude = calculateMagnitudePhase(&mPhaseOffset);
226226

227-
ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset);
227+
ALOGD("%s(), magnitude = %f, phaseOffset = %f\n", __func__,
228+
magnitude, mPhaseOffset);
228229
if (mPhaseOffset != kPhaseInvalid) {
229230
// One pole averaging filter for magnitude.
230231
setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));

apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,20 @@ BaseSineAnalyzer::result_code DataPathAnalyzer::processInputFrame(const float *f
169169

170170
if (transformSample(sample)) {
171171
// Analyze magnitude and phase on every period.
172-
if (mPhaseOffset != kPhaseInvalid) {
173-
double diff = fabs(calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset));
172+
if (mPhaseOffset != kPhaseInvalid &&
173+
mMagnitude >= kMinSmoothedMagnitude) {
174+
double diff = fabs(
175+
calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset));
174176
if (diff < mPhaseTolerance) {
175177
mMaxMagnitude = std::max(mMagnitude, mMaxMagnitude);
176178
}
179+
constexpr int kMinPhaseCount = 4;
180+
if (mPhaseCount >= kMinPhaseCount) {
181+
mPhaseErrorSum += diff;
182+
mPhaseErrorCount++;
183+
}
177184
mPreviousPhaseOffset = mPhaseOffset;
185+
mPhaseCount++;
178186
}
179187
}
180188
break;
@@ -207,6 +215,9 @@ std::string DataPathAnalyzer::analyze() {
207215

208216
void DataPathAnalyzer::reset() {
209217
BaseSineAnalyzer::reset();
218+
mPhaseErrorSum = 0.0;
219+
mPhaseErrorCount = 0;
220+
mPhaseCount = 0;
210221
mPreviousPhaseOffset = 999.0; // Arbitrary high offset to prevent early lock.
211222
mMaxMagnitude = 0.0;
212223
}
@@ -227,3 +238,14 @@ int DataPathAnalyzer::getAnalysisResult() {
227238
return mAnalysisResult;
228239
}
229240

241+
double DataPathAnalyzer::getAveragePhaseError() {
242+
return mPhaseErrorCount > 0 ? mPhaseErrorSum / mPhaseErrorCount : M_PI;
243+
}
244+
245+
int DataPathAnalyzer::getPhaseCount() { return mPhaseCount; }
246+
247+
bool DataPathAnalyzer::isPhaseJitterValid() {
248+
// Arbitrary number of measurements to be considered valid.
249+
constexpr int kMinPhaseErrorCount = 5;
250+
return mPhaseErrorCount >= kMinPhaseErrorCount;
251+
}

apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,25 @@ class DataPathAnalyzer : public BaseSineAnalyzer {
2929
void reset() override;
3030

3131
double getMaxMagnitude();
32+
double getAveragePhaseError();
33+
int getPhaseCount();
34+
bool isPhaseJitterValid();
3235

3336
std::string getFrequencyResponse();
3437
std::string getDistortionReport();
3538
int getAnalysisResult();
3639

3740
private:
41+
static constexpr double kMinSmoothedMagnitude = 0.001;
42+
3843
double calculatePhaseError(double p1, double p2);
3944

4045
double mPreviousPhaseOffset = 0.0;
4146
double mPhaseTolerance = 2 * M_PI / 48;
4247
double mMaxMagnitude = 0.0;
48+
int mPhaseCount = 0;
49+
double mPhaseErrorSum = 0.0;
50+
int mPhaseErrorCount = 0;
4351

4452
// For multi-tone analysis
4553
std::vector<float> mFftBuffer;

apps/OboeTester/app/src/main/cpp/jni-bridge.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,24 @@ Java_com_mobileer_oboetester_TestDataPathsActivity_getPhaseDataPaths(JNIEnv *env
10101010
return engine.mActivityDataPath.getDataPathAnalyzer()->getPhaseOffset();
10111011
}
10121012

1013+
JNIEXPORT double JNICALL
1014+
Java_com_mobileer_oboetester_TestDataPathsActivity_getAveragePhaseError(
1015+
JNIEnv* env, jobject instance) {
1016+
return engine.mActivityDataPath.getDataPathAnalyzer()->getAveragePhaseError();
1017+
}
1018+
1019+
JNIEXPORT bool JNICALL
1020+
Java_com_mobileer_oboetester_TestDataPathsActivity_isPhaseJitterValid(
1021+
JNIEnv* env, jobject instance) {
1022+
return engine.mActivityDataPath.getDataPathAnalyzer()->isPhaseJitterValid();
1023+
}
1024+
1025+
JNIEXPORT int JNICALL
1026+
Java_com_mobileer_oboetester_TestDataPathsActivity_getPhaseCount(JNIEnv *env,
1027+
jobject instance) {
1028+
return engine.mActivityDataPath.getDataPathAnalyzer()->getPhaseCount();
1029+
}
1030+
10131031
JNIEXPORT void JNICALL
10141032
Java_com_mobileer_oboetester_TestDataPathsActivity_setSignalType(JNIEnv *env,
10151033
jobject instance,

apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,13 +344,13 @@ protected TestResult testCurrentConfigurations() throws InterruptedException {
344344
long now = System.currentTimeMillis();
345345
long startedAt = now;
346346
long endTime = System.currentTimeMillis() + (mDurationSeconds * 1000);
347-
boolean finishedEarly = false;
348-
while (now < endTime && !finishedEarly) {
347+
while (now < endTime) {
349348
Thread.sleep(100); // Let test run.
350349
now = System.currentTimeMillis();
351-
finishedEarly = isFinishedEarly();
352-
if (finishedEarly) {
350+
double runningTimeSeconds = (now - startedAt) / 1000.0;
351+
if (isFinishedEarly(runningTimeSeconds)) {
353352
log("Finished early after " + (now - startedAt) + " msec.");
353+
break;
354354
}
355355
}
356356
}
@@ -585,7 +585,7 @@ protected AudioDeviceInfo findCompatibleInputDevice(int outputDeviceType) {
585585
return null;
586586
}
587587

588-
protected boolean isFinishedEarly() {
588+
protected boolean isFinishedEarly(double runningTimeSeconds) {
589589
return false;
590590
}
591591

apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
8787
public static final boolean VALUE_DEFAULT_USE_OUTPUT_DEVICES = true;
8888

8989
public static final int DURATION_SECONDS = 4;
90+
public static final int EARLY_STOP_DURATION_SECONDS = 1;
9091
private final static double MIN_REQUIRED_MAGNITUDE = 0.001;
9192
private final static double MAX_ALLOWED_JITTER = 0.1; // Matches CTS Verifier
9293
// This must match the value of kPhaseInvalid in BaseSineAnalyzer.h
@@ -135,8 +136,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
135136
private double mMaxMagnitude;
136137
private int mPhaseCount;
137138
private double mPhase;
138-
private double mPhaseErrorSum;
139-
private double mPhaseErrorCount;
140139

141140
private boolean mSkipRemainingTests;
142141

@@ -256,35 +255,17 @@ public void startSniffer() {
256255
mMaxMagnitude = -1.0;
257256
mPhaseCount = 0;
258257
mPhase = 0.0;
259-
mPhaseErrorSum = 0.0;
260-
mPhaseErrorCount = 0;
261258
super.startSniffer();
262259
}
263260

264261
private void gatherData() {
265262
mMagnitude = getMagnitude();
266263
mMaxMagnitude = getMaxMagnitude();
267-
Log.d(TAG, String.format(Locale.getDefault(), "magnitude = %7.4f, maxMagnitude = %7.4f",
268-
mMagnitude, mMaxMagnitude));
269-
// Only look at the phase if we have a signal.
270-
if (mMagnitude >= MIN_REQUIRED_MAGNITUDE) {
271-
double phase = getPhaseDataPaths();
272-
if (phase != PHASE_INVALID) {
273-
// Wait for the analyzer to get a lock on the signal.
274-
// Arbitrary number of phase measurements before we start measuring jitter.
275-
final int kMinPhaseMeasurementsRequired = 4;
276-
if (mPhaseCount >= kMinPhaseMeasurementsRequired) {
277-
double phaseError = Math.abs(calculatePhaseError(phase, mPhase));
278-
// collect average error
279-
mPhaseErrorSum += phaseError;
280-
mPhaseErrorCount++;
281-
Log.d(TAG, String.format(Locale.getDefault(), "phase = %7.4f, mPhase = %7.4f, phaseError = %7.4f, jitter = %7.4f",
282-
phase, mPhase, phaseError, getAveragePhaseError()));
283-
}
284-
mPhase = phase;
285-
}
286-
mPhaseCount++;
287-
}
264+
mPhase = getPhaseDataPaths();
265+
mPhaseCount = getPhaseCount();
266+
Log.d(TAG, String.format(Locale.getDefault(),
267+
"magnitude = %7.4f, maxMagnitude = %7.4f, phase = %7.4f, phaseCount = %d, jitter = %7.4f",
268+
mMagnitude, mMaxMagnitude, mPhase, mPhaseCount, getAveragePhaseError()));
288269
}
289270

290271
public String getCurrentStatusReport() {
@@ -366,6 +347,9 @@ public String getShortReport() {
366347
native double getMaxMagnitude();
367348

368349
native double getPhaseDataPaths();
350+
native int getPhaseCount();
351+
native double getAveragePhaseError();
352+
native boolean isPhaseJitterValid();
369353

370354
native void setSignalType(int type);
371355
native String getFrequencyResponse();
@@ -457,10 +441,13 @@ protected String whyShouldTestBeSkipped() {
457441
}
458442

459443
@Override
460-
protected boolean isFinishedEarly() {
461-
return (mMaxMagnitude > MIN_REQUIRED_MAGNITUDE)
462-
&& (getAveragePhaseError() < MAX_ALLOWED_JITTER)
463-
&& isPhaseJitterValid();
444+
protected boolean isFinishedEarly(double runningTimeSeconds) {
445+
if (mSignalType == 0) { // Sine
446+
boolean passed = mMagnitude > MIN_REQUIRED_MAGNITUDE && getAveragePhaseError() < MAX_ALLOWED_JITTER
447+
&& isPhaseJitterValid();
448+
return passed && (runningTimeSeconds >= EARLY_STOP_DURATION_SECONDS);
449+
}
450+
return false;
464451
}
465452

466453
// @return reasons for failure of empty string
@@ -491,18 +478,6 @@ public String didTestFail() {
491478
return why;
492479
}
493480

494-
private double getAveragePhaseError() {
495-
// If we have no measurements then return maximum possible phase jitter
496-
// to avoid dividing by zero.
497-
return (mPhaseErrorCount > 0) ? (mPhaseErrorSum / mPhaseErrorCount) : Math.PI;
498-
}
499-
500-
private boolean isPhaseJitterValid() {
501-
// Arbitrary number of measurements to be considered valid.
502-
final int kMinPhaseErrorCount = 5;
503-
return mPhaseErrorCount >= kMinPhaseErrorCount;
504-
}
505-
506481
String getOneLineSummary() {
507482
StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration;
508483
StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;

0 commit comments

Comments
 (0)