aboutsummaryrefslogtreecommitdiff
path: root/apps/OboeTester/app/src/main/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'apps/OboeTester/app/src/main/java/com')
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceAdapter.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceInfoConverter.java57
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceListEntry.java14
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/audio_device/CommunicationDeviceSpinner.java21
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/miditools/synth/SynthEngine.java3
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AnalyzerActivity.java30
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioOutputTester.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioQueryTools.java24
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioRecordThread.java7
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamBase.java86
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamTester.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedGlitchActivity.java39
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedTestRunner.java20
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseAutoGlitchActivity.java210
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseOboeTesterActivity.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BufferSizeView.java11
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/CommunicationDeviceView.java111
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DeviceReportActivity.java126
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DynamicWorkloadActivity.java388
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/EchoActivity.java30
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalFileWriter.java55
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalTapToToneActivity.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java11
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/GlitchActivity.java87
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java29
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MainActivity.java42
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ManualGlitchActivity.java128
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MicrophoneInfoConverter.java6
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MultiLineChart.java254
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeEngine.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeSniffer.java36
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioOutputStream.java2
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java74
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/RoundTripLatencyActivity.java61
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java157
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java45
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneActivity.java56
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneTester.java10
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java241
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java210
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDataPathsActivity.java901
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java209
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestInputActivity.java29
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java65
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivityBase.java9
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestPlugLatencyActivity.java86
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestRouteDuringCallbackActivity.java177
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/VolumeBarView.java4
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WaveformView.java24
-rw-r--r--apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WorkloadView.java43
50 files changed, 3379 insertions, 873 deletions
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceAdapter.java b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceAdapter.java
index 6e22da99..6444b2a7 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceAdapter.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceAdapter.java
@@ -16,13 +16,13 @@ package com.mobileer.audio_device;
*/
import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.mobileer.oboetester.R;
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceInfoConverter.java b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceInfoConverter.java
index d0228fdb..14c635d2 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceInfoConverter.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceInfoConverter.java
@@ -15,7 +15,13 @@ package com.mobileer.audio_device;
* limitations under the License.
*/
+import android.media.AudioDescriptor;
import android.media.AudioDeviceInfo;
+import android.media.AudioProfile;
+import android.os.Build;
+
+import java.util.List;
+import java.util.Locale;
public class AudioDeviceInfoConverter {
@@ -49,11 +55,11 @@ public class AudioDeviceInfoConverter {
sb.append("\nChannel masks: ");
int[] channelMasks = adi.getChannelMasks();
- sb.append(intArrayToString(channelMasks));
+ sb.append(intArrayToStringHex(channelMasks));
sb.append("\nChannel index masks: ");
int[] channelIndexMasks = adi.getChannelIndexMasks();
- sb.append(intArrayToString(channelIndexMasks));
+ sb.append(intArrayToStringHex(channelIndexMasks));
sb.append("\nEncodings: ");
int[] encodings = adi.getEncodings();
@@ -62,6 +68,36 @@ public class AudioDeviceInfoConverter {
sb.append("\nSample Rates: ");
int[] sampleRates = adi.getSampleRates();
sb.append(intArrayToString(sampleRates));
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ sb.append("\nAddress: ");
+ sb.append(adi.getAddress());
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+ sb.append("\nEncapsulation Metadata Types: ");
+ int[] encapsulationMetadataTypes = adi.getEncapsulationMetadataTypes();
+ sb.append(intArrayToString(encapsulationMetadataTypes));
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
+ sb.append("\nEncapsulation Modes: ");
+ int[] encapsulationModes = adi.getEncapsulationModes();
+ sb.append(intArrayToString(encapsulationModes));
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ sb.append("\nAudio Descriptors: ");
+ List<AudioDescriptor> audioDescriptors = adi.getAudioDescriptors();
+ sb.append(audioDescriptors);
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
+ sb.append("\nAudio Profiles: ");
+ List<AudioProfile> audioProfiles = adi.getAudioProfiles();
+ sb.append(audioProfiles);
+ }
+
sb.append("\n");
return sb.toString();
}
@@ -76,7 +112,22 @@ public class AudioDeviceInfoConverter {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < integerArray.length; i++){
sb.append(integerArray[i]);
- if (i != integerArray.length -1) sb.append(" ");
+ if (i != integerArray.length - 1) sb.append(" ");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Converts an integer array into a hexadecimal string where each int is separated by a space
+ *
+ * @param integerArray the integer array to convert to a string
+ * @return string containing all the integer values separated by spaces
+ */
+ private static String intArrayToStringHex(int[] integerArray){
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < integerArray.length; i++){
+ sb.append(String.format(Locale.getDefault(), "0x%02X", integerArray[i]));
+ if (i != integerArray.length - 1) sb.append(" ");
}
return sb.toString();
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceListEntry.java b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceListEntry.java
index a0ea1837..baceb475 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceListEntry.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/AudioDeviceListEntry.java
@@ -32,9 +32,16 @@ public class AudioDeviceListEntry {
private int mId;
private String mName;
- public AudioDeviceListEntry(int deviceId, String deviceName){
+ private AudioDeviceInfo mDeviceInfo;
+
+ public AudioDeviceListEntry(int deviceId, String deviceName) {
+ this(deviceId, deviceName, null);
+ }
+
+ public AudioDeviceListEntry(int deviceId, String deviceName, AudioDeviceInfo deviceInfo) {
mId = deviceId;
mName = deviceName;
+ mDeviceInfo = deviceInfo;
}
public int getId() {
@@ -45,6 +52,8 @@ public class AudioDeviceListEntry {
return mName;
}
+ public AudioDeviceInfo getDeviceInfo() { return mDeviceInfo; }
+
public String toString(){
return getName();
}
@@ -87,7 +96,8 @@ public class AudioDeviceListEntry {
listEntries.add(new AudioDeviceListEntry(info.getId(),
info.getId() + ": " +
info.getProductName() + " " +
- AudioDeviceInfoConverter.typeToString(info.getType())));
+ AudioDeviceInfoConverter.typeToString(info.getType()),
+ info));
}
}
return listEntries;
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/CommunicationDeviceSpinner.java b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/CommunicationDeviceSpinner.java
index fcdcf412..46a924f5 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/CommunicationDeviceSpinner.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/audio_device/CommunicationDeviceSpinner.java
@@ -29,8 +29,10 @@ import com.mobileer.oboetester.R;
import java.util.List;
public class CommunicationDeviceSpinner extends Spinner {
- private static final int CLEAR_DEVICE_ID = 0;
private static final String TAG = CommunicationDeviceSpinner.class.getName();
+ // menu positions
+ public static final int POS_CLEAR = 0;
+ public static final int POS_DEVICES = 1; // base position for device list
private AudioDeviceAdapter mDeviceAdapter;
private AudioManager mAudioManager;
private Context mContext;
@@ -85,10 +87,9 @@ public class CommunicationDeviceSpinner extends Spinner {
mDeviceAdapter = new AudioDeviceAdapter(context);
setAdapter(mDeviceAdapter);
- // Add a default entry to the list and select it
- mDeviceAdapter.add(new AudioDeviceListEntry(CLEAR_DEVICE_ID,
- mContext.getString(R.string.auto_select)));
- setSelection(0);
+ // Add default entries to the list and select one.
+ addDefaultDevicesOptions();
+ setSelection(POS_CLEAR);
setupCommunicationDeviceListener();
}
@@ -108,9 +109,8 @@ public class CommunicationDeviceSpinner extends Spinner {
private void updateDeviceList() {
mDeviceAdapter.clear();
- mDeviceAdapter.add(new AudioDeviceListEntry(CLEAR_DEVICE_ID,
- mContext.getString(R.string.clear)));
- setSelection(0);
+ addDefaultDevicesOptions();
+ setSelection(POS_CLEAR);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
List<AudioDeviceInfo> commDeviceList = mAudioManager.getAvailableCommunicationDevices();
mCommDeviceArray = commDeviceList.toArray(new AudioDeviceInfo[0]);
@@ -123,4 +123,9 @@ public class CommunicationDeviceSpinner extends Spinner {
}
}, null);
}
+
+ private void addDefaultDevicesOptions() {
+ mDeviceAdapter.add(new AudioDeviceListEntry(POS_CLEAR,
+ mContext.getString(R.string.clear_comm)));
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/miditools/synth/SynthEngine.java b/apps/OboeTester/app/src/main/java/com/mobileer/miditools/synth/SynthEngine.java
index 1e1c3a8b..da5d7512 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/miditools/synth/SynthEngine.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/miditools/synth/SynthEngine.java
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
+import java.util.Locale;
/**
* Very simple polyphonic, single channel synthesizer. It runs a background
@@ -178,7 +179,7 @@ public class SynthEngine extends MidiReceiver {
public void logMidiMessage(byte[] data, int offset, int count) {
String text = "Received: ";
for (int i = 0; i < count; i++) {
- text += String.format("0x%02X, ", data[offset + i]);
+ text += String.format(Locale.getDefault(), "0x%02X, ", data[offset + i]);
}
Log.i(TAG, text);
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AnalyzerActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AnalyzerActivity.java
index 368d5bc8..5e22c0bc 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AnalyzerActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AnalyzerActivity.java
@@ -21,16 +21,17 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
+import java.util.Locale;
/**
* Activity to measure latency on a full duplex stream.
@@ -78,8 +79,8 @@ public class AnalyzerActivity extends TestInputActivity {
report.append("build.fingerprint = " + Build.FINGERPRINT + "\n");
try {
PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
- report.append(String.format("test.version = %s\n", pinfo.versionName));
- report.append(String.format("test.version.code = %d\n", pinfo.versionCode));
+ report.append(String.format(Locale.getDefault(), "test.version = %s\n", pinfo.versionName));
+ report.append(String.format(Locale.getDefault(), "test.version.code = %d\n", pinfo.versionCode));
} catch (PackageManager.NameNotFoundException e) {
}
report.append("time.millis = " + System.currentTimeMillis() + "\n");
@@ -87,18 +88,18 @@ public class AnalyzerActivity extends TestInputActivity {
// INPUT
report.append(mAudioInputTester.actualConfiguration.dump());
AudioStreamBase inStream = mAudioInputTester.getCurrentAudioStream();
- report.append(String.format("in.burst.frames = %d\n", inStream.getFramesPerBurst()));
- report.append(String.format("in.xruns = %d\n", inStream.getXRunCount()));
+ report.append(String.format(Locale.getDefault(), "in.burst.frames = %d\n", inStream.getFramesPerBurst()));
+ report.append(String.format(Locale.getDefault(), "in.xruns = %d\n", inStream.getXRunCount()));
// OUTPUT
report.append(mAudioOutTester.actualConfiguration.dump());
AudioStreamBase outStream = mAudioOutTester.getCurrentAudioStream();
- report.append(String.format("out.burst.frames = %d\n", outStream.getFramesPerBurst()));
+ report.append(String.format(Locale.getDefault(), "out.burst.frames = %d\n", outStream.getFramesPerBurst()));
int bufferSize = outStream.getBufferSizeInFrames();
- report.append(String.format("out.buffer.size.frames = %d\n", bufferSize));
+ report.append(String.format(Locale.getDefault(), "out.buffer.size.frames = %d\n", bufferSize));
int bufferCapacity = outStream.getBufferCapacityInFrames();
- report.append(String.format("out.buffer.capacity.frames = %d\n", bufferCapacity));
- report.append(String.format("out.xruns = %d\n", outStream.getXRunCount()));
+ report.append(String.format(Locale.getDefault(), "out.buffer.capacity.frames = %d\n", bufferCapacity));
+ report.append(String.format(Locale.getDefault(), "out.xruns = %d\n", outStream.getXRunCount()));
return report.toString();
}
@@ -160,11 +161,4 @@ public class AnalyzerActivity extends TestInputActivity {
}
}
- private void writeTestInBackground(final String resultString) {
- new Thread() {
- public void run() {
- writeTestResult(resultString);
- }
- }.start();
- }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioOutputTester.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioOutputTester.java
index 2c305fb5..04c3f2e3 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioOutputTester.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioOutputTester.java
@@ -51,6 +51,10 @@ public class AudioOutputTester extends AudioStreamTester {
mOboeAudioOutputStream.setSignalType(type);
}
+ public void setAmplitude(float amplitude) {
+ mOboeAudioOutputStream.setAmplitude(amplitude);
+ }
+
public int getLastErrorCallbackResult() {return mOboeAudioOutputStream.getLastErrorCallbackResult();};
public long getFramesRead() {return mOboeAudioOutputStream.getFramesRead();};
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioQueryTools.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioQueryTools.java
index 421d098b..a4dd6fc4 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioQueryTools.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioQueryTools.java
@@ -23,6 +23,7 @@ import android.os.Build;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.util.Locale;
public class AudioQueryTools {
private static String GETPROP_EXECUTABLE_PATH = "/system/bin/getprop";
@@ -58,6 +59,10 @@ public class AudioQueryTools {
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_PRO));
report.append("\nLowLatency Feature : "
+ packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY));
+ report.append("\nAudio Output Feature : "
+ + packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT));
+ report.append("\nMicrophone Feature : "
+ + packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE));
report.append("\nMIDI Feature : "
+ packageManager.hasSystemFeature(PackageManager.FEATURE_MIDI));
report.append("\nUSB Host Feature : "
@@ -69,14 +74,24 @@ public class AudioQueryTools {
public static String getAudioManagerReport(AudioManager audioManager) {
StringBuffer report = new StringBuffer();
- String unprocessedSupport = audioManager.getParameters(AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED);
- report.append("\nSUPPORT_UNPROCESSED : " + ((unprocessedSupport == null) ? "null" : "yes"));
+ String unprocessedSupport = audioManager.getProperty(
+ AudioManager.PROPERTY_SUPPORT_AUDIO_SOURCE_UNPROCESSED);
+ report.append("\nSUPPORT_AUDIO_SOURCE_UNPROCESSED : " + ((unprocessedSupport == null) ?
+ "null" : unprocessedSupport));
+ String outputFramesPerBuffer = audioManager.getProperty(
+ AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
+ report.append("\nOUTPUT_FRAMES_PER_BUFFER : " + ((outputFramesPerBuffer == null) ?
+ "null" : outputFramesPerBuffer));
+ String outputSampleRate = audioManager.getProperty(
+ AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
+ report.append("\nOUTPUT_SAMPLE_RATE : " + ((outputSampleRate == null) ? "null" :
+ outputSampleRate));
return report.toString();
}
private static String formatKeyValueLine(String key, String value) {
int numSpaces = Math.max(1, 21 - key.length());
- String spaces = String.format("%0" + numSpaces + "d", 0).replace("0", " ");
+ String spaces = String.format(Locale.getDefault(), "%0" + numSpaces + "d", 0).replace("0", " ");
return "\n" + key + spaces + ": " + value;
}
@@ -112,6 +127,9 @@ public class AudioQueryTools {
}
public static String getMediaPerformanceClass() {
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S) {
+ return formatKeyValueLine("Media Perf Class", "not supported");
+ }
int mpc = Build.VERSION.MEDIA_PERFORMANCE_CLASS;
String text = (mpc == 0) ? "not declared" : convertSdkToShortName(mpc);
return formatKeyValueLine("Media Perf Class",
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioRecordThread.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioRecordThread.java
index 7b472237..bc254552 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioRecordThread.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioRecordThread.java
@@ -17,6 +17,7 @@
package com.mobileer.oboetester;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
@@ -40,6 +41,8 @@ class AudioRecordThread implements Runnable {
private int mTaskCountdown;
private boolean mCaptureEnabled = true;
+ private AudioDeviceInfo mDeviceInfo;
+
public AudioRecordThread(int frameRate, int channelCount, int maxFrames) {
mSampleRate = frameRate;
mChannelCount = channelCount;
@@ -59,6 +62,7 @@ class AudioRecordThread implements Runnable {
channelConfig,
audioFormat,
2 * minRecordBuffSizeInBytes);
+ mRecorder.setPreferredDevice(mDeviceInfo);
if (mRecorder.getState() == AudioRecord.STATE_UNINITIALIZED) {
throw new RuntimeException("Could not make the AudioRecord - UNINITIALIZED");
}
@@ -159,4 +163,7 @@ class AudioRecordThread implements Runnable {
return mCaptureBuffer.readMostRecent(buffer);
}
+ public void setInputDevice(AudioDeviceInfo deviceInfo) {
+ mDeviceInfo = deviceInfo;
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamBase.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamBase.java
index b6eb6ff1..14eb163e 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamBase.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamBase.java
@@ -17,6 +17,7 @@
package com.mobileer.oboetester;
import java.io.IOException;
+import java.util.Locale;
/**
* Base class for any audio input or output.
@@ -25,10 +26,43 @@ public abstract class AudioStreamBase {
private StreamConfiguration mRequestedStreamConfiguration;
private StreamConfiguration mActualStreamConfiguration;
- AudioStreamBase.DoubleStatistics mLatencyStatistics;
-
+ private AudioStreamBase.DoubleStatistics mLatencyStatistics;
+ private SampleRateMonitor mSampleRateMonitor = new SampleRateMonitor();
private int mBufferSizeInFrames;
+ private class SampleRateMonitor {
+ private static final int SIZE = 16; // power of 2
+ private static final long MASK = SIZE - 1L;
+ private long[] times = new long[SIZE];
+ private long[] frames = new long[SIZE];
+ private long cursor;
+
+ void add(long numFrames) {
+ int index = (int) (cursor & MASK);
+ frames[index] = numFrames;
+ times[index] = System.currentTimeMillis();
+ cursor++;
+ }
+
+ int getRate() {
+ if (cursor < 2) return 0;
+ long numValid = Math.min((long)SIZE, cursor);
+ int oldestIndex = (int)((cursor - numValid) & MASK);
+ int newestIndex = (int)((cursor - 1) & MASK);
+ long deltaTime = times[newestIndex] - times[oldestIndex];
+ long deltaFrames = frames[newestIndex] - frames[oldestIndex];
+ if (deltaTime <= 0) {
+ return -1;
+ }
+ long sampleRate = (deltaFrames * 1000) / deltaTime;
+ return (int) sampleRate;
+ }
+
+ void reset() {
+ cursor = 0;
+ }
+ }
+
public StreamStatus getStreamStatus() {
StreamStatus status = new StreamStatus();
status.bufferSize = getBufferSizeInFrames();
@@ -41,6 +75,8 @@ public abstract class AudioStreamBase {
status.callbackTimeStr = getCallbackTimeStr();
status.cpuLoad = getCpuLoad();
status.state = getState();
+ mSampleRateMonitor.add(status.framesRead);
+ status.measuredRate = mSampleRateMonitor.getRate();
return status;
}
@@ -48,6 +84,11 @@ public abstract class AudioStreamBase {
return mLatencyStatistics;
}
+ public void setPerformanceHintEnabled(boolean checked) {
+ }
+ public void setHearWorkload(boolean checked) {
+ }
+
public static class DoubleStatistics {
private double sum;
private int count;
@@ -68,7 +109,7 @@ public abstract class AudioStreamBase {
public String dump() {
if (count == 0) return "?";
- return String.format("%3.1f/%3.1f/%3.1f ms", minimum, getAverage(), maximum);
+ return String.format(Locale.getDefault(), "%3.1f/%3.1f/%3.1f ms", minimum, getAverage(), maximum);
}
}
@@ -84,8 +125,9 @@ public abstract class AudioStreamBase {
public int state;
public long callbackCount;
public int framesPerCallback;
- public double cpuLoad;
+ public float cpuLoad;
public String callbackTimeStr;
+ public int measuredRate;
// These are constantly changing.
String dump(int framesPerBurst) {
@@ -96,21 +138,22 @@ public abstract class AudioStreamBase {
buffer.append("time between callbacks = " + callbackTimeStr + "\n");
- buffer.append("written "
- + String.format("0x%08X", framesWritten)
- + " - read " + String.format("0x%08X", framesRead)
- + " = " + (framesWritten - framesRead) + " frames\n");
+ buffer.append("wr "
+ + String.format(Locale.getDefault(), "%Xh", framesWritten)
+ + " - rd " + String.format(Locale.getDefault(), "%Xh", framesRead)
+ + " = " + (framesWritten - framesRead) + " fr"
+ + ", SR = " + ((measuredRate <= 0) ? "?" : measuredRate) + "\n");
- String cpuLoadText = String.format("%2d%c", (int)(cpuLoad * 100), '%');
+ String cpuLoadText = String.format(Locale.getDefault(), "%2d%c", (int)(cpuLoad * 100), '%');
buffer.append(
convertStateToString(state)
+ ", #cb=" + callbackCount
- + ", f/cb=" + String.format("%3d", framesPerCallback)
- + ", " + cpuLoadText + " cpu"
+ + ", f/cb=" + String.format(Locale.getDefault(), "%3d", framesPerCallback)
+ + ", " + cpuLoadText + " CPU"
+ "\n");
buffer.append("buffer size = ");
- if (bufferSize < 0) {
+ if (bufferSize <= 0 || framesPerBurst <= 0) {
buffer.append("?");
} else {
int numBuffers = bufferSize / framesPerBurst;
@@ -151,14 +194,19 @@ public abstract class AudioStreamBase {
mLatencyStatistics = new AudioStreamBase.DoubleStatistics();
}
+ public void onStart() {
+ mSampleRateMonitor.reset();
+ }
+ public void onStop() {
+ mSampleRateMonitor.reset();
+ }
+
public abstract boolean isInput();
public void startPlayback() throws IOException {}
public void stopPlayback() throws IOException {}
- public abstract int write(float[] buffer, int offset, int length);
-
public abstract void close();
public int getChannelCount() {
@@ -195,17 +243,15 @@ public abstract class AudioStreamBase {
public double getLatency() { return -1.0; }
- public double getCpuLoad() { return 0.0; }
+ public float getCpuLoad() { return 0.0f; }
+ public float getAndResetMaxCpuLoad() { return 0.0f; }
+ public int getAndResetCpuMask() { return 0; }
public String getCallbackTimeStr() { return "?"; };
public int getState() { return -1; }
- public boolean isThresholdSupported() {
- return false;
- }
-
- public void setWorkload(double workload) {}
+ public void setWorkload(int workload) {}
public abstract int getXRunCount();
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamTester.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamTester.java
index 32bc2ae6..be740b3c 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamTester.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AudioStreamTester.java
@@ -33,7 +33,7 @@ class AudioStreamTester {
}
public void reset() {
- setWorkload(0.0);
+ setWorkload(0);
requestedConfiguration.reset(); // TODO consider making new ones
actualConfiguration.reset();
}
@@ -46,7 +46,7 @@ class AudioStreamTester {
mCurrentAudioStream.startPlayback();
}
- public void setWorkload(double workload) {
+ public void setWorkload(int workload) {
mCurrentAudioStream.setWorkload(workload);
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedGlitchActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedGlitchActivity.java
index e8befcde..ae53a7f6 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedGlitchActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedGlitchActivity.java
@@ -1,3 +1,19 @@
+/*
+ * 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.
+ */
+
package com.mobileer.oboetester;
import android.os.Bundle;
@@ -5,6 +21,12 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.Spinner;
+/**
+ * Look for glitches with various configurations.
+ * A sine wave is played and continuously recorded using loopback.
+ * An analyzer locks to the phase and magnitude of the detected sine wave.
+ * It then compares the incoming signal with a predicted sine wave.
+ */
public class AutomatedGlitchActivity extends BaseAutoGlitchActivity {
private Spinner mDurationSpinner;
@@ -43,6 +65,8 @@ public class AutomatedGlitchActivity extends BaseAutoGlitchActivity {
mDurationSpinner = (Spinner) findViewById(R.id.spinner_glitch_duration);
mDurationSpinner.setOnItemSelectedListener(new DurationSpinnerListener());
+
+ setAnalyzerText(getString(R.string.auto_glitch_instructions));
}
@Override
@@ -75,9 +99,7 @@ public class AutomatedGlitchActivity extends BaseAutoGlitchActivity {
requestedInConfig.setChannelCount(inChannels);
requestedOutConfig.setChannelCount(outChannels);
- setTolerance(0.3f); // FIXME remove
-
- testConfigurations();
+ testCurrentConfigurations();
}
private void testConfiguration(int performanceMode,
@@ -98,10 +120,17 @@ public class AutomatedGlitchActivity extends BaseAutoGlitchActivity {
mTestResults.clear();
+ // Test with STEREO on both input and output.
+ testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
+ StreamConfiguration.SHARING_MODE_EXCLUSIVE,
+ UNSPECIFIED, STEREO, STEREO);
+
+ // Test EXCLUSIVE mode with a configuration most likely to work.
testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
StreamConfiguration.SHARING_MODE_EXCLUSIVE,
UNSPECIFIED);
+ // Test various combinations.
for (int perfMode : PERFORMANCE_MODES) {
for (int sampleRate : SAMPLE_RATES) {
testConfiguration(perfMode,
@@ -110,10 +139,10 @@ public class AutomatedGlitchActivity extends BaseAutoGlitchActivity {
}
}
- analyzeTestResults();
+ compareFailedTestsWithNearestPassingTest();
} catch (InterruptedException e) {
- analyzeTestResults();
+ compareFailedTestsWithNearestPassingTest();
} catch (Exception e) {
log(e.getMessage());
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedTestRunner.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedTestRunner.java
index 83417809..27fc0456 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedTestRunner.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/AutomatedTestRunner.java
@@ -7,6 +7,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@@ -184,8 +185,9 @@ public class AutomatedTestRunner extends LinearLayout implements Runnable {
private void stopAutoThread() {
try {
if (mAutoThread != null) {
- log("Disable background test thread.");
- new RuntimeException("Disable background test thread.").printStackTrace();
+ Log.d(TestAudioActivity.TAG,
+ "Who called stopAutoThread()?",
+ new RuntimeException("Just for debugging."));
mThreadEnabled = false;
mAutoThread.interrupt();
mAutoThread.join(100);
@@ -231,7 +233,13 @@ public class AutomatedTestRunner extends LinearLayout implements Runnable {
}
// Only call from UI thread.
+ public void onTestStarted() {
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ // Only call from UI thread.
public void onTestFinished() {
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
updateStartStopButtons(false);
mShareButton.setEnabled(true);
}
@@ -262,6 +270,12 @@ public class AutomatedTestRunner extends LinearLayout implements Runnable {
@Override
public void run() {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ onTestStarted();
+ }
+ });
logClear();
log("=== STARTED at " + new Date());
log(mActivity.getTestName());
@@ -274,7 +288,7 @@ public class AutomatedTestRunner extends LinearLayout implements Runnable {
mFailCount = 0;
try {
mActivity.runTest();
- log("Tests finished without exception.");
+ log("Tests finished.");
} catch(Exception e) {
log("EXCEPTION: " + e.getMessage());
} finally {
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 5bc16fb5..847dc563 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,13 +16,18 @@
package com.mobileer.oboetester;
+import static com.mobileer.oboetester.StreamConfiguration.convertChannelMaskToText;
+
import android.content.Context;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Bundle;
-import android.support.annotation.Nullable;
+
+import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Locale;
public class BaseAutoGlitchActivity extends GlitchActivity {
@@ -37,8 +42,17 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
protected int mGapMillis = DEFAULT_GAP_MILLIS;
private String mTestName = "";
+ protected AudioManager mAudioManager;
+
protected ArrayList<TestResult> mTestResults = new ArrayList<TestResult>();
+ public static boolean arrayContains(int[] haystack, int needle) {
+ for (int n: haystack) {
+ if (n == needle) return true;
+ }
+ return false;
+ }
+
void logDeviceInfo() {
log("\n############################");
log("\nDevice Info:");
@@ -53,27 +67,35 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
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;
public final int deviceId;
public final int mmapUsed;
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();
deviceId = configuration.getDeviceId();
mmapUsed = configuration.isMMap() ? 1 : 0;
performanceMode = configuration.getPerformanceMode();
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;
+ count += (channelMask != other.channelMask) ? 1 : 0;
count += (deviceId != other.deviceId) ? 1 : 0;
count += (mmapUsed != other.mmapUsed) ? 1 : 0;
count += (performanceMode != other.performanceMode) ? 1 : 0;
@@ -81,10 +103,11 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
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"));
+ text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "channelMask"));
text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "deviceId"));
text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "mmapUsed"));
text.append(TestDataPathsActivity.comparePassedField(prefix,this, passed, "performanceMode"));
@@ -96,6 +119,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
return "D=" + deviceId
+ ", " + ((mmapUsed > 0) ? "MMAP" : "Lgcy")
+ ", ch=" + channelText(channelUsed, channelCount)
+ + ", cm=" + convertChannelMaskToText(channelMask)
+ "," + StreamConfiguration.convertPerformanceModeToText(performanceMode)
+ "," + StreamConfiguration.convertSharingModeToText(sharingMode);
}
@@ -103,8 +127,8 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
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
@@ -120,8 +144,8 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
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();
}
@@ -147,7 +171,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
StringBuffer text = new StringBuffer();
text.append("Compare with passed test #" + passed.testIndex + "\n");
text.append(input.comparePassedDirection("IN", passed.input));
- text.append(TestDataPathsActivity.comparePassedField("IN", this, passed, "inputPreset"));
+ text.append(TestDataPathsActivity.comparePassedInputPreset("IN", this, passed));
text.append(output.comparePassedDirection("OUT", passed.output));
text.append(TestDataPathsActivity.comparePassedField("I/O",this, passed, "sampleRate"));
@@ -177,6 +201,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
@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);
@@ -208,12 +233,12 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
? getOutputChannel() : getInputChannel();
return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "INP")
+ (config.isMMap() ? "-M" : "-L")
- + ", ID = " + String.format("%2d", config.getDeviceId())
- + ", SR = " + String.format("%5d", config.getSampleRate())
+ + "-" + StreamConfiguration.convertSharingModeToText(config.getSharingMode())
+ + ", ID = " + String.format(Locale.getDefault(), "%2d", config.getDeviceId())
+ ", Perf = " + StreamConfiguration.convertPerformanceModeToText(
- config.getPerformanceMode())
- + ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode())
- + ", ch = " + channelText(channel, config.getChannelCount());
+ config.getPerformanceMode())
+ + ",\n ch = " + channelText(channel, config.getChannelCount())
+ + ", cm = " + convertChannelMaskToText(config.getChannelMask());
}
protected String getStreamText(AudioStreamBase stream) {
@@ -230,15 +255,14 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
// Run one test based on the requested input/output configurations.
@Nullable
- protected TestResult testConfigurations() throws InterruptedException {
- int result = TEST_RESULT_SKIPPED;
+ protected TestResult testCurrentConfigurations() throws InterruptedException {
mAutomatedTestRunner.incrementTestCount();
- if ((getSingleTestIndex() >= 0) && (mAutomatedTestRunner.getTestCount() != getSingleTestIndex())) {
+ if ((getSingleTestIndex() >= 0) && (getTestCount() != getSingleTestIndex())) {
return null;
}
- log("========================== #" + mAutomatedTestRunner.getTestCount());
-
+ log("========================== #" + getTestCount());
+ int result = 0;
StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
@@ -246,6 +270,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;
log("Requested:");
+ log(" SR = " + requestedOutConfig.getSampleRate());
log(" " + getConfigText(requestedInConfig));
log(" " + getConfigText(requestedOutConfig));
@@ -253,7 +278,9 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
boolean openFailed = false;
try {
openAudio(); // this will fill in actualConfig
+
log("Actual:");
+ log(" SR = " + actualOutConfig.getSampleRate());
// Set output size to a level that will avoid glitches.
AudioStreamBase outStream = mAudioOutTester.getCurrentAudioStream();
int sizeFrames = outStream.getBufferCapacityInFrames() / 2;
@@ -271,7 +298,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
}
TestResult testResult = new TestResult(
- mAutomatedTestRunner.getTestCount(),
+ getTestCount(),
mTestName,
mAudioInputTester.actualConfiguration,
getInputChannel(),
@@ -279,14 +306,14 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
getOutputChannel()
);
- // The test would only be worth running if we got the configuration we requested on input or output.
- String skipReason = shouldTestBeSkipped();
+ // The test will only be worth running if we got the configuration we requested on input or output.
+ String skipReason = whyShouldTestBeSkipped();
boolean skipped = skipReason.length() > 0;
boolean valid = !openFailed && !skipped;
boolean startFailed = false;
if (valid) {
try {
- startAudioTest();
+ startAudioTest(); // Start running the test in the background.
} catch (IOException e) {
e.printStackTrace();
valid = false;
@@ -324,7 +351,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
}
if (openFailed || startFailed) {
- appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n");
+ appendFailedSummary("------ #" + getTestCount() + "\n");
appendFailedSummary(getConfigText(requestedInConfig) + "\n");
appendFailedSummary(getConfigText(requestedOutConfig) + "\n");
appendFailedSummary(reason + "\n");
@@ -342,7 +369,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
resultText += reason;
log(" " + resultText);
if (!passed) {
- appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n");
+ appendFailedSummary("------ #" + getTestCount() + "\n");
appendFailedSummary(" " + getConfigText(actualInConfig) + "\n");
appendFailedSummary(" " + getConfigText(actualOutConfig) + "\n");
appendFailedSummary(" " + resultText + "\n");
@@ -352,6 +379,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
mAutomatedTestRunner.incrementPassCount();
result = TEST_RESULT_PASSED;
}
+
}
mAutomatedTestRunner.flushLog();
@@ -362,28 +390,123 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
testResult.setResult(result);
mTestResults.add(testResult);
}
-
return testResult;
}
+ protected int getTestCount() {
+ return mAutomatedTestRunner.getTestCount();
+ }
+
+ 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;
+ }
+
+ /**
+ * Are outputs mixed in the air or by a loopback plug?
+ * @param type device type, eg AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
+ * @return true if stereo output channels get mixed to mono input
+ */
+ protected boolean isDeviceTypeMixedForLoopback(int type) {
+ switch(type) {
+ // Mixed in the air.
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE:
+ // Mixed in the loopback fun-plug.
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_USB_HEADSET:
+ return true;
+
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ default:
+ return false; // channels are discrete
+ }
+ }
+
+ protected ArrayList<Integer> getCompatibleDeviceTypes(int type) {
+ ArrayList<Integer> compatibleTypes = new ArrayList<Integer>();
+ switch(type) {
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE:
+ compatibleTypes.add(AudioDeviceInfo.TYPE_BUILTIN_MIC);
+ break;
+ case AudioDeviceInfo.TYPE_BUILTIN_MIC:
+ compatibleTypes.add(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ break;
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ compatibleTypes.add(AudioDeviceInfo.TYPE_USB_DEVICE);
+ // A USB Device is often mistaken for a headset.
+ compatibleTypes.add(AudioDeviceInfo.TYPE_USB_HEADSET);
+ break;
+ default:
+ compatibleTypes.add(type);
+ break;
+ }
+ return compatibleTypes;
+ }
+
+ /**
+ * Scan available device for one with a compatible device type for loopback testing.
+ * @return deviceId
+ */
+
+ protected AudioDeviceInfo findCompatibleInputDevice(int outputDeviceType) {
+ ArrayList<Integer> compatibleDeviceTypes = getCompatibleDeviceTypes(outputDeviceType);
+ AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+ for (AudioDeviceInfo candidate : devices) {
+ if (compatibleDeviceTypes.contains(candidate.getType())) {
+ return candidate;
+ }
+ }
+ return null;
+ }
+
protected boolean isFinishedEarly() {
return false;
}
- protected String shouldTestBeSkipped() {
+ /**
+ * Figure out if a test should be skipped and return the reason.
+ *
+ * @return reason for skipping or an empty string
+ */
+ protected String whyShouldTestBeSkipped() {
String why = "";
StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration;
StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;
- // No point running the test if we don't get the sharing mode we requested.
+ // No point running the test if we don't get any of the sharing modes we requested.
if (actualInConfig.getSharingMode() != requestedInConfig.getSharingMode()
- || actualOutConfig.getSharingMode() != requestedOutConfig.getSharingMode()) {
+ && actualOutConfig.getSharingMode() != requestedOutConfig.getSharingMode()) {
log("Did not get requested sharing mode.");
- why += "share";
+ why += "share,";
+ }
+ if (actualInConfig.getPerformanceMode() != requestedInConfig.getPerformanceMode()
+ && actualOutConfig.getPerformanceMode() != requestedOutConfig.getPerformanceMode()) {
+ log("Did not get requested performance mode.");
+ why += "perf,";
+ }
+ if (actualInConfig.isMMap() != requestedInConfig.isMMap()
+ && actualOutConfig.isMMap() != requestedOutConfig.isMMap()) {
+ log("Did not get requested MMAP data path.");
+ why += "mmap,";
}
- // We don't skip based on performance mode because if you request LOW_LATENCY you might
- // get a smaller burst than if you request NONE.
return why;
}
@@ -399,9 +522,23 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
appendFailedSummary(text + "\n");
}
- protected void analyzeTestResults() {
- logAnalysis("\n==== ANALYSIS ===========");
- logAnalysis("Compare failed configuration with closest one that passed.");
+ private int countPassingTests() {
+ int numPassed = 0;
+ for (TestResult other : mTestResults) {
+ if (other.passed()) {
+ numPassed++;
+ }
+ }
+ return numPassed;
+ }
+
+ protected void compareFailedTestsWithNearestPassingTest() {
+ logAnalysis("\n==== COMPARISON ANALYSIS ===========");
+ if (countPassingTests() == 0) {
+ logAnalysis("Comparison skipped because NO tests passed.");
+ return;
+ }
+ logAnalysis("Compare failed tests with others that passed.");
// Analyze each failed test.
for (TestResult testResult : mTestResults) {
if (testResult.failed()) {
@@ -419,6 +556,7 @@ public class BaseAutoGlitchActivity extends GlitchActivity {
}
}
+
@Nullable
private TestResult[] findClosestPassingTestResults(TestResult testResult) {
int minDifferences = Integer.MAX_VALUE;
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseOboeTesterActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseOboeTesterActivity.java
index 860b8145..4bf57496 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseOboeTesterActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BaseOboeTesterActivity.java
@@ -20,9 +20,9 @@ import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
/**
* Support requesting RECORD_AUDIO permission.
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BufferSizeView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BufferSizeView.java
index ce3b7a44..432e0ff3 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BufferSizeView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/BufferSizeView.java
@@ -23,6 +23,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.RadioButton;
+import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.LinearLayout;
@@ -36,6 +37,7 @@ public class BufferSizeView extends LinearLayout {
private TextView mTextLabel;
private SeekBar mFader;
private ExponentialTaper mTaper;
+ private RadioGroup mBufferSizeGroup;
private RadioButton mBufferSizeRadio1;
private RadioButton mBufferSizeRadio2;
private RadioButton mBufferSizeRadio3;
@@ -97,6 +99,8 @@ public class BufferSizeView extends LinearLayout {
mTaper = new ExponentialTaper(0.0, 1.0, 10.0);
mFader.setProgress(0);
+ mBufferSizeGroup = (RadioGroup) findViewById(R.id.bufferSizeGroup);
+
mBufferSizeRadio1 = (RadioButton) findViewById(R.id.bufferSize1);
mBufferSizeRadio1.setOnClickListener(new View.OnClickListener() {
@Override
@@ -118,13 +122,18 @@ public class BufferSizeView extends LinearLayout {
onSizeRadioButtonClicked(view, 3);
}
});
+
mNumBursts = DEFAULT_NUM_BURSTS;
updateRadioButtons();
updateBufferSize();
}
public void updateRadioButtons() {
- if (mBufferSizeRadio3 != null) {
+ if (mNumBursts == USE_FADER && mBufferSizeGroup != null) {
+ // Clear all the radio buttons using the group.
+ // If you clear a checked button directly then it stops working.
+ mBufferSizeGroup.clearCheck();
+ } else if (mBufferSizeRadio3 != null) {
mBufferSizeRadio1.setChecked(mNumBursts == 1);
mBufferSizeRadio2.setChecked(mNumBursts == 2);
mBufferSizeRadio3.setChecked(mNumBursts == 3);
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/CommunicationDeviceView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/CommunicationDeviceView.java
index 4e8df135..3e4c96df 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/CommunicationDeviceView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/CommunicationDeviceView.java
@@ -16,7 +16,11 @@
package com.mobileer.oboetester;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
@@ -31,11 +35,27 @@ import android.widget.TextView;
import com.mobileer.audio_device.CommunicationDeviceSpinner;
+import java.util.Locale;
+
public class CommunicationDeviceView extends LinearLayout {
- private CheckBox mSpeakerphoneCheckbox;
- private TextView mIsSpeakerphoneText;
+
private AudioManager mAudioManager;
+ private CheckBox mSpeakerphoneCheckbox;
+ private CheckBox mScoCheckbox;
+ private TextView mSpeakerStatusView;
+ private TextView mScoStatusView;
+ private BroadcastReceiver mScoStateReceiver;
+ private boolean mScoStateReceiverRegistered = false;
private CommunicationDeviceSpinner mDeviceSpinner;
+ private int mScoState;
+ private CommDeviceSniffer mCommDeviceSniffer = new CommDeviceSniffer();;
+
+ protected class CommDeviceSniffer extends NativeSniffer {
+ @Override
+ public void updateStatusText() {
+ showCommDeviceStatus();
+ }
+ }
public CommunicationDeviceView(Context context) {
super(context);
@@ -66,6 +86,29 @@ public class CommunicationDeviceView extends LinearLayout {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mSpeakerphoneCheckbox = (CheckBox) findViewById(R.id.setSpeakerphoneOn);
+ mSpeakerphoneCheckbox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onSetSpeakerphoneOn(view);
+ }
+ });
+ mSpeakerStatusView = (TextView) findViewById(R.id.spkr_status_view);
+
+ mScoCheckbox = (CheckBox) findViewById(R.id.setBluetoothScoOn);
+ mScoCheckbox.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onStartStopBluetoothSco(view);
+ }
+ });
+ mScoStatusView = (TextView) findViewById(R.id.sco_status_view);
+ mScoStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mScoState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
+ showCommDeviceStatus();
+ }
+ };
mDeviceSpinner = (CommunicationDeviceSpinner) findViewById(R.id.comm_devices_spinner);
mDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@@ -74,15 +117,15 @@ public class CommunicationDeviceView extends LinearLayout {
AudioDeviceInfo[] commDeviceArray = mDeviceSpinner.getCommunicationsDevices();
if (commDeviceArray != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- if (position == 0) {
+ if (position == CommunicationDeviceSpinner.POS_CLEAR) {
mAudioManager.clearCommunicationDevice();
} else {
- AudioDeviceInfo selectedDevice = commDeviceArray[position - 1]; // skip "Clear"
+ AudioDeviceInfo selectedDevice = commDeviceArray[position - CommunicationDeviceSpinner.POS_DEVICES]; // skip "Clear"
mAudioManager.setCommunicationDevice(selectedDevice);
}
- showCommDeviceStatus();
}
}
+ showCommDeviceStatus();
}
public void onNothingSelected(AdapterView<?> parent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
@@ -92,19 +135,22 @@ public class CommunicationDeviceView extends LinearLayout {
}
});
- mSpeakerphoneCheckbox.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- onSetSpeakerphoneOn(view);
- }
- });
- mIsSpeakerphoneText = (TextView) findViewById(R.id.isSpeakerphoneOn);
showCommDeviceStatus();
}
- public void cleanup() {
+ public void onStart() {
+ registerScoStateReceiver();
+ mCommDeviceSniffer.startSniffer();
+ }
+
+
+ public void onStop() {
+ mCommDeviceSniffer.stopSniffer();
mSpeakerphoneCheckbox.setChecked(false);
setSpeakerPhoneOn(false);
+ mScoCheckbox.setChecked(false);
+ mAudioManager.stopBluetoothSco();
+ unregisterScoStateReceiver();
}
public void onSetSpeakerphoneOn(View view) {
@@ -122,14 +168,47 @@ public class CommunicationDeviceView extends LinearLayout {
private void showCommDeviceStatus() {
boolean enabled = mAudioManager.isSpeakerphoneOn();
- String text = (enabled ? "ON" : "OFF");
+ String text = ":" + (enabled ? "ON" : "OFF");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
AudioDeviceInfo commDeviceInfo = mAudioManager.getCommunicationDevice();
if (commDeviceInfo != null) {
- text += ", CommDev=" + commDeviceInfo.getId();
+ text += ", #" + commDeviceInfo.getId();
}
}
- mIsSpeakerphoneText.setText(" => " + text);
+ mSpeakerStatusView.setText(text + ",");
+
+ if (mScoState == AudioManager.SCO_AUDIO_STATE_CONNECTING) {
+ text = ":WAIT";
+ } else if (mScoState == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
+ text = ":CON";
+ } else if (mScoState == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
+ text = ":DISCON";
+ }
+ mScoStatusView.setText(text);
+ }
+
+ public void onStartStopBluetoothSco(View view) {
+ CheckBox checkBox = (CheckBox) view;
+ if (checkBox.isChecked()) {
+ mAudioManager.startBluetoothSco();
+ } else {
+ mAudioManager.stopBluetoothSco();
+ }
+ }
+
+ private synchronized void registerScoStateReceiver() {
+ if (!mScoStateReceiverRegistered) {
+ getContext().registerReceiver(mScoStateReceiver,
+ new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
+ mScoStateReceiverRegistered = true;
+ }
+ }
+
+ private synchronized void unregisterScoStateReceiver() {
+ if (mScoStateReceiverRegistered) {
+ getContext().unregisterReceiver(mScoStateReceiver);
+ mScoStateReceiverRegistered = false;
+ }
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DeviceReportActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DeviceReportActivity.java
index a9e8e606..31146236 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DeviceReportActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DeviceReportActivity.java
@@ -19,13 +19,22 @@ package com.mobileer.oboetester;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MicrophoneInfo;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiManager;
import android.os.Build;
import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.widget.TextView;
+import android.widget.Toast;
import com.mobileer.audio_device.AudioDeviceInfoConverter;
@@ -33,10 +42,10 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
- * Guide the user through a series of tests plugging in and unplugging a headset.
- * Print a summary at the end of any failures.
+ * Print a report of all the available audio devices.
*/
public class DeviceReportActivity extends Activity {
@@ -63,6 +72,8 @@ public class DeviceReportActivity extends Activity {
MyAudioDeviceCallback mDeviceCallback = new MyAudioDeviceCallback();
private TextView mAutoTextView;
private AudioManager mAudioManager;
+ private UsbManager mUsbManager;
+ private MidiManager mMidiManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -70,6 +81,26 @@ public class DeviceReportActivity extends Activity {
setContentView(R.layout.activity_device_report);
mAutoTextView = (TextView) findViewById(R.id.text_log_device_report);
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+ mMidiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.menu_main, menu);
+ MenuItem settings = menu.findItem(R.id.action_share);
+ settings.setOnMenuItemClickListener(item -> {
+ if(mAutoTextView !=null) {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_SEND);
+ sendIntent.putExtra(Intent.EXTRA_TEXT, mAutoTextView.getText().toString());
+ sendIntent.setType("text/plain");
+ Intent shareIntent = Intent.createChooser(sendIntent, null);
+ startActivity(shareIntent);
+ }
+ return false;
+ });
+ return true;
}
@Override
@@ -112,9 +143,78 @@ public class DeviceReportActivity extends Activity {
report.append(item);
}
report.append(reportAllMicrophones());
+ report.append(reportUsbDevices());
+ report.append(reportMidiDevices());
log(report.toString());
}
+ public String reportUsbDevices() {
+ StringBuffer report = new StringBuffer();
+ report.append("\n############################");
+ report.append("\nUsb Device Report:\n");
+ try {
+ HashMap<String, UsbDevice> usbDeviceList = mUsbManager.getDeviceList();
+ for (UsbDevice usbDevice : usbDeviceList.values()) {
+ report.append("\n==== USB Device ========= " + usbDevice.getDeviceId());
+ report.append("\nProduct Name : " + usbDevice.getProductName());
+ report.append("\nProduct ID : 0x" + Integer.toHexString(usbDevice.getProductId()));
+ report.append("\nManufacturer Name : " + usbDevice.getManufacturerName());
+ report.append("\nVendor ID : 0x" + Integer.toHexString(usbDevice.getVendorId()));
+ report.append("\nDevice Name : " + usbDevice.getDeviceName());
+ report.append("\nDevice Protocol : " + usbDevice.getDeviceProtocol());
+ report.append("\nDevice Class : " + usbDevice.getDeviceClass());
+ report.append("\nDevice Subclass : " + usbDevice.getDeviceSubclass());
+ report.append("\nVersion : " + usbDevice.getVersion());
+ report.append("\n" + usbDevice);
+ report.append("\n");
+ }
+ } catch (Exception e) {
+ Log.e(TestAudioActivity.TAG, "Caught ", e);
+ showErrorToast(e.getMessage());
+ report.append("\nERROR: " + e.getMessage() + "\n");
+ }
+ return report.toString();
+ }
+
+ public String reportMidiDevices() {
+ StringBuffer report = new StringBuffer();
+ report.append("\n############################");
+ report.append("\nMidi Device Report:\n");
+ try {
+ MidiDeviceInfo[] midiDeviceInfos = mMidiManager.getDevices();
+ for (MidiDeviceInfo midiDeviceInfo : midiDeviceInfos) {
+ report.append("\n==== MIDI Device ========= " + midiDeviceInfo.getId());
+ addMidiDeviceInfoToDeviceReport(midiDeviceInfo, report);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Set<MidiDeviceInfo> umpDeviceInfos =
+ mMidiManager.getDevicesForTransport(MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+ for (MidiDeviceInfo midiDeviceInfo : umpDeviceInfos) {
+ report.append("\n==== UMP Device ========= " + midiDeviceInfo.getId());
+ addMidiDeviceInfoToDeviceReport(midiDeviceInfo, report);
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TestAudioActivity.TAG, "Caught ", e);
+ showErrorToast(e.getMessage());
+ report.append("\nERROR: " + e.getMessage() + "\n");
+ }
+ return report.toString();
+ }
+
+ private void addMidiDeviceInfoToDeviceReport(MidiDeviceInfo midiDeviceInfo,
+ StringBuffer report){
+ report.append("\nInput Count : " + midiDeviceInfo.getInputPortCount());
+ report.append("\nOutput Count : " + midiDeviceInfo.getOutputPortCount());
+ report.append("\nType : " + midiDeviceInfo.getType());
+ report.append("\nIs Private : " + midiDeviceInfo.isPrivate());
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ report.append("\nDefault Protocol : " + midiDeviceInfo.getDefaultProtocol());
+ }
+ report.append("\n" + midiDeviceInfo);
+ report.append("\n");
+ }
+
public String reportAllMicrophones() {
StringBuffer report = new StringBuffer();
report.append("\n############################");
@@ -127,8 +227,12 @@ public class DeviceReportActivity extends Activity {
report.append(micItem);
}
} catch (IOException e) {
- e.printStackTrace();
+ Log.e(TestAudioActivity.TAG, "Caught ", e);
return e.getMessage();
+ } catch (Exception e) {
+ Log.e(TestAudioActivity.TAG, "Caught ", e);
+ showErrorToast(e.getMessage());
+ report.append("\nERROR: " + e.getMessage() + "\n");
}
} else {
report.append("\nMicrophoneInfo not available on V" + android.os.Build.VERSION.SDK_INT);
@@ -169,4 +273,20 @@ public class DeviceReportActivity extends Activity {
});
}
+ protected void showErrorToast(String message) {
+ String text = "Error: " + message;
+ Log.e(TestAudioActivity.TAG, text);
+ showToast(text);
+ }
+
+ protected void showToast(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(DeviceReportActivity.this,
+ message,
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DynamicWorkloadActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DynamicWorkloadActivity.java
new file mode 100644
index 00000000..8c94a3b7
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/DynamicWorkloadActivity.java
@@ -0,0 +1,388 @@
+/*
+ * 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.
+ */
+
+package com.mobileer.oboetester;
+
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * Demonstrate the behavior of a changing CPU load on underruns.
+ * Display the workload and the callback duration in a chart.
+ * Enable or disable PerformanceHints (ADPF) using a checkbox.
+ * This might boost the CPU frequency when Oboe is taking too long to compute the next buffer.
+ * ADPF docs at: https://developer.android.com/reference/android/os/PerformanceHintManager
+ */
+public class DynamicWorkloadActivity extends TestOutputActivityBase {
+ private static final int WORKLOAD_HIGH_MIN = 30;
+ private static final int WORKLOAD_HIGH_MAX = 150;
+ // When the CPU is completely saturated then the load will be above 1.0.
+ public static final double LOAD_RECOVERY_HIGH = 1.0;
+ // Use a slightly lower value for going low so that the comparator has hysteresis.
+ public static final double LOAD_RECOVERY_LOW = 0.95;
+
+ private static final float MARGIN_ABOVE_WORKLOAD_FOR_CPU = 1.2f;
+
+ // By default, set high workload to 70 voices, which is reasonable for most devices.
+ public static final double WORKLOAD_PROGRESS_FOR_70_VOICES = 0.53;
+
+ private Button mStopButton;
+ private Button mStartButton;
+ private TextView mResultView;
+ private LinearLayout mAffinityLayout;
+ private ArrayList<CheckBox> mAffinityBoxes = new ArrayList<CheckBox>();
+ private WorkloadUpdateThread mUpdateThread;
+
+ private MultiLineChart mMultiLineChart;
+ private MultiLineChart.Trace mMaxCpuLoadTrace;
+ private MultiLineChart.Trace mWorkloadTrace;
+ private CheckBox mUseAltAdpfBox;
+ private CheckBox mPerfHintBox;
+ private boolean mDrawChartAlways = true;
+ private CheckBox mDrawAlwaysBox;
+ private int mCpuCount;
+
+ private static final int WORKLOAD_LOW = 1;
+ private int mWorkloadHigh; // this will get set later
+ private WorkloadView mDynamicWorkloadView;
+
+ // Periodically query the status of the streams.
+ protected class WorkloadUpdateThread {
+ public static final int SNIFFER_UPDATE_PERIOD_MSEC = 40;
+ public static final int SNIFFER_UPDATE_DELAY_MSEC = 300;
+ public static final int SNIFFER_TOGGLE_PERIOD_MSEC = 3000;
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_RUN_LOW = 1;
+ private static final int STATE_RUN_HIGH = 2;
+
+ private Handler mHandler;
+
+ private int mWorkloadCurrent = 1;
+
+ private int mState = STATE_IDLE;
+ private long mLastToggleTime = 0;
+ private long mRecoveryTimeBegin;
+ private long mRecoveryTimeEnd;
+ private long mStartTimeNanos;
+
+ String stateToString(int state) {
+ switch(state) {
+ case STATE_IDLE:
+ return "Idle";
+ case STATE_RUN_LOW:
+ return "low";
+ case STATE_RUN_HIGH:
+ return "HIGH";
+ default:
+ return "Unrecognized";
+ }
+ }
+
+ // Display status info for the stream.
+ private Runnable runnableCode = new Runnable() {
+ @Override
+ public void run() {
+ int nextWorkload = mWorkloadCurrent;
+ AudioStreamBase stream = mAudioOutTester.getCurrentAudioStream();
+ float cpuLoad = stream.getCpuLoad();
+ float maxCpuLoad = stream.getAndResetMaxCpuLoad();
+ int cpuMask = stream.getAndResetCpuMask();
+ long now = System.currentTimeMillis();
+ boolean drawChartOnce = false;
+
+ switch (mState) {
+ case STATE_IDLE:
+ drawChartOnce = true; // clear old chart
+ mState = STATE_RUN_LOW;
+ mLastToggleTime = now;
+ break;
+ case STATE_RUN_LOW:
+ nextWorkload = WORKLOAD_LOW;
+ if ((now - mLastToggleTime) > SNIFFER_TOGGLE_PERIOD_MSEC) {
+ mLastToggleTime = now;
+ mState = STATE_RUN_HIGH;
+ mRecoveryTimeBegin = 0;
+ mRecoveryTimeEnd = 0;
+ }
+ break;
+ case STATE_RUN_HIGH:
+ nextWorkload = mWorkloadHigh;
+ if ((now - mLastToggleTime) > SNIFFER_TOGGLE_PERIOD_MSEC) {
+ mLastToggleTime = now;
+ mState = STATE_RUN_LOW;
+ // Draw now when a CPU spike will not affect the result.
+ drawChartOnce = true;
+ }
+
+ if (mRecoveryTimeBegin == 0) {
+ if (maxCpuLoad > LOAD_RECOVERY_HIGH) {
+ mRecoveryTimeBegin = now;
+ }
+ } else if (mRecoveryTimeEnd == 0) {
+ if (maxCpuLoad < LOAD_RECOVERY_LOW) {
+ mRecoveryTimeEnd = now;
+ }
+ } else if (maxCpuLoad > LOAD_RECOVERY_LOW) {
+ mRecoveryTimeEnd = now;
+ }
+ break;
+ }
+ stream.setWorkload((int) nextWorkload);
+ mWorkloadCurrent = nextWorkload;
+ // Update chart
+ float nowMicros = (System.nanoTime() - mStartTimeNanos) * 0.001f;
+ mMultiLineChart.addX(nowMicros);
+ mMaxCpuLoadTrace.add((float) maxCpuLoad);
+ mWorkloadTrace.add((float) mWorkloadCurrent);
+ if (drawChartOnce || mDrawChartAlways){
+ mMultiLineChart.update();
+ }
+
+ // Display numbers
+ String recoveryTimeString = (mRecoveryTimeEnd <= mRecoveryTimeBegin) ?
+ "---" : ((mRecoveryTimeEnd - mRecoveryTimeBegin) + " msec");
+ String message =
+ "#Voices = " + (int) nextWorkload
+ + "\nWorkState = " + stateToString(mState)
+ + "\nCPU = " + String.format(Locale.getDefault(), "%6.3f%c", cpuLoad * 100, '%')
+ + "\ncores = " + cpuMaskToString(cpuMask, mCpuCount)
+ + "\nRecovery = " + recoveryTimeString;
+ postResult(message);
+
+ mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
+ }
+ };
+
+ private void start() {
+ stop();
+ mStartTimeNanos = System.nanoTime();
+ mMultiLineChart.reset();
+ mState = STATE_IDLE;
+ mHandler = new Handler(Looper.getMainLooper());
+ // Start the initial runnable task by posting through the handler
+ mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
+ }
+
+ private void stop() {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(runnableCode);
+ }
+ }
+
+ }
+
+ private void setWorkloadHigh(int workloadHigh) {
+ mWorkloadHigh = workloadHigh;
+ }
+
+
+ /**
+ * This text will look best in a monospace font.
+ * @param cpuMask CPU core bit mask
+ * @return a text display of the selected cores like "--2-45-7"
+ */
+ // TODO move this to some utility class
+ private String cpuMaskToString(int cpuMask, int cpuCount) {
+ String text = "";
+ long longMask = ((long) cpuMask) & 0x0FFFFFFFFL;
+ int index = 0;
+ while (longMask != 0 || index < cpuCount) {
+ text += ((longMask & 1) != 0) ? hexDigit(index) : "-";
+ longMask = longMask >> 1;
+ index++;
+ }
+ return text;
+ }
+
+ private char hexDigit(int n) {
+ byte x = (byte)(n & 0x0F);
+ if (x < 10) return (char)('0' + x);
+ else return (char)('A' + x);
+ }
+
+ @Override
+ protected void inflateActivity() {
+ setContentView(R.layout.activity_dynamic_workload);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mAudioOutTester = addAudioOutputTester();
+
+ mResultView = (TextView) findViewById(R.id.resultView);
+ mResultView.setTypeface(Typeface.MONOSPACE);
+ mStartButton = (Button) findViewById(R.id.button_start);
+ mStopButton = (Button) findViewById(R.id.button_stop);
+
+ mDynamicWorkloadView = (WorkloadView) findViewById(R.id.dynamic_workload_view);
+ mWorkloadView.setVisibility(View.GONE);
+
+ // Add a row of checkboxes for setting CPU affinity.
+ mCpuCount = NativeEngine.getCpuCount();
+ final int defaultCpuAffinityMask = 0;
+ View.OnClickListener checkBoxListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ // Create a mack from all the checkboxes.
+ int mask = 0;
+ for (int cpuIndex = 0; cpuIndex < mCpuCount; cpuIndex++) {
+ CheckBox checkBox = mAffinityBoxes.get(cpuIndex);
+ if (checkBox.isChecked()) {
+ mask |= (1 << cpuIndex);
+ }
+ }
+ NativeEngine.setCpuAffinityMask(mask);
+ }
+ };
+ mAffinityLayout = (LinearLayout) findViewById(R.id.affinityLayout);
+ for (int cpuIndex = 0; cpuIndex < mCpuCount; cpuIndex++) {
+ CheckBox checkBox = new CheckBox(DynamicWorkloadActivity.this);
+ mAffinityLayout.addView(checkBox);
+ mAffinityBoxes.add(checkBox);
+ checkBox.setText(cpuIndex + "");
+ checkBox.setOnClickListener(checkBoxListener);
+ if (((1 << cpuIndex) & defaultCpuAffinityMask) != 0) {
+ checkBox.setChecked(true);
+ }
+ }
+ NativeEngine.setCpuAffinityMask(defaultCpuAffinityMask);
+
+ mMultiLineChart = (MultiLineChart) findViewById(R.id.multiline_chart);
+ mMaxCpuLoadTrace = mMultiLineChart.createTrace("CPU", Color.RED,
+ 0.0f, 2.0f);
+ mWorkloadTrace = mMultiLineChart.createTrace("Work", Color.BLUE,
+ 0.0f, (MARGIN_ABOVE_WORKLOAD_FOR_CPU * WORKLOAD_HIGH_MAX));
+
+ mPerfHintBox = (CheckBox) findViewById(R.id.enable_perf_hint);
+
+ // TODO remove when finished with ADPF experiments.
+ mUseAltAdpfBox = (CheckBox) findViewById(R.id.use_alternative_adpf);
+ mUseAltAdpfBox.setOnClickListener(buttonView -> {
+ CheckBox checkBox = (CheckBox) buttonView;
+ setUseAlternativeAdpf(checkBox.isChecked());
+ mPerfHintBox.setEnabled(!checkBox.isChecked());
+ });
+ mUseAltAdpfBox.setVisibility(View.GONE);
+
+ mPerfHintBox.setOnClickListener(buttonView -> {
+ CheckBox checkBox = (CheckBox) buttonView;
+ setPerformanceHintEnabled(checkBox.isChecked());
+ mUseAltAdpfBox.setEnabled(!checkBox.isChecked());
+ });
+
+ CheckBox hearWorkloadBox = (CheckBox) findViewById(R.id.hear_workload);
+ hearWorkloadBox.setOnClickListener(buttonView -> {
+ CheckBox checkBox = (CheckBox) buttonView;
+ setHearWorkload(checkBox.isChecked());
+ });
+
+ mDrawAlwaysBox = (CheckBox) findViewById(R.id.draw_always);
+ mDrawAlwaysBox.setOnClickListener(buttonView -> {
+ CheckBox checkBox = (CheckBox) buttonView;
+ mDrawChartAlways = checkBox.isChecked();
+ });
+
+ if (mDynamicWorkloadView != null) {
+ mDynamicWorkloadView.setWorkloadReceiver((w) -> {
+ setWorkloadHigh(w);
+ });
+
+ mDynamicWorkloadView.setLabel("High Workload");
+ mDynamicWorkloadView.setRange(WORKLOAD_HIGH_MIN, WORKLOAD_HIGH_MAX);
+ mDynamicWorkloadView.setFaderNormalizedProgress(WORKLOAD_PROGRESS_FOR_70_VOICES);
+ }
+
+ updateButtons(false);
+ updateEnabledWidgets();
+ hideSettingsViews(); // make more room
+ }
+
+ private void setHearWorkload(boolean checked) {
+ mAudioOutTester.getCurrentAudioStream().setHearWorkload(checked);
+ }
+
+ private void setPerformanceHintEnabled(boolean checked) {
+ mAudioOutTester.getCurrentAudioStream().setPerformanceHintEnabled(checked);
+ }
+
+ private void updateButtons(boolean running) {
+ mStartButton.setEnabled(!running);
+ mStopButton.setEnabled(running);
+ mPerfHintBox.setEnabled(running);
+ }
+
+ private void postResult(final String text) {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ mResultView.setText(text);
+ }
+ });
+ }
+
+ @Override
+ int getActivityType() {
+ return ACTIVITY_DYNAMIC_WORKLOAD;
+ }
+
+ public void startTest(View view) {
+ try {
+ openAudio();
+ } catch (IOException e) {
+ e.printStackTrace();
+ showErrorToast("Open audio failed!");
+ return;
+ }
+ try {
+ super.startAudio();
+ updateButtons(true);
+ postResult("Running test");
+ mUpdateThread = new WorkloadUpdateThread();
+ mUpdateThread.start();
+ } catch (IOException e) {
+ e.printStackTrace();
+ showErrorToast("Start audio failed! " + e.getMessage());
+ return;
+ }
+ }
+
+ public void stopTest(View view) {
+ onStopTest();
+ }
+
+ @Override
+ public void onStopTest() {
+ WorkloadUpdateThread updateThread = mUpdateThread;
+ if (updateThread != null) {
+ updateThread.stop();
+ }
+ updateButtons(false);
+ super.onStopTest();
+ }
+}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/EchoActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/EchoActivity.java
index 1d8e4dcc..9ce258a5 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/EchoActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/EchoActivity.java
@@ -42,9 +42,8 @@ public class EchoActivity extends TestInputActivity {
private Button mStartButton;
private Button mStopButton;
private TextView mStatusTextView;
- private CommunicationDeviceView mCommunicationDeviceView;
- private ColdStartSniffer mNativeSniffer = new ColdStartSniffer(this);
+ private ColdStartSniffer mNativeSniffer = new ColdStartSniffer();
protected static final int MAX_DELAY_TIME_PROGRESS = 1000;
@@ -71,10 +70,6 @@ public class EchoActivity extends TestInputActivity {
private int mInputLatency;
private int mOutputLatency;
- public ColdStartSniffer(Activity activity) {
- super(activity);
- }
-
@Override
public void startSniffer() {
stableCallCount = 0;
@@ -83,16 +78,10 @@ public class EchoActivity extends TestInputActivity {
super.startSniffer();
}
- public void run() {
+ @Override
+ public boolean isComplete() {
mInputLatency = getColdStartInputMillis();
mOutputLatency = getColdStartOutputMillis();
- updateStatusText();
- if (!isComplete()) {
- reschedule();
- }
- }
-
- private boolean isComplete() {
if (mInputLatency > 0 && mOutputLatency > 0) {
stableCallCount++;
}
@@ -116,11 +105,6 @@ public class EchoActivity extends TestInputActivity {
}
@Override
- public String getShortReport() {
- return getCurrentStatusReport();
- }
-
- @Override
public void updateStatusText() {
String message = getCurrentStatusReport();
mStatusTextView.setText(message);
@@ -163,14 +147,6 @@ public class EchoActivity extends TestInputActivity {
hideSettingsViews();
}
- @Override
- protected void onStop() {
- if (mCommunicationDeviceView != null) {
- mCommunicationDeviceView.cleanup();
- }
- super.onStop();
- }
-
private void setDelayTimeByPosition(int progress) {
mDelayTime = mTaperDelayTime.linearToExponential(
((double)progress)/MAX_DELAY_TIME_PROGRESS);
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalFileWriter.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalFileWriter.java
new file mode 100644
index 00000000..759690e4
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalFileWriter.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package com.mobileer.oboetester;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+public class ExternalFileWriter {
+ private static final String TAG = "OboeTester";
+ private Context mContext;
+
+ public ExternalFileWriter(Context context) {
+ mContext = context;
+ }
+
+ public File writeStringToExternalFile(String result, String fileName) throws IOException {
+ File dir = mContext.getExternalFilesDir(null);
+ File resultFile = new File(dir, fileName);
+ Log.d(TAG, "EXTFILE = " + resultFile.getAbsolutePath());
+ Writer writer = null;
+ try {
+ writer = new OutputStreamWriter(new FileOutputStream(resultFile));
+ writer.write(result);
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return resultFile;
+ }
+}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalTapToToneActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalTapToToneActivity.java
index 06cf26d2..c978da37 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalTapToToneActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExternalTapToToneActivity.java
@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;
@@ -58,6 +59,7 @@ public class ExternalTapToToneActivity extends Activity {
mTapToToneTester.resetLatency();
mTapToToneTester.start();
updateButtons(true);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} catch (IOException e) {
e.printStackTrace();
showErrorToast("Start audio failed! " + e.getMessage());
@@ -68,11 +70,13 @@ public class ExternalTapToToneActivity extends Activity {
public void stopTest(View view) {
mTapToToneTester.stop();
updateButtons(false);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override
public void onStop() {
mTapToToneTester.stop();
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onStop();
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java
index 557ceb41..c744329b 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/ExtraTestsActivity.java
@@ -29,4 +29,15 @@ public class ExtraTestsActivity extends BaseOboeTesterActivity {
launchTestActivity(TestErrorCallbackActivity.class);
}
+ public void onLaunchRouteDuringCallbackTest(View view) {
+ launchTestThatDoesRecording(TestRouteDuringCallbackActivity.class);
+ }
+
+ public void onLaunchDynamicWorkloadTest(View view) {
+ launchTestActivity(DynamicWorkloadActivity.class);
+ }
+
+ public void onLaunchColdStartLatencyTest(View view) {
+ launchTestActivity(TestColdStartLatencyActivity.class);
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/GlitchActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/GlitchActivity.java
index ce0182c1..63abe5cf 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/GlitchActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/GlitchActivity.java
@@ -23,6 +23,7 @@ import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
+import java.util.Locale;
/**
* Activity to measure the number of glitches.
@@ -46,22 +47,23 @@ public class GlitchActivity extends AnalyzerActivity {
native int getStateFrameCount(int state);
native int getGlitchCount();
+
+ // Number of frames in last glitch.
+ native int getGlitchLength();
+ native double getPhase();
native double getSignalToNoiseDB();
native double getPeakAmplitude();
native double getSineAmplitude();
+ native int getSinePeriod();
- private GlitchSniffer mGlitchSniffer;
- private NativeSniffer mNativeSniffer = createNativeSniffer();
+ protected NativeSniffer mNativeSniffer = createNativeSniffer();
synchronized NativeSniffer createNativeSniffer() {
- if (mGlitchSniffer == null) {
- mGlitchSniffer = new GlitchSniffer(this);
- }
- return mGlitchSniffer;
+ return new GlitchSniffer();
}
// Note that these strings must match the enum result_code in LatencyAnalyzer.h
- String stateToString(int resultCode) {
+ static String stateToString(int resultCode) {
switch (resultCode) {
case STATE_IDLE:
return "IDLE";
@@ -80,6 +82,10 @@ public class GlitchActivity extends AnalyzerActivity {
}
}
+ static String magnitudeToString(double magnitude) {
+ return String.format(Locale.US, "%6.4f", magnitude);
+ }
+
// Periodically query for glitches from the native detector.
protected class GlitchSniffer extends NativeSniffer {
@@ -100,10 +106,6 @@ public class GlitchActivity extends AnalyzerActivity {
private double mPeakAmplitude;
private double mSineAmplitude;
- public GlitchSniffer(Activity activity) {
- super(activity);
- }
-
@Override
public void startSniffer() {
long now = System.currentTimeMillis();
@@ -119,7 +121,7 @@ public class GlitchActivity extends AnalyzerActivity {
super.startSniffer();
}
- public void run() {
+ private void gatherData() {
int state = getAnalyzerState();
mSignalToNoiseDB = getSignalToNoiseDB();
mPeakAmplitude = getPeakAmplitude();
@@ -163,8 +165,6 @@ public class GlitchActivity extends AnalyzerActivity {
mLastGlitchFrames = glitchFrames;
mLastLockedFrames = lockedFrames;
mLastResetCount = resetCount;
-
- reschedule();
}
private String getCurrentStatusReport() {
@@ -173,39 +173,46 @@ public class GlitchActivity extends AnalyzerActivity {
StringBuffer message = new StringBuffer();
message.append("state = " + stateToString(mPreviousState) + "\n");
- message.append(String.format("unlocked.frames = %d\n", mLastUnlockedFrames));
- message.append(String.format("locked.frames = %d\n", mLastLockedFrames));
- message.append(String.format("glitch.frames = %d\n", mLastGlitchFrames));
- message.append(String.format("reset.count = %d\n", mLastResetCount - mStartResetCount));
- message.append(String.format("peak.amplitude = %8.6f\n", mPeakAmplitude));
- message.append(String.format("sine.amplitude = %8.6f\n", mSineAmplitude));
+ message.append(String.format(Locale.getDefault(), "unlocked.frames = %d\n", mLastUnlockedFrames));
+ message.append(String.format(Locale.getDefault(), "locked.frames = %d\n", mLastLockedFrames));
+ message.append(String.format(Locale.getDefault(), "glitch.frames = %d\n", mLastGlitchFrames));
+ message.append(String.format(Locale.getDefault(), "reset.count = %d\n", mLastResetCount - mStartResetCount));
+ message.append(String.format(Locale.getDefault(), "peak.amplitude = %8.6f\n", mPeakAmplitude));
+ message.append(String.format(Locale.getDefault(), "sine.amplitude = %8.6f\n", mSineAmplitude));
if (mLastLockedFrames > 0) {
- message.append(String.format("signal.noise.ratio.db = %5.1f\n", mSignalToNoiseDB));
+ message.append(String.format(Locale.getDefault(), "signal.noise.ratio.db = %5.1f\n", mSignalToNoiseDB));
}
- message.append(String.format("time.total = %4.2f seconds\n", totalSeconds));
+ message.append(String.format(Locale.getDefault(), "time.total = %4.2f seconds\n", totalSeconds));
if (mLastLockedFrames > 0) {
- message.append(String.format("time.no.glitches = %4.2f\n", mSecondsWithoutGlitches));
- message.append(String.format("max.time.no.glitches = %4.2f\n",
+ message.append(String.format(Locale.getDefault(), "time.no.glitches = %4.2f\n", mSecondsWithoutGlitches));
+ message.append(String.format(Locale.getDefault(), "max.time.no.glitches = %4.2f\n",
mMaxSecondsWithoutGlitches));
- message.append(String.format("glitch.count = %d\n", mLastGlitchCount));
+ message.append(String.format(Locale.getDefault(), "glitch.length = %d\n", getGlitchLength()));
+ message.append(String.format(Locale.getDefault(), "glitch.count = %d\n", mLastGlitchCount));
}
return message.toString();
}
- @Override
public String getShortReport() {
- String resultText = "#glitches = " + getLastGlitchCount()
+ String resultText = "amplitude: peak = " + magnitudeToString(mPeakAmplitude)
+ + ", sine = " + magnitudeToString(mSineAmplitude) + "\n";
+ if (mPeakAmplitude < 0.01) {
+ resultText += "WARNING: volume is very low!\n";
+ }
+ resultText += "#glitches = " + getLastGlitchCount()
+ ", #resets = " + getLastResetCount()
+ ", max no glitch = " + getMaxSecondsWithNoGlitch() + " secs\n";
- resultText += String.format("SNR = %5.1f db", mSignalToNoiseDB);
+ resultText += String.format(Locale.getDefault(), "SNR = %5.1f db", mSignalToNoiseDB);
resultText += ", #locked = " + mLastLockedFrames;
return resultText;
}
@Override
public void updateStatusText() {
+ gatherData();
mLastGlitchReport = getCurrentStatusReport();
setAnalyzerText(mLastGlitchReport);
+ maybeDisplayWaveform();
}
public double getMaxSecondsWithNoGlitch() {
@@ -227,13 +234,16 @@ public class GlitchActivity extends AnalyzerActivity {
protected void onGlitchDetected() {
}
+ protected void maybeDisplayWaveform() {}
+
protected void setAnalyzerText(String s) {
mAnalyzerTextView.setText(s);
}
/**
* Set tolerance to deviations from expected value.
- * The normalized value will be converted in the native code.
+ * The normalized value will be scaled by the measured magnitude
+ * of the sine wave..
* @param tolerance normalized between 0.0 and 1.0
*/
public native void setTolerance(float tolerance);
@@ -256,6 +266,12 @@ public class GlitchActivity extends AnalyzerActivity {
return mOutputChannel;
}
+ /**
+ * Set the duration of a periodic forced glitch.
+ * @param frames or zero for no glitch
+ */
+ public native void setForcedGlitchDuration(int frames);
+
public native void setInputChannelNative(int channel);
public native void setOutputChannelNative(int channel);
@@ -332,18 +348,10 @@ public class GlitchActivity extends AnalyzerActivity {
onTestBegan();
}
- public void onCancel(View view) {
- stopAudioTest();
- onTestFinished();
- }
-
// Called on UI thread
public void onStopAudioTest(View view) {
stopAudioTest();
onTestFinished();
- mStartButton.setEnabled(true);
- mStopButton.setEnabled(false);
- mShareButton.setEnabled(false);
keepScreenOn(false);
}
@@ -365,6 +373,7 @@ public class GlitchActivity extends AnalyzerActivity {
}
public void stopTest() {
+ mNativeSniffer.stopSniffer();
stopAudio();
}
@@ -374,11 +383,11 @@ public class GlitchActivity extends AnalyzerActivity {
}
public double getMaxSecondsWithNoGlitch() {
- return mGlitchSniffer.getMaxSecondsWithNoGlitch();
+ return ((GlitchSniffer)mNativeSniffer).getMaxSecondsWithNoGlitch();
}
public String getShortReport() {
- return mNativeSniffer.getShortReport();
+ return ((GlitchSniffer)mNativeSniffer).getShortReport();
}
@Override
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java
index 594e7c41..4746ec72 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/IntentBasedTestSupport.java
@@ -39,13 +39,20 @@ public class IntentBasedTestSupport {
public static final String KEY_IN_USE_MMAP = "in_use_mmap";
public static final String KEY_OUT_USE_MMAP = "out_use_mmap";
- public static final boolean VALUE_DEFAULT_USE_MMAP = true;
+ public static final boolean VALUE_DEFAULT_USE_MMAP = NativeEngine.isMMapSupported();
public static final String KEY_IN_PRESET = "in_preset";
public static final String KEY_SAMPLE_RATE = "sample_rate";
public static final int VALUE_DEFAULT_SAMPLE_RATE = 48000;
public static final String VALUE_UNSPECIFIED = "unspecified";
+ public static final String KEY_OUT_USAGE = "out_usage";
+ public static final String VALUE_USAGE_MEDIA = "media";
+ public static final String VALUE_USAGE_VOICE_COMMUNICATION = "voice_communication";
+ public static final String VALUE_USAGE_ALARM = "alarm";
+ public static final String VALUE_USAGE_NOTIFICATION = "notification";
+ public static final String VALUE_USAGE_GAME = "game";
+
public static final String KEY_IN_API = "in_api";
public static final String KEY_OUT_API = "out_api";
public static final String VALUE_API_AAUDIO = "aaudio";
@@ -138,6 +145,21 @@ public class IntentBasedTestSupport {
return StreamConfiguration.SHARING_MODE_EXCLUSIVE;
}
}
+ public static int getUsageFromText(String text) {
+ if (VALUE_USAGE_GAME.equals(text)) {
+ return StreamConfiguration.USAGE_GAME;
+ } else if (VALUE_USAGE_VOICE_COMMUNICATION.equals(text)) {
+ return StreamConfiguration.USAGE_VOICE_COMMUNICATION;
+ } else if (VALUE_USAGE_MEDIA.equals(text)) {
+ return StreamConfiguration.USAGE_MEDIA;
+ } else if (VALUE_USAGE_ALARM.equals(text)) {
+ return StreamConfiguration.USAGE_ALARM;
+ } else if (VALUE_USAGE_NOTIFICATION.equals(text)) {
+ return StreamConfiguration.USAGE_NOTIFICATION;
+ } else {
+ return StreamConfiguration.UNSPECIFIED;
+ }
+ }
public static void configureStreamsFromBundle(Bundle bundle,
StreamConfiguration requestedInConfig,
@@ -278,6 +300,11 @@ public class IntentBasedTestSupport {
int sharingMode = getSharingFromText(text);
requestedOutConfig.setSharingMode(sharingMode);
+ text = bundle.getString(KEY_OUT_USAGE, VALUE_USAGE_MEDIA);
+ int usage = getUsageFromText(text);
+ requestedOutConfig.setUsage(usage);
+
+
}
public static void configureInputStreamFromBundle(Bundle bundle,
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MainActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MainActivity.java
index 70eca907..145dfbc0 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MainActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MainActivity.java
@@ -57,9 +57,7 @@ public class MainActivity extends BaseOboeTesterActivity {
protected TextView mDeviceView;
private TextView mVersionTextView;
private TextView mBuildTextView;
- private TextView mBluetoothScoStatusView;
private Bundle mBundleFromIntent;
- private BroadcastReceiver mScoStateReceiver;
private CheckBox mWorkaroundsCheckBox;
private CheckBox mBackgroundCheckBox;
private static String mVersionText;
@@ -117,21 +115,6 @@ public class MainActivity extends BaseOboeTesterActivity {
mBuildTextView = (TextView) findViewById(R.id.text_build_info);
mBuildTextView.setText(Build.DISPLAY);
- mBluetoothScoStatusView = (TextView) findViewById(R.id.textBluetoothScoStatus);
- mScoStateReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
- if (state == AudioManager.SCO_AUDIO_STATE_CONNECTING) {
- mBluetoothScoStatusView.setText("CONNECTING");
- } else if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
- mBluetoothScoStatusView.setText("CONNECTED");
- } else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
- mBluetoothScoStatusView.setText("DISCONNECTED");
- }
- }
- };
-
saveIntentBundleForLaterProcessing(getIntent());
}
@@ -139,15 +122,6 @@ public class MainActivity extends BaseOboeTesterActivity {
return mVersionText;
}
- private void registerScoStateReceiver() {
- registerReceiver(mScoStateReceiver,
- new IntentFilter(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED));
- }
-
- private void unregisterScoStateReceiver() {
- unregisterReceiver(mScoStateReceiver);
- }
-
private void logScreenSize() {
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
@@ -213,16 +187,9 @@ public class MainActivity extends BaseOboeTesterActivity {
public void onResume(){
super.onResume();
mWorkaroundsCheckBox.setChecked(NativeEngine.areWorkaroundsEnabled());
- registerScoStateReceiver();
processBundleFromIntent();
}
- @Override
- public void onPause(){
- unregisterScoStateReceiver();
- super.onPause();
- }
-
private void updateNativeAudioUI() {
AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
String audioManagerSampleRate = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
@@ -312,13 +279,4 @@ public class MainActivity extends BaseOboeTesterActivity {
OboeAudioStream.setCallbackSize(callbackSize);
}
- public void onStartStopBluetoothSco(View view) {
- CheckBox checkBox = (CheckBox) view;
- AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- if (checkBox.isChecked()) {
- myAudioMgr.startBluetoothSco();
- } else {
- myAudioMgr.stopBluetoothSco();
- }
- }
}
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 699344df..9281cd59 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,13 +16,21 @@
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;
import java.io.IOException;
+import java.util.Locale;
public class ManualGlitchActivity extends GlitchActivity {
@@ -30,15 +38,24 @@ public class ManualGlitchActivity extends GlitchActivity {
public static final int VALUE_DEFAULT_BUFFER_BURSTS = 2;
public static final String KEY_TOLERANCE = "tolerance";
- private static final float DEFAULT_TOLERANCE = 0.1f;
+ private static final float DEFAULT_TOLERANCE = 0.10f;
private static final long MIN_DISPLAY_PERIOD_MILLIS = 500;
+ private static final int WAVEFORM_SIZE = 400;
private TextView mTextTolerance;
private SeekBar mFaderTolerance;
protected ExponentialTaper mTaperTolerance;
+
+ private CheckBox mForceGlitchesBox;
+ private CheckBox mAutoScopeBox;
private WaveformView mWaveformView;
- private float[] mWaveform = new float[256];
+ private LinearLayout mLayoutGlitch;
+
+
+ private NumberedRadioButtons mInputChannelBoxes;
+ private NumberedRadioButtons mOutputChannelBoxes;
+ private float[] mWaveform = new float[WAVEFORM_SIZE];
private long mLastDisplayTime;
private float mTolerance = DEFAULT_TOLERANCE;
@@ -62,7 +79,56 @@ public class ManualGlitchActivity extends GlitchActivity {
float tolerance = (float) mTaperTolerance.linearToExponential(
((double)progress) / FADER_PROGRESS_MAX);
setTolerance(tolerance);
- mTextTolerance.setText("Tolerance = " + String.format("%5.3f", tolerance));
+ 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, String prompt) {
+ mRow = new LinearLayout(context);
+ mRow.setOrientation(LinearLayout.HORIZONTAL);
+ TextView textView = new TextView(context);
+ textView.setText(prompt);
+ 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
@@ -75,7 +141,17 @@ public class ManualGlitchActivity extends GlitchActivity {
mFaderTolerance.setOnSeekBarChangeListener(mToleranceListener);
setToleranceFader(DEFAULT_TOLERANCE);
+ 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), "IN:");
+ mLayoutGlitch.addView(mInputChannelBoxes.getView());
+ mOutputChannelBoxes = new NumberedRadioButtons(this, 8,
+ (int index) -> setOutputChannel(index), "OUT:");
+ mLayoutGlitch.addView(mOutputChannelBoxes.getView());
}
private void setToleranceFader(float tolerance) {
@@ -112,7 +188,12 @@ public class ManualGlitchActivity extends GlitchActivity {
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
@@ -147,7 +228,7 @@ public class ManualGlitchActivity extends GlitchActivity {
void stopAutomaticTest() {
String report = getCommonTestReport()
- + String.format("tolerance = %5.3f\n", mTolerance)
+ + String.format(Locale.getDefault(), "tolerance = %5.3f\n", mTolerance)
+ mLastGlitchReport;
onStopAudioTest(null);
maybeWriteTestResult(report);
@@ -156,13 +237,8 @@ public class ManualGlitchActivity extends GlitchActivity {
// Only call from UI thread.
@Override
- public void onTestFinished() {
- super.onTestFinished();
- }
-
- // Only call from UI thread.
- @Override
public void onTestBegan() {
+ mAutoScopeBox.setChecked(true);
mWaveformView.clearSampleData();
mWaveformView.postInvalidate();
super.onTestBegan();
@@ -171,19 +247,45 @@ public class ManualGlitchActivity extends GlitchActivity {
// Called on UI thread
@Override
protected void onGlitchDetected() {
+ if (mAutoScopeBox.isChecked()) {
+ mAutoScopeBox.setChecked(false); // stop auto drawing of waveform
+ mLastDisplayTime = 0; // force draw first glitch
+ }
long now = System.currentTimeMillis();
+ Log.i(TAG,"onGlitchDetected: glitch");
if ((now - mLastDisplayTime) > MIN_DISPLAY_PERIOD_MILLIS) {
mLastDisplayTime = now;
int numSamples = getGlitch(mWaveform);
mWaveformView.setSampleData(mWaveform, 0, numSamples);
+ int glitchLength = getGlitchLength();
+ int[] cursors = new int[glitchLength > 0 ? 2 : 1];
+ int startOfGlitch = getSinePeriod();
+ cursors[0] = startOfGlitch;
+ if (glitchLength > 0) {
+ cursors[1] = startOfGlitch + getGlitchLength();
+ }
+ mWaveformView.setCursorData(cursors);
+ Log.i(TAG,"onGlitchDetected: glitch, numSamples = " + numSamples);
mWaveformView.postInvalidate();
}
}
-
- private float[] getGlitchWaveform() {
- return mWaveform;
+ @Override
+ protected void maybeDisplayWaveform() {
+ if (!mAutoScopeBox.isChecked()) return;
+ long now = System.currentTimeMillis();
+ if ((now - mLastDisplayTime) > MIN_DISPLAY_PERIOD_MILLIS) {
+ mLastDisplayTime = now;
+ int numSamples = getRecentSamples(mWaveform);
+ mWaveformView.setSampleData(mWaveform, 0, numSamples);
+ mWaveformView.setCursorData(null);
+ mWaveformView.postInvalidate();
+ }
}
private native int getGlitch(float[] mWaveform);
+ private native int getRecentSamples(float[] mWaveform);
+ public void onForceGlitchClicked(View view) {
+ setForcedGlitchDuration(mForceGlitchesBox.isChecked() ? 100 : 0);
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MicrophoneInfoConverter.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MicrophoneInfoConverter.java
index 8dbe502b..e96b4cb3 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MicrophoneInfoConverter.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MicrophoneInfoConverter.java
@@ -4,6 +4,7 @@ import android.media.MicrophoneInfo;
import android.util.Pair;
import java.util.List;
+import java.util.Locale;
public class MicrophoneInfoConverter {
@@ -39,7 +40,7 @@ public class MicrophoneInfoConverter {
static String convertCoordinates(MicrophoneInfo.Coordinate3F coordinates) {
if (coordinates == MicrophoneInfo.POSITION_UNKNOWN) return "Unknown";
- return String.format("{ %6.4g, %5.3g, %5.3g }",
+ return String.format(Locale.getDefault(), "{ %6.4g, %5.3g, %5.3g }",
coordinates.x, coordinates.y, coordinates.z);
}
@@ -60,6 +61,9 @@ public class MicrophoneInfoConverter {
sb.append("\nType : " + micInfo.getType());
List<Pair<Integer, Integer>> mapping = micInfo.getChannelMapping();
+ if (mapping == null) {
+ throw new RuntimeException("MicrophoneInfo. getChannelMapping() returned null!");
+ }
sb.append("\nChannelMapping: {");
for (Pair<Integer, Integer> pair : mapping) {
sb.append("[" + pair.first + "," + pair.second + "], ");
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MultiLineChart.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MultiLineChart.java
new file mode 100644
index 00000000..9b21e985
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/MultiLineChart.java
@@ -0,0 +1,254 @@
+package com.mobileer.oboetester;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * Draw a chart with multiple traces
+ */
+public class MultiLineChart extends View {
+ public static final int NUM_DATA_VALUES = 512;
+ private Paint mWavePaint;
+ private Paint mCursorPaint;
+ private int mBackgroundColor = 0xFFF0F0F0;
+ private int mLineColor = Color.RED;
+ private Paint mBackgroundPaint;
+ float[] mVertices = new float[4];
+
+ CircularFloatArray mXData = new CircularFloatArray(NUM_DATA_VALUES);
+ private ArrayList<Trace> mTraceList = new ArrayList<>();
+
+ public MultiLineChart(Context context) {
+ super(context);
+ init(null, 0);
+ }
+
+ public MultiLineChart(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs, 0);
+ }
+
+ public MultiLineChart(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(attrs, defStyle);
+ }
+
+ private void init(AttributeSet attrs, int defStyle) {
+ // Load attributes
+ final TypedArray a = getContext().obtainStyledAttributes(
+ attrs, R.styleable.MultiLineChart, defStyle, 0);
+
+ mBackgroundColor = a.getColor(
+ R.styleable.MultiLineChart_backgroundColor,
+ mBackgroundColor);
+ mLineColor = a.getColor(
+ R.styleable.MultiLineChart_backgroundColor,
+ mLineColor);
+
+ a.recycle();
+
+ mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWavePaint.setColor(mLineColor);
+
+ mCursorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mCursorPaint.setColor(Color.RED);
+ mCursorPaint.setStrokeWidth(3.0f);
+
+ mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackgroundPaint.setColor(mBackgroundColor);
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ canvas.drawRect(0.0f, 0.0f, getWidth(),
+ getHeight(), mBackgroundPaint);
+
+ for (Trace trace : mTraceList) {
+ drawTrace(canvas, trace);
+ }
+ }
+
+ void drawTrace(Canvas canvas, Trace trace) {
+ // Determine bounds and XY conversion.
+ int numPoints = mXData.size();
+ if (numPoints < 2) return;
+ // Allocate array for polyline.
+ int arraySize = (numPoints - 1) * 4;
+ if (arraySize > mVertices.length) {
+ mVertices = new float[arraySize];
+ }
+ // Setup scaling.
+ float previousX = 0.0f;
+ float previousY = 0.0f;
+ float xMax = getXData(1);
+ float xRange = xMax - getXData(numPoints);
+ float yMin = trace.getMin();
+ float yRange = trace.getMax() - yMin;
+ float width = getWidth();
+ float height = getHeight();
+ float xScaler = width / xRange;
+ float yScaler = height / yRange;
+ // Iterate through the available data.
+ int vertexIndex = 0;
+ for (int i = 1; i < numPoints; i++) {
+ float xData = getXData(i);
+ float yData = trace.get(i);
+ float xPos = width - ((xMax - xData) * xScaler);
+ float yPos = height - ((yData - yMin) * yScaler);
+ if (i > 1) {
+ // Each line segment requires 4 values!
+ mVertices[vertexIndex++] = previousX;
+ mVertices[vertexIndex++] = previousY;
+ mVertices[vertexIndex++] = xPos;
+ mVertices[vertexIndex++] = yPos;
+ }
+ previousX = xPos;
+ previousY = yPos;
+ }
+ canvas.drawLines(mVertices, 0, vertexIndex, trace.paint);
+ }
+
+ public float getXData(int i) {
+ return mXData.get(i);
+ }
+
+ public MultiLineChart.Trace createTrace(String name, int color, float min, float max) {
+ Trace trace = new Trace(name, color, NUM_DATA_VALUES, min, max);
+ mTraceList.add(trace);
+ return trace;
+ }
+
+ public void update() {
+ post(new Runnable() {
+ public void run() {
+ postInvalidate();
+ }
+ });
+ }
+
+ public void addX(float value) {
+ mXData.add(value);
+ }
+
+ public void reset() {
+ mXData.clear();
+ for (Trace trace : mTraceList) {
+ trace.reset();
+ }
+ }
+
+ public static class Trace {
+ private final String mName;
+ public Paint paint;
+ protected float mMin;
+ protected float mMax;
+ protected CircularFloatArray mData;
+
+ private Trace(String name, int color, int numValues, float min, float max) {
+ mName = name;
+ mMin = min;
+ mMax = max;
+ mData = new CircularFloatArray(numValues);
+
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ paint.setColor(color);
+ paint.setStrokeWidth(3.0f);
+ }
+
+ public void reset() {
+ mData.clear();
+ }
+
+ public void add(float value) {
+ mData.add(value);
+ // Take the hit here instead of when drawing.
+ mData.add(Math.min(mMax, Math.max(mMin, value)));
+ }
+
+ public int size() {
+ return mData.size();
+ }
+
+ /**
+ * Fetch a previous value. A delayIndex of 1 will return the most recently written value.
+ * A delayIndex of 2 will return the previously written value;
+ * @param delayIndex positive index of previously written data
+ * @return old value
+ */
+ public float get(int delayIndex) {
+ return mData.get(delayIndex);
+ }
+ public float getMax() {
+ return mMax;
+ }
+ public float getMin() {
+ return mMin;
+ }
+
+ public void setMin(float min) {
+ mMin = min;
+ }
+ public void setMax(float max) {
+ mMax = max;
+ }
+ }
+
+ // Use explicit type for performance reasons.
+ private static class CircularFloatArray {
+ private float[] mData;
+ private int mIndexMask;
+ private int mCursor; // next location to be written
+
+ public CircularFloatArray(int numValuesPowerOf2) {
+ if ((numValuesPowerOf2 & (numValuesPowerOf2 - 1)) != 0) {
+ throw new IllegalArgumentException("numValuesPowerOf2 not 2^N, was " + numValuesPowerOf2);
+ }
+ mData = new float[numValuesPowerOf2];
+ mIndexMask = numValuesPowerOf2 - 1;
+ }
+
+ /**
+ * Add one value to the array.
+ * This may overwrite the oldest data.
+ * @param value
+ */
+ public void add(float value) {
+ int index = mCursor & mIndexMask;
+ mData[index] = value;
+ mCursor++;
+ }
+
+ /**
+ * Number of valid entries.
+ * @return
+ */
+ public int size() {
+ return Math.min(mCursor, mData.length);
+ }
+
+ /**
+ * Fetch a previous value. A delayIndex of 1 will return the most recently written value.
+ * A delayIndex of 2 will return the previously written value;
+ * @param delayIndex positive index of previously written data
+ * @return old value
+ */
+ public float get(int delayIndex) {
+ int index = (mCursor - delayIndex) & mIndexMask;
+ return mData[index];
+ }
+
+ public void clear() {
+ mCursor = 0;
+ }
+ }
+} \ No newline at end of file
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeEngine.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeEngine.java
index 6978bdca..985797b1 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeEngine.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeEngine.java
@@ -9,4 +9,8 @@ public class NativeEngine {
static native void setWorkaroundsEnabled(boolean enabled);
static native boolean areWorkaroundsEnabled();
+
+ static native int getCpuCount();
+
+ static native void setCpuAffinityMask(int mask);
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeSniffer.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeSniffer.java
index 1bc1232c..222173d7 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeSniffer.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/NativeSniffer.java
@@ -16,23 +16,24 @@
package com.mobileer.oboetester;
-import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
abstract class NativeSniffer implements Runnable {
public static final int SNIFFER_UPDATE_PERIOD_MSEC = 100;
public static final int SNIFFER_UPDATE_DELAY_MSEC = 200;
- private final Activity activity;
protected Handler mHandler = new Handler(Looper.getMainLooper()); // UI thread
protected volatile boolean mEnabled = true;
- public NativeSniffer(Activity activity) {
- this.activity = activity;
+ @Override
+ public void run() {
+ if (mEnabled && !isComplete()) {
+ updateStatusText();
+ mHandler.postDelayed(this, SNIFFER_UPDATE_PERIOD_MSEC);
+ }
}
public void startSniffer() {
- long now = System.currentTimeMillis();
// Start the initial runnable task by posting through the handler
mEnabled = true;
mHandler.postDelayed(this, SNIFFER_UPDATE_DELAY_MSEC);
@@ -42,28 +43,19 @@ abstract class NativeSniffer implements Runnable {
mEnabled = false;
if (mHandler != null) {
mHandler.removeCallbacks(this);
+ // Final update of the text.
+ mHandler.post(this);
}
-
- activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- updateStatusText();
- }
- });
}
- public void reschedule() {
- updateStatusText();
- // Reschedule so this task repeats
- if (mEnabled) {
- mHandler.postDelayed(this, SNIFFER_UPDATE_PERIOD_MSEC);
- }
+ /**
+ * You can override this is if you want to control when sniffing is finished.
+ * @return true if finished
+ */
+ public boolean isComplete() {
+ return false;
}
public abstract void updateStatusText();
- public String getShortReport() {
- return "no-report";
- }
-
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioOutputStream.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioOutputStream.java
index 92cde485..7a727db4 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioOutputStream.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioOutputStream.java
@@ -31,4 +31,6 @@ public class OboeAudioOutputStream extends OboeAudioStream {
public native void setChannelEnabled(int channelIndex, boolean enabled);
public native void setSignalType(int type);
+
+ public native void setAmplitude(float amplitude);
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java
index 5ff2dd12..5c8b2109 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/OboeAudioStream.java
@@ -29,7 +29,7 @@ abstract class OboeAudioStream extends AudioStreamBase {
@Override
public void stopPlayback() throws IOException {
int result = stopPlaybackNative();
- if (result < 0) {
+ if (result != 0) {
throw new IOException("Stop Playback failed! result = " + result);
}
}
@@ -39,19 +39,13 @@ abstract class OboeAudioStream extends AudioStreamBase {
@Override
public void startPlayback() throws IOException {
int result = startPlaybackNative();
- if (result < 0) {
+ if (result != 0) {
throw new IOException("Start Playback failed! result = " + result);
}
}
public native int startPlaybackNative();
- // Write disabled because the synth is in native code.
- @Override
- public int write(float[] buffer, int offset, int length) {
- return 0;
- }
-
@Override
public void open(StreamConfiguration requestedConfiguration,
StreamConfiguration actualConfiguration, int bufferSizeInFrames) throws IOException {
@@ -66,6 +60,7 @@ abstract class OboeAudioStream extends AudioStreamBase {
requestedConfiguration.getInputPreset(),
requestedConfiguration.getUsage(),
requestedConfiguration.getContentType(),
+ requestedConfiguration.getBufferCapacityInFrames(),
requestedConfiguration.getDeviceId(),
requestedConfiguration.getSessionId(),
requestedConfiguration.getChannelConversionAllowed(),
@@ -76,7 +71,11 @@ abstract class OboeAudioStream extends AudioStreamBase {
);
if (result < 0) {
streamIndex = INVALID_STREAM_INDEX;
- throw new IOException("Open failed! result = " + result);
+ String message = "Open "
+ + (isInput() ? "Input" : "Output")
+ + " failed! result = " + result + ", "
+ + StreamConfiguration.convertErrorToText(result);
+ throw new IOException(message);
} else {
streamIndex = result;
}
@@ -98,6 +97,9 @@ abstract class OboeAudioStream extends AudioStreamBase {
actualConfiguration.setDirection(isInput()
? StreamConfiguration.DIRECTION_INPUT
: StreamConfiguration.DIRECTION_OUTPUT);
+ actualConfiguration.setHardwareChannelCount(getHardwareChannelCount());
+ actualConfiguration.setHardwareSampleRate(getHardwareSampleRate());
+ actualConfiguration.setHardwareFormat(getHardwareFormat());
}
private native int openNative(
@@ -111,6 +113,7 @@ abstract class OboeAudioStream extends AudioStreamBase {
int inputPreset,
int usage,
int contentType,
+ int bufferCapacityInFrames,
int deviceId,
int sessionId,
boolean channelConversionAllowed,
@@ -141,16 +144,23 @@ abstract class OboeAudioStream extends AudioStreamBase {
private native int getBufferSizeInFrames(int streamIndex);
@Override
- public boolean isThresholdSupported() {
- return true;
- }
-
- @Override
public int setBufferSizeInFrames(int thresholdFrames) {
return setBufferSizeInFrames(streamIndex, thresholdFrames);
}
private native int setBufferSizeInFrames(int streamIndex, int thresholdFrames);
+ @Override
+ public void setPerformanceHintEnabled(boolean checked) {
+ setPerformanceHintEnabled(streamIndex, checked);
+ }
+ private native void setPerformanceHintEnabled(int streamIndex, boolean checked);
+
+ @Override
+ public void setHearWorkload(boolean checked) {
+ setHearWorkload(streamIndex, checked);
+ }
+ private native void setHearWorkload(int streamIndex, boolean checked);
+
public int getNativeApi() {
return getNativeApi(streamIndex);
}
@@ -207,6 +217,21 @@ abstract class OboeAudioStream extends AudioStreamBase {
}
private native int getChannelMask(int streamIndex);
+ public int getHardwareChannelCount() {
+ return getHardwareChannelCount(streamIndex);
+ }
+ private native int getHardwareChannelCount(int streamIndex);
+
+ public int getHardwareSampleRate() {
+ return getHardwareSampleRate(streamIndex);
+ }
+ private native int getHardwareSampleRate(int streamIndex);
+
+ public int getHardwareFormat() {
+ return getHardwareFormat(streamIndex);
+ }
+ private native int getHardwareFormat(int streamIndex);
+
public int getDeviceId() {
return getDeviceId(streamIndex);
}
@@ -217,6 +242,7 @@ abstract class OboeAudioStream extends AudioStreamBase {
}
private native int getSessionId(int streamIndex);
+
public boolean isMMap() {
return isMMap(streamIndex);
}
@@ -256,10 +282,22 @@ abstract class OboeAudioStream extends AudioStreamBase {
private native double getTimestampLatency(int streamIndex);
@Override
- public double getCpuLoad() {
+ public float getCpuLoad() {
return getCpuLoad(streamIndex);
}
- private native double getCpuLoad(int streamIndex);
+ private native float getCpuLoad(int streamIndex);
+
+ @Override
+ public float getAndResetMaxCpuLoad() {
+ return getAndResetMaxCpuLoad(streamIndex);
+ }
+ private native float getAndResetMaxCpuLoad(int streamIndex);
+
+ @Override
+ public int getAndResetCpuMask() {
+ return getAndResetCpuMask(streamIndex);
+ }
+ private native int getAndResetCpuMask(int streamIndex);
@Override
public String getCallbackTimeStr() {
@@ -268,7 +306,7 @@ abstract class OboeAudioStream extends AudioStreamBase {
public native String getCallbackTimeString();
@Override
- public native void setWorkload(double workload);
+ public native void setWorkload(int workload);
@Override
public int getState() {
@@ -278,6 +316,8 @@ abstract class OboeAudioStream extends AudioStreamBase {
public static native void setCallbackReturnStop(boolean b);
+ public static native void setHangTimeMillis(int hangTimeMillis);
+
public static native void setUseCallback(boolean checked);
public static native void setCallbackSize(int callbackSize);
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/RoundTripLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/RoundTripLatencyActivity.java
index 69207688..8f81f558 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/RoundTripLatencyActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/RoundTripLatencyActivity.java
@@ -21,12 +21,15 @@ import static com.mobileer.oboetester.IntentBasedTestSupport.configureStreamsFro
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import java.io.File;
import java.io.IOException;
+import java.util.Locale;
/**
* Activity to measure latency on a full duplex stream.
@@ -132,18 +135,18 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
mTimestampLatencies.calculateMeanAbsoluteDeviation(timestampLatencyMean);
}
message = "average.latency.msec = "
- + String.format(LATENCY_FORMAT, meanLatency) + "\n"
+ + String.format(Locale.getDefault(), LATENCY_FORMAT, meanLatency) + "\n"
+ "mean.absolute.deviation = "
- + String.format(LATENCY_FORMAT, meanAbsoluteDeviation) + "\n"
+ + String.format(Locale.getDefault(), LATENCY_FORMAT, meanAbsoluteDeviation) + "\n"
+ "average.confidence = "
- + String.format(CONFIDENCE_FORMAT, mAverageConfidence) + "\n"
- + "min.latency.msec = " + String.format(LATENCY_FORMAT, mLatencies.getMin()) + "\n"
- + "max.latency.msec = " + String.format(LATENCY_FORMAT, mLatencies.getMax()) + "\n"
+ + String.format(Locale.getDefault(), CONFIDENCE_FORMAT, mAverageConfidence) + "\n"
+ + "min.latency.msec = " + String.format(Locale.getDefault(), LATENCY_FORMAT, mLatencies.getMin()) + "\n"
+ + "max.latency.msec = " + String.format(Locale.getDefault(), LATENCY_FORMAT, mLatencies.getMax()) + "\n"
+ "num.iterations = " + mLatencies.count() + "\n"
+ "timestamp.latency.msec = "
- + String.format(LATENCY_FORMAT, timestampLatencyMean) + "\n"
+ + String.format(Locale.getDefault(), LATENCY_FORMAT, timestampLatencyMean) + "\n"
+ "timestamp.latency.mad = "
- + String.format(LATENCY_FORMAT, timestampLatencyMAD) + "\n";
+ + String.format(Locale.getDefault(), LATENCY_FORMAT, timestampLatencyMAD) + "\n";
}
message += "num.failed = " + mBadCount + "\n";
message += "\n"; // mark end of average report
@@ -208,7 +211,10 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
} else {
message = getResultString();
}
- onAnalyzerDone();
+ File resultFile = onAnalyzerDone();
+ if (resultFile != null) {
+ message = "result.file = " + resultFile.getAbsolutePath() + "\n" + message;
+ }
} else {
message = getProgressText();
message += "please wait... " + counter + "\n";
@@ -248,21 +254,23 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
int progress = getAnalyzerProgress();
int state = getAnalyzerState();
int resetCount = getResetCount();
- String message = String.format("progress = %d\nstate = %d\n#resets = %d\n",
+ String message = String.format(Locale.getDefault(), "progress = %d\nstate = %d\n#resets = %d\n",
progress, state, resetCount);
message += mAverageLatencyTestRunner.getLastReport();
return message;
}
- private void onAnalyzerDone() {
+ private File onAnalyzerDone() {
+ File resultFile = null;
if (mTestRunningByIntent) {
String report = getCommonTestReport();
report += getResultString();
- maybeWriteTestResult(report);
+ resultFile = maybeWriteTestResult(report);
}
mTestRunningByIntent = false;
mHasRecording = true;
stopAudioTest();
+ return resultFile;
}
@NonNull
@@ -272,8 +280,8 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
double confidence = getMeasuredConfidence();
String message = "";
- message += String.format("confidence = " + CONFIDENCE_FORMAT + "\n", confidence);
- message += String.format("result.text = %s\n", resultCodeToString(result));
+ message += String.format(Locale.getDefault(), "confidence = " + CONFIDENCE_FORMAT + "\n", confidence);
+ message += String.format(Locale.getDefault(), "result.text = %s\n", resultCodeToString(result));
// Only report valid latencies.
if (result == 0) {
@@ -282,26 +290,26 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
int bufferSize = mAudioOutTester.getCurrentAudioStream().getBufferSizeInFrames();
int latencyEmptyFrames = latencyFrames - bufferSize;
double latencyEmptyMillis = latencyEmptyFrames * 1000.0 / getSampleRate();
- message += String.format("latency.msec = " + LATENCY_FORMAT + "\n", latencyMillis);
- message += String.format("latency.frames = %d\n", latencyFrames);
- message += String.format("latency.empty.msec = " + LATENCY_FORMAT + "\n", latencyEmptyMillis);
- message += String.format("latency.empty.frames = %d\n", latencyEmptyFrames);
+ message += String.format(Locale.getDefault(), "latency.msec = " + LATENCY_FORMAT + "\n", latencyMillis);
+ message += String.format(Locale.getDefault(), "latency.frames = %d\n", latencyFrames);
+ message += String.format(Locale.getDefault(), "latency.empty.msec = " + LATENCY_FORMAT + "\n", latencyEmptyMillis);
+ message += String.format(Locale.getDefault(), "latency.empty.frames = %d\n", latencyEmptyFrames);
}
- message += String.format("rms.signal = %7.5f\n", getSignalRMS());
- message += String.format("rms.noise = %7.5f\n", getBackgroundRMS());
- message += String.format("correlation = " + CONFIDENCE_FORMAT + "\n",
+ message += String.format(Locale.getDefault(), "rms.signal = %7.5f\n", getSignalRMS());
+ message += String.format(Locale.getDefault(), "rms.noise = %7.5f\n", getBackgroundRMS());
+ message += String.format(Locale.getDefault(), "correlation = " + CONFIDENCE_FORMAT + "\n",
getMeasuredCorrelation());
double timestampLatency = getTimestampLatencyMillis();
- message += String.format("timestamp.latency.msec = " + LATENCY_FORMAT + "\n",
+ message += String.format(Locale.getDefault(), "timestamp.latency.msec = " + LATENCY_FORMAT + "\n",
timestampLatency);
if (mTimestampLatencyStats.count() > 0) {
- message += String.format("timestamp.latency.mad = " + LATENCY_FORMAT + "\n",
+ message += String.format(Locale.getDefault(), "timestamp.latency.mad = " + LATENCY_FORMAT + "\n",
mTimestampLatencyStats.calculateMeanAbsoluteDeviation(timestampLatency));
}
message += "timestamp.latency.count = " + mTimestampLatencyStats.count() + "\n";
- message += String.format("reset.count = %d\n", resetCount);
- message += String.format("result = %d\n", result);
+ message += String.format(Locale.getDefault(), "reset.count = %d\n", resetCount);
+ message += String.format(Locale.getDefault(), "result = %d\n", result);
return message;
}
@@ -348,6 +356,9 @@ public class RoundTripLatencyActivity extends AnalyzerActivity {
hideSettingsViews();
mBufferSizeView.setFaderNormalizedProgress(0.0); // for lowest latency
+
+ mCommunicationDeviceView = (CommunicationDeviceView) findViewById(R.id.comm_device_view);
+
}
@Override
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java
index ea9a9e8c..5c1f32b7 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfiguration.java
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
/**
* Container for the properties of a Stream.
@@ -44,6 +45,7 @@ public class StreamConfiguration {
public static final int AUDIO_FORMAT_PCM_FLOAT = 2; // must match AAUDIO
public static final int AUDIO_FORMAT_PCM_24 = 3; // must match AAUDIO
public static final int AUDIO_FORMAT_PCM_32 = 4; // must match AAUDIO
+ public static final int AUDIO_FORMAT_IEC61937 = 5; // must match AAUDIO
public static final int DIRECTION_OUTPUT = 0; // must match AAUDIO
public static final int DIRECTION_INPUT = 1; // must match AAUDIO
@@ -72,7 +74,25 @@ public class StreamConfiguration {
public static final int INPUT_PRESET_UNPROCESSED = 9; // must match Oboe
public static final int INPUT_PRESET_VOICE_PERFORMANCE = 10; // must match Oboe
+ public static final int ERROR_BASE = -900; // must match Oboe
public static final int ERROR_DISCONNECTED = -899; // must match Oboe
+ public static final int ERROR_ILLEGAL_ARGUMENT = -898; // must match Oboe
+ public static final int ERROR_INTERNAL = -896; // must match Oboe
+ public static final int ERROR_INVALID_STATE = -895; // must match Oboe
+ public static final int ERROR_INVALID_HANDLE = -892; // must match Oboe
+ public static final int ERROR_UNIMPLEMENTED = -890; // must match Oboe
+ public static final int ERROR_UNAVAILABLE = -889; // must match Oboe
+ public static final int ERROR_NO_FREE_HANDLES = -888; // must match Oboe
+ public static final int ERROR_NO_MEMORY = -887; // must match Oboe
+ public static final int ERROR_NULL = -886; // must match Oboe
+ public static final int ERROR_TIMEOUT = -885; // must match Oboe
+ public static final int ERROR_WOULD_BLOCK = -884; // must match Oboe
+ public static final int ERROR_INVALID_FORMAT = -883; // must match Oboe
+ public static final int ERROR_OUT_OF_RANGE = -882; // must match Oboe
+ public static final int ERROR_NO_SERVICE = -881; // must match Oboe
+ public static final int ERROR_INVALID_RATE = -880; // must match Oboe
+ public static final int ERROR_CLOSED = -869; // must match Oboe
+ public static final int ERROR_OK = 0; // must match Oboe
public static final int USAGE_MEDIA = 1;
public static final int USAGE_VOICE_COMMUNICATION = 2;
@@ -277,6 +297,9 @@ public class StreamConfiguration {
private int mFramesPerBurst;
private boolean mMMap;
private int mChannelMask;
+ private int mHardwareChannelCount;
+ private int mHardwareSampleRate;
+ private int mHardwareFormat;
public StreamConfiguration() {
reset();
@@ -327,6 +350,9 @@ public class StreamConfiguration {
mChannelConversionAllowed = false;
mRateConversionQuality = RATE_CONVERSION_QUALITY_NONE;
mMMap = NativeEngine.isMMapSupported();
+ mHardwareChannelCount = UNSPECIFIED;
+ mHardwareSampleRate = UNSPECIFIED;
+ mHardwareFormat = UNSPECIFIED;
}
public int getFramesPerBurst() {
@@ -486,6 +512,8 @@ public class StreamConfiguration {
return "I32";
case AUDIO_FORMAT_PCM_FLOAT:
return "Float";
+ case AUDIO_FORMAT_IEC61937:
+ return "IEC61937";
default:
return "Invalid";
}
@@ -563,6 +591,25 @@ public class StreamConfiguration {
}
}
+ static String convertRateConversionQualityToText(int quality) {
+ switch(quality) {
+ case RATE_CONVERSION_QUALITY_NONE:
+ return "None";
+ case RATE_CONVERSION_QUALITY_FASTEST:
+ return "Fastest";
+ case RATE_CONVERSION_QUALITY_LOW:
+ return "Low";
+ case RATE_CONVERSION_QUALITY_MEDIUM:
+ return "Medium";
+ case RATE_CONVERSION_QUALITY_HIGH:
+ return "High";
+ case RATE_CONVERSION_QUALITY_BEST:
+ return "Best";
+ default:
+ return "?=" + quality;
+ }
+ }
+
public static int convertTextToChannelMask(String text) {
return mChannelMaskStringToIntegerMap.get(text);
}
@@ -571,26 +618,30 @@ public class StreamConfiguration {
public String dump() {
String prefix = (getDirection() == DIRECTION_INPUT) ? "in" : "out";
StringBuffer message = new StringBuffer();
- message.append(String.format("%s.channels = %d\n", prefix, mChannelCount));
- message.append(String.format("%s.perf = %s\n", prefix,
- convertPerformanceModeToText(mPerformanceMode).toLowerCase()));
+ message.append(String.format(Locale.getDefault(), "%s.channels = %d\n", prefix, mChannelCount));
+ message.append(String.format(Locale.getDefault(), "%s.perf = %s\n", prefix,
+ convertPerformanceModeToText(mPerformanceMode).toLowerCase(Locale.getDefault())));
if (getDirection() == DIRECTION_INPUT) {
- message.append(String.format("%s.preset = %s\n", prefix,
- convertInputPresetToText(mInputPreset).toLowerCase()));
+ message.append(String.format(Locale.getDefault(), "%s.preset = %s\n", prefix,
+ convertInputPresetToText(mInputPreset).toLowerCase(Locale.getDefault())));
} else {
- message.append(String.format("%s.preset = %s\n", prefix,
- convertUsageToText(mUsage).toLowerCase()));
- message.append(String.format("%s.contentType = %s\n", prefix,
- convertContentTypeToText(mContentType).toLowerCase()));
+ message.append(String.format(Locale.getDefault(), "%s.usage = %s\n", prefix,
+ convertUsageToText(mUsage).toLowerCase(Locale.getDefault())));
+ message.append(String.format(Locale.getDefault(), "%s.contentType = %s\n", prefix,
+ convertContentTypeToText(mContentType).toLowerCase(Locale.getDefault())));
}
- message.append(String.format("%s.sharing = %s\n", prefix,
- convertSharingModeToText(mSharingMode).toLowerCase()));
- message.append(String.format("%s.api = %s\n", prefix,
- convertNativeApiToText(getNativeApi()).toLowerCase()));
- message.append(String.format("%s.rate = %d\n", prefix, mSampleRate));
- message.append(String.format("%s.device = %d\n", prefix, mDeviceId));
- message.append(String.format("%s.mmap = %s\n", prefix, isMMap() ? "yes" : "no"));
- message.append(String.format("%s.rate.conversion.quality = %d\n", prefix, mRateConversionQuality));
+ message.append(String.format(Locale.getDefault(), "%s.sharing = %s\n", prefix,
+ convertSharingModeToText(mSharingMode).toLowerCase(Locale.getDefault())));
+ message.append(String.format(Locale.getDefault(), "%s.api = %s\n", prefix,
+ convertNativeApiToText(getNativeApi()).toLowerCase(Locale.getDefault())));
+ message.append(String.format(Locale.getDefault(), "%s.rate = %d\n", prefix, mSampleRate));
+ message.append(String.format(Locale.getDefault(), "%s.device = %d\n", prefix, mDeviceId));
+ message.append(String.format(Locale.getDefault(), "%s.mmap = %s\n", prefix, isMMap() ? "yes" : "no"));
+ message.append(String.format(Locale.getDefault(), "%s.rate.conversion.quality = %d\n", prefix, mRateConversionQuality));
+ message.append(String.format(Locale.getDefault(), "%s.hardware.channels = %d\n", prefix, mHardwareChannelCount));
+ message.append(String.format(Locale.getDefault(), "%s.hardware.sampleRate = %d\n", prefix, mHardwareSampleRate));
+ message.append(String.format(Locale.getDefault(), "%s.hardware.format = %s\n", prefix,
+ convertFormatToText(mHardwareFormat).toLowerCase(Locale.getDefault())));
return message.toString();
}
@@ -622,7 +673,7 @@ public class StreamConfiguration {
}
private static boolean matchInputPreset(String text, int preset) {
- return convertInputPresetToText(preset).toLowerCase().equals(text);
+ return convertInputPresetToText(preset).toLowerCase(Locale.getDefault()).equals(text);
}
/**
@@ -631,7 +682,7 @@ public class StreamConfiguration {
* @return inputPreset, eg. INPUT_PRESET_CAMCORDER
*/
public static int convertTextToInputPreset(String text) {
- text = text.toLowerCase();
+ text = text.toLowerCase(Locale.getDefault());
if (matchInputPreset(text, INPUT_PRESET_GENERIC)) {
return INPUT_PRESET_GENERIC;
} else if (matchInputPreset(text, INPUT_PRESET_CAMCORDER)) {
@@ -726,4 +777,72 @@ public class StreamConfiguration {
return mChannelMaskStrings;
}
+ public int getHardwareChannelCount() {
+ return mHardwareChannelCount;
+ }
+
+ public void setHardwareChannelCount(int hardwareChannelCount) {
+ this.mHardwareChannelCount = hardwareChannelCount;
+ }
+
+ public int getHardwareSampleRate() {
+ return mHardwareSampleRate;
+ }
+
+ public void setHardwareSampleRate(int hardwareSampleRate) {
+ this.mHardwareSampleRate = hardwareSampleRate;
+ }
+
+ public int getHardwareFormat() {
+ return mHardwareFormat;
+ }
+
+ public void setHardwareFormat(int hardwareFormat) {
+ this.mHardwareFormat = hardwareFormat;
+ }
+
+ static String convertErrorToText(int error) {
+ switch (error) {
+ case ERROR_BASE:
+ return "ErrorBase";
+ case ERROR_DISCONNECTED:
+ return "ErrorDisconnected";
+ case ERROR_ILLEGAL_ARGUMENT:
+ return "ErrorIllegalArgument";
+ case ERROR_INTERNAL:
+ return "ErrorInternal";
+ case ERROR_INVALID_STATE:
+ return "ErrorInvalidState";
+ case ERROR_INVALID_HANDLE:
+ return "ErrorInvalidHandle";
+ case ERROR_UNIMPLEMENTED:
+ return "ErrorUnimplemented";
+ case ERROR_UNAVAILABLE:
+ return "ErrorUnavailable";
+ case ERROR_NO_FREE_HANDLES:
+ return "ErrorNoFreeHandles";
+ case ERROR_NO_MEMORY:
+ return "ErrorNoMemory";
+ case ERROR_NULL:
+ return "ErrorNull";
+ case ERROR_TIMEOUT:
+ return "ErrorTimeout";
+ case ERROR_WOULD_BLOCK:
+ return "ErrorWouldBlock";
+ case ERROR_INVALID_FORMAT:
+ return "ErrorInvalidFormat";
+ case ERROR_OUT_OF_RANGE:
+ return "ErrorOutOfRange";
+ case ERROR_NO_SERVICE:
+ return "ErrorNoService";
+ case ERROR_INVALID_RATE:
+ return "ErrorInvalidRate";
+ case ERROR_CLOSED:
+ return "ErrorClosed";
+ case ERROR_OK:
+ return "ErrorOk";
+ default:
+ return "?=" + error;
+ }
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java
index eeb962f0..418d08e5 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/StreamConfigurationView.java
@@ -39,6 +39,8 @@ import android.util.Log;
import com.mobileer.audio_device.AudioDeviceListEntry;
import com.mobileer.audio_device.AudioDeviceSpinner;
+import java.util.Locale;
+
/**
* View for Editing a requested StreamConfiguration
* and displaying the actual StreamConfiguration.
@@ -63,7 +65,8 @@ public class StreamConfigurationView extends LinearLayout {
private Spinner mChannelMaskSpinner;
private TextView mActualChannelMaskView;
private TextView mActualFormatView;
-
+ private Spinner mCapacitySpinner;
+ private TextView mActualCapacityView;
private TableRow mInputPresetTableRow;
private Spinner mInputPresetSpinner;
private TextView mActualInputPresetView;
@@ -288,6 +291,10 @@ public class StreamConfigurationView extends LinearLayout {
});
mActualFormatView = (TextView) findViewById(R.id.actualAudioFormat);
mFormatSpinner = (Spinner) findViewById(R.id.spinnerFormat);
+
+ mActualCapacityView = (TextView) findViewById(R.id.actualCapacity);
+ mCapacitySpinner = (Spinner) findViewById(R.id.spinnerCapacity);
+
mRateConversionQualitySpinner = (Spinner) findViewById(R.id.spinnerSRCQuality);
mActualPerformanceView = (TextView) findViewById(R.id.actualPerformanceMode);
@@ -388,13 +395,17 @@ public class StreamConfigurationView extends LinearLayout {
int channelMask = StreamConfiguration.convertTextToChannelMask(text);
config.setChannelMask(channelMask);
config.setChannelCount(0);
- Log.d(TAG, String.format("Set channel mask as %s(%#x)", text, channelMask));
+ Log.d(TAG, String.format(Locale.getDefault(), "Set channel mask as %s(%#x)", text, channelMask));
} else {
config.setChannelCount(mChannelCountSpinner.getSelectedItemPosition());
config.setChannelMask(StreamConfiguration.UNSPECIFIED);
Log.d(TAG, "Set channel count as " + mChannelCountSpinner.getSelectedItemPosition());
}
+ text = mCapacitySpinner.getSelectedItem().toString();
+ int bufferCapacity = Integer.parseInt(text);
+ config.setBufferCapacityInFrames(bufferCapacity);
+
config.setMMap(mRequestedMMapView.isChecked());
config.setChannelConversionAllowed(mChannelConversionBox.isChecked());
config.setFormatConversionAllowed(mFormatConversionBox.isChecked());
@@ -418,6 +429,7 @@ public class StreamConfigurationView extends LinearLayout {
mFormatConversionBox.setEnabled(enabled);
mChannelCountSpinner.setEnabled(enabled);
mChannelMaskSpinner.setEnabled(enabled);
+ mCapacitySpinner.setEnabled(enabled);
mInputPresetSpinner.setEnabled(enabled);
mUsageSpinner.setEnabled(enabled);
mContentTypeSpinner.setEnabled(enabled);
@@ -465,14 +477,27 @@ public class StreamConfigurationView extends LinearLayout {
mActualSessionIdView.setText("S#: " + actualConfiguration.getSessionId());
value = actualConfiguration.getChannelMask();
mActualChannelMaskView.setText(StreamConfiguration.convertChannelMaskToText(value));
+ mActualCapacityView.setText(actualConfiguration.getBufferCapacityInFrames() + "");
boolean isMMap = actualConfiguration.isMMap();
- mStreamInfoView.setText("burst = " + actualConfiguration.getFramesPerBurst()
- + ", capacity = " + actualConfiguration.getBufferCapacityInFrames()
- + ", devID = " + actualConfiguration.getDeviceId()
- + ", " + (actualConfiguration.isMMap() ? "MMAP" : "Legacy")
- + (isMMap ? ", " + StreamConfiguration.convertSharingModeToText(sharingMode) : "")
- );
+
+ String msg = "";
+ msg += "burst = " + actualConfiguration.getFramesPerBurst();
+ msg += ", devID = " + actualConfiguration.getDeviceId();
+ msg += ", " + (actualConfiguration.isMMap() ? "MMAP" : "Legacy");
+ msg += (isMMap ? ", " + StreamConfiguration.convertSharingModeToText(sharingMode) : "");
+
+ int hardwareChannelCount = actualConfiguration.getHardwareChannelCount();
+ int hardwareSampleRate = actualConfiguration.getHardwareSampleRate();
+ int hardwareFormat = actualConfiguration.getHardwareFormat();
+ msg += "\nHW: #ch=" + (hardwareChannelCount ==
+ StreamConfiguration.UNSPECIFIED ? "?" : hardwareChannelCount);
+ msg += ", SR=" + (hardwareSampleRate ==
+ StreamConfiguration.UNSPECIFIED ? "?" : hardwareSampleRate);
+ msg += ", format=" + (hardwareFormat == StreamConfiguration.UNSPECIFIED ?
+ "?" : StreamConfiguration.convertFormatToText(hardwareFormat));
+
+ mStreamInfoView.setText(msg);
mHideableView.requestLayout();
}
@@ -531,7 +556,7 @@ public class StreamConfigurationView extends LinearLayout {
if (mAcousticEchoCanceler != null) {
mAcousticEchoCanceler.setEnabled(mAcousticEchoCancelerCheckBox.isChecked());
} else {
- Log.e(TAG, String.format("Could not create AcousticEchoCanceler"));
+ Log.e(TAG, String.format(Locale.getDefault(), "Could not create AcousticEchoCanceler"));
}
}
// If AGC is not available, the checkbox will be disabled in initializeViews().
@@ -540,7 +565,7 @@ public class StreamConfigurationView extends LinearLayout {
if (mAutomaticGainControl != null) {
mAutomaticGainControl.setEnabled(mAutomaticGainControlCheckBox.isChecked());
} else {
- Log.e(TAG, String.format("Could not create AutomaticGainControl"));
+ Log.e(TAG, String.format(Locale.getDefault(), "Could not create AutomaticGainControl"));
}
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneActivity.java
index 2da6bf5f..ad2c3c97 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneActivity.java
@@ -18,6 +18,8 @@ package com.mobileer.oboetester;
import android.Manifest;
import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
import android.media.midi.MidiDevice;
import android.media.midi.MidiDeviceInfo;
import android.media.midi.MidiInputPort;
@@ -30,9 +32,13 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Toast;
+import com.mobileer.audio_device.AudioDeviceListEntry;
+import com.mobileer.audio_device.AudioDeviceSpinner;
import com.mobileer.miditools.MidiOutputPortConnectionSelector;
import com.mobileer.miditools.MidiPortConnector;
import com.mobileer.miditools.MidiTools;
@@ -59,6 +65,8 @@ public class TapToToneActivity extends TestOutputActivityBase {
private MidiOutputPortConnectionSelector mPortSelector;
private final MyNoteListener mTestListener = new MyNoteListener();
+ private AudioDeviceSpinner mInputDeviceSpinner;
+
@Override
protected void inflateActivity() {
setContentView(R.layout.activity_tap_to_tone);
@@ -103,11 +111,16 @@ public class TapToToneActivity extends TestOutputActivityBase {
return true;
});
+ mCommunicationDeviceView = (CommunicationDeviceView) findViewById(R.id.comm_device_view);
+
mStartButton = (Button) findViewById(R.id.button_start);
mStopButton = (Button) findViewById(R.id.button_stop);
updateButtons(false);
updateEnabledWidgets();
+
+ mInputDeviceSpinner = (AudioDeviceSpinner) findViewById(R.id.input_devices_spinner);
+ mInputDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_INPUTS);
}
private void updateButtons(boolean running) {
@@ -252,28 +265,6 @@ public class TapToToneActivity extends TestOutputActivityBase {
}
}
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.menu_main, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
-
- //noinspection SimplifiableIfStatement
- if (id == R.id.action_settings) {
- return true;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
public void startTest(View view) {
try {
openAudio();
@@ -284,8 +275,7 @@ public class TapToToneActivity extends TestOutputActivityBase {
}
try {
super.startAudio();
- mTapToToneTester.resetLatency();
- mTapToToneTester.start();
+ startTapToToneTester();
updateButtons(true);
} catch (IOException e) {
e.printStackTrace();
@@ -295,9 +285,25 @@ public class TapToToneActivity extends TestOutputActivityBase {
}
public void stopTest(View view) {
- mTapToToneTester.stop();
+ stopTapToToneTester();
stopAudio();
closeAudio();
updateButtons(false);
}
+
+ private void startTapToToneTester() throws IOException {
+ AudioDeviceInfo deviceInfo =
+ ((AudioDeviceListEntry) mInputDeviceSpinner.getSelectedItem()).getDeviceInfo();
+ mTapToToneTester.setInputDevice(deviceInfo);
+ mInputDeviceSpinner.setEnabled(false);
+ mTapToToneTester.resetLatency();
+ mTapToToneTester.start();
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void stopTapToToneTester() {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mInputDeviceSpinner.setEnabled(true);
+ mTapToToneTester.stop();
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneTester.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneTester.java
index bd931534..ce92a25a 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneTester.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TapToToneTester.java
@@ -1,9 +1,11 @@
package com.mobileer.oboetester;
import android.app.Activity;
+import android.media.AudioDeviceInfo;
import android.widget.TextView;
import java.io.IOException;
+import java.util.Locale;
/**
* Measure tap-to-tone latency by and update the waveform display.
@@ -176,7 +178,7 @@ public class TapToToneTester {
mLatencyMax = latencyMillis;
}
- text = String.format("tap-to-tone latency = %3d msec\n", latencyMillis);
+ text = String.format(Locale.getDefault(), "tap-to-tone latency = %3d msec\n", latencyMillis);
}
mWaveformView.setSampleData(result.filtered);
}
@@ -185,7 +187,7 @@ public class TapToToneTester {
int averageLatencySamples = mLatencySumSamples / mMeasurementCount;
int averageLatencyMillis = 1000 * averageLatencySamples / result.frameRate;
final String plural = (mMeasurementCount == 1) ? "test" : "tests";
- text = text + String.format("min = %3d, avg = %3d, max = %3d, %d %s",
+ text = text + String.format(Locale.getDefault(), "min = %3d, avg = %3d, max = %3d, %d %s",
mLatencyMin, averageLatencyMillis, mLatencyMax, mMeasurementCount, plural);
}
final String postText = text;
@@ -198,4 +200,8 @@ public class TapToToneTester {
mArmed = true;
}
+
+ void setInputDevice(AudioDeviceInfo deviceInfo) {
+ mRecorder.setInputDevice(deviceInfo);
+ }
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java
index f61873fa..9043f28d 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestAudioActivity.java
@@ -16,34 +16,32 @@
package com.mobileer.oboetester;
-import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.support.v4.app.ActivityCompat;
-import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
+import android.widget.AdapterView;
import android.widget.Button;
import android.widget.CheckBox;
+import android.widget.Spinner;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
import java.util.ArrayList;
+import java.util.Locale;
/**
* Base class for other Activities.
@@ -57,9 +55,11 @@ abstract class TestAudioActivity extends Activity {
public static final int AUDIO_STATE_OPEN = 0;
public static final int AUDIO_STATE_STARTED = 1;
public static final int AUDIO_STATE_PAUSED = 2;
- public static final int AUDIO_STATE_STOPPED = 3;
- public static final int AUDIO_STATE_CLOSING = 4;
- public static final int AUDIO_STATE_CLOSED = 5;
+ public static final int AUDIO_STATE_FLUSHED = 3;
+ public static final int AUDIO_STATE_STOPPED = 4;
+ public static final int AUDIO_STATE_RELEASED = 5;
+ public static final int AUDIO_STATE_CLOSING = 6;
+ public static final int AUDIO_STATE_CLOSED = 7;
public static final int COLOR_ACTIVE = 0xFFD0D0A0;
public static final int COLOR_IDLE = 0xFFD0D0D0;
@@ -75,8 +75,7 @@ abstract class TestAudioActivity extends Activity {
public static final int ACTIVITY_GLITCHES = 6;
public static final int ACTIVITY_TEST_DISCONNECT = 7;
public static final int ACTIVITY_DATA_PATHS = 8;
-
- private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 1001;
+ public static final int ACTIVITY_DYNAMIC_WORKLOAD = 9;
private int mAudioState = AUDIO_STATE_CLOSED;
@@ -84,10 +83,16 @@ abstract class TestAudioActivity extends Activity {
private Button mOpenButton;
private Button mStartButton;
private Button mPauseButton;
+ private Button mFlushButton;
private Button mStopButton;
+ private Button mReleaseButton;
private Button mCloseButton;
private MyStreamSniffer mStreamSniffer;
private CheckBox mCallbackReturnStopBox;
+ private Spinner mHangTimeSpinner;
+
+ // Only set in some activities
+ protected CommunicationDeviceView mCommunicationDeviceView;
private int mSampleRate;
private int mSingleTestIndex = -1;
private static boolean mBackgroundEnabled;
@@ -96,6 +101,7 @@ abstract class TestAudioActivity extends Activity {
protected boolean mTestRunningByIntent;
protected String mResultFileName;
private String mTestResults;
+ private ExternalFileWriter mExternalFileWriter = new ExternalFileWriter(this);
public String getTestName() {
return "TestAudio";
@@ -225,6 +231,10 @@ abstract class TestAudioActivity extends Activity {
super.onStart();
resetConfiguration();
setActivityType(getActivityType());
+ // TODO Use LifeCycleObserver instead of this.
+ if (mCommunicationDeviceView != null) {
+ mCommunicationDeviceView.onStart();
+ }
}
protected void resetConfiguration() {
@@ -289,6 +299,9 @@ abstract class TestAudioActivity extends Activity {
Log.i(TAG, "onStop() called so stop the test =========================");
onStopTest();
}
+ if (mCommunicationDeviceView != null) {
+ mCommunicationDeviceView.onStop();
+ }
super.onStop();
}
@@ -307,7 +320,9 @@ abstract class TestAudioActivity extends Activity {
mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE);
mStartButton.setBackgroundColor(mAudioState == AUDIO_STATE_STARTED ? COLOR_ACTIVE : COLOR_IDLE);
mPauseButton.setBackgroundColor(mAudioState == AUDIO_STATE_PAUSED ? COLOR_ACTIVE : COLOR_IDLE);
+ mFlushButton.setBackgroundColor(mAudioState == AUDIO_STATE_FLUSHED ? COLOR_ACTIVE : COLOR_IDLE);
mStopButton.setBackgroundColor(mAudioState == AUDIO_STATE_STOPPED ? COLOR_ACTIVE : COLOR_IDLE);
+ mReleaseButton.setBackgroundColor(mAudioState == AUDIO_STATE_RELEASED ? COLOR_ACTIVE : COLOR_IDLE);
mCloseButton.setBackgroundColor(mAudioState == AUDIO_STATE_CLOSED ? COLOR_ACTIVE : COLOR_IDLE);
}
setConfigViewsEnabled(mAudioState == AUDIO_STATE_CLOSED);
@@ -368,7 +383,6 @@ abstract class TestAudioActivity extends Activity {
if (streamContext.configurationView != null) {
streamContext.configurationView.setOutput(false);
}
- streamContext.tester = AudioInputTester.getInstance();
mStreamContexts.add(streamContext);
return streamContext;
}
@@ -407,7 +421,9 @@ abstract class TestAudioActivity extends Activity {
if (mOpenButton != null) {
mStartButton = (Button) findViewById(R.id.button_start);
mPauseButton = (Button) findViewById(R.id.button_pause);
+ mFlushButton = (Button) findViewById(R.id.button_flush);
mStopButton = (Button) findViewById(R.id.button_stop);
+ mReleaseButton = (Button) findViewById(R.id.button_release);
mCloseButton = (Button) findViewById(R.id.button_close);
}
mStreamContexts = new ArrayList<StreamContext>();
@@ -423,6 +439,24 @@ abstract class TestAudioActivity extends Activity {
}
OboeAudioStream.setCallbackReturnStop(false);
+ mHangTimeSpinner = (Spinner) findViewById(R.id.spinner_hang_time);
+ if (mHangTimeSpinner != null) {
+ mHangTimeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ String hangTimeText = (String) mHangTimeSpinner.getAdapter().getItem(position);
+ int hangTimeMillis = Integer.parseInt(hangTimeText);
+ Log.d(TAG, "Hang Time = " + hangTimeMillis + " msec");
+
+ OboeAudioStream.setHangTimeMillis(hangTimeMillis);
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ OboeAudioStream.setHangTimeMillis(0);
+ }
+ });
+ }
+ OboeAudioStream.setHangTimeMillis(0);
+
mStreamSniffer = new MyStreamSniffer();
}
@@ -438,8 +472,8 @@ abstract class TestAudioActivity extends Activity {
}
protected void showErrorToast(String message) {
+ Log.e(TAG, "showErrorToast(\"" + message + "\")");
String text = "Error: " + message;
- Log.e(TAG, text);
showToast(text);
}
@@ -454,20 +488,39 @@ abstract class TestAudioActivity extends Activity {
});
}
+ private void onStartAllContexts() {
+ for (StreamContext streamContext : mStreamContexts) {
+ streamContext.tester.getCurrentAudioStream().onStart();
+ }
+ }
+ private void onStopAllContexts() {
+ for (StreamContext streamContext : mStreamContexts) {
+ streamContext.tester.getCurrentAudioStream().onStop();
+ }
+ }
+
public void openAudio(View view) {
try {
openAudio();
} catch (Exception e) {
- showErrorToast(e.getMessage());
+ showErrorToast("openAudio() caught " + e.getMessage());
+ }
+ }
+
+ void clearHangTime() {
+ OboeAudioStream.setHangTimeMillis(0);
+ if (mHangTimeSpinner != null) {
+ mHangTimeSpinner.setSelection(0);
}
}
public void startAudio(View view) {
Log.i(TAG, "startAudio() called =======================================");
+ clearHangTime(); // start running
try {
startAudio();
} catch (Exception e) {
- showErrorToast(e.getMessage());
+ showErrorToast("startAudio() caught " + e.getMessage());
}
keepScreenOn(true);
}
@@ -490,10 +543,18 @@ abstract class TestAudioActivity extends Activity {
keepScreenOn(false);
}
+ public void flushAudio(View view) {
+ flushAudio();
+ }
+
public void closeAudio(View view) {
closeAudio();
}
+ public void releaseAudio(View view) {
+ releaseAudio();
+ }
+
public int getSampleRate() {
return mSampleRate;
}
@@ -507,18 +568,25 @@ abstract class TestAudioActivity extends Activity {
applyConfigurationViewsToModels();
}
- int sampleRate = 0;
+ int sampleRate = 0; // Use the OUTPUT sample rate for INPUT
// Open output streams then open input streams.
// This is so that the capacity of input stream can be expanded to
// match the burst size of the output for full duplex.
for (StreamContext streamContext : mStreamContexts) {
- if (!streamContext.isInput()) {
+ if (!streamContext.isInput()) { // OUTPUT?
openStreamContext(streamContext);
int streamSampleRate = streamContext.tester.actualConfiguration.getSampleRate();
if (sampleRate == 0) {
sampleRate = streamSampleRate;
}
+
+ if (shouldSetStreamControlByAttributes()) {
+ // Associate volume keys with this output stream.
+ int actualUsage = streamContext.tester.actualConfiguration.getUsage();
+ int actualContentType = streamContext.tester.actualConfiguration.getContentType();
+ setStreamControlByAttributes(actualUsage, actualContentType);
+ }
}
}
for (StreamContext streamContext : mStreamContexts) {
@@ -530,9 +598,29 @@ abstract class TestAudioActivity extends Activity {
}
}
updateEnabledWidgets();
+ onStartAllContexts();
mStreamSniffer.startStreamSniffer();
}
+ protected boolean shouldSetStreamControlByAttributes() {
+ return true;
+ }
+
+ /**
+ * Associate the volume keys with the stream we are playing.
+ * @param usage usage for the stream
+ * @param contentType tupe of the stream
+ */
+ private void setStreamControlByAttributes(int usage, int contentType) {
+ AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage)
+ .setContentType(contentType).build();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ int volumeControlStream = attributes.getVolumeControlStream();
+ Log.i(TAG, "setVolumeControlStream(" + volumeControlStream + ")");
+ setVolumeControlStream(volumeControlStream);
+ }
+ }
+
/**
* @param deviceId
* @return true if the device is TYPE_BLUETOOTH_SCO
@@ -564,7 +652,7 @@ abstract class TestAudioActivity extends Activity {
try {
streamContext.configurationView.setupEffects(sessionId);
} catch (Exception e) {
- showErrorToast(e.getMessage());
+ showErrorToast("openStreamContext() caught " + e.getMessage());
}
}
streamContext.configurationView.updateDisplay(streamContext.tester.actualConfiguration);
@@ -576,21 +664,28 @@ abstract class TestAudioActivity extends Activity {
private native int pauseNative();
+ private native int flushNative();
+
private native int stopNative();
+ private native int releaseNative();
+
protected native void setActivityType(int activityType);
private native int getFramesPerCallback();
+ public native void setUseAlternativeAdpf(boolean enabled);
+
private static native void setDefaultAudioValues(int audioManagerSampleRate, int audioManagerFramesPerBurst);
public void startAudio() throws IOException {
Log.i(TAG, "startAudio() called =========================");
int result = startNative();
- if (result < 0) {
- showErrorToast("Start failed with " + result);
- throw new IOException("startNative returned " + result);
+ if (result != 0) {
+ showErrorToast("Start failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
+ throw new IOException("startNative returned " + result + ", " + StreamConfiguration.convertErrorToText(result));
} else {
+ onStartAllContexts();
for (StreamContext streamContext : mStreamContexts) {
StreamConfigurationView configView = streamContext.configurationView;
if (configView != null) {
@@ -603,26 +698,49 @@ abstract class TestAudioActivity extends Activity {
}
protected void toastPauseError(int result) {
- showErrorToast("Pause failed with " + result);
+ showErrorToast("Pause failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
}
public void pauseAudio() {
int result = pauseNative();
- if (result < 0) {
+ if (result != 0) {
toastPauseError(result);
} else {
mAudioState = AUDIO_STATE_PAUSED;
updateEnabledWidgets();
+ onStopAllContexts();
+ }
+ }
+
+ public void flushAudio() {
+ int result = flushNative();
+ if (result != 0) {
+ showErrorToast("Flush failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
+ } else {
+ mAudioState = AUDIO_STATE_FLUSHED;
+ updateEnabledWidgets();
}
}
public void stopAudio() {
int result = stopNative();
- if (result < 0) {
- showErrorToast("Stop failed with " + result);
+ if (result != 0) {
+ showErrorToast("Stop failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
} else {
mAudioState = AUDIO_STATE_STOPPED;
updateEnabledWidgets();
+ onStopAllContexts();
+ }
+ }
+
+ public void releaseAudio() {
+ int result = releaseNative();
+ if (result != 0) {
+ showErrorToast("Release failed with " + result + ", " + StreamConfiguration.convertErrorToText(result));
+ } else {
+ mAudioState = AUDIO_STATE_RELEASED;
+ updateEnabledWidgets();
+ onStopAllContexts();
}
}
@@ -684,24 +802,6 @@ abstract class TestAudioActivity extends Activity {
myAudioMgr.stopBluetoothSco();
}
- @Override
- public void onRequestPermissionsResult(int requestCode,
- String[] permissions,
- int[] grantResults) {
-
- if (MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE != requestCode) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- return;
- }
- // If request is cancelled, the result arrays are empty.
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- writeTestResult(mTestResults);
- } else {
- showToast("Writing external storage needed for test results.");
- }
- }
-
@NonNull
protected String getCommonTestReport() {
StringBuffer report = new StringBuffer();
@@ -709,8 +809,8 @@ abstract class TestAudioActivity extends Activity {
report.append("build.fingerprint = " + Build.FINGERPRINT + "\n");
try {
PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
- report.append(String.format("test.version = %s\n", pinfo.versionName));
- report.append(String.format("test.version.code = %d\n", pinfo.versionCode));
+ report.append(String.format(Locale.getDefault(), "test.version = %s\n", pinfo.versionName));
+ report.append(String.format(Locale.getDefault(), "test.version.code = %d\n", pinfo.versionCode));
} catch (PackageManager.NameNotFoundException e) {
}
report.append("time.millis = " + System.currentTimeMillis() + "\n");
@@ -733,48 +833,17 @@ abstract class TestAudioActivity extends Activity {
return report.toString();
}
- void writeTestResultIfPermitted(String resultString) {
- // Here, thisActivity is the current activity
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
- mTestResults = resultString;
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
- } else {
- // Permission has already been granted
- writeTestResult(resultString);
- }
- }
-
- void maybeWriteTestResult(String resultString) {
+ File maybeWriteTestResult(String resultString) {
+ File fileWritten = null;
if (mResultFileName != null) {
- writeTestResultIfPermitted(resultString);
- };
- }
-
- // Run this in a background thread.
- void writeTestResult(String resultString) {
- File resultFile = new File(mResultFileName);
- Writer writer = null;
- try {
- writer = new OutputStreamWriter(new FileOutputStream(resultFile));
- writer.write(resultString);
- } catch (
- IOException e) {
- e.printStackTrace();
- showErrorToast(" writing result file. " + e.getMessage());
- } finally {
- if (writer != null) {
- try {
- writer.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
+ try {
+ fileWritten = mExternalFileWriter.writeStringToExternalFile(resultString, mResultFileName);
+ } catch (IOException e) {
+ e.printStackTrace();
+ showErrorToast(" writing result file. " + e.getMessage());
}
+ mResultFileName = null;
}
-
- mResultFileName = null;
+ return fileWritten;
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java
new file mode 100644
index 00000000..b88b611c
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestColdStartLatencyActivity.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package com.mobileer.oboetester;
+
+import static com.mobileer.oboetester.TestAudioActivity.TAG;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.RadioButton;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.Random;
+
+/**
+ * Test for getting the cold start latency
+ */
+public class TestColdStartLatencyActivity extends Activity {
+
+ private TextView mStatusView;
+ private MyStreamSniffer mStreamSniffer;
+ private AudioManager mAudioManager;
+ private RadioButton mOutputButton;
+ private RadioButton mInputButton;
+ private CheckBox mLowLatencyCheckBox;
+ private CheckBox mMmapCheckBox;
+ private CheckBox mExclusiveCheckBox;
+ private Spinner mStartStabilizeDelaySpinner;
+ private Spinner mCloseOpenDelaySpinner;
+ private Spinner mOpenStartDelaySpinner;
+ private Button mStartButton;
+ private Button mStopButton;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_cold_start_latency);
+ mStatusView = (TextView) findViewById(R.id.text_status);
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ mStartButton = (Button) findViewById(R.id.button_start_test);
+ mStopButton = (Button) findViewById(R.id.button_stop_test);
+ mOutputButton = (RadioButton) findViewById(R.id.direction_output);
+ mInputButton = (RadioButton) findViewById(R.id.direction_input);
+ mMmapCheckBox = (CheckBox) findViewById(R.id.checkbox_mmap);
+ mExclusiveCheckBox = (CheckBox) findViewById(R.id.checkbox_exclusive);
+ mLowLatencyCheckBox = (CheckBox) findViewById(R.id.checkbox_low_latency);
+ mStartStabilizeDelaySpinner = (Spinner) findViewById(R.id.spinner_start_stabilize_time);
+ mStartStabilizeDelaySpinner.setSelection(7); // Set to 1000 ms by default
+ mCloseOpenDelaySpinner = (Spinner) findViewById(R.id.spinner_close_open_time);
+ mOpenStartDelaySpinner = (Spinner) findViewById(R.id.spinner_open_start_time);
+
+ setButtonsEnabled(false);
+ }
+
+ public void onStartColdStartLatencyTest(View view) {
+ keepScreenOn(true);
+ stopSniffer();
+ mStreamSniffer = new MyStreamSniffer();
+ mStreamSniffer.start();
+ setButtonsEnabled(true);
+ }
+
+ public void onStopColdStartLatencyTest(View view) {
+ keepScreenOn(false);
+ stopSniffer();
+ setButtonsEnabled(false);
+ }
+
+ private void setButtonsEnabled(boolean running) {
+ mStartButton.setEnabled(!running);
+ mStopButton.setEnabled(running);
+ mOutputButton.setEnabled(!running);
+ mInputButton.setEnabled(!running);
+ mLowLatencyCheckBox.setEnabled(!running);
+ mMmapCheckBox.setEnabled(!running);
+ mExclusiveCheckBox.setEnabled(!running);
+ mStartStabilizeDelaySpinner.setEnabled(!running);
+ mCloseOpenDelaySpinner.setEnabled(!running);
+ mOpenStartDelaySpinner.setEnabled(!running);
+ }
+
+ protected class MyStreamSniffer extends Thread {
+ boolean enabled = true;
+ StringBuffer statusBuffer = new StringBuffer();
+ int loopCount;
+
+ @Override
+ public void run() {
+ boolean useInput = mInputButton.isChecked();
+ boolean useLowLatency = mLowLatencyCheckBox.isChecked();
+ boolean useMmap = mMmapCheckBox.isChecked();
+ boolean useExclusive = mExclusiveCheckBox.isChecked();
+ Log.d(TAG,(useInput ? "IN" : "OUT")
+ + ", " + (useLowLatency ? "LOW_LATENCY" : "NOT LOW_LATENCY")
+ + ", " + (useMmap ? "MMAP" : "NOT MMAP")
+ + ", " + (useExclusive ? "EXCLUSIVE" : "SHARED"));
+ String closeSleepTimeText =
+ (String) mCloseOpenDelaySpinner.getAdapter().getItem(
+ mCloseOpenDelaySpinner.getSelectedItemPosition());
+ int closedSleepTimeMillis = Integer.parseInt(closeSleepTimeText);
+ Log.d(TAG, "Sleep before open time = " + closedSleepTimeMillis + " msec");
+ String openSleepTimeText = (String) mOpenStartDelaySpinner.getAdapter().getItem(
+ mOpenStartDelaySpinner.getSelectedItemPosition());
+ int openSleepTimeMillis = Integer.parseInt(openSleepTimeText);
+ Log.d(TAG, "Sleep after open Time = " + openSleepTimeMillis + " msec");
+ String startStabilizeTimeText = (String) mStartStabilizeDelaySpinner.getAdapter().getItem(
+ mStartStabilizeDelaySpinner.getSelectedItemPosition());
+ int startSleepTimeMillis = Integer.parseInt(startStabilizeTimeText);
+ Log.d(TAG, "Sleep after start Time = " + startSleepTimeMillis + " msec");
+ while (enabled) {
+ loopCount++;
+ try {
+ sleep(closedSleepTimeMillis);
+ openStream(useInput, useLowLatency, useMmap, useExclusive);
+ log("-------#" + loopCount + " Device Id: " + getAudioDeviceId());
+ log("open() Latency: " + getOpenTimeMicros() / 1000 + " msec");
+ sleep(openSleepTimeMillis);
+ startStream();
+ log("requestStart() Latency: " + getStartTimeMicros() / 1000 + " msec");
+ sleep(startSleepTimeMillis);
+ log("Cold Start Latency: " + getColdStartTimeMicros() / 1000 + " msec");
+ closeStream();
+ } catch (InterruptedException e) {
+ enabled = false;
+ } finally {
+ closeStream();
+ }
+ }
+ }
+
+ // Log to screen and logcat.
+ private void log(String text) {
+ statusBuffer.append(text + "\n");
+ showStatus(statusBuffer.toString());
+ }
+
+ // Stop the test thread.
+ void finish() {
+ enabled = false;
+ interrupt();
+ try {
+ join(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void showStatus(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mStatusView.setText(message);
+ }
+ });
+ }
+
+ private native int openStream(boolean useInput, boolean useLowLatency, boolean useMmap,
+ boolean useExclusive);
+ private native int startStream();
+ private native int closeStream();
+ private native int getOpenTimeMicros();
+ private native int getStartTimeMicros();
+ private native int getColdStartTimeMicros();
+ private native int getAudioDeviceId();
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ stopSniffer();
+ }
+
+ private void stopSniffer() {
+ if (mStreamSniffer != null) {
+ mStreamSniffer.finish();
+ mStreamSniffer = null;
+ }
+ }
+
+ protected void keepScreenOn(boolean on) {
+ if (on) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+}
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 b0aea932..74879f14 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
@@ -17,49 +17,74 @@
package com.mobileer.oboetester;
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;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.CheckBox;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
import com.mobileer.audio_device.AudioDeviceInfoConverter;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Locale;
/**
- * Play a recognizable tone on each channel of each speaker device
- * and listen for the result through a microphone.
- * Also test each microphone channel and device.
- * Try each InputPreset.
+ * Play a recognizable tone on each channel of an output device
+ * and listen for the result through an input.
+ * Test various channels, InputPresets, ChannelMasks and SampleRates.
+ *
+ * Select device types based on priority of attached peripherals.
+ * Print devices types being tested.
*
* The analysis is based on a cosine transform of a single
* frequency. The magnitude indicates the level.
* The variations in phase, "jitter" indicate how noisy the
- * signal is or whether it is corrupted. A noisy room may have
- * energy at the target frequency but the phase will be random.
+ * signal is or whether it is corrupted. A very noisy room may have
+ * lots of 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 {
public static final String KEY_USE_INPUT_PRESETS = "use_input_presets";
public static final boolean VALUE_DEFAULT_USE_INPUT_PRESETS = true;
- public static final String KEY_USE_INPUT_DEVICES = "use_input_devices";
- public static final boolean VALUE_DEFAULT_USE_INPUT_DEVICES = true;
+ public static final String KEY_USE_ALL_SAMPLE_RATES = "use_all_sample_rates";
+ public static final boolean VALUE_DEFAULT_USE_ALL_SAMPLE_RATES = false;
+
+ public static final String KEY_SINGLE_TEST_INDEX = "single_test_index";
+ public static final int VALUE_DEFAULT_SINGLE_TEST_INDEX = -1;
+ public static final String KEY_USE_ALL_CHANNEL_COUNTS = "use_all_channel_counts";
+ public static final boolean VALUE_DEFAULT_USE_ALL_CHANNEL_COUNTS = true;
+ public static final String KEY_USE_INPUT_CHANNEL_MASKS = "use_input_channel_masks";
+ public static final boolean VALUE_DEFAULT_USE_INPUT_CHANNEL_MASKS = false;
+ public static final String KEY_OUTPUT_CHANNEL_MASKS_LEVEL = "output_channel_masks_level";
+
+ // The following KEYs are for old deprecated commands.
+ public static final String KEY_USE_ALL_OUTPUT_CHANNEL_MASKS = "use_all_output_channel_masks";
+ public static final boolean VALUE_DEFAULT_USE_ALL_OUTPUT_CHANNEL_MASKS = false;
+
+ // How many tests should be run in a specific category, eg. channel masks?
+ private static final int COVERAGE_LEVEL_NONE = 0;
+ private static final int COVERAGE_LEVEL_SOME = 1;
+ private static final int COVERAGE_LEVEL_ALL = 2;
+ public static final String KEY_USE_INPUT_DEVICES = "use_input_devices";
+ public static final boolean VALUE_DEFAULT_USE_INPUT_DEVICES = false;
public static final String KEY_USE_OUTPUT_DEVICES = "use_output_devices";
public static final boolean VALUE_DEFAULT_USE_OUTPUT_DEVICES = true;
- public static final String KEY_SINGLE_TEST_INDEX = "single_test_index";
- public static final int VALUE_DEFAULT_SINGLE_TEST_INDEX = -1;
public static final int DURATION_SECONDS = 3;
private final static double MIN_REQUIRED_MAGNITUDE = 0.001;
@@ -70,6 +95,42 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
private final static double MAX_ALLOWED_JITTER = 2.0 * PHASE_PER_BIN;
private final static String MAGNITUDE_FORMAT = "%7.5f";
+ // These define the values returned by the Java API deviceInfo.getChannelMasks().
+ public static final int JAVA_CHANNEL_IN_LEFT = 1 << 2; // AudioFormat.CHANNEL_IN_LEFT
+ public static final int JAVA_CHANNEL_IN_RIGHT = 1 << 3; // AudioFormat.CHANNEL_IN_RIGHT
+ public static final int JAVA_CHANNEL_IN_FRONT = 1 << 4; // AudioFormat.CHANNEL_IN_FRONT
+ public static final int JAVA_CHANNEL_IN_BACK = 1 << 5; // AudioFormat.CHANNEL_IN_BACK
+
+ // These do not have corresponding Java definitions.
+ // They match definitions in system/media/audio/include/system/audio-hal-enums.h
+ public static final int JAVA_CHANNEL_IN_BACK_LEFT = 1 << 16;
+ public static final int JAVA_CHANNEL_IN_BACK_RIGHT = 1 << 17;
+ public static final int JAVA_CHANNEL_IN_CENTER = 1 << 18;
+ public static final int JAVA_CHANNEL_IN_LOW_FREQUENCY = 1 << 20;
+ public static final int JAVA_CHANNEL_IN_TOP_LEFT = 1 << 21;
+ public static final int JAVA_CHANNEL_IN_TOP_RIGHT = 1 << 22;
+
+ public static final int JAVA_CHANNEL_IN_MONO = JAVA_CHANNEL_IN_FRONT;
+ public static final int JAVA_CHANNEL_IN_STEREO = JAVA_CHANNEL_IN_LEFT | JAVA_CHANNEL_IN_RIGHT;
+ public static final int JAVA_CHANNEL_IN_FRONT_BACK = JAVA_CHANNEL_IN_FRONT | JAVA_CHANNEL_IN_BACK;
+ public static final int JAVA_CHANNEL_IN_2POINT0POINT2 = JAVA_CHANNEL_IN_LEFT |
+ JAVA_CHANNEL_IN_RIGHT |
+ JAVA_CHANNEL_IN_TOP_LEFT |
+ JAVA_CHANNEL_IN_TOP_RIGHT;
+ public static final int JAVA_CHANNEL_IN_2POINT1POINT2 =
+ JAVA_CHANNEL_IN_2POINT0POINT2 | JAVA_CHANNEL_IN_LOW_FREQUENCY;
+ public static final int JAVA_CHANNEL_IN_3POINT0POINT2 =
+ JAVA_CHANNEL_IN_2POINT0POINT2 | JAVA_CHANNEL_IN_CENTER;
+ public static final int JAVA_CHANNEL_IN_3POINT1POINT2 =
+ JAVA_CHANNEL_IN_3POINT0POINT2 | JAVA_CHANNEL_IN_LOW_FREQUENCY;
+ public static final int JAVA_CHANNEL_IN_5POINT1 = JAVA_CHANNEL_IN_LEFT |
+ JAVA_CHANNEL_IN_CENTER |
+ JAVA_CHANNEL_IN_RIGHT |
+ JAVA_CHANNEL_IN_BACK_LEFT |
+ JAVA_CHANNEL_IN_BACK_RIGHT |
+ JAVA_CHANNEL_IN_LOW_FREQUENCY;
+ public static final int JAVA_CHANNEL_UNDEFINED = -1;
+
final int TYPE_BUILTIN_SPEAKER_SAFE = 0x18; // API 30
private double mMagnitude;
@@ -79,22 +140,73 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
private double mPhaseErrorSum;
private double mPhaseErrorCount;
- AudioManager mAudioManager;
private CheckBox mCheckBoxInputPresets;
- private CheckBox mCheckBoxInputDevices;
- private CheckBox mCheckBoxOutputDevices;
+ private CheckBox mCheckBoxAllChannels;
+ private CheckBox mCheckBoxInputChannelMasks;
+ private RadioGroup mRadioGroupOutputChannelMasks;
+ private RadioButton mRadioOutputChannelMasksNone;
+ private RadioButton mRadioOutputChannelMasksSome;
+ private RadioButton mRadioOutputChannelMasksAll;
+ private CheckBox mCheckBoxAllSampleRates;
+ private TextView mInstructionsTextView;
private static final int[] INPUT_PRESETS = {
- // VOICE_RECOGNITION gets tested in testInputs()
- // StreamConfiguration.INPUT_PRESET_VOICE_RECOGNITION,
StreamConfiguration.INPUT_PRESET_GENERIC,
StreamConfiguration.INPUT_PRESET_CAMCORDER,
- // TODO Resolve issue with echo cancellation killing the signal.
- StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION,
StreamConfiguration.INPUT_PRESET_UNPROCESSED,
+ // Do not use INPUT_PRESET_VOICE_COMMUNICATION because AEC kills the signal.
+ StreamConfiguration.INPUT_PRESET_VOICE_RECOGNITION,
StreamConfiguration.INPUT_PRESET_VOICE_PERFORMANCE,
};
+ private static final int[] SHORT_OUTPUT_CHANNEL_MASKS = {
+ StreamConfiguration.CHANNEL_MONO,
+ StreamConfiguration.CHANNEL_STEREO,
+ StreamConfiguration.CHANNEL_2POINT1, // Smallest mask with more than two channels.
+ StreamConfiguration.CHANNEL_5POINT1, // This mask is very common.
+ StreamConfiguration.CHANNEL_7POINT1POINT4, // More than 8 channels might break.
+ };
+
+ private static final int[] ALL_OUTPUT_CHANNEL_MASKS = {
+ StreamConfiguration.CHANNEL_MONO,
+ StreamConfiguration.CHANNEL_STEREO,
+ StreamConfiguration.CHANNEL_2POINT1,
+ StreamConfiguration.CHANNEL_TRI,
+ StreamConfiguration.CHANNEL_TRI_BACK,
+ StreamConfiguration.CHANNEL_3POINT1,
+ StreamConfiguration.CHANNEL_2POINT0POINT2,
+ StreamConfiguration.CHANNEL_2POINT1POINT2,
+ StreamConfiguration.CHANNEL_3POINT0POINT2,
+ StreamConfiguration.CHANNEL_3POINT1POINT2,
+ StreamConfiguration.CHANNEL_QUAD,
+ StreamConfiguration.CHANNEL_QUAD_SIDE,
+ StreamConfiguration.CHANNEL_SURROUND,
+ StreamConfiguration.CHANNEL_PENTA,
+ StreamConfiguration.CHANNEL_5POINT1,
+ StreamConfiguration.CHANNEL_5POINT1_SIDE,
+ StreamConfiguration.CHANNEL_6POINT1,
+ StreamConfiguration.CHANNEL_7POINT1,
+ StreamConfiguration.CHANNEL_5POINT1POINT2,
+ StreamConfiguration.CHANNEL_5POINT1POINT4,
+ StreamConfiguration.CHANNEL_7POINT1POINT2,
+ StreamConfiguration.CHANNEL_7POINT1POINT4,
+ };
+
+ private static final int[] SAMPLE_RATES = {
+ 8000,
+ 11025,
+ 12000,
+ 16000,
+ 22050,
+ 24000,
+ 32000,
+ 44100,
+ 48000,
+ 64000,
+ 88200,
+ 96000,
+ };
+
@NonNull
public static String comparePassedField(String prefix, Object failed, Object passed, String name) {
try {
@@ -109,16 +221,25 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
return "ERROR - cannot access " + name;
}
}
+ public static String comparePassedInputPreset(String prefix, TestResult failed, TestResult passed) {
+ int failedValue = failed.inputPreset;
+ int passedValue = passed.inputPreset;
+ return (failedValue == passedValue) ? ""
+ : (prefix + " inPreset: passed = "
+ + StreamConfiguration.convertInputPresetToText(passedValue)
+ + ", failed = "
+ + StreamConfiguration.convertInputPresetToText(failedValue)
+ + "\n");
+ }
public static double calculatePhaseError(double p1, double p2) {
- double diff = Math.abs(p1 - p2);
+ double diff = p1 - p2;
// Wrap around the circle.
- while (diff > (2 * Math.PI)) {
- diff -= (2 * Math.PI);
+ while (diff > Math.PI) {
+ diff -= 2 * Math.PI;
}
- // A phase error close to 2*PI is actually a small phase error.
- if (diff > Math.PI) {
- diff = (2 * Math.PI) - diff;
+ while (diff < -Math.PI) {
+ diff += 2 * Math.PI;
}
return diff;
}
@@ -126,10 +247,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
// Periodically query for magnitude and phase from the native detector.
protected class DataPathSniffer extends NativeSniffer {
- public DataPathSniffer(Activity activity) {
- super(activity);
- }
-
@Override
public void startSniffer() {
mMagnitude = -1.0;
@@ -141,30 +258,28 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
super.startSniffer();
}
- @Override
- public void run() {
+ private void gatherData() {
mMagnitude = getMagnitude();
mMaxMagnitude = getMaxMagnitude();
- Log.d(TAG, String.format("magnitude = %7.4f, maxMagnitude = %7.4f",
+ 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 = 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;
if (mPhaseCount >= kMinPhaseMeasurementsRequired) {
- double phaseError = calculatePhaseError(phase, mPhase);
- // low pass filter
+ double phaseError = Math.abs(calculatePhaseError(phase, mPhase));
+ // collect average error
mPhaseErrorSum += phaseError;
mPhaseErrorCount++;
- Log.d(TAG, String.format("phase = %7.4f, diff = %7.4f, jitter = %7.4f",
- phase, phaseError, getAveragePhaseError()));
+ 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++;
}
- reschedule();
}
public String getCurrentStatusReport() {
@@ -179,7 +294,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
return message.toString();
}
- @Override
public String getShortReport() {
return "maxMag = " + getMagnitudeText(mMaxMagnitude)
+ ", jitter = " + getJitterText();
@@ -187,6 +301,7 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
@Override
public void updateStatusText() {
+ gatherData();
mLastGlitchReport = getCurrentStatusReport();
runOnUiThread(() -> {
setAnalyzerText(mLastGlitchReport);
@@ -194,18 +309,34 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
}
}
+ // Write to status and command view
+ private void setInstructionsText(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mInstructionsTextView.setText(text);
+ }
+ });
+ }
+
private String getJitterText() {
return isPhaseJitterValid() ? getMagnitudeText(getAveragePhaseError()) : "?";
}
@Override
NativeSniffer createNativeSniffer() {
- return new TestDataPathsActivity.DataPathSniffer(this);
+ return new TestDataPathsActivity.DataPathSniffer();
+ }
+
+ @Override
+ public String getShortReport() {
+ return ((DataPathSniffer) mNativeSniffer).getShortReport();
}
native double getMagnitude();
native double getMaxMagnitude();
- native double getPhase();
+
+ native double getPhaseDataPaths();
@Override
protected void inflateActivity() {
@@ -215,10 +346,18 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
@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);
+ mCheckBoxAllChannels = (CheckBox)findViewById(R.id.checkbox_paths_all_channels);
+ mCheckBoxInputChannelMasks = (CheckBox)findViewById(R.id.checkbox_paths_in_channel_masks);
+ mCheckBoxAllSampleRates =
+ (CheckBox)findViewById(R.id.checkbox_paths_all_sample_rates);
+
+ mInstructionsTextView = (TextView) findViewById(R.id.text_instructions);
+
+ mRadioGroupOutputChannelMasks = (RadioGroup) findViewById(R.id.group_ch_mask_options);
+ mRadioOutputChannelMasksNone = (RadioButton) findViewById(R.id.radio_out_ch_masks_none);
+ mRadioOutputChannelMasksSome = (RadioButton) findViewById(R.id.radio_out_ch_masks_some);
+ mRadioOutputChannelMasksAll = (RadioButton) findViewById(R.id.radio_out_ch_masks_all);
}
@Override
@@ -232,7 +371,7 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
}
static String getMagnitudeText(double value) {
- return String.format(MAGNITUDE_FORMAT, value);
+ return String.format(Locale.getDefault(), MAGNITUDE_FORMAT, value);
}
protected String getConfigText(StreamConfiguration config) {
@@ -244,35 +383,27 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
}
@Override
- protected String shouldTestBeSkipped() {
- String why = "";
+ protected String whyShouldTestBeSkipped() {
+ String why = super.whyShouldTestBeSkipped();
StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration;
StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;
- // No point running the test if we don't get the data path we requested.
- if (actualInConfig.isMMap() != requestedInConfig.isMMap()) {
- log("Did not get requested MMap input stream");
- why += "mmap";
- }
- if (actualOutConfig.isMMap() != requestedOutConfig.isMMap()) {
- log("Did not get requested MMap output stream");
- why += "mmap";
- }
+
// Did we request a device and not get that device?
if (requestedInConfig.getDeviceId() != 0
&& (requestedInConfig.getDeviceId() != actualInConfig.getDeviceId())) {
- why += ", inDev(" + requestedInConfig.getDeviceId()
- + "!=" + actualInConfig.getDeviceId() + ")";
+ why += "inDev(" + requestedInConfig.getDeviceId()
+ + "!=" + actualInConfig.getDeviceId() + "),";
}
if (requestedOutConfig.getDeviceId() != 0
&& (requestedOutConfig.getDeviceId() != actualOutConfig.getDeviceId())) {
why += ", outDev(" + requestedOutConfig.getDeviceId()
- + "!=" + actualOutConfig.getDeviceId() + ")";
+ + "!=" + actualOutConfig.getDeviceId() + "),";
}
if ((requestedInConfig.getInputPreset() != actualInConfig.getInputPreset())) {
why += ", inPre(" + requestedInConfig.getInputPreset()
- + "!=" + actualInConfig.getInputPreset() + ")";
+ + "!=" + actualInConfig.getInputPreset() + "),";
}
return why;
}
@@ -288,10 +419,6 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
@Override
public String didTestFail() {
String why = "";
- StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
- StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
- StreamConfiguration actualInConfig = mAudioInputTester.actualConfiguration;
- StreamConfiguration actualOutConfig = mAudioOutTester.actualConfiguration;
if (mMaxMagnitude <= MIN_REQUIRED_MAGNITUDE) {
why += ", mag";
}
@@ -322,280 +449,387 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
+ ", IN" + (actualInConfig.isMMap() ? "-M" : "-L")
+ " D=" + actualInConfig.getDeviceId()
+ ", ch=" + channelText(getInputChannel(), actualInConfig.getChannelCount())
+ + ", SR=" + actualInConfig.getSampleRate()
+ ", OUT" + (actualOutConfig.isMMap() ? "-M" : "-L")
+ " D=" + actualOutConfig.getDeviceId()
+ ", ch=" + channelText(getOutputChannel(), actualOutConfig.getChannelCount())
+ + ", SR=" + actualOutConfig.getSampleRate()
+ ", mag = " + getMagnitudeText(mMaxMagnitude);
}
- void setupDeviceCombo(int numInputChannels,
- int inputChannel,
- int numOutputChannels,
- int outputChannel) throws InterruptedException {
- // Configure settings
- StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
- StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
-
- requestedInConfig.reset();
- requestedOutConfig.reset();
-
- 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);
-
- requestedInConfig.setChannelCount(numInputChannels);
- requestedOutConfig.setChannelCount(numOutputChannels);
-
- requestedInConfig.setMMap(false);
- requestedOutConfig.setMMap(false);
-
- setInputChannel(inputChannel);
- setOutputChannel(outputChannel);
- }
-
- private TestResult testConfigurationsAddMagJitter() throws InterruptedException {
- TestResult testResult = testConfigurations();
+ @Override
+ protected TestResult testCurrentConfigurations() throws InterruptedException {
+ TestResult testResult = super.testCurrentConfigurations();
if (testResult != null) {
testResult.addComment("mag = " + TestDataPathsActivity.getMagnitudeText(mMagnitude)
+ ", jitter = " + getJitterText());
- }
- return testResult;
- }
-
- void testPresetCombo(int inputPreset,
- int numInputChannels,
- int inputChannel,
- int numOutputChannels,
- int outputChannel,
- boolean mmapEnabled
- ) throws InterruptedException {
- setupDeviceCombo(numInputChannels, inputChannel, numOutputChannels, outputChannel);
- StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
- requestedInConfig.setInputPreset(inputPreset);
- requestedInConfig.setMMap(mmapEnabled);
-
- mMagnitude = -1.0;
- TestResult testResult = testConfigurationsAddMagJitter();
- if (testResult != null) {
+ logOneLineSummary(testResult);
int result = testResult.result;
- String summary = getOneLineSummary()
- + ", inPre = "
- + StreamConfiguration.convertInputPresetToText(inputPreset)
- + "\n";
- appendSummary(summary);
if (result == TEST_RESULT_FAILED) {
- if (getMagnitude() < 0.000001) {
- testResult.addComment("The input is completely SILENT!");
- } else if (inputPreset == StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION) {
- testResult.addComment("Maybe sine wave blocked by Echo Cancellation!");
+ int id = mAudioOutTester.actualConfiguration.getDeviceId();
+ int deviceType = getDeviceInfoById(id).getType();
+ int channelCount = mAudioOutTester.actualConfiguration.getChannelCount();
+ if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
+ && channelCount == 2
+ && getOutputChannel() == 1) {
+ testResult.addComment("Maybe EARPIECE does not mix stereo to mono!");
+ }
+ if (deviceType == TYPE_BUILTIN_SPEAKER_SAFE
+ && channelCount == 2
+ && getOutputChannel() == 0) {
+ testResult.addComment("Maybe SPEAKER_SAFE dropped channel zero!");
}
}
}
+ return testResult;
}
- void testPresetCombo(int inputPreset,
- int numInputChannels,
- int inputChannel,
- int numOutputChannels,
- int outputChannel
- ) throws InterruptedException {
- if (NativeEngine.isMMapSupported()) {
- testPresetCombo(inputPreset, numInputChannels, inputChannel,
- numOutputChannels, outputChannel, true);
- }
- testPresetCombo(inputPreset, numInputChannels, inputChannel,
- numOutputChannels, outputChannel, false);
- }
-
- void testPresetCombo(int inputPreset) throws InterruptedException {
- setTestName("Test InPreset = " + StreamConfiguration.convertInputPresetToText(inputPreset));
- testPresetCombo(inputPreset, 1, 0, 1, 0);
+ private void logSection(String name) {
+ logBoth("\n#" + (getTestCount() + 1) + " ########### " + name + "\n");
}
private void testInputPresets() throws InterruptedException {
- logBoth("\nTest InputPreset -------");
-
+ logSection("InputPreset");
+ StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
+ int originalPreset = requestedInConfig.getInputPreset();
for (int inputPreset : INPUT_PRESETS) {
- testPresetCombo(inputPreset);
+ requestedInConfig.setInputPreset(inputPreset);
+ testPerformancePaths();
+ }
+ requestedInConfig.setInputPreset(originalPreset);
+ }
+
+ // The native out channel mask is its channel mask shifted right by 2 bits.
+ // See AudioFormat.convertChannelOutMaskToNativeMask()
+ int convertJavaOutChannelMaskToNativeChannelMask(int javaChannelMask) {
+ return javaChannelMask >> 2;
+ }
+
+ // The native channel mask in AAudio and Oboe is different than the Java IN channel mask.
+ // See AAudioConvert_aaudioToAndroidChannelLayoutMask()
+ int convertJavaInChannelMaskToNativeChannelMask(int javaChannelMask) {
+ switch (javaChannelMask) {
+ case JAVA_CHANNEL_IN_MONO:
+ return StreamConfiguration.CHANNEL_MONO;
+ case JAVA_CHANNEL_IN_STEREO:
+ return StreamConfiguration.CHANNEL_STEREO;
+ case JAVA_CHANNEL_IN_FRONT_BACK:
+ return StreamConfiguration.CHANNEL_FRONT_BACK;
+ case JAVA_CHANNEL_IN_2POINT0POINT2:
+ return StreamConfiguration.CHANNEL_2POINT0POINT2;
+ case JAVA_CHANNEL_IN_2POINT1POINT2:
+ return StreamConfiguration.CHANNEL_2POINT1POINT2;
+ case JAVA_CHANNEL_IN_3POINT0POINT2:
+ return StreamConfiguration.CHANNEL_3POINT0POINT2;
+ case JAVA_CHANNEL_IN_3POINT1POINT2:
+ return StreamConfiguration.CHANNEL_3POINT1POINT2;
+ case JAVA_CHANNEL_IN_5POINT1:
+ return StreamConfiguration.CHANNEL_5POINT1;
+ default:
+ log("Unimplemented java channel mask: " + javaChannelMask + "\n");
+ return JAVA_CHANNEL_UNDEFINED;
}
-// TODO Resolve issue with echo cancellation killing the signal.
-// testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION,
-// 1, 0, 2, 0);
-// testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION,
-// 1, 0, 2, 1);
-// testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION,
-// 2, 0, 2, 0);
-// testPresetCombo(StreamConfiguration.INPUT_PRESET_VOICE_COMMUNICATION,
-// 2, 0, 2, 1);
- }
-
- void testInputDeviceCombo(int deviceId,
- int numInputChannels,
- int inputChannel,
- boolean mmapEnabled) throws InterruptedException {
- final int numOutputChannels = 2;
- setupDeviceCombo(numInputChannels, inputChannel, numOutputChannels, 0);
+ }
- StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
- requestedInConfig.setInputPreset(StreamConfiguration.INPUT_PRESET_VOICE_RECOGNITION);
- requestedInConfig.setDeviceId(deviceId);
- requestedInConfig.setMMap(mmapEnabled);
+ void logOneLineSummary(TestResult testResult) {
+ logOneLineSummary(testResult, "");
+ }
- mMagnitude = -1.0;
- TestResult testResult = testConfigurationsAddMagJitter();
- if (testResult != null) {
- appendSummary(getOneLineSummary() + "\n");
+ void logOneLineSummary(TestResult testResult, String extra) {
+ int result = testResult.result;
+ String oneLineSummary;
+ if (result == TEST_RESULT_SKIPPED) {
+ oneLineSummary = "#" + mAutomatedTestRunner.getTestCount() + extra + ", SKIP";
+ } else if (result == TEST_RESULT_FAILED) {
+ oneLineSummary = getOneLineSummary() + extra + ", FAIL";
+ } else {
+ oneLineSummary = getOneLineSummary() + extra;
}
+ appendSummary(oneLineSummary + "\n");
}
- void testInputDeviceCombo(int deviceId,
- int deviceType,
- int numInputChannels,
- int inputChannel) throws InterruptedException {
+ void logBoth(String text) {
+ log(text);
+ appendSummary(text + "\n");
+ }
- String typeString = AudioDeviceInfoConverter.typeToString(deviceType);
- setTestName("Test InDev: #" + deviceId + " " + typeString
- + "_" + inputChannel + "/" + numInputChannels);
- if (NativeEngine.isMMapSupported()) {
- testInputDeviceCombo(deviceId, numInputChannels, inputChannel, true);
- }
- testInputDeviceCombo(deviceId, numInputChannels, inputChannel, false);
- }
-
- void testInputDevices() throws InterruptedException {
- logBoth("\nTest Input Devices -------");
-
- AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
- int numTested = 0;
- for (AudioDeviceInfo deviceInfo : devices) {
- log("----\n"
- + AudioDeviceInfoConverter.toString(deviceInfo) + "\n");
- if (!deviceInfo.isSource()) continue; // FIXME log as error?!
- int deviceType = deviceInfo.getType();
- if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_MIC) {
- int id = deviceInfo.getId();
- int[] channelCounts = deviceInfo.getChannelCounts();
- numTested++;
- // Always test mono and stereo.
- testInputDeviceCombo(id, deviceType, 1, 0);
- testInputDeviceCombo(id, deviceType, 2, 0);
- testInputDeviceCombo(id, deviceType, 2, 1);
- 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++) {
- testInputDeviceCombo(id, deviceType, numChannels, channel);
- }
- }
- }
- }
- } else {
- log("Device skipped for type.");
- }
+ void logFailed(String text) {
+ log(text);
+ logAnalysis(text + "\n");
+ }
+
+ private void testDeviceOutputInfo(AudioDeviceInfo outputDeviceInfo) throws InterruptedException {
+ AudioDeviceInfo inputDeviceInfo = findCompatibleInputDevice(outputDeviceInfo.getType());
+ showDeviceInfo(outputDeviceInfo, inputDeviceInfo);
+ if (inputDeviceInfo == null) {
+ return;
+ }
+
+ StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
+ StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
+ requestedInConfig.reset();
+ requestedOutConfig.reset();
+ requestedInConfig.setDeviceId(inputDeviceInfo.getId());
+ requestedOutConfig.setDeviceId(outputDeviceInfo.getId());
+ resetChannelConfigurations(requestedInConfig, requestedOutConfig);
+
+ if (mCheckBoxAllChannels.isChecked()) {
+ runOnUiThread(() -> mCheckBoxAllChannels.setEnabled(false));
+ testOutputChannelCounts(inputDeviceInfo, outputDeviceInfo);
+ }
+
+ if (mCheckBoxInputPresets.isChecked()) {
+ runOnUiThread(() -> mCheckBoxInputPresets.setEnabled(false));
+ testInputPresets();
+ }
+
+ if (mCheckBoxAllSampleRates.isChecked()) {
+ logSection("Sample Rates");
+ runOnUiThread(() -> mCheckBoxAllSampleRates.setEnabled(false));
+ for (int sampleRate : SAMPLE_RATES) {
+ requestedInConfig.setSampleRate(sampleRate);
+ requestedOutConfig.setSampleRate(sampleRate);
+ testPerformancePaths();
+ }
+ }
+ requestedInConfig.setSampleRate(0);
+ requestedOutConfig.setSampleRate(0);
+
+ // Channel Masks added to AAudio API in S_V2
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2
+ && isDeviceTypeMixedForLoopback(outputDeviceInfo.getType())) {
+
+ if (mCheckBoxInputChannelMasks.isChecked()) { // INPUT?
+ logSection("Input Channel Masks");
+ runOnUiThread(() -> mCheckBoxInputChannelMasks.setEnabled(false));
+
+ resetChannelConfigurations(requestedInConfig, requestedOutConfig);
+ requestedInConfig.setChannelCount(0);
+ // Test the reported channel masks.
+ int[] channelMasks = inputDeviceInfo.getChannelMasks();
+ if (channelMasks.length > 0) {
+ for (int channelMask : channelMasks) {
+ int nativeChannelMask =
+ convertJavaInChannelMaskToNativeChannelMask(channelMask);
+ if (nativeChannelMask == JAVA_CHANNEL_UNDEFINED) {
+ log("channelMask: " + channelMask + " not supported. Skipping.\n");
+ continue;
+ }
+ log("\n#### nativeChannelMask = "
+ + convertChannelMaskToText(nativeChannelMask) + "\n");
+ int channelCount = Integer.bitCount(nativeChannelMask);
+ requestedInConfig.setChannelMask(nativeChannelMask);
+ for (int channel = 0; channel < channelCount; channel++) {
+ setInputChannel(channel);
+ testPerformancePaths();
+ }
+ }
+ }
+ resetChannelConfigurations(requestedInConfig, requestedOutConfig);
+ }
+
+ runOnUiThread(() -> mRadioGroupOutputChannelMasks.setEnabled(false));
+ if (!mRadioOutputChannelMasksNone.isChecked()) { // OUTPUT?
+ logSection("Output Channel Masks");
+ requestedInConfig.setChannelCount(1);
+ for (int channelMask : mRadioOutputChannelMasksAll.isChecked() ?
+ ALL_OUTPUT_CHANNEL_MASKS : SHORT_OUTPUT_CHANNEL_MASKS) {
+ int channelCount = Integer.bitCount(channelMask);
+ requestedOutConfig.setChannelMask(channelMask);
+ for (int channel = 0; channel < channelCount; channel++) {
+ setOutputChannel(channel);
+ testPerformancePaths();
+ }
+ }
+ resetChannelConfigurations(requestedInConfig, requestedOutConfig);
+ }
+ }
+ }
+
+ private void resetChannelConfigurations(StreamConfiguration requestedInConfig, StreamConfiguration requestedOutConfig) {
+ requestedInConfig.setChannelMask(0);
+ requestedOutConfig.setChannelMask(0);
+ requestedInConfig.setChannelCount(1);
+ requestedOutConfig.setChannelCount(1);
+ setInputChannel(0);
+ setOutputChannel(0);
+ }
+
+ private void showDeviceInfo(AudioDeviceInfo outputDeviceInfo, AudioDeviceInfo inputDeviceInfo) {
+ String deviceText = "OUT: type = "
+ + AudioDeviceInfoConverter.typeToString(outputDeviceInfo.getType())
+ + ", #ch = " + findLargestChannelCount(outputDeviceInfo.getChannelCounts());
+
+ setInstructionsText(deviceText);
+
+ if (inputDeviceInfo == null) {
+ deviceText += "ERROR - cannot find compatible device type for input!";
+ } else {
+ deviceText = "IN: type = "
+ + AudioDeviceInfoConverter.typeToString(inputDeviceInfo.getType())
+ + ", #ch = " + findLargestChannelCount(inputDeviceInfo.getChannelCounts())
+ + "\n" + deviceText;
}
+ setInstructionsText(deviceText);
+ }
- if (numTested == 0) {
- log("NO INPUT DEVICE FOUND!\n");
+ public static int findLargestChannelCount(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ return 2;
}
+ return findLargestInt(arr);
}
- void testOutputDeviceCombo(int deviceId,
- int deviceType,
- int numOutputChannels,
- int outputChannel,
- boolean mmapEnabled) throws InterruptedException {
- final int numInputChannels = 2; // TODO review, done because of mono problems on some devices
- setupDeviceCombo(numInputChannels, 0, numOutputChannels, outputChannel);
+ public static int findLargestInt(int[] arr) {
+ if (arr == null || arr.length == 0) {
+ throw new IllegalArgumentException("Array cannot be empty");
+ }
+
+ int max = arr[0];
+ for (int i = 1; i < arr.length; i++) {
+ if (arr[i] > max) {
+ max = arr[i];
+ }
+ }
+ return max;
+ }
+ private void testOutputChannelCounts(AudioDeviceInfo inputDeviceInfo, AudioDeviceInfo outputDeviceInfo) throws InterruptedException {
+ logSection("Output Channel Counts");
+ ArrayList<Integer> channelCountsTested =new ArrayList<Integer>();
+ StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
- requestedOutConfig.setDeviceId(deviceId);
- requestedOutConfig.setMMap(mmapEnabled);
- mMagnitude = -1.0;
- TestResult testResult = testConfigurationsAddMagJitter();
- if (testResult != null) {
- int result = testResult.result;
- appendSummary(getOneLineSummary() + "\n");
- if (result == TEST_RESULT_FAILED) {
- if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
- && numOutputChannels == 2
- && outputChannel == 1) {
- testResult.addComment("Maybe EARPIECE does not mix stereo to mono!");
+ int[] outputChannelCounts = outputDeviceInfo.getChannelCounts();
+ if (isDeviceTypeMixedForLoopback(outputDeviceInfo.getType())) {
+ requestedInConfig.setChannelCount(1);
+ setInputChannel(0);
+ // test mono
+ requestedOutConfig.setChannelCount(1);
+ channelCountsTested.add(1);
+ setOutputChannel(0);
+ testPerformancePaths();
+ // test stereo
+ requestedOutConfig.setChannelCount(2);
+ channelCountsTested.add(2);
+ setOutputChannel(0);
+ testPerformancePaths();
+ setOutputChannel(1);
+ testPerformancePaths();
+ // Test channels for each channelCount above 2
+ for (int numChannels : outputChannelCounts) {
+ log("numChannels = " + numChannels);
+ if (numChannels > 4) {
+ log("numChannels forced to 4!");
}
- if (deviceType == TYPE_BUILTIN_SPEAKER_SAFE
- && numOutputChannels == 2
- && outputChannel == 0) {
- testResult.addComment("Maybe SPEAKER_SAFE dropped channel zero!");
+ if (!channelCountsTested.contains(numChannels)) {
+ requestedOutConfig.setChannelCount(numChannels);
+ channelCountsTested.add(numChannels);
+ for (int channel = 0; channel < numChannels; channel++) {
+ setOutputChannel(channel);
+ testPerformancePaths();
+ }
+ }
+ }
+ } else {
+ // test mono
+ testMatchingChannels(1);
+ channelCountsTested.add(1);
+ // Test two matching stereo channels.
+ testMatchingChannels(2);
+ channelCountsTested.add(2);
+ // Test matching channels for each channelCount above 2
+ for (int numChannels : outputChannelCounts) {
+ log("numChannels = " + numChannels);
+ if (numChannels > 4) {
+ log("numChannels forced to 4!");
+ numChannels = 4;
+ }
+ if (!channelCountsTested.contains(numChannels)) {
+ testMatchingChannels(numChannels);
+ channelCountsTested.add(numChannels);
}
}
}
+ // Restore defaults.
+ requestedInConfig.setChannelCount(1);
+ setInputChannel(0);
+ requestedOutConfig.setChannelCount(1);
+ setOutputChannel(0);
+ }
+
+ private void testMatchingChannels(int numChannels) throws InterruptedException {
+ mAudioInputTester.requestedConfiguration.setChannelCount(numChannels);
+ mAudioOutTester.requestedConfiguration.setChannelCount(numChannels);
+ for (int channel = 0; channel < numChannels; channel++) {
+ setInputChannel(channel);
+ setOutputChannel(channel);
+ testPerformancePaths();
+ }
}
- void testOutputDeviceCombo(int deviceId,
- int deviceType,
- int numOutputChannels,
- int outputChannel) throws InterruptedException {
- String typeString = AudioDeviceInfoConverter.typeToString(deviceType);
- setTestName("Test OutDev: #" + deviceId + " " + typeString
- + "_" + outputChannel + "/" + numOutputChannels);
+ private void testPerformancePaths() throws InterruptedException {
+ StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration;
+ StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
+
+ requestedInConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED);
+ requestedOutConfig.setSharingMode(StreamConfiguration.SHARING_MODE_SHARED);
+
+ // Legacy NONE
+ requestedInConfig.setMMap(false);
+ requestedOutConfig.setMMap(false);
+ requestedInConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_NONE);
+ requestedOutConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_NONE);
+ testCurrentConfigurations();
+
+ // Legacy LOW_LATENCY
+ requestedInConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY);
+ requestedOutConfig.setPerformanceMode(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY);
+ testCurrentConfigurations();
+
+ // MMAP LowLatency
if (NativeEngine.isMMapSupported()) {
- testOutputDeviceCombo(deviceId, deviceType, numOutputChannels, outputChannel, true);
+ requestedInConfig.setMMap(true);
+ requestedOutConfig.setMMap(true);
+ testCurrentConfigurations();
}
- testOutputDeviceCombo(deviceId, deviceType, numOutputChannels, outputChannel, false);
- }
-
- void logBoth(String text) {
- log(text);
- appendSummary(text + "\n");
- }
+ requestedInConfig.setMMap(false);
+ requestedOutConfig.setMMap(false);
- void logFailed(String text) {
- log(text);
- logAnalysis(text + "\n");
}
- void testOutputDevices() throws InterruptedException {
- logBoth("\nTest Output Devices -------");
-
- AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
- int numTested = 0;
- for (AudioDeviceInfo deviceInfo : devices) {
- log("----\n"
- + AudioDeviceInfoConverter.toString(deviceInfo) + "\n");
- if (!deviceInfo.isSink()) continue;
- int deviceType = deviceInfo.getType();
- if (deviceType == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER
- || deviceType == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE
- || deviceType == TYPE_BUILTIN_SPEAKER_SAFE) {
- int id = deviceInfo.getId();
- int[] channelCounts = deviceInfo.getChannelCounts();
- numTested++;
- // Always test mono and stereo.
- testOutputDeviceCombo(id, deviceType, 1, 0);
- testOutputDeviceCombo(id, deviceType, 2, 0);
- testOutputDeviceCombo(id, deviceType, 2, 1);
- 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, channel);
- }
- }
- }
- }
- } else {
- log("Device skipped for type.");
- }
+ 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;
}
- if (numTested == 0) {
- log("NO OUTPUT DEVICE FOUND!\n");
+ 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);
}
}
@@ -610,31 +844,25 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
mTestResults.clear();
mDurationSeconds = DURATION_SECONDS;
- if (mCheckBoxInputPresets.isChecked()) {
- runOnUiThread(() -> mCheckBoxInputPresets.setEnabled(false));
- testInputPresets();
- }
- if (mCheckBoxInputDevices.isChecked()) {
- runOnUiThread(() -> mCheckBoxInputDevices.setEnabled(false));
- testInputDevices();
- }
- if (mCheckBoxOutputDevices.isChecked()) {
- runOnUiThread(() -> mCheckBoxOutputDevices.setEnabled(false));
- testOutputDevices();
- }
+ runOnUiThread(() -> keepScreenOn(true));
- analyzeTestResults();
+ testOutputDeviceTypes();
+
+ compareFailedTestsWithNearestPassingTest();
} catch (InterruptedException e) {
- analyzeTestResults();
+ compareFailedTestsWithNearestPassingTest();
} catch (Exception e) {
log(e.getMessage());
showErrorToast(e.getMessage());
} finally {
runOnUiThread(() -> {
mCheckBoxInputPresets.setEnabled(true);
- mCheckBoxInputDevices.setEnabled(true);
- mCheckBoxOutputDevices.setEnabled(true);
+ mCheckBoxAllChannels.setEnabled(true);
+ mCheckBoxInputChannelMasks.setEnabled(true);
+ mRadioGroupOutputChannelMasks.setEnabled(true);
+ mCheckBoxAllSampleRates.setEnabled(true);
+ keepScreenOn(false);
});
}
}
@@ -645,22 +873,59 @@ public class TestDataPathsActivity extends BaseAutoGlitchActivity {
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
configureStreamsFromBundle(mBundleFromIntent, requestedInConfig, requestedOutConfig);
- boolean shouldUseInputPresets = mBundleFromIntent.getBoolean(KEY_USE_INPUT_PRESETS,
+ // These are the current supported options.
+ final boolean shouldUseInputPresets = mBundleFromIntent.getBoolean(KEY_USE_INPUT_PRESETS,
VALUE_DEFAULT_USE_INPUT_PRESETS);
- boolean shouldUseInputDevices = mBundleFromIntent.getBoolean(KEY_USE_INPUT_DEVICES,
- VALUE_DEFAULT_USE_INPUT_DEVICES);
- boolean shouldUseOutputDevices = mBundleFromIntent.getBoolean(KEY_USE_OUTPUT_DEVICES,
- VALUE_DEFAULT_USE_OUTPUT_DEVICES);
- int singleTestIndex = mBundleFromIntent.getInt(KEY_SINGLE_TEST_INDEX,
+ final boolean shouldUseAllSampleRates =
+ mBundleFromIntent.getBoolean(KEY_USE_ALL_SAMPLE_RATES,
+ VALUE_DEFAULT_USE_ALL_SAMPLE_RATES);
+
+ final int singleTestIndex = mBundleFromIntent.getInt(KEY_SINGLE_TEST_INDEX,
VALUE_DEFAULT_SINGLE_TEST_INDEX);
+ // The old deprecated commands will get mapped to the closest new options.
+ final boolean shouldUseInputDevices = mBundleFromIntent.getBoolean(KEY_USE_INPUT_DEVICES,
+ VALUE_DEFAULT_USE_INPUT_CHANNEL_MASKS);
+ final boolean shouldUseInputChannelMasks =
+ mBundleFromIntent.getBoolean(KEY_USE_INPUT_CHANNEL_MASKS,
+ shouldUseInputDevices);
+
+ final boolean shouldUseOutputDevices = mBundleFromIntent.getBoolean(KEY_USE_OUTPUT_DEVICES,
+ VALUE_DEFAULT_USE_ALL_CHANNEL_COUNTS);
+ final boolean shouldUseAllChannelCounts =
+ mBundleFromIntent.getBoolean(KEY_USE_ALL_CHANNEL_COUNTS,
+ shouldUseOutputDevices);
+
+ final boolean shouldUseAllOutputChannelMasks =
+ mBundleFromIntent.getBoolean(KEY_USE_ALL_OUTPUT_CHANNEL_MASKS,
+ VALUE_DEFAULT_USE_ALL_OUTPUT_CHANNEL_MASKS);
+ final int defaultOutputChannelMasksLevel = shouldUseAllOutputChannelMasks
+ ? COVERAGE_LEVEL_ALL : COVERAGE_LEVEL_SOME;
+ final int outputChannelMasksLevel = mBundleFromIntent.getInt(KEY_OUTPUT_CHANNEL_MASKS_LEVEL,
+ defaultOutputChannelMasksLevel);
+
runOnUiThread(() -> {
mCheckBoxInputPresets.setChecked(shouldUseInputPresets);
- mCheckBoxInputDevices.setChecked(shouldUseInputDevices);
- mCheckBoxOutputDevices.setChecked(shouldUseOutputDevices);
+ mCheckBoxAllSampleRates.setChecked(shouldUseAllSampleRates);
mAutomatedTestRunner.setTestIndexText(singleTestIndex);
+ mCheckBoxAllChannels.setChecked(shouldUseAllChannelCounts);
+ mCheckBoxInputChannelMasks.setChecked(shouldUseInputChannelMasks);
+ switch(outputChannelMasksLevel) {
+ case COVERAGE_LEVEL_ALL:
+ mRadioOutputChannelMasksAll.setChecked(true);
+ break;
+ case COVERAGE_LEVEL_SOME:
+ mRadioOutputChannelMasksSome.setChecked(true);
+ break;
+ case COVERAGE_LEVEL_NONE:
+ default:
+ mRadioOutputChannelMasksNone.setChecked(true);
+ break;
+ }
});
+ // This will sync with the above checkbox code because it will log on the UI
+ // thread before running any tests.
mAutomatedTestRunner.startTest();
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java
index 2f3c78c5..b74c3b10 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestDisconnectActivity.java
@@ -20,9 +20,16 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.TextView;
import java.io.IOException;
@@ -47,9 +54,14 @@ public class TestDisconnectActivity extends TestAudioActivity {
private volatile boolean mTestFailed;
private volatile boolean mSkipTest;
private volatile int mPlugCount;
+ private volatile int mUsbDeviceAttachedCount;
+ private volatile int mPlugState;
+ private volatile int mPlugMicrophone;
private BroadcastReceiver mPluginReceiver = new PluginBroadcastReceiver();
private Button mFailButton;
private Button mSkipButton;
+ private CheckBox mCheckBoxInputs;
+ private CheckBox mCheckBoxOutputs;
protected AutomatedTestRunner mAutomatedTestRunner;
@@ -58,15 +70,68 @@ public class TestDisconnectActivity extends TestAudioActivity {
public class PluginBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- mPlugCount++;
+ switch (intent.getAction()) {
+ case Intent.ACTION_HEADSET_PLUG: {
+ mPlugMicrophone = intent.getIntExtra("microphone", -1);
+ mPlugState = intent.getIntExtra("state", -1);
+ mPlugCount++;
+ } break;
+ case UsbManager.ACTION_USB_DEVICE_ATTACHED:
+ case UsbManager.ACTION_USB_DEVICE_DETACHED: {
+ UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+ final boolean hasAudioPlayback =
+ containsAudioStreamingInterface(device, UsbConstants.USB_DIR_OUT);
+ final boolean hasAudioCapture =
+ containsAudioStreamingInterface(device, UsbConstants.USB_DIR_IN);
+ if (hasAudioPlayback || hasAudioCapture) {
+ mPlugState =
+ intent.getAction() == UsbManager.ACTION_USB_DEVICE_ATTACHED ? 1 : 0;
+ mUsbDeviceAttachedCount++;
+ mPlugMicrophone = hasAudioCapture ? 1 : 0;
+ }
+ } break;
+ default:
+ break;
+ }
runOnUiThread(new Runnable() {
@Override
public void run() {
- String message = "Intent.HEADSET_PLUG #" + mPlugCount;
+ String message = "HEADSET_PLUG #" + mPlugCount
+ + ", USB_DEVICE_DE/ATTACHED #" + mUsbDeviceAttachedCount
+ + ", mic = " + mPlugMicrophone
+ + ", state = " + mPlugState;
mPlugTextView.setText(message);
+ log(message);
}
});
}
+
+ private static final int AUDIO_STREAMING_SUB_CLASS = 2;
+
+ /**
+ * Figure out if an UsbDevice contains audio input/output streaming interface or not.
+ *
+ * @param device the given UsbDevice
+ * @param direction the direction of the audio streaming interface
+ * @return true if the UsbDevice contains the audio input/output streaming interface.
+ */
+ private boolean containsAudioStreamingInterface(UsbDevice device, int direction) {
+ final int interfaceCount = device.getInterfaceCount();
+ for (int i = 0; i < interfaceCount; ++i) {
+ UsbInterface usbInterface = device.getInterface(i);
+ if (usbInterface.getInterfaceClass() != UsbConstants.USB_CLASS_AUDIO
+ && usbInterface.getInterfaceSubclass() != AUDIO_STREAMING_SUB_CLASS) {
+ continue;
+ }
+ final int endpointCount = usbInterface.getEndpointCount();
+ for (int j = 0; j < endpointCount; ++j) {
+ if (usbInterface.getEndpoint(j).getDirection() == direction) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
@Override
@@ -85,6 +150,9 @@ public class TestDisconnectActivity extends TestAudioActivity {
mStatusTextView = (TextView) findViewById(R.id.text_status);
mPlugTextView = (TextView) findViewById(R.id.text_plug_events);
+ mCheckBoxInputs = (CheckBox)findViewById(R.id.checkbox_disco_inputs);
+ mCheckBoxOutputs = (CheckBox)findViewById(R.id.checkbox_disco_outputs);
+
mFailButton = (Button) findViewById(R.id.button_fail);
mSkipButton = (Button) findViewById(R.id.button_skip);
updateFailSkipButton(false);
@@ -138,6 +206,8 @@ public class TestDisconnectActivity extends TestAudioActivity {
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
this.registerReceiver(mPluginReceiver, filter);
}
@@ -185,15 +255,25 @@ public class TestDisconnectActivity extends TestAudioActivity {
private String getConfigText(StreamConfiguration config) {
return ((config.getDirection() == StreamConfiguration.DIRECTION_OUTPUT) ? "OUT" : "IN")
+ ", Perf = " + StreamConfiguration.convertPerformanceModeToText(
- config.getPerformanceMode())
+ config.getPerformanceMode())
+ ", " + StreamConfiguration.convertSharingModeToText(config.getSharingMode())
- + ", " + config.getSampleRate();
+ + ", " + config.getSampleRate()
+ + ", SRC = " + StreamConfiguration.convertRateConversionQualityToText(config.getRateConversionQuality());
+ }
+
+ private void log(Exception e) {
+ Log.e(TestAudioActivity.TAG, "Caught ", e);
+ mAutomatedTestRunner.log("Caught " + e);
}
private void log(String text) {
mAutomatedTestRunner.log(text);
}
+ private void flushLog() {
+ mAutomatedTestRunner.flushLog();
+ }
+
private void appendFailedSummary(String text) {
mAutomatedTestRunner.appendFailedSummary(text);
}
@@ -202,14 +282,36 @@ public class TestDisconnectActivity extends TestAudioActivity {
int perfMode,
int sharingMode,
int sampleRate,
+ int sampleRateConversionQuality,
boolean requestPlugin) throws InterruptedException {
if ((getSingleTestIndex() >= 0) && (mAutomatedTestRunner.getTestCount() != getSingleTestIndex())) {
mAutomatedTestRunner.incrementTestCount();
return;
}
+ if (!isInput && !mCheckBoxOutputs.isChecked()) {
+ return;
+ }
+ if (isInput && !mCheckBoxInputs.isChecked()) {
+ return;
+ }
+
+ updateFailSkipButton(true);
+
String actualConfigText = "none";
mSkipTest = false;
+ mTestFailed = false;
+
+ // Try to synchronize with the current headset state, IN or OUT.
+ while (mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && !mTestFailed) {
+ if (requestPlugin != (mPlugState == 0)) {
+ String message = "SYNC: " + (requestPlugin ? "UNplug" : "Plug IN") + " headset now!";
+ setInstructionsText(message);
+ Thread.sleep(POLL_DURATION_MILLIS);
+ } else {
+ break;
+ }
+ }
AudioInputTester mAudioInTester = null;
AudioOutputTester mAudioOutTester = null;
@@ -234,8 +336,9 @@ public class TestDisconnectActivity extends TestAudioActivity {
requestedConfig.setPerformanceMode(perfMode);
requestedConfig.setSharingMode(sharingMode);
requestedConfig.setSampleRate(sampleRate);
+
if (sampleRate != 0) {
- requestedConfig.setRateConversionQuality(StreamConfiguration.RATE_CONVERSION_QUALITY_MEDIUM);
+ requestedConfig.setRateConversionQuality(sampleRateConversionQuality);
}
log("========================== #" + mAutomatedTestRunner.getTestCount());
@@ -246,20 +349,24 @@ public class TestDisconnectActivity extends TestAudioActivity {
Thread.sleep(SETTLING_TIME_MILLIS);
if (!mAutomatedTestRunner.isThreadEnabled()) return;
boolean openFailed = false;
+ boolean hasMicFailed = false;
AudioStreamBase stream = null;
try {
openAudio();
log("Actual:");
actualConfigText = getConfigText(actualConfig)
- + ", " + (actualConfig.isMMap() ? "MMAP" : "Legacy");
+ + ", " + ((actualConfig.isMMap() ? "MMAP" : "Legacy")
+ + ", Dev = " + actualConfig.getDeviceId()
+ );
log(actualConfigText);
+ flushLog();
stream = (isInput)
? mAudioInTester.getCurrentAudioStream()
: mAudioOutTester.getCurrentAudioStream();
} catch (IOException e) {
openFailed = true;
- log(e.getMessage());
+ log(e);
}
// The test is only worth running if we got the configuration we requested.
@@ -285,14 +392,13 @@ public class TestDisconnectActivity extends TestAudioActivity {
} catch (IOException e) {
e.printStackTrace();
valid = false;
- log(e.getMessage());
+ log(e);
}
}
int oldPlugCount = mPlugCount;
if (!openFailed && valid) {
mTestFailed = false;
- updateFailSkipButton(true);
// poll until stream started
while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest &&
stream.getState() == StreamConfiguration.STREAM_STATE_STARTING) {
@@ -305,15 +411,18 @@ public class TestDisconnectActivity extends TestAudioActivity {
// Wait for Java plug count to change or stream to disconnect.
while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest &&
stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) {
+ flushLog();
Thread.sleep(POLL_DURATION_MILLIS);
if (mPlugCount > oldPlugCount) {
timeoutCount = TIME_TO_FAILURE_MILLIS / POLL_DURATION_MILLIS;
break;
}
}
+
// Wait for timeout or stream to disconnect.
while (!mTestFailed && mAutomatedTestRunner.isThreadEnabled() && !mSkipTest && (timeoutCount > 0) &&
stream.getState() == StreamConfiguration.STREAM_STATE_STARTED) {
+ flushLog();
Thread.sleep(POLL_DURATION_MILLIS);
timeoutCount--;
if (timeoutCount == 0) {
@@ -322,15 +431,26 @@ public class TestDisconnectActivity extends TestAudioActivity {
setStatusText("Plug detected by Java.\nCounting down to Oboe failure: " + timeoutCount);
}
}
- if (!mTestFailed) {
- int error = stream.getLastErrorCallbackResult();
- if (error != StreamConfiguration.ERROR_DISCONNECTED) {
- log("onEerrorCallback error = " + error
- + ", expected " + StreamConfiguration.ERROR_DISCONNECTED);
- mTestFailed = true;
+
+ if (mSkipTest) {
+ setStatusText("Skipped");
+ } else {
+ if (mTestFailed) {
+ // Check whether the peripheral has a microphone.
+ // Sometimes the microphones does not appear on the first HEADSET_PLUG event.
+ if (isInput && (mPlugMicrophone == 0)) {
+ hasMicFailed = true;
+ }
+ } else {
+ int error = stream.getLastErrorCallbackResult();
+ if (error != StreamConfiguration.ERROR_DISCONNECTED) {
+ log("onErrorCallback error = " + error
+ + ", expected " + StreamConfiguration.ERROR_DISCONNECTED);
+ mTestFailed = true;
+ }
}
+ setStatusText(mTestFailed ? "Failed" : "Passed - detected");
}
- setStatusText(mTestFailed ? "Failed" : "Passed - detected");
}
updateFailSkipButton(false);
setInstructionsText("Wait...");
@@ -352,6 +472,9 @@ public class TestDisconnectActivity extends TestAudioActivity {
boolean passed = !mTestFailed;
String resultText = requestPlugin ? "plugIN" : "UNplug";
resultText += ", " + (passed ? TEXT_PASS : TEXT_FAIL);
+ if (hasMicFailed) {
+ resultText += ", Headset has no mic!";
+ }
log(resultText);
if (!passed) {
appendFailedSummary("------ #" + mAutomatedTestRunner.getTestCount() + "\n");
@@ -365,17 +488,27 @@ public class TestDisconnectActivity extends TestAudioActivity {
} else {
log(TEXT_SKIP);
}
+ flushLog();
// Give hardware time to settle between tests.
Thread.sleep(1000);
mAutomatedTestRunner.incrementTestCount();
}
private void testConfiguration(boolean isInput, int performanceMode,
- int sharingMode, int sampleRate) throws InterruptedException {
+ int sharingMode, int sampleRate,
+ int sampleRateConversionQuality) throws InterruptedException {
boolean requestPlugin = true; // plug IN
- testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin);
+ testConfiguration(isInput, performanceMode, sharingMode, sampleRate,
+ sampleRateConversionQuality, requestPlugin);
requestPlugin = false; // UNplug
- testConfiguration(isInput, performanceMode, sharingMode, sampleRate, requestPlugin);
+ testConfiguration(isInput, performanceMode, sharingMode, sampleRate,
+ sampleRateConversionQuality, requestPlugin);
+ }
+
+ private void testConfiguration(boolean isInput, int performanceMode,
+ int sharingMode, int sampleRate) throws InterruptedException {
+ testConfiguration(isInput, performanceMode, sharingMode, sampleRate,
+ StreamConfiguration.RATE_CONVERSION_QUALITY_NONE);
}
private void testConfiguration(boolean isInput, int performanceMode,
@@ -390,25 +523,45 @@ public class TestDisconnectActivity extends TestAudioActivity {
testConfiguration(true, performanceMode, sharingMode);
}
+ private void testConfiguration(int performanceMode,
+ int sharingMode, int sampleRate,
+ int sampleRateConversionQuality) throws InterruptedException {
+ testConfiguration(false, performanceMode, sharingMode, sampleRate, sampleRateConversionQuality);
+ testConfiguration(true, performanceMode, sharingMode, sampleRate, sampleRateConversionQuality);
+ }
+
@Override
public void runTest() {
+
+ runOnUiThread(() -> keepScreenOn(true));
+
mPlugCount = 0;
+
// Try several different configurations.
try {
- testConfiguration(false, StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
- StreamConfiguration.SHARING_MODE_EXCLUSIVE, 44100);
- testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
- StreamConfiguration.SHARING_MODE_EXCLUSIVE);
- testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
- StreamConfiguration.SHARING_MODE_SHARED);
testConfiguration(StreamConfiguration.PERFORMANCE_MODE_NONE,
StreamConfiguration.SHARING_MODE_SHARED);
+ if (NativeEngine.isMMapExclusiveSupported()){
+ testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
+ StreamConfiguration.SHARING_MODE_EXCLUSIVE);
+ }
+ testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
+ StreamConfiguration.SHARING_MODE_SHARED);
+ testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
+ StreamConfiguration.SHARING_MODE_SHARED, 44100,
+ StreamConfiguration.RATE_CONVERSION_QUALITY_NONE);
+ testConfiguration(StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY,
+ StreamConfiguration.SHARING_MODE_SHARED, 44100,
+ StreamConfiguration.RATE_CONVERSION_QUALITY_MEDIUM);
} catch (InterruptedException e) {
- log(e.getMessage());
- showErrorToast(e.getMessage());
+ log("Test CANCELLED - INVALID!");
+ } catch (Exception e) {
+ log(e);
+ showErrorToast("Caught " + e);
} finally {
- setInstructionsText("Test completed.");
+ setInstructionsText("Test finished.");
updateFailSkipButton(false);
+ runOnUiThread(() -> keepScreenOn(false));
}
}
}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestInputActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestInputActivity.java
index 7f33d803..a6c9bbd6 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestInputActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestInputActivity.java
@@ -17,17 +17,15 @@
package com.mobileer.oboetester;
import android.content.Intent;
-import android.media.audiofx.AcousticEchoCanceler;
-import android.media.audiofx.AutomaticGainControl;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.support.v4.content.FileProvider;
import android.view.View;
import android.widget.RadioButton;
+import androidx.annotation.NonNull;
+import androidx.core.content.FileProvider;
import java.io.File;
import java.io.IOException;
@@ -39,12 +37,12 @@ import java.io.IOException;
public class TestInputActivity extends TestAudioActivity {
protected AudioInputTester mAudioInputTester;
- private static final int NUM_VOLUME_BARS = 4;
+ // Note that this must match the number of volume bars defined in the layout file.
+ private static final int NUM_VOLUME_BARS = 8;
private VolumeBarView[] mVolumeBars = new VolumeBarView[NUM_VOLUME_BARS];
private InputMarginView mInputMarginView;
private int mInputMarginBursts = 0;
private WorkloadView mWorkloadView;
- private CommunicationDeviceView mCommunicationDeviceView;
public native void setMinimumFramesBeforeRead(int frames);
public native int saveWaveFile(String absolutePath);
@@ -67,6 +65,10 @@ public class TestInputActivity extends TestAudioActivity {
mVolumeBars[1] = (VolumeBarView) findViewById(R.id.volumeBar1);
mVolumeBars[2] = (VolumeBarView) findViewById(R.id.volumeBar2);
mVolumeBars[3] = (VolumeBarView) findViewById(R.id.volumeBar3);
+ mVolumeBars[4] = (VolumeBarView) findViewById(R.id.volumeBar4);
+ mVolumeBars[5] = (VolumeBarView) findViewById(R.id.volumeBar5);
+ mVolumeBars[6] = (VolumeBarView) findViewById(R.id.volumeBar6);
+ mVolumeBars[7] = (VolumeBarView) findViewById(R.id.volumeBar7);
mInputMarginView = (InputMarginView) findViewById(R.id.input_margin_view);
@@ -76,21 +78,13 @@ public class TestInputActivity extends TestAudioActivity {
mWorkloadView = (WorkloadView) findViewById(R.id.workload_view);
if (mWorkloadView != null) {
- mWorkloadView.setAudioStreamTester(mAudioInputTester);
+ mWorkloadView.setWorkloadReceiver((w) -> mAudioInputTester.setWorkload(w));
}
mCommunicationDeviceView = (CommunicationDeviceView) findViewById(R.id.comm_device_view);
}
@Override
- protected void onStop() {
- if (mCommunicationDeviceView != null) {
- mCommunicationDeviceView.cleanup();
- }
- super.onStop();
- }
-
- @Override
int getActivityType() {
return ACTIVITY_TEST_INPUT;
}
@@ -150,11 +144,6 @@ public class TestInputActivity extends TestAudioActivity {
resetVolumeBars();
}
- @Override
- protected void toastPauseError(int result) {
- showToast("Pause not implemented. Returned " + result);
- }
-
protected int saveWaveFile(File file) {
// Pass filename to native to write WAV file
int result = saveWaveFile(file.getAbsolutePath());
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java
index 0d5d0ce3..f2ac5ed5 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivity.java
@@ -16,19 +16,18 @@
package com.mobileer.oboetester;
-import android.content.Context;
-import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
-import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.CheckBox;
+import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import java.io.IOException;
+import java.util.Locale;
/**
* Test basic output.
@@ -38,7 +37,9 @@ public final class TestOutputActivity extends TestOutputActivityBase {
public static final int MAX_CHANNEL_BOXES = 16;
private CheckBox[] mChannelBoxes;
private Spinner mOutputSignalSpinner;
- protected CommunicationDeviceView mCommunicationDeviceView;
+ private TextView mVolumeTextView;
+ private SeekBar mVolumeSeekBar;
+ private CheckBox mShouldSetStreamControlByAttributes;
private class OutputSignalSpinnerListener implements android.widget.AdapterView.OnItemSelectedListener {
@Override
@@ -52,6 +53,21 @@ public final class TestOutputActivity extends TestOutputActivityBase {
}
}
+ private SeekBar.OnSeekBarChangeListener mVolumeChangeListener = new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ setVolume(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ };
+
@Override
protected void inflateActivity() {
setContentView(R.layout.activity_test_output);
@@ -90,6 +106,12 @@ public final class TestOutputActivity extends TestOutputActivityBase {
mOutputSignalSpinner.setSelection(StreamConfiguration.NATIVE_API_UNSPECIFIED);
mCommunicationDeviceView = (CommunicationDeviceView) findViewById(R.id.comm_device_view);
+
+ mVolumeTextView = (TextView) findViewById(R.id.textVolumeSlider);
+ mVolumeSeekBar = (SeekBar) findViewById(R.id.faderVolumeSlider);
+ mVolumeSeekBar.setOnSeekBarChangeListener(mVolumeChangeListener);
+
+ mShouldSetStreamControlByAttributes = (CheckBox) findViewById(R.id.enableSetStreamControlByAttributes);
}
@Override
@@ -97,16 +119,9 @@ public final class TestOutputActivity extends TestOutputActivityBase {
return ACTIVITY_TEST_OUTPUT;
}
- @Override
- protected void onStop() {
- if (mCommunicationDeviceView != null) {
- mCommunicationDeviceView.cleanup();
- }
- super.onStop();
- }
-
public void openAudio() throws IOException {
super.openAudio();
+ mShouldSetStreamControlByAttributes.setEnabled(false);
}
private void configureChannelBoxes(int channelCount) {
@@ -116,6 +131,20 @@ public final class TestOutputActivity extends TestOutputActivityBase {
}
}
+ private void setVolume(int progress) {
+ // Convert from (0, 500) range to (-50, 0).
+ double decibels = (progress - 500) / 10.0f;
+ double amplitude = Math.pow(10.0, decibels / 20.0);
+ // When the slider is all way to the left, set a zero amplitude.
+ if (progress == 0) {
+ amplitude = 0;
+ }
+ mVolumeTextView.setText("Volume(dB): " + String.format(Locale.getDefault(), "%.1f",
+ decibels));
+ mAudioOutTester.setAmplitude((float) amplitude);
+ }
+
+
public void stopAudio() {
configureChannelBoxes(0);
mOutputSignalSpinner.setEnabled(true);
@@ -128,9 +157,16 @@ public final class TestOutputActivity extends TestOutputActivityBase {
super.pauseAudio();
}
+ public void releaseAudio() {
+ configureChannelBoxes(0);
+ mOutputSignalSpinner.setEnabled(true);
+ super.releaseAudio();
+ }
+
public void closeAudio() {
configureChannelBoxes(0);
mOutputSignalSpinner.setEnabled(true);
+ mShouldSetStreamControlByAttributes.setEnabled(true);
super.closeAudio();
}
@@ -149,6 +185,11 @@ public final class TestOutputActivity extends TestOutputActivityBase {
}
@Override
+ protected boolean shouldSetStreamControlByAttributes() {
+ return mShouldSetStreamControlByAttributes.isChecked();
+ }
+
+ @Override
public void startTestUsingBundle() {
try {
StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration;
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivityBase.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivityBase.java
index 5c17e928..731351b9 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivityBase.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestOutputActivityBase.java
@@ -26,7 +26,7 @@ abstract class TestOutputActivityBase extends TestAudioActivity {
AudioOutputTester mAudioOutTester;
private BufferSizeView mBufferSizeView;
- private WorkloadView mWorkloadView;
+ protected WorkloadView mWorkloadView;
@Override boolean isOutput() { return true; }
@@ -40,13 +40,14 @@ abstract class TestOutputActivityBase extends TestAudioActivity {
super.findAudioCommon();
mBufferSizeView = (BufferSizeView) findViewById(R.id.buffer_size_view);
mWorkloadView = (WorkloadView) findViewById(R.id.workload_view);
+ if (mWorkloadView != null) {
+ mWorkloadView.setWorkloadReceiver((w) -> mAudioOutTester.setWorkload(w));
+ }
}
@Override
public AudioOutputTester addAudioOutputTester() {
- AudioOutputTester audioOutTester = super.addAudioOutputTester();
- mWorkloadView.setAudioStreamTester(audioOutTester);
- return audioOutTester;
+ return super.addAudioOutputTester();
}
@Override
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestPlugLatencyActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestPlugLatencyActivity.java
index 3110447b..b55fa127 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestPlugLatencyActivity.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestPlugLatencyActivity.java
@@ -40,6 +40,7 @@ import java.util.HashMap;
public class TestPlugLatencyActivity extends TestAudioActivity {
public static final int POLL_DURATION_MILLIS = 1;
+ public static final int TIMEOUT_MILLIS = 1000;
private TextView mInstructionsTextView;
private TextView mPlugTextView;
@@ -48,6 +49,7 @@ public class TestPlugLatencyActivity extends TestAudioActivity {
private AudioManager mAudioManager;
private volatile int mPlugCount = 0;
+ private long mTimeoutAtMillis;
private AudioOutputTester mAudioOutTester;
@@ -58,12 +60,17 @@ public class TestPlugLatencyActivity extends TestAudioActivity {
@Override
public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
boolean isBootingUp = mDevices.isEmpty();
+ AudioDeviceInfo outputDeviceInfo = null;
for (AudioDeviceInfo info : addedDevices) {
mDevices.put(info.getId(), info);
if (!isBootingUp)
{
- log("Device Added");
+ log("====== Device Added =======");
log(adiToString(info));
+ // Only process OUTPUT devices because that is what we are testing.
+ if (info.isSink()) {
+ outputDeviceInfo = info;
+ }
}
}
@@ -71,17 +78,26 @@ public class TestPlugLatencyActivity extends TestAudioActivity {
if (isBootingUp) {
log("Starting stream with existing audio devices");
}
- updateLatency(false /* wasDeviceRemoved */);
+ if (outputDeviceInfo != null) {
+ updateLatency(false /* wasDeviceRemoved */);
+ }
}
public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ AudioDeviceInfo outputDeviceInfo = null;
for (AudioDeviceInfo info : removedDevices) {
mDevices.remove(info.getId());
- log("Device Removed");
+ log("====== Device Removed =======");
log(adiToString(info));
+ // Only process OUTPUT devices because that is what we are testing.
+ if (info.isSink()) {
+ outputDeviceInfo = info;
+ }
}
- updateLatency(true /* wasDeviceRemoved */);
+ if (outputDeviceInfo != null) {
+ updateLatency(true /* wasDeviceRemoved */);
+ }
}
}
@@ -153,41 +169,79 @@ public class TestPlugLatencyActivity extends TestAudioActivity {
startAudio();
}
- private long calculateLatencyMs(boolean wasDeviceRemoved) {
+ private void setupTimeout() {
+ mTimeoutAtMillis = System.currentTimeMillis() + TIMEOUT_MILLIS;
+ }
- long startMillis = System.currentTimeMillis();
+ private void sleepOrTimeout(String message) throws InterruptedException {
+ Thread.sleep(POLL_DURATION_MILLIS);
+ if (System.currentTimeMillis() >= mTimeoutAtMillis) {
+ throw new InterruptedException(message);
+ }
+ }
+ private long calculateLatencyMs(boolean wasDeviceRemoved) {
+ long testStartMillis = System.currentTimeMillis();
+ long frameReadMillis = -1;
+ final int TIMEOUT_MAX = 100;
+ int timeout;
try {
+ long callbackMillis = -1;
if (wasDeviceRemoved && (mAudioOutTester != null)) {
+ log("Wait for error callback != 0");
// Keep querying as long as error is ok
+ setupTimeout();
while (mAudioOutTester.getLastErrorCallbackResult() == 0) {
- Thread.sleep(POLL_DURATION_MILLIS);
+ sleepOrTimeout("timed out waiting while error==0");
}
- log("Error callback at " + (System.currentTimeMillis() - startMillis) + " ms");
+ callbackMillis = System.currentTimeMillis();
+ log("Error callback at " + (callbackMillis - testStartMillis) + " ms. " +
+ "WAIT -> CALLBACK = took " + (callbackMillis - testStartMillis) + " ms");
}
closeAudio();
- log("Audio closed at " + (System.currentTimeMillis() - startMillis) + " ms");
+ long closedMillis = System.currentTimeMillis();
+ if (callbackMillis == -1) {
+ log("Audio closed at " + (closedMillis - testStartMillis) + " ms");
+ } else {
+ log("Audio closed at " + (closedMillis - testStartMillis) + " ms. " +
+ "CALLBACK -> CLOSED took " + (closedMillis - callbackMillis) + " ms");
+ }
+
clearStreamContexts();
mAudioOutTester = addAudioOutputTester();
openAudio();
- log("Audio opened at " + (System.currentTimeMillis() - startMillis) + " ms");
+ long openedMillis = System.currentTimeMillis();
+ log("Audio opened at " + (openedMillis - testStartMillis) + " ms. " +
+ "CLOSED -> OPENED took " + (openedMillis - closedMillis) + " ms");
AudioStreamBase stream = mAudioOutTester.getCurrentAudioStream();
startAudioTest();
- log("Audio starting at " + (System.currentTimeMillis() - startMillis) + " ms");
+ long startingMillis = System.currentTimeMillis();
+ log("Audio starting at " + (startingMillis - testStartMillis) + " ms. " +
+ "OPENED -> STARTING took " + (startingMillis - openedMillis) + " ms");
+
+ setupTimeout();
while (stream.getState() == StreamConfiguration.STREAM_STATE_STARTING) {
- Thread.sleep(POLL_DURATION_MILLIS);
+ sleepOrTimeout("timed out waiting while STATE_STARTING");
}
- log("Audio started at " + (System.currentTimeMillis() - startMillis) + " ms");
+ long startedMillis = System.currentTimeMillis();
+ log("Audio started at " + (startedMillis - testStartMillis) + " ms. " +
+ "STARTING -> STARTED took " + (startedMillis - startingMillis) + " ms");
+
+ setupTimeout();
while (mAudioOutTester.getFramesRead() == 0) {
- Thread.sleep(POLL_DURATION_MILLIS);
+ sleepOrTimeout("timed out waiting while framesRead()==0");
}
- log("First frame read at " + (System.currentTimeMillis() - startMillis) + " ms");
+ frameReadMillis = System.currentTimeMillis();
+ log("First frame read at " + (frameReadMillis - testStartMillis) + " ms. " +
+ "STARTED -> READ took " + (frameReadMillis - startedMillis) + " ms");
} catch (IOException | InterruptedException e) {
+ log("EXCEPTION: " + e);
e.printStackTrace();
+ closeAudio();
return -1;
}
- return System.currentTimeMillis() - startMillis;
+ return frameReadMillis - testStartMillis;
}
public static String adiToString(AudioDeviceInfo adi) {
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestRouteDuringCallbackActivity.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestRouteDuringCallbackActivity.java
new file mode 100644
index 00000000..4dfce073
--- /dev/null
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/TestRouteDuringCallbackActivity.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2023 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.
+ */
+
+package com.mobileer.oboetester;
+
+import static com.mobileer.oboetester.TestAudioActivity.TAG;
+
+import android.app.Activity;
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.RadioButton;
+import android.widget.TextView;
+
+import java.util.Random;
+
+/**
+ * Try to crash in the native AAudio code by causing a routing change
+ * while playing audio. The buffer may get deleted while we are writing to it!
+ * See b/274815060
+ */
+public class TestRouteDuringCallbackActivity extends Activity {
+
+ private TextView mStatusView;
+ private MyStreamSniffer mStreamSniffer;
+ private AudioManager mAudioManager;
+ private RadioButton mOutputButton;
+ private RadioButton mInputButton;
+ private Button mStartButton;
+ private Button mStopButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_routing_crash);
+ mStatusView = (TextView) findViewById(R.id.text_callback_status);
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+ mStartButton = (Button) findViewById(R.id.button_start_test);
+ mStopButton = (Button) findViewById(R.id.button_stop_test);
+ mOutputButton = (RadioButton) findViewById(R.id.direction_output);
+ mInputButton = (RadioButton) findViewById(R.id.direction_input);
+ setButtonsEnabled(false);
+ }
+
+ public void onStartRoutingTest(View view) {
+ startRoutingTest();
+ }
+
+ public void onStopRoutingTest(View view) {
+ stopRoutingTest();
+ }
+
+ private void setButtonsEnabled(boolean running) {
+ mStartButton.setEnabled(!running);
+ mStopButton.setEnabled(running);
+ mOutputButton.setEnabled(!running);
+ mInputButton.setEnabled(!running);
+ }
+
+ // Change routing while the stream is playing.
+ // Keep trying until we crash.
+ protected class MyStreamSniffer extends Thread {
+ boolean enabled = true;
+ int routingOption = 0;
+ StringBuffer statusBuffer = new StringBuffer();
+ int loopCount;
+
+ @Override
+ public void run() {
+ routingOption = 0;
+ changeRoute(routingOption);
+ int result;
+ Random random = new Random();
+ while (enabled) {
+ loopCount++;
+ if (routingOption == 0) {
+ statusBuffer = new StringBuffer();
+ }
+ try {
+ sleep(100);
+ boolean useInput = mInputButton.isChecked();
+ result = startStream(useInput);
+ sleep(100);
+ log("-------#" + loopCount + ", " + (useInput ? "IN" : "OUT")
+ + "\nstartStream() returned " + result);
+ int sleepTimeMillis = 500 + random.nextInt(500);
+ sleep(sleepTimeMillis);
+ routingOption = (routingOption == 0) ? 1 : 0;
+ log( "changeRoute " + routingOption);
+ changeRoute(routingOption);
+ sleep(50);
+ } catch (InterruptedException e) {
+ } finally {
+ result = stopStream();
+ log("stopStream() returned " + result);
+ }
+ }
+ changeRoute(0);
+ }
+
+ // Log to screen and logcat.
+ private void log(String text) {
+ Log.d(TAG, "RoutingCrash: " + text);
+ statusBuffer.append(text + ", sleep " + getSleepTimeMicros() + " us\n");
+ showStatus(statusBuffer.toString());
+ }
+
+ // Stop the test thread.
+ void finish() {
+ enabled = false;
+ interrupt();
+ try {
+ join(2000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ protected void showStatus(final String message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mStatusView.setText(message);
+ }
+ });
+ }
+
+ private void changeRoute(int option) {
+ mAudioManager.setSpeakerphoneOn(option > 0);
+ }
+
+ private native int startStream(boolean useInput);
+ private native int getSleepTimeMicros();
+ private native int stopStream();
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ stopRoutingTest();
+ }
+
+ private void startRoutingTest() {
+ stopRoutingTest();
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setButtonsEnabled(true);
+ mStreamSniffer = new MyStreamSniffer();
+ mStreamSniffer.start();
+ }
+
+ private void stopRoutingTest() {
+ if (mStreamSniffer != null) {
+ mStreamSniffer.finish();
+ mStreamSniffer = null;
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setButtonsEnabled(false);
+ }
+ }
+}
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/VolumeBarView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/VolumeBarView.java
index c6a2dc24..0dc6f4df 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/VolumeBarView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/VolumeBarView.java
@@ -24,6 +24,8 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import java.util.Locale;
+
/**
* Display volume in dB as a red bar.
* Display the amplitude, 0.0 to 1.0, as text in the bar.
@@ -81,7 +83,7 @@ public class VolumeBarView extends View {
float volumeWidth = ((MIN_VOLUME_DB - mVolume) / MIN_VOLUME_DB) * mCurrentWidth;
canvas.drawRect(0.0f, 0.0f, volumeWidth,
mCurrentHeight, mBarPaint);
- String text = String.format(FORMAT_AMPLITUDE, mClippedAmplitude);
+ String text = String.format(Locale.getDefault(), FORMAT_AMPLITUDE, mClippedAmplitude);
mTextPaint.getTextBounds(text, 0, text.length(), mTextBounds);
canvas.drawText(text,
20.0f,
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WaveformView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WaveformView.java
index fb50914b..4f4705c0 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WaveformView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WaveformView.java
@@ -27,6 +27,7 @@ import android.view.View;
/**
* Display an audio waveform in a custom View.
+ * Data is assumed to have a constant x-axis increment, ie. isochronous.
*/
public class WaveformView extends View {
private static final float MESSAGE_TEXT_SIZE = 80;
@@ -105,7 +106,18 @@ public class WaveformView extends View {
if (localData == null || mSampleCount == 0) {
return;
}
+
float xScale = ((float) mCurrentWidth) / (mSampleCount - 1);
+
+ // Draw cursors.
+ if (mCursors != null) {
+ for (int i = 0; i < mCursors.length; i++) {
+ float x = mCursors[i] * xScale;
+ canvas.drawLine(x, 0, x, mCurrentHeight, mCursorPaint);
+ }
+ }
+
+ // Draw waveform.
float x0 = 0.0f;
if (xScale < 1.0) {
// Draw a vertical bar for multiple samples.
@@ -135,16 +147,11 @@ public class WaveformView extends View {
y0 = y1;
}
}
- if (mCursors != null) {
- for (int i = 0; i < mCursors.length; i++) {
- float x = mCursors[i] * xScale;
- canvas.drawLine(x, 0, x, mCurrentHeight, mCursorPaint);
- }
- }
}
/**
- * Copy data into internal buffer then repaint.
+ * Copy data into internal buffer.
+ * Caller should then postInvalidate().
*/
public void setSampleData(float[] samples) {
setSampleData(samples, 0, samples.length);
@@ -170,7 +177,8 @@ public class WaveformView extends View {
}
/**
- * Copy cursor positions into internal buffer then repaint.
+ * Copy cursor positions into internal buffer.
+ * Caller should then postInvalidate().
*/
public void setCursorData(int[] cursors) {
if (cursors == null) {
diff --git a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WorkloadView.java b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WorkloadView.java
index 78d78df7..74027c2c 100644
--- a/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WorkloadView.java
+++ b/apps/OboeTester/app/src/main/java/com/mobileer/oboetester/WorkloadView.java
@@ -18,20 +18,29 @@ package com.mobileer.oboetester;
import android.content.Context;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
-public class WorkloadView extends LinearLayout {
+import java.util.Locale;
- private AudioStreamTester mAudioStreamTester;
+public class WorkloadView extends LinearLayout {
protected static final int FADER_PROGRESS_MAX = 1000; // must match layout
protected TextView mTextView;
protected SeekBar mSeekBar;
+
+ private String mLabel = "Workload";
protected ExponentialTaper mExponentialTaper;
+ public interface WorkloadReceiver {
+ void setWorkload(int workload);
+ }
+
+ WorkloadReceiver mWorkloadReceiver;
+
private SeekBar.OnSeekBarChangeListener mChangeListener = new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
@@ -64,12 +73,8 @@ public class WorkloadView extends LinearLayout {
initializeViews(context);
}
- public AudioStreamTester getAudioStreamTester() {
- return mAudioStreamTester;
- }
-
- public void setAudioStreamTester(AudioStreamTester audioStreamTester) {
- mAudioStreamTester = audioStreamTester;
+ public void setWorkloadReceiver(WorkloadReceiver workloadReceiver) {
+ mWorkloadReceiver = workloadReceiver;
}
void setFaderNormalizedProgress(double fraction) {
@@ -90,15 +95,21 @@ public class WorkloadView extends LinearLayout {
mTextView = (TextView) findViewById(R.id.textWorkload);
mSeekBar = (SeekBar) findViewById(R.id.faderWorkload);
mSeekBar.setOnSeekBarChangeListener(mChangeListener);
- mExponentialTaper = new ExponentialTaper(0.0, 100.0, 10.0);
+ setRange(0.0, 100.0);
//mSeekBar.setProgress(0);
}
+ void setRange(double dMin, double dMax) {
+ mExponentialTaper = new ExponentialTaper(dMin, dMax, 10.0);
+ }
+
private void setValueByPosition(int progress) {
- double workload = mExponentialTaper.linearToExponential(
+ int workload = (int) mExponentialTaper.linearToExponential(
((double)progress) / FADER_PROGRESS_MAX);
- mAudioStreamTester.setWorkload(workload);
- mTextView.setText("Workload = " + String.format("%6.2f", workload));
+ if (mWorkloadReceiver != null) {
+ mWorkloadReceiver.setWorkload(workload);
+ }
+ mTextView.setText(getLabel() + " = " + String.format(Locale.getDefault(), "%3d", workload));
}
@Override
@@ -106,4 +117,12 @@ public class WorkloadView extends LinearLayout {
super.setEnabled(enabled);
mSeekBar.setEnabled(enabled);
}
+
+ public String getLabel() {
+ return mLabel;
+ }
+
+ public void setLabel(String label) {
+ this.mLabel = label;
+ }
}