From 50360df13e907264427163d5a337cfbf1e51c6a5 Mon Sep 17 00:00:00 2001 From: Ji-Jie Lin Date: Thu, 5 Mar 2026 12:55:53 +0800 Subject: [PATCH] Fix TestDataPathsActivity jitter calculation 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. --- .../src/main/cpp/analyzer/BaseSineAnalyzer.h | 11 ++-- .../main/cpp/analyzer/DataPathAnalyzer.cpp | 26 ++++++++- .../src/main/cpp/analyzer/DataPathAnalyzer.h | 8 +++ .../app/src/main/cpp/jni-bridge.cpp | 18 ++++++ .../oboetester/BaseAutoGlitchActivity.java | 10 ++-- .../oboetester/TestDataPathsActivity.java | 57 ++++++------------- 6 files changed, 77 insertions(+), 53 deletions(-) diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h index 5bcf06f8a..275f5bce5 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h @@ -130,7 +130,7 @@ class BaseSineAnalyzer : public LoopbackProcessor { switch (mSignalType) { case Chirp: { if (mFrameCounter < getSampleRate() * kChirpDurationSeconds) { - float sinOut = sinf(mOutputPhase); + double sinOut = sin(mOutputPhase); // Simple linear chirp from kChirpStartFrequency to mChirpEndFrequencyActual // in kChirpDurationSeconds seconds. double freq = kChirpStartFrequency @@ -157,7 +157,7 @@ class BaseSineAnalyzer : public LoopbackProcessor { } case Sine: default: { - float sinOut = sinf(mOutputPhase); + double sinOut = sin(mOutputPhase); incrementOutputPhase(); output = (sinOut * mOutputAmplitude) + (mWhiteNoise.nextRandomDouble() * getNoiseAmplitude()); @@ -214,8 +214,8 @@ class BaseSineAnalyzer : public LoopbackProcessor { */ bool transformSample(float sample) { // Compare incoming signal with the reference input sine wave. - mSinAccumulator += static_cast(sample) * sinf(mInputPhase); - mCosAccumulator += static_cast(sample) * cosf(mInputPhase); + mSinAccumulator += static_cast(sample) * sin(mInputPhase); + mCosAccumulator += static_cast(sample) * cos(mInputPhase); incrementInputPhase(); mFramesAccumulated++; @@ -224,7 +224,8 @@ class BaseSineAnalyzer : public LoopbackProcessor { const double coefficient = 0.1; double magnitude = calculateMagnitudePhase(&mPhaseOffset); - ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset); + ALOGD("%s(), magnitude = %f, phaseOffset = %f\n", __func__, + magnitude, mPhaseOffset); if (mPhaseOffset != kPhaseInvalid) { // One pole averaging filter for magnitude. setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient)); diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.cpp b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.cpp index c9e28c795..f812a4085 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.cpp +++ b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.cpp @@ -169,12 +169,20 @@ BaseSineAnalyzer::result_code DataPathAnalyzer::processInputFrame(const float *f if (transformSample(sample)) { // Analyze magnitude and phase on every period. - if (mPhaseOffset != kPhaseInvalid) { - double diff = fabs(calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset)); + if (mPhaseOffset != kPhaseInvalid && + mMagnitude >= kMinSmoothedMagnitude) { + double diff = fabs( + calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset)); if (diff < mPhaseTolerance) { mMaxMagnitude = std::max(mMagnitude, mMaxMagnitude); } + constexpr int kMinPhaseCount = 4; + if (mPhaseCount >= kMinPhaseCount) { + mPhaseErrorSum += diff; + mPhaseErrorCount++; + } mPreviousPhaseOffset = mPhaseOffset; + mPhaseCount++; } } break; @@ -207,6 +215,9 @@ std::string DataPathAnalyzer::analyze() { void DataPathAnalyzer::reset() { BaseSineAnalyzer::reset(); + mPhaseErrorSum = 0.0; + mPhaseErrorCount = 0; + mPhaseCount = 0; mPreviousPhaseOffset = 999.0; // Arbitrary high offset to prevent early lock. mMaxMagnitude = 0.0; } @@ -227,3 +238,14 @@ int DataPathAnalyzer::getAnalysisResult() { return mAnalysisResult; } +double DataPathAnalyzer::getAveragePhaseError() { + return mPhaseErrorCount > 0 ? mPhaseErrorSum / mPhaseErrorCount : M_PI; +} + +int DataPathAnalyzer::getPhaseCount() { return mPhaseCount; } + +bool DataPathAnalyzer::isPhaseJitterValid() { + // Arbitrary number of measurements to be considered valid. + constexpr int kMinPhaseErrorCount = 5; + return mPhaseErrorCount >= kMinPhaseErrorCount; + } diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h index 7d5f8761a..45d513faa 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h @@ -29,17 +29,25 @@ class DataPathAnalyzer : public BaseSineAnalyzer { void reset() override; double getMaxMagnitude(); + double getAveragePhaseError(); + int getPhaseCount(); + bool isPhaseJitterValid(); std::string getFrequencyResponse(); std::string getDistortionReport(); int getAnalysisResult(); private: + static constexpr double kMinSmoothedMagnitude = 0.001; + double calculatePhaseError(double p1, double p2); double mPreviousPhaseOffset = 0.0; double mPhaseTolerance = 2 * M_PI / 48; double mMaxMagnitude = 0.0; + int mPhaseCount = 0; + double mPhaseErrorSum = 0.0; + int mPhaseErrorCount = 0; // For multi-tone analysis std::vector mFftBuffer; diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index 8d5c01d78..b6a8f28cf 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -1007,6 +1007,24 @@ Java_com_mobileer_oboetester_TestDataPathsActivity_getPhaseDataPaths(JNIEnv *env return engine.mActivityDataPath.getDataPathAnalyzer()->getPhaseOffset(); } +JNIEXPORT double JNICALL +Java_com_mobileer_oboetester_TestDataPathsActivity_getAveragePhaseError( + JNIEnv* env, jobject instance) { + return engine.mActivityDataPath.getDataPathAnalyzer()->getAveragePhaseError(); +} + +JNIEXPORT bool JNICALL +Java_com_mobileer_oboetester_TestDataPathsActivity_isPhaseJitterValid( + JNIEnv* env, jobject instance) { + return engine.mActivityDataPath.getDataPathAnalyzer()->isPhaseJitterValid(); +} + +JNIEXPORT int JNICALL +Java_com_mobileer_oboetester_TestDataPathsActivity_getPhaseCount(JNIEnv *env, + jobject instance) { + return engine.mActivityDataPath.getDataPathAnalyzer()->getPhaseCount(); +} + JNIEXPORT void JNICALL Java_com_mobileer_oboetester_TestDataPathsActivity_setSignalType(JNIEnv *env, jobject instance, diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java index d5b7a243a..f26243330 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java @@ -344,13 +344,13 @@ protected TestResult testCurrentConfigurations() throws InterruptedException { long now = System.currentTimeMillis(); long startedAt = now; long endTime = System.currentTimeMillis() + (mDurationSeconds * 1000); - boolean finishedEarly = false; - while (now < endTime && !finishedEarly) { + while (now < endTime) { Thread.sleep(100); // Let test run. now = System.currentTimeMillis(); - finishedEarly = isFinishedEarly(); - if (finishedEarly) { + double runningTimeSeconds = (now - startedAt) / 1000.0; + if (isFinishedEarly(runningTimeSeconds)) { log("Finished early after " + (now - startedAt) + " msec."); + break; } } } @@ -585,7 +585,7 @@ protected AudioDeviceInfo findCompatibleInputDevice(int outputDeviceType) { return null; } - protected boolean isFinishedEarly() { + protected boolean isFinishedEarly(double runningTimeSeconds) { return false; } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java index f11e2a583..16fef568e 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java @@ -87,6 +87,7 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity { public static final boolean VALUE_DEFAULT_USE_OUTPUT_DEVICES = true; public static final int DURATION_SECONDS = 4; + public static final int EARLY_STOP_DURATION_SECONDS = 1; private final static double MIN_REQUIRED_MAGNITUDE = 0.001; private final static double MAX_ALLOWED_JITTER = 0.1; // Matches CTS Verifier // This must match the value of kPhaseInvalid in BaseSineAnalyzer.h @@ -135,8 +136,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity { private double mMaxMagnitude; private int mPhaseCount; private double mPhase; - private double mPhaseErrorSum; - private double mPhaseErrorCount; private boolean mSkipRemainingTests; @@ -256,35 +255,17 @@ public void startSniffer() { mMaxMagnitude = -1.0; mPhaseCount = 0; mPhase = 0.0; - mPhaseErrorSum = 0.0; - mPhaseErrorCount = 0; super.startSniffer(); } private void gatherData() { mMagnitude = getMagnitude(); mMaxMagnitude = getMaxMagnitude(); - Log.d(TAG, String.format(Locale.getDefault(), "magnitude = %7.4f, maxMagnitude = %7.4f", - mMagnitude, mMaxMagnitude)); - // Only look at the phase if we have a signal. - if (mMagnitude >= MIN_REQUIRED_MAGNITUDE) { - double phase = getPhaseDataPaths(); - if (phase != PHASE_INVALID) { - // Wait for the analyzer to get a lock on the signal. - // Arbitrary number of phase measurements before we start measuring jitter. - final int kMinPhaseMeasurementsRequired = 4; - if (mPhaseCount >= kMinPhaseMeasurementsRequired) { - double phaseError = Math.abs(calculatePhaseError(phase, mPhase)); - // collect average error - mPhaseErrorSum += phaseError; - mPhaseErrorCount++; - Log.d(TAG, String.format(Locale.getDefault(), "phase = %7.4f, mPhase = %7.4f, phaseError = %7.4f, jitter = %7.4f", - phase, mPhase, phaseError, getAveragePhaseError())); - } - mPhase = phase; - } - mPhaseCount++; - } + mPhase = getPhaseDataPaths(); + mPhaseCount = getPhaseCount(); + Log.d(TAG, String.format(Locale.getDefault(), + "magnitude = %7.4f, maxMagnitude = %7.4f, phase = %7.4f, phaseCount = %d, jitter = %7.4f", + mMagnitude, mMaxMagnitude, mPhase, mPhaseCount, getAveragePhaseError())); } public String getCurrentStatusReport() { @@ -366,6 +347,9 @@ public String getShortReport() { native double getMaxMagnitude(); native double getPhaseDataPaths(); + native int getPhaseCount(); + native double getAveragePhaseError(); + native boolean isPhaseJitterValid(); native void setSignalType(int type); native String getFrequencyResponse(); @@ -457,10 +441,13 @@ protected String whyShouldTestBeSkipped() { } @Override - protected boolean isFinishedEarly() { - return (mMaxMagnitude > MIN_REQUIRED_MAGNITUDE) - && (getAveragePhaseError() < MAX_ALLOWED_JITTER) - && isPhaseJitterValid(); + protected boolean isFinishedEarly(double runningTimeSeconds) { + if (mSignalType == 0) { // Sine + boolean passed = mMagnitude > MIN_REQUIRED_MAGNITUDE && getAveragePhaseError() < MAX_ALLOWED_JITTER + && isPhaseJitterValid(); + return passed && (runningTimeSeconds >= EARLY_STOP_DURATION_SECONDS); + } + return false; } // @return reasons for failure of empty string @@ -491,18 +478,6 @@ public String didTestFail() { return why; } - private double getAveragePhaseError() { - // If we have no measurements then return maximum possible phase jitter - // to avoid dividing by zero. - return (mPhaseErrorCount > 0) ? (mPhaseErrorSum / mPhaseErrorCount) : Math.PI; - } - - private boolean isPhaseJitterValid() { - // Arbitrary number of measurements to be considered valid. - final int kMinPhaseErrorCount = 5; - return mPhaseErrorCount >= kMinPhaseErrorCount; - } - String getOneLineSummary() { StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration; StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;