diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h index 90836c0bc..7c7fc80f2 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/BaseSineAnalyzer.h @@ -46,6 +46,7 @@ class BaseSineAnalyzer : public LoopbackProcessor { } double getPhaseOffset() { + ALOGD("%s(), mPhaseOffset = %f\n", __func__, mPhaseOffset); return mPhaseOffset; } @@ -129,7 +130,6 @@ class BaseSineAnalyzer : public LoopbackProcessor { double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean)); if (phasePtr != nullptr) { double phase = atan2(cosMean, sinMean); - *phasePtr = phase; } return magnitude; @@ -153,6 +153,7 @@ 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)); resetAccumulator(); diff --git a/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h index dc06dec7a..583ac42b8 100644 --- a/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h +++ b/apps/OboeTester/app/src/main/cpp/analyzer/GlitchAnalyzer.h @@ -54,10 +54,6 @@ class GlitchAnalyzer : public BaseSineAnalyzer { return mSinePeriod; } - float getPhaseOffset() const { - return mPhaseOffset; - } - int32_t getGlitchCount() const { return mGlitchCount; } @@ -188,8 +184,8 @@ class GlitchAnalyzer : public BaseSineAnalyzer { // 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, offset = %f, prev = %f", -// __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset); + ALOGD("%s() mag = %f, mPhaseOffset = %f", + __func__, mMagnitude, mPhaseOffset); if (mMagnitude > mThreshold) { if (fabs(mPhaseOffset) < kMaxPhaseError) { mState = STATE_LOCKED; @@ -232,7 +228,7 @@ class GlitchAnalyzer : public BaseSineAnalyzer { // Must be a multiple of the period or the calculation will not be accurate. if (transformSample(sample, mInputPhase)) { // Adjust phase to account for sample rate drift. - mInputPhase += mPhaseOffset; + // FIXME mInputPhase += mPhaseOffset; mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod; mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod; diff --git a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp index faa1d3259..9cb9ffe6b 100644 --- a/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp +++ b/apps/OboeTester/app/src/main/cpp/jni-bridge.cpp @@ -812,6 +812,12 @@ Java_com_mobileer_oboetester_TestDataPathsActivity_getMaxMagnitude(JNIEnv *env, return engine.mActivityDataPath.getDataPathAnalyzer()->getMaxMagnitude(); } +JNIEXPORT double JNICALL +Java_com_mobileer_oboetester_TestDataPathsActivity_getPhaseDataPaths(JNIEnv *env, + jobject instance) { + return engine.mActivityDataPath.getDataPathAnalyzer()->getPhaseOffset(); +} + JNIEXPORT void JNICALL Java_com_mobileer_oboetester_GlitchActivity_setTolerance(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 012966cfa..1ea62dab6 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 @@ -16,11 +16,15 @@ package com.mobileer.oboetester; +import static com.mobileer.oboetester.StreamConfiguration.UNSPECIFIED; import static com.mobileer.oboetester.StreamConfiguration.convertChannelMaskToText; import android.content.Context; +import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Bundle; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.IOException; @@ -40,6 +44,8 @@ public class BaseAutoGlitchActivity extends GlitchActivity { protected int mGapMillis = DEFAULT_GAP_MILLIS; private String mTestName = ""; + protected AudioManager mAudioManager; + protected ArrayList mTestResults = new ArrayList(); void logDeviceInfo() { @@ -56,7 +62,12 @@ void setTestName(String name) { mTestName = name; } - private static class TestDirection { + @Override + public int getDeviceId() { + return super.getDeviceId(); + } + + private static class TestStreamOptions { public final int channelUsed; public final int channelCount; public final int channelMask; @@ -65,7 +76,7 @@ private static class TestDirection { public final int performanceMode; public final int sharingMode; - public TestDirection(StreamConfiguration configuration, int channelUsed) { + public TestStreamOptions(StreamConfiguration configuration, int channelUsed) { this.channelUsed = channelUsed; channelCount = configuration.getChannelCount(); channelMask = configuration.getChannelMask(); @@ -75,7 +86,7 @@ public TestDirection(StreamConfiguration configuration, int channelUsed) { sharingMode = configuration.getSharingMode(); } - int countDifferences(TestDirection other) { + int countDifferences(TestStreamOptions other) { int count = 0; count += (channelUsed != other.channelUsed) ? 1 : 0; count += (channelCount != other.channelCount) ? 1 : 0; @@ -87,7 +98,7 @@ int countDifferences(TestDirection other) { return count; } - public String comparePassedDirection(String prefix, TestDirection passed) { + public String comparePassedDirection(String prefix, TestStreamOptions passed) { StringBuffer text = new StringBuffer(); text.append(TestDataPathsActivity.comparePassedField(prefix, this, passed, "channelUsed")); text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "channelCount")); @@ -111,8 +122,8 @@ public String toString() { protected static class TestResult { final int testIndex; - final TestDirection input; - final TestDirection output; + final TestStreamOptions input; + final TestStreamOptions output; public final int inputPreset; public final int sampleRate; final String testName; // name or purpose of test @@ -128,8 +139,8 @@ public TestResult(int testIndex, int outputChannel) { this.testIndex = testIndex; this.testName = testName; - input = new TestDirection(inputConfiguration, inputChannel); - output = new TestDirection(outputConfiguration, outputChannel); + input = new TestStreamOptions(inputConfiguration, inputChannel); + output = new TestStreamOptions(outputConfiguration, outputChannel); sampleRate = outputConfiguration.getSampleRate(); this.inputPreset = inputConfiguration.getInputPreset(); } @@ -185,6 +196,7 @@ public int getResult(int result) { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mAutomatedTestRunner = findViewById(R.id.auto_test_runner); mAutomatedTestRunner.setActivity(this); @@ -220,7 +232,7 @@ protected String getConfigText(StreamConfiguration config) { + ", SR = " + String.format(Locale.getDefault(), "%5d", config.getSampleRate()) + ", Perf = " + StreamConfiguration.convertPerformanceModeToText( config.getPerformanceMode()) - + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()) + + ",\n " + StreamConfiguration.convertSharingModeToText(config.getSharingMode()) + ", ch = " + channelText(channel, config.getChannelCount()) + ", cm = " + convertChannelMaskToText(config.getChannelMask()); } @@ -240,14 +252,41 @@ protected String getStreamText(AudioStreamBase stream) { // Run one test based on the requested input/output configurations. @Nullable protected TestResult testInOutConfigurations() throws InterruptedException { - int result = TEST_RESULT_SKIPPED; + +// +// StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; +// StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; +// +// // Check to make sure the input output device types are compatible. +// if (requestedInConfig.getDeviceId() == UNSPECIFIED) { +// if (requestedOutConfig.getDeviceId() != UNSPECIFIED) { +// int compatibleDeviceId = findCompatibleDevice(requestedOutConfig.getDeviceId(), +// true /* isInput */); +// requestedInConfig.setDeviceId(compatibleDeviceId); +// } +// } else if (requestedOutConfig.getDeviceId() == UNSPECIFIED) { +// if (requestedInConfig.getDeviceId() != UNSPECIFIED) { +// int compatibleDeviceId = findCompatibleDevice(requestedInConfig.getDeviceId(), +// false /* isInput */); +// requestedOutConfig.setDeviceId(compatibleDeviceId); +// } +// } + + TestResult testResult = testCurrentConfigurations(); + + return testResult; + } + + + @NonNull + protected TestResult testCurrentConfigurations() throws InterruptedException { mAutomatedTestRunner.incrementTestCount(); if ((getSingleTestIndex() >= 0) && (mAutomatedTestRunner.getTestCount() != getSingleTestIndex())) { return null; } log("========================== #" + mAutomatedTestRunner.getTestCount()); - + int result = 0; StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; @@ -372,10 +411,92 @@ protected TestResult testInOutConfigurations() throws InterruptedException { testResult.setResult(result); mTestResults.add(testResult); } - return testResult; } + protected AudioDeviceInfo getDeviceInfoById(int deviceId) { + AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL); + for (AudioDeviceInfo deviceInfo : devices) { + if (deviceInfo.getId() == deviceId) { + return deviceInfo; + } + } + return null; + } + + protected AudioDeviceInfo getDeviceInfoByType(int deviceType, int flags) { + AudioDeviceInfo[] devices = mAudioManager.getDevices(flags); + for (AudioDeviceInfo deviceInfo : devices) { + if (deviceInfo.getType() == deviceType) { + return deviceInfo; + } + } + return null; + } + + protected boolean isDeviceTypeMixed(int type) { + switch(type) { + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_USB_HEADSET: + return true; + case AudioDeviceInfo.TYPE_USB_DEVICE: + default: + return false; + } + } + + protected int getCompatibleDeviceType(int type) { + int compatibleType; + switch(type) { + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE: + compatibleType = AudioDeviceInfo.TYPE_BUILTIN_MIC; + break; + case AudioDeviceInfo.TYPE_BUILTIN_MIC: + compatibleType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; + break; + default: + compatibleType = type; + break; + } + return compatibleType; + } + + /** + * Scan available device for one with a compatible device type for loopback testing. + * @return deviceId + */ + private int findCompatibleDevice(int deviceId, boolean isLookingForInput) { + AudioDeviceInfo deviceInfo = getDeviceInfoById(deviceId); + if (deviceInfo == null) { + log("deviceInfo is NULL !!!!!!\n"); + return UNSPECIFIED; + } + int compatibleDeviceType = getCompatibleDeviceType(deviceInfo.getType()); + AudioDeviceInfo[] devices = mAudioManager.getDevices( + isLookingForInput ? AudioManager.GET_DEVICES_INPUTS : AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo candidate : devices) { + if (candidate.getType() == compatibleDeviceType) { + return candidate.getId(); + } + } + return UNSPECIFIED; + } + + protected AudioDeviceInfo findCompatibleDevice(AudioDeviceInfo deviceInfo, boolean isLookingForInput) { + int compatibleDeviceType = getCompatibleDeviceType(deviceInfo.getType()); + AudioDeviceInfo[] devices = mAudioManager.getDevices( + isLookingForInput ? AudioManager.GET_DEVICES_INPUTS : AudioManager.GET_DEVICES_OUTPUTS); + for (AudioDeviceInfo candidate : devices) { + if (candidate.getType() == compatibleDeviceType) { + return candidate; + } + } + return null; + } + protected boolean isFinishedEarly() { return false; } diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ManualGlitchActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ManualGlitchActivity.java index 0ab0cc75c..8a2414c1b 100644 --- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ManualGlitchActivity.java +++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ManualGlitchActivity.java @@ -16,12 +16,16 @@ package com.mobileer.oboetester; +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.util.Log; import android.view.View; import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; import android.widget.SeekBar; import android.widget.TextView; @@ -46,6 +50,11 @@ public class ManualGlitchActivity extends GlitchActivity { private CheckBox mForceGlitchesBox; private CheckBox mAutoScopeBox; private WaveformView mWaveformView; + private LinearLayout mLayoutGlitch; + + + private NumberedRadioButtons mInputChannelBoxes; + private NumberedRadioButtons mOutputChannelBoxes; private float[] mWaveform = new float[WAVEFORM_SIZE]; private long mLastDisplayTime; @@ -73,6 +82,55 @@ protected void setToleranceProgress(int progress) { mTextTolerance.setText("Tolerance = " + String.format(Locale.getDefault(), "%5.3f", tolerance)); } + static class NumberedRadioButtons { + LinearLayout mRow; + RadioButton[] mRadioButtons; + + public interface SelectionListener { + void onSelected(int index); + } + + NumberedRadioButtons(Context context, int numBoxes, SelectionListener listener) { + mRow = new LinearLayout(context); + mRow.setOrientation(LinearLayout.HORIZONTAL); + TextView textView = new TextView(context); + textView.setText("IN:"); + mRow.addView(textView); + RadioGroup rg = new RadioGroup(context); + rg.setOrientation(LinearLayout.HORIZONTAL); + mRadioButtons = new RadioButton[numBoxes]; + for (int i = 0; i < numBoxes; i++) { + mRadioButtons[i] = new RadioButton(context); + mRadioButtons[i].setText("" + i); + mRadioButtons[i].setId(i); + rg.addView(mRadioButtons[i]); + } + mRow.addView(rg); + + //set listener to radio button group + rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + listener.onSelected(checkedId); + } + }); + + mRadioButtons[0].setChecked(true); + } + + public View getView() { + return mRow; + } + + public void setMaxEnabled(int max) { + max = Math.min(max, mRadioButtons.length); + for (int i = 0; i < mRadioButtons.length; i++) { + mRadioButtons[i].setEnabled(i < max); + } + mRadioButtons[0].setChecked(true); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -86,6 +144,14 @@ protected void onCreate(Bundle savedInstanceState) { mForceGlitchesBox = (CheckBox) findViewById(R.id.boxForceGlitch); mAutoScopeBox = (CheckBox) findViewById(R.id.boxAutoScope); mWaveformView = (WaveformView) findViewById(R.id.waveview_audio); + + mLayoutGlitch = (LinearLayout) findViewById(R.id.layoutGlitch); + mInputChannelBoxes = new NumberedRadioButtons(this, 8, + (int index) -> setInputChannel(index)); + mLayoutGlitch.addView(mInputChannelBoxes.getView()); + mOutputChannelBoxes = new NumberedRadioButtons(this, 8, + (int index) -> setOutputChannel(index)); + mLayoutGlitch.addView(mOutputChannelBoxes.getView()); } private void setToleranceFader(float tolerance) { @@ -122,7 +188,12 @@ public void giveAdvice(String s) { public void startAudioTest() throws IOException { super.startAudioTest(); + setToleranceProgress(mFaderTolerance.getProgress()); + int inChannels = mAudioInputTester.getCurrentAudioStream().getChannelCount(); + mInputChannelBoxes.setMaxEnabled(inChannels); + int outChannels = mAudioOutTester.getCurrentAudioStream().getChannelCount(); + mOutputChannelBoxes.setMaxEnabled(outChannels); } @Override 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 49e66f4c7..ac6dbda51 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 @@ -19,8 +19,6 @@ import static com.mobileer.oboetester.IntentBasedTestSupport.configureStreamsFromBundle; import static com.mobileer.oboetester.StreamConfiguration.convertChannelMaskToText; -import android.app.Activity; -import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Build; @@ -34,6 +32,23 @@ import java.lang.reflect.Field; import java.util.Locale; +/** + * TODO + * Select device types based on priority or maybe offer checkboxes. + * Print devices types being tested. + * Avoid mismatch such as mic input and USB output. + * Avoid duplicating tests by not using Unspecified (except for ch-mask) + * + * Find available device types. + * Decide which device types to test. + * For each type find partner type, then decide other options: + * channels for speaker/mic and wired headset usb headset get mixed to one + * so allow channel mask downmix test + * check each channelCount if specified + * channels for USB device are 1:1, so match channels directly, FFT per input? + * + */ + /** * Play a recognizable tone on each channel of each speaker device * and listen for the result through a microphone. @@ -46,7 +61,11 @@ * signal is or whether it is corrupted. A noisy room may have * energy at the target frequency but the phase will be random. * - * This test requires a quiet room but no other hardware. + * This test requires a quiet room if you are testing speaker/mic pairs. + * It can also be used to test using analog loopback adapters + * or USB devices configured in loopback mode. + * + * */ public class TestDataPathsActivity extends BaseAutoGlitchActivity { @@ -122,7 +141,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity { private double mPhaseErrorSum; private double mPhaseErrorCount; - AudioManager mAudioManager; private CheckBox mCheckBoxInputPresets; private CheckBox mCheckBoxInputDevices; private CheckBox mCheckBoxOutputDevices; @@ -233,7 +251,7 @@ private void gatherData() { mMagnitude, mMaxMagnitude)); // Only look at the phase if we have a signal. if (mMagnitude >= MIN_REQUIRED_MAGNITUDE) { - double phase = getPhase(); + 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; @@ -294,6 +312,8 @@ public String getShortReport() { native double getMagnitude(); native double getMaxMagnitude(); + native double getPhaseDataPaths(); + @Override protected void inflateActivity() { setContentView(R.layout.activity_data_paths); @@ -302,7 +322,6 @@ protected void inflateActivity() { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mCheckBoxInputPresets = (CheckBox)findViewById(R.id.checkbox_paths_input_presets); mCheckBoxInputDevices = (CheckBox)findViewById(R.id.checkbox_paths_input_devices); mCheckBoxOutputDevices = (CheckBox)findViewById(R.id.checkbox_paths_output_devices); @@ -463,8 +482,9 @@ void setupDeviceCombo(int inputChannelCount, setOutputChannel(outputChannel); } - private TestResult testConfigurationsAddMagJitter() throws InterruptedException { - TestResult testResult = testInOutConfigurations(); + @Override + protected TestResult testCurrentConfigurations() throws InterruptedException { + TestResult testResult = super.testCurrentConfigurations(); if (testResult != null) { testResult.addComment("mag = " + TestDataPathsActivity.getMagnitudeText(mMagnitude) + ", jitter = " + getJitterText()); @@ -487,7 +507,7 @@ void testPresetCombo(int inputPreset, requestedInConfig.setMMap(mmapEnabled); mMagnitude = -1.0; - TestResult testResult = testConfigurationsAddMagJitter(); + TestResult testResult = testCurrentConfigurations(); if (testResult != null) { int result = testResult.result; String extra = ", inPre = " @@ -557,7 +577,7 @@ void testInputDeviceCombo(int deviceId, requestedInConfig.setMMap(mmapEnabled); mMagnitude = -1.0; - TestResult testResult = testConfigurationsAddMagJitter(); + TestResult testResult = testCurrentConfigurations(); if (testResult != null) { logOneLineSummary(testResult); } @@ -718,7 +738,7 @@ void testOutputDeviceCombo(int deviceId, requestedOutConfig.setMMap(mmapEnabled); mMagnitude = -1.0; - TestResult testResult = testConfigurationsAddMagJitter(); + TestResult testResult = testCurrentConfigurations(); if (testResult != null) { logOneLineSummary(testResult); int result = testResult.result; @@ -772,54 +792,195 @@ void testOutputDevices() throws InterruptedException { if (!deviceInfo.isSink()) continue; int deviceType = deviceInfo.getType(); if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER - || deviceType == TYPE_BUILTIN_SPEAKER_SAFE) { - int id = deviceInfo.getId(); - int[] channelCounts = deviceInfo.getChannelCounts(); + || deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE + || deviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET + || deviceType == AudioDeviceInfo.TYPE_USB_HEADSET + || deviceType == AudioDeviceInfo.TYPE_USB_DEVICE + ) { numTested++; - // Always test mono and stereo. - testOutputDeviceCombo(id, deviceType, 1, 0, 0, 48000); - testOutputDeviceCombo(id, deviceType, 2, 0, 0, 48000); - testOutputDeviceCombo(id, deviceType, 2, 0, 1, 48000); - if (channelCounts.length > 0) { - for (int numChannels : channelCounts) { - // Test higher channel counts. - if (numChannels > 2) { - log("numChannels = " + numChannels + "\n"); - for (int channel = 0; channel < numChannels; channel++) { - testOutputDeviceCombo(id, deviceType, numChannels, 0, channel, - 48000); - } - } - } + testDeviceOutputInfo(deviceInfo); + } else { + log("Device skipped because DeviceType is not testable."); + } + } + if (numTested == 0) { + log("NO OUTPUT DEVICE FOUND!\n"); + } + } + + private void testDeviceOutputInfo(AudioDeviceInfo outputDeviceInfo) throws InterruptedException { + AudioDeviceInfo inputDeviceInfo = findCompatibleDevice( outputDeviceInfo, true); + if (inputDeviceInfo == null) { + log("ERROR - cannot find compatible device type!\n"); + return; + } + + log("=========== OUTPUT TYPE = " + + AudioDeviceInfoConverter.typeToString(outputDeviceInfo.getType()) + "\n"); + StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; + StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; + requestedInConfig.reset(); + requestedOutConfig.reset(); + + requestedInConfig.setDeviceId(inputDeviceInfo.getId()); + requestedOutConfig.setDeviceId(outputDeviceInfo.getId()); + + testOutputChannelCounts(inputDeviceInfo, outputDeviceInfo); +/* + runOnUiThread(() -> mCheckBoxAllSampleRates.setEnabled(false)); + if (mCheckBoxAllSampleRates.isChecked()) { + for (int sampleRate : SAMPLE_RATES) { + testOutputDeviceCombo(id, deviceType, 1, 0, 0, sampleRate); + } + } + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2 + && deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { + runOnUiThread(() -> mCheckBoxAllOutputChannelMasks.setEnabled(false)); + + for (int channelMask : mCheckBoxAllOutputChannelMasks.isChecked() ? + ALL_OUTPUT_CHANNEL_MASKS : SHORT_OUTPUT_CHANNEL_MASKS) { + log("channelMask = " + convertChannelMaskToText(channelMask) + "\n"); + int channelCount = Integer.bitCount(channelMask); + for (int channel = 0; channel < channelCount; channel++) { + testOutputDeviceCombo(id, deviceType, channelCount, channelMask, + channel, 48000); } + } + } + */ + } - runOnUiThread(() -> mCheckBoxAllSampleRates.setEnabled(false)); - if (mCheckBoxAllSampleRates.isChecked()) { - for (int sampleRate : SAMPLE_RATES) { - testOutputDeviceCombo(id, deviceType, 1, 0, 0, sampleRate); + private boolean arrayContains(int[] a, int b) { + for (int c: a) { + if (c == b) return true; + } + return false; + } + + private void testOutputChannelCounts(AudioDeviceInfo inputDeviceInfo, AudioDeviceInfo outputDeviceInfo) throws InterruptedException { + StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; + StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; + // If not channels counts reported then test mono and stereo + int[] inputChannelCounts = inputDeviceInfo.getChannelCounts(); + int[] outputChannelCounts = outputDeviceInfo.getChannelCounts(); + if (isDeviceTypeMixed(outputDeviceInfo.getType())) { + requestedInConfig.setChannelCount(1); + setInputChannel(0); + if (outputChannelCounts.length == 0) { + requestedOutConfig.setChannelCount(1); + setOutputChannel(0); + testPerformancePaths(); + + requestedOutConfig.setChannelCount(2); + testPerformancePaths(); + + setOutputChannel(1); + testPerformancePaths(); + } else { + for (int numChannels : outputChannelCounts) { + log("numChannels = " + numChannels + "\n"); + requestedOutConfig.setChannelCount(numChannels); + for (int channel = 0; channel < numChannels; channel++) { + setOutputChannel(channel); + testPerformancePaths(); } } - - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2 - && deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) { - runOnUiThread(() -> mCheckBoxAllOutputChannelMasks.setEnabled(false)); - - for (int channelMask : mCheckBoxAllOutputChannelMasks.isChecked() ? - ALL_OUTPUT_CHANNEL_MASKS : SHORT_OUTPUT_CHANNEL_MASKS) { - log("channelMask = " + convertChannelMaskToText(channelMask) + "\n"); - int channelCount = Integer.bitCount(channelMask); - for (int channel = 0; channel < channelCount; channel++) { - testOutputDeviceCombo(id, deviceType, channelCount, channelMask, - channel, 48000); + } + } else { + // Test two matching stereo channels. + if (outputChannelCounts.length == 0) { + requestedInConfig.setChannelCount(2); + requestedOutConfig.setChannelCount(2); + setInputChannel(0); + setOutputChannel(0); + log("test matching stereo channel[0]\n"); + testPerformancePaths(); + + setInputChannel(1); + setOutputChannel(1); + log("test matching stereo channel[1]\n"); + testPerformancePaths(); + } else { + // Test each matching channels for each channelCount + for (int numChannels : outputChannelCounts) { + log("numChannels = " + numChannels + "\n"); + if (arrayContains(inputChannelCounts, numChannels)) { + requestedInConfig.setChannelCount(numChannels); + requestedOutConfig.setChannelCount(numChannels); + for (int channel = 0; channel < numChannels; channel++) { + setInputChannel(channel); + setOutputChannel(channel); + log("test matching channel = " + channel + "\n"); + testPerformancePaths(); } } } - } else { - log("Device skipped because DeviceType is not testable."); } } - if (numTested == 0) { - log("NO OUTPUT DEVICE FOUND!\n"); + // Restore defaults. + requestedInConfig.setChannelCount(1); + setInputChannel(0); + requestedOutConfig.setChannelCount(1); + setOutputChannel(0); + } + + + private void testPerformancePaths() throws InterruptedException { + StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; + StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; + + requestedInConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY); + requestedOutConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY); + requestedInConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED); + requestedOutConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED); + +// if (NativeEngine.isMMapSupported()) { +// requestedInConfig.setMMap(true); +// requestedOutConfig.setMMap(true); +// testCurrentConfigurations(); +// } + + requestedInConfig.setMMap(false); + requestedOutConfig.setMMap(false); + testCurrentConfigurations(); + + requestedInConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_NONE); + requestedOutConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_NONE); + testCurrentConfigurations(); + } + + private void testOutputDeviceTypes() throws InterruptedException { + // Determine which output device type to test based on priorities. + AudioDeviceInfo info = getDeviceInfoByType(AudioDeviceInfo.TYPE_USB_DEVICE, + AudioManager.GET_DEVICES_OUTPUTS); + if (info != null) { + testDeviceOutputInfo(info); + return; + } + info = getDeviceInfoByType(AudioDeviceInfo.TYPE_USB_HEADSET, + AudioManager.GET_DEVICES_OUTPUTS); + if (info != null) { + testDeviceOutputInfo(info); + return; + } + info = getDeviceInfoByType(AudioDeviceInfo.TYPE_WIRED_HEADSET, + AudioManager.GET_DEVICES_OUTPUTS); + if (info != null) { + testDeviceOutputInfo(info); + return; + } + // Test both SPEAKER and SPEAKER_SAFE + info = getDeviceInfoByType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, + AudioManager.GET_DEVICES_OUTPUTS); + if (info != null) { + testDeviceOutputInfo(info); + // Continue on to SPEAKER_SAFE + } + info = getDeviceInfoByType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE, + AudioManager.GET_DEVICES_OUTPUTS); + if (info != null) { + testDeviceOutputInfo(info); } } @@ -836,18 +997,20 @@ public void runTest() { runOnUiThread(() -> keepScreenOn(true)); - if (mCheckBoxInputPresets.isChecked()) { - runOnUiThread(() -> mCheckBoxInputPresets.setEnabled(false)); - testInputPresets(); - } - if (mCheckBoxInputDevices.isChecked()) { - runOnUiThread(() -> mCheckBoxInputDevices.setEnabled(false)); - testInputDevices(); - } - if (mCheckBoxOutputDevices.isChecked()) { - runOnUiThread(() -> mCheckBoxOutputDevices.setEnabled(false)); - testOutputDevices(); - } + testOutputDeviceTypes(); + +// if (mCheckBoxInputPresets.isChecked()) { +// runOnUiThread(() -> mCheckBoxInputPresets.setEnabled(false)); +// testInputPresets(); +// } +// if (mCheckBoxInputDevices.isChecked()) { +// runOnUiThread(() -> mCheckBoxInputDevices.setEnabled(false)); +// testInputDevices(); +// } +// if (mCheckBoxOutputDevices.isChecked()) { +// runOnUiThread(() -> mCheckBoxOutputDevices.setEnabled(false)); +// testOutputDevices(); +// } compareFailedTestsWithNearestPassingTest(); diff --git a/apps/OboeTester/app/src/main/res/layout/activity_manual_glitches.xml b/apps/OboeTester/app/src/main/res/layout/activity_manual_glitches.xml index 735a7b243..f470e01d8 100644 --- a/apps/OboeTester/app/src/main/res/layout/activity_manual_glitches.xml +++ b/apps/OboeTester/app/src/main/res/layout/activity_manual_glitches.xml @@ -44,6 +44,7 @@ android:orientation="horizontal" />