Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OboeTester: Fix problem with phaseJitter=0 in DataPaths #1978

Merged
merged 2 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ class BaseSineAnalyzer : public LoopbackProcessor {
mScaledTolerance = mMagnitude * getTolerance();
}

/**
*
* @return valid phase or kPhaseInvalid=-999
*/
double getPhaseOffset() {
ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset);
return mPhaseOffset;
Expand Down Expand Up @@ -129,7 +133,18 @@ class BaseSineAnalyzer : public LoopbackProcessor {
double cosMean = mCosAccumulator / mFramesAccumulated;
double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
if (phasePtr != nullptr) {
double phase = atan2(cosMean, sinMean);
double phase;
if (magnitude < kMinValidMagnitude) {
phase = kPhaseInvalid;
ALOGD("%s() mag very low! sinMean = %7.5f, cosMean = %7.5f",
__func__, sinMean, cosMean);
} else {
phase = atan2(cosMean, sinMean);
if (phase == 0.0) {
ALOGD("%s() phase zero! sinMean = %7.5f, cosMean = %7.5f",
__func__, sinMean, cosMean);
}
}
*phasePtr = phase;
}
return magnitude;
Expand All @@ -153,9 +168,12 @@ class BaseSineAnalyzer : public LoopbackProcessor {
if (mFramesAccumulated == mSinePeriod) {
const double coefficient = 0.1;
double magnitude = calculateMagnitudePhase(&mPhaseOffset);
ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset);
// One pole averaging filter.
setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));

ALOGD("%s(), phaseOffset = %f\n", __func__, mPhaseOffset);
if (mPhaseOffset != kPhaseInvalid) {
// One pole averaging filter.
setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
}
resetAccumulator();
return true;
} else {
Expand Down Expand Up @@ -193,9 +211,19 @@ class BaseSineAnalyzer : public LoopbackProcessor {
double mPhaseIncrement = 0.0;
double mOutputPhase = 0.0;
double mOutputAmplitude = 0.75;
// This is the phase offset between the output sine wave and the recorded
// signal at the tuned frequency.
// If this jumps around then we are probably just hearing noise.
// Noise can cause the magnitude to be high but mPhaseOffset will be pretty random.
// If we are tracking a sine wave then mPhaseOffset should be consistent.
double mPhaseOffset = 0.0;
// kPhaseInvalid indicates that the phase measurement cannot be used.
// We were seeing times when a magnitude of zero was causing atan2(s,c) to
// return a phase of zero, which looked valid to Java. This is a way of passing
// an error code back to Java as a single value to avoid race conditions.
static constexpr double kPhaseInvalid = -999.0;
philburk marked this conversation as resolved.
Show resolved Hide resolved
double mMagnitude = 0.0;
static constexpr double kMinValidMagnitude = 2.0 / (1 << 16);
philburk marked this conversation as resolved.
Show resolved Hide resolved
int32_t mFramesAccumulated = 0;
double mSinAccumulator = 0.0;
double mCosAccumulator = 0.0;
Expand Down
10 changes: 6 additions & 4 deletions apps/OboeTester/app/src/main/cpp/analyzer/DataPathAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ class DataPathAnalyzer : public BaseSineAnalyzer {

if (transformSample(sample, mOutputPhase)) {
// Analyze magnitude and phase on every period.
double diff = fabs(calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset));
if (diff < mPhaseTolerance) {
mMaxMagnitude = std::max(mMagnitude, mMaxMagnitude);
if (mPhaseOffset != kPhaseInvalid) {
double diff = fabs(calculatePhaseError(mPhaseOffset, mPreviousPhaseOffset));
if (diff < mPhaseTolerance) {
mMaxMagnitude = std::max(mMagnitude, mMaxMagnitude);
}
mPreviousPhaseOffset = mPhaseOffset;
}
mPreviousPhaseOffset = mPhaseOffset;
}
return result;
}
Expand Down
21 changes: 12 additions & 9 deletions apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,17 +183,20 @@ class GlitchAnalyzer : public BaseSineAnalyzer {
mFramesAccumulated++;
// Must be a multiple of the period or the calculation will not be accurate.
if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
setMagnitude(calculateMagnitudePhase(&mPhaseOffset));
ALOGD("%s() mag = %f, mPhaseOffset = %f",
__func__, mMagnitude, mPhaseOffset);
if (mMagnitude > mThreshold) {
if (fabs(mPhaseOffset) < kMaxPhaseError) {
mState = STATE_LOCKED;
mConsecutiveBadFrames = 0;
double magnitude = calculateMagnitudePhase(&mPhaseOffset);
if (mPhaseOffset != kPhaseInvalid) {
setMagnitude(magnitude);
ALOGD("%s() mag = %f, mPhaseOffset = %f",
__func__, magnitude, mPhaseOffset);
if (mMagnitude > mThreshold) {
if (fabs(mPhaseOffset) < kMaxPhaseError) {
mState = STATE_LOCKED;
mConsecutiveBadFrames = 0;
// ALOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
}
// Adjust mInputPhase to match measured phase
mInputPhase += mPhaseOffset;
}
// Adjust mInputPhase to match measured phase
mInputPhase += mPhaseOffset;
}
resetAccumulator();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ protected void log(String text) {
protected void appendFailedSummary(String text) {
mAutomatedTestRunner.appendFailedSummary(text);
}

protected void appendSummary(String text) {
mAutomatedTestRunner.appendSummary(text);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import static com.mobileer.oboetester.IntentBasedTestSupport.configureStreamsFromBundle;
import static com.mobileer.oboetester.StreamConfiguration.convertChannelMaskToText;

import android.app.Instrumentation;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.CheckBox;
import android.widget.RadioButton;
import android.widget.RadioGroup;
Expand Down Expand Up @@ -88,11 +90,13 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {

public static final int DURATION_SECONDS = 3;
private final static double MIN_REQUIRED_MAGNITUDE = 0.001;
private final static double MAX_SINE_FREQUENCY = 1000.0;
private final static int MAX_SINE_FREQUENCY = 1000;
private final static int TYPICAL_SAMPLE_RATE = 48000;
private final static double FRAMES_PER_CYCLE = TYPICAL_SAMPLE_RATE / MAX_SINE_FREQUENCY;
private final static double PHASE_PER_BIN = 2.0 * Math.PI / FRAMES_PER_CYCLE;
private final static double MAX_ALLOWED_JITTER = 2.0 * PHASE_PER_BIN;
private final static double MAX_ALLOWED_JITTER = 0.5 * PHASE_PER_BIN;
// This must match the value of kPhaseInvalid in BaseSineAnalyzer.h
private final static double PHASE_INVALID = -999.0;
private final static String MAGNITUDE_FORMAT = "%7.5f";

// These define the values returned by the Java API deviceInfo.getChannelMasks().
Expand Down Expand Up @@ -266,18 +270,20 @@ private void gatherData() {
// Only look at the phase if we have a signal.
if (mMagnitude >= MIN_REQUIRED_MAGNITUDE) {
double phase = getPhaseDataPaths();
// 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()));
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;
}
mPhase = phase;
mPhaseCount++;
}
}
Expand Down Expand Up @@ -454,7 +460,8 @@ String getOneLineSummary() {
+ " D=" + actualOutConfig.getDeviceId()
+ ", ch=" + channelText(getOutputChannel(), actualOutConfig.getChannelCount())
+ ", SR=" + actualOutConfig.getSampleRate()
+ ", mag = " + getMagnitudeText(mMaxMagnitude);
+ ", mag = " + getMagnitudeText(mMaxMagnitude)
+ ", jitter = " + getJitterText();
}

@Override
Expand Down Expand Up @@ -533,18 +540,14 @@ int convertJavaInChannelMaskToNativeChannelMask(int javaChannelMask) {
}

void logOneLineSummary(TestResult testResult) {
logOneLineSummary(testResult, "");
}

void logOneLineSummary(TestResult testResult, String extra) {
int result = testResult.result;
String oneLineSummary;
if (result == TEST_RESULT_SKIPPED) {
oneLineSummary = "#" + mAutomatedTestRunner.getTestCount() + extra + ", SKIP";
oneLineSummary = "#" + mAutomatedTestRunner.getTestCount() + ", SKIP";
} else if (result == TEST_RESULT_FAILED) {
oneLineSummary = getOneLineSummary() + extra + ", FAIL";
oneLineSummary = getOneLineSummary() + ", FAIL";
} else {
oneLineSummary = getOneLineSummary() + extra;
oneLineSummary = getOneLineSummary();
}
appendSummary(oneLineSummary + "\n");
}
Expand All @@ -554,11 +557,6 @@ void logBoth(String text) {
appendSummary(text + "\n");
}

void logFailed(String text) {
log(text);
logAnalysis(text + "\n");
}

private void testDeviceOutputInfo(AudioDeviceInfo outputDeviceInfo) throws InterruptedException {
AudioDeviceInfo inputDeviceInfo = findCompatibleInputDevice(outputDeviceInfo.getType());
showDeviceInfo(outputDeviceInfo, inputDeviceInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ private void init() {
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCurrentWidth = w;
mCurrentHeight = h;
mOffsetY = 0.5f * h;
mScaleY = 0.0f - mOffsetY;
mOffsetY = 0.5f * h; // Center waveform vertically in the viewport.
// Scale down so that we can see the top of the waveforms if they are clipped.
mScaleY = -0.95f * mOffsetY; // Negate so positive values are on top.
}

public String getMessage() {
Expand Down Expand Up @@ -121,8 +122,8 @@ protected void onDraw(Canvas canvas) {
float x0 = 0.0f;
if (xScale < 1.0) {
// Draw a vertical bar for multiple samples.
float ymin = mOffsetY;
float ymax = mOffsetY;
float ymin = mOffsetY; // vertical center
float ymax = mOffsetY; // vertical center
for (int i = 0; i < mSampleCount; i++) {
float x1 = i * xScale;
if ((int) x0 != (int) x1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@
android:orientation="horizontal">

<CheckBox
android:id="@+id/checkbox_paths_input_presets"
android:id="@+id/checkbox_paths_all_channels"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="InPre" />
android:text="AllCh" />

<CheckBox
android:id="@+id/checkbox_paths_all_channels"
android:id="@+id/checkbox_paths_input_presets"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="AllCh" />
android:text="InPre" />

<CheckBox
android:id="@+id/checkbox_paths_all_sample_rates"
Expand Down
Loading