summaryrefslogtreecommitdiff
path: root/LoopbackApp/app/src
diff options
context:
space:
mode:
authorGlenn Kasten <gkasten@google.com>2017-02-28 16:16:14 -0800
committerGlenn Kasten <gkasten@google.com>2017-02-28 16:19:03 -0800
commit9e72ade37314f00216313b2cdcbbf5c16c5252e8 (patch)
tree42eb0f6b66ca33c60f7ab70b84f4a77cba634ed4 /LoopbackApp/app/src
parent144dd00dcb045631df54ec7c755288a1dadeddc3 (diff)
downloaddrrickorang-9e72ade37314f00216313b2cdcbbf5c16c5252e8.tar.gz
Version 17
Snap to commit fa135d12707f0454d81492ad9deb4ba74b054fc6 Enable summary to be in screen capture Loopback App crashes when performing calibration Loopback: lock in portrait mode Don't allow jitter buffer to become overfull Loopback: fix typo in test result log Add performance mode Loopback include string.h for memset Attempt to get Android.mk working again LoopbackApp: Automatically adjust the sound level
Diffstat (limited to 'LoopbackApp/app/src')
-rw-r--r--LoopbackApp/app/src/main/AndroidManifest.xml7
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java1
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java215
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java56
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java22
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java39
-rw-r--r--LoopbackApp/app/src/main/java/org/drrickorang/loopback/SoundLevelCalibration.java116
-rw-r--r--LoopbackApp/app/src/main/jni/Android.mk14
-rw-r--r--LoopbackApp/app/src/main/jni/audio_utils/fifo.c7
-rw-r--r--LoopbackApp/app/src/main/jni/audio_utils/fifo.h2
-rw-r--r--LoopbackApp/app/src/main/jni/jni_sles.c4
-rw-r--r--LoopbackApp/app/src/main/jni/jni_sles.h2
-rw-r--r--LoopbackApp/app/src/main/jni/sles.cpp66
-rw-r--r--LoopbackApp/app/src/main/jni/sles.h9
-rw-r--r--LoopbackApp/app/src/main/res/layout/main_activity.xml14
-rw-r--r--LoopbackApp/app/src/main/res/layout/settings_activity.xml67
-rw-r--r--LoopbackApp/app/src/main/res/values/strings.xml18
17 files changed, 586 insertions, 73 deletions
diff --git a/LoopbackApp/app/src/main/AndroidManifest.xml b/LoopbackApp/app/src/main/AndroidManifest.xml
index 1b2f5f3..508f37f 100644
--- a/LoopbackApp/app/src/main/AndroidManifest.xml
+++ b/LoopbackApp/app/src/main/AndroidManifest.xml
@@ -23,8 +23,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.drrickorang.loopback"
- android:versionCode="15"
- android:versionName="0.9.72">
+ android:versionCode="17"
+ android:versionName="0.9.74">
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@@ -41,7 +41,8 @@
android:name="org.drrickorang.loopback.LoopbackActivity"
android:theme="@android:style/Theme.Holo.Light"
android:configChanges="orientation|keyboardHidden|screenLayout"
- android:launchMode="singleTop">
+ android:launchMode="singleTop"
+ android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
index 988bc08..f132e3f 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/Constant.java
@@ -29,6 +29,7 @@ public class Constant {
public static final int LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY = 222;
public static final int LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD = 223;
+ public static final int LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_CALIBRATION = 224;
public static final int AUDIO_THREAD_TYPE_JAVA = 0;
public static final int AUDIO_THREAD_TYPE_NATIVE = 1;
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
index 17b3bf8..9cfa768 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackActivity.java
@@ -105,6 +105,7 @@ public class LoopbackActivity extends Activity
LoopbackAudioThread mAudioThread = null;
NativeAudioThread mNativeAudioThread = null;
+ private Thread mCalibrationThread;
private WavePlotView mWavePlotView;
private String mTestStartTimeString = "IncorrectTime"; // The time the test begins
private static final String FILE_SAVE_PATH = "file://mnt/sdcard/";
@@ -113,7 +114,6 @@ public class LoopbackActivity extends Activity
private TextView mTextInfo;
private TextView mTextViewCurrentLevel;
private TextView mTextViewResultSummary;
- private Toast mToast;
private int mTestType;
private double [] mWaveData; // this is where we store the data for the wave plot
@@ -138,6 +138,7 @@ public class LoopbackActivity extends Activity
private static final String INTENT_PLAYER_BUFFER = "PlayerBuffer";
private static final String INTENT_AUDIO_THREAD = "AudioThread";
private static final String INTENT_MIC_SOURCE = "MicSource";
+ private static final String INTENT_PERFORMANCE_MODE = "PerformanceMode";
private static final String INTENT_AUDIO_LEVEL = "AudioLevel";
private static final String INTENT_IGNORE_FIRST_FRAMES = "IgnoreFirstFrames";
private static final String INTENT_TEST_TYPE = "TestType";
@@ -155,6 +156,7 @@ public class LoopbackActivity extends Activity
// Note: these values should only be assigned in restartAudioSystem()
private int mAudioThreadType = Constant.UNKNOWN;
private int mMicSource;
+ private int mPerformanceMode;
private int mSamplingRate;
private int mChannelIndex;
private int mSoundLevel;
@@ -488,6 +490,10 @@ public class LoopbackActivity extends Activity
restoreInstanceState(savedInstanceState);
}
+ if (!hasRecordAudioPermission()) {
+ requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY);
+ }
+
applyIntent(getIntent());
}
@@ -542,6 +548,19 @@ public class LoopbackActivity extends Activity
// Note: for native mode, player and recorder buffer sizes are the same, and can only be
// set through player buffer size
+ boolean hasRecordAudioPermission = hasRecordAudioPermission();
+ boolean hasWriteFilePermission = hasWriteFilePermission();
+ if (!hasRecordAudioPermission || !hasWriteFilePermission) {
+ if (!hasRecordAudioPermission) {
+ log("Missing Permission: RECORD_AUDIO");
+ }
+
+ if (!hasWriteFilePermission) {
+ log("Missing Permission: WRITE_EXTERNAL_STORAGE");
+ }
+
+ return;
+ }
if (b.containsKey(INTENT_BUFFER_TEST_DURATION)) {
getApp().setBufferTestDuration(b.getInt(INTENT_BUFFER_TEST_DURATION));
@@ -586,6 +605,11 @@ public class LoopbackActivity extends Activity
mIntentRunning = true;
}
+ if (b.containsKey(INTENT_PERFORMANCE_MODE)) {
+ getApp().setPerformanceMode(b.getInt(INTENT_PERFORMANCE_MODE));
+ mIntentRunning = true;
+ }
+
if (b.containsKey(INTENT_IGNORE_FIRST_FRAMES)) {
getApp().setIgnoreFirstFrames(b.getInt(INTENT_IGNORE_FIRST_FRAMES));
mIntentRunning = true;
@@ -595,8 +619,10 @@ public class LoopbackActivity extends Activity
int audioLevel = b.getInt(INTENT_AUDIO_LEVEL);
if (audioLevel >= 0) {
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- am.setStreamVolume(AudioManager.STREAM_MUSIC,
- audioLevel, 0);
+ am.setStreamVolume(AudioManager.STREAM_MUSIC, audioLevel, 0);
+ getApp().setSoundLevelCalibrationEnabled(false);
+ } else { // AudioLevel of -1 means automatically calibrate
+ getApp().setSoundLevelCalibrationEnabled(true);
}
mIntentRunning = true;
}
@@ -631,12 +657,19 @@ public class LoopbackActivity extends Activity
refreshState();
// if no test is specified then Latency Test will be run
- if (b.containsKey(INTENT_TEST_TYPE)
- && b.getInt(INTENT_TEST_TYPE) ==
- Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD) {
- startBufferTest();
- } else {
- startLatencyTest();
+ int testType = b.getInt(INTENT_TEST_TYPE,
+ Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY);
+ switch (testType) {
+ case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
+ startBufferTest();
+ break;
+ case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_CALIBRATION:
+ doCalibration();
+ break;
+ case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
+ default:
+ startLatencyTest();
+ break;
}
}
@@ -763,6 +796,7 @@ public class LoopbackActivity extends Activity
mTestStartTimeString = (String) DateFormat.format("MMddkkmmss",
System.currentTimeMillis());
mMicSource = getApp().getMicSource();
+ mPerformanceMode = getApp().getPerformanceMode();
mIgnoreFirstFrames = getApp().getIgnoreFirstFrames();
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
mSoundLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC);
@@ -798,7 +832,7 @@ public class LoopbackActivity extends Activity
expectedPlayerBufferPeriod, captureHolder);
mAudioThread = new LoopbackAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
- mRecorderBufferSizeInBytes, micSourceMapped, mRecorderBufferPeriod,
+ mRecorderBufferSizeInBytes, micSourceMapped, /* no performance mode */ mRecorderBufferPeriod,
mPlayerBufferPeriod, mTestType, mBufferTestDurationInSeconds,
mBufferTestWavePlotDurationInSeconds, getApplicationContext(),
mChannelIndex, captureHolder);
@@ -808,10 +842,11 @@ public class LoopbackActivity extends Activity
break;
case Constant.AUDIO_THREAD_TYPE_NATIVE:
micSourceMapped = getApp().mapMicSource(Constant.AUDIO_THREAD_TYPE_NATIVE, mMicSource);
+ int performanceModeMapped = getApp().mapPerformanceMode(mPerformanceMode);
// Note: mRecorderBufferSizeInBytes will not actually be used, since recorder buffer
// size = player buffer size in native mode
mNativeAudioThread = new NativeAudioThread(mSamplingRate, mPlayerBufferSizeInBytes,
- mRecorderBufferSizeInBytes, micSourceMapped, mTestType,
+ mRecorderBufferSizeInBytes, micSourceMapped, performanceModeMapped, mTestType,
mBufferTestDurationInSeconds, mBufferTestWavePlotDurationInSeconds,
mIgnoreFirstFrames, captureHolder);
mNativeAudioThread.setMessageHandler(mMessageHandler);
@@ -822,7 +857,12 @@ public class LoopbackActivity extends Activity
startLoadThreads();
- refreshState();
+ mMessageHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ refreshState();
+ }
+ });
}
@@ -874,7 +914,7 @@ public class LoopbackActivity extends Activity
switch (state) {
case LATENCY_TEST_STARTED:
findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
- findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE);
+ mTextViewResultSummary.setText("");
findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
latencyStart.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_stop, 0, 0, 0);
@@ -883,7 +923,6 @@ public class LoopbackActivity extends Activity
case LATENCY_TEST_ENDED:
findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
- findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
latencyStart.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_play_arrow, 0, 0, 0);
bufferStart.setEnabled(true);
@@ -891,7 +930,7 @@ public class LoopbackActivity extends Activity
case BUFFER_TEST_STARTED:
findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.INVISIBLE);
- findViewById(R.id.resultSummary).setVisibility(View.INVISIBLE);
+ mTextViewResultSummary.setText("");
findViewById(R.id.glitchReportPanel).setVisibility(View.INVISIBLE);
bufferStart.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_stop, 0, 0, 0);
@@ -900,7 +939,6 @@ public class LoopbackActivity extends Activity
case BUFFER_TEST_ENDED:
findViewById(R.id.zoomAndSaveControlPanel).setVisibility(View.VISIBLE);
- findViewById(R.id.resultSummary).setVisibility(View.VISIBLE);
bufferStart.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_play_arrow, 0, 0, 0);
latencyStart.setEnabled(true);
@@ -909,6 +947,59 @@ public class LoopbackActivity extends Activity
}
}
+ private void doCalibrationIfEnabled(final Runnable onComplete) {
+ if (getApp().isSoundLevelCalibrationEnabled()) {
+ doCalibration(onComplete);
+ } else {
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ }
+
+ private void doCalibration() {
+ doCalibration(null);
+ }
+
+ private void doCalibration(final Runnable onComplete) {
+ if (isBusy()) {
+ showToast("Test in progress... please wait");
+ return;
+ }
+
+ if (!hasRecordAudioPermission()) {
+ requestRecordAudioPermission(PERMISSIONS_REQUEST_RECORD_AUDIO_LATENCY);
+ // Returning, otherwise we don't know if user accepted or rejected.
+ return;
+ }
+
+ showToast("Calibrating sound level...");
+ final SoundLevelCalibration calibration =
+ new SoundLevelCalibration(getApp().getSamplingRate(),
+ getApp().getPlayerBufferSizeInBytes(),
+ getApp().getRecorderBufferSizeInBytes(),
+ getApp().getMicSource(), getApp().getPerformanceMode(), this);
+
+ calibration.setChangeListener(new SoundLevelCalibration.SoundLevelChangeListener() {
+ @Override
+ void onChange(int newLevel) {
+ refreshSoundLevelBar();
+ }
+ });
+
+ mCalibrationThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ calibration.calibrate();
+ showToast("Calibration complete");
+ if (onComplete != null) {
+ onComplete.run();
+ }
+ }
+ });
+
+ mCalibrationThread.start();
+ }
/** Start the latency test. */
public void onButtonLatencyTest(View view) throws InterruptedException {
@@ -927,10 +1018,30 @@ public class LoopbackActivity extends Activity
}
private void startLatencyTest() {
+ if (isBusy()) {
+ showToast("Test in progress... please wait");
+ return;
+ }
- if (!isBusy()) {
- mBarMasterLevel.setEnabled(false);
+ doCalibrationIfEnabled(latencyTestRunnable);
+ }
+
+ private Runnable latencyTestRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (isBusy()) {
+ showToast("Test in progress... please wait");
+ return;
+ }
+
+ mBarMasterLevel.post(new Runnable() {
+ @Override
+ public void run() {
+ mBarMasterLevel.setEnabled(false);
+ }
+ });
resetBufferPeriodRecord(mRecorderBufferPeriod, mPlayerBufferPeriod);
+
mTestType = Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY;
restartAudioSystem();
try {
@@ -940,21 +1051,19 @@ public class LoopbackActivity extends Activity
}
switch (mAudioThreadType) {
- case Constant.AUDIO_THREAD_TYPE_JAVA:
- if (mAudioThread != null) {
- mAudioThread.runTest();
- }
- break;
- case Constant.AUDIO_THREAD_TYPE_NATIVE:
- if (mNativeAudioThread != null) {
- mNativeAudioThread.runTest();
- }
- break;
+ case Constant.AUDIO_THREAD_TYPE_JAVA:
+ if (mAudioThread != null) {
+ mAudioThread.runTest();
+ }
+ break;
+ case Constant.AUDIO_THREAD_TYPE_NATIVE:
+ if (mNativeAudioThread != null) {
+ mNativeAudioThread.runTest();
+ }
+ break;
}
- } else {
- showToast("Test in progress... please wait");
}
- }
+ };
/** Start the Buffer (Glitch Detection) Test. */
@@ -1024,6 +1133,22 @@ public class LoopbackActivity extends Activity
}
}
+ public void onButtonCalibrateSoundLevel(final View view) {
+ view.setEnabled(false);
+ Runnable onComplete = new Runnable() {
+ @Override
+ public void run() {
+ view.post(new Runnable() {
+ @Override
+ public void run() {
+ view.setEnabled(true);
+ }
+ });
+ }
+ };
+ doCalibration(onComplete);
+ }
+
/***
* Show dialog to choose to save files with filename dialog or not
*/
@@ -1544,6 +1669,12 @@ public class LoopbackActivity extends Activity
s.append(String.format(" Mic: %s", micSourceName));
}
+ // performance mode
+ String performanceModeName = getApp().getPerformanceModeString(mPerformanceMode);
+ if (performanceModeName != null) {
+ s.append(String.format(" Performance Mode: %s", performanceModeName));
+ }
+
// sound level at start of test
s.append(String.format(" Sound Level: %d/%d", mSoundLevel,
mBarMasterLevel.getMax()));
@@ -1584,17 +1715,15 @@ public class LoopbackActivity extends Activity
}
- public void showToast(String msg) {
- if (mToast == null) {
- mToast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG);
- } else {
- mToast.setText(msg);
- }
-
- {
- mToast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 10, 10);
- mToast.show();
- }
+ public void showToast(final String msg) {
+ // Make sure UI manipulations are only done on the UI thread
+ LoopbackActivity.this.runOnUiThread(new Runnable() {
+ public void run() {
+ Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 10, 10);
+ toast.show();
+ }
+ });
}
@@ -1827,7 +1956,7 @@ public class LoopbackActivity extends Activity
switch (mTestType) {
case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_LATENCY:
- sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames);
+ sb.append(INTENT_IGNORE_FIRST_FRAMES + " = " + mIgnoreFirstFrames + endline);
if (mCorrelation.isValid()) {
sb.append(String.format("LatencyMs = %.2f", mCorrelation.mEstimatedLatencyMs)
+ endline);
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
index a0c4b61..f38ef5f 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/LoopbackApplication.java
@@ -44,6 +44,7 @@ public class LoopbackApplication extends Application {
private int mRecorderBuffSizeInBytes = 0; // for both native and java
private int mAudioThreadType = Constant.AUDIO_THREAD_TYPE_JAVA; //0:Java, 1:Native (JNI)
private int mMicSource = 3; //maps to MediaRecorder.AudioSource.VOICE_RECOGNITION;
+ private int mPerformanceMode = -1; // DEFAULT
private int mIgnoreFirstFrames = 0;
private int mBufferTestDurationInSeconds = 5;
private int mBufferTestWavePlotDurationInSeconds = 7;
@@ -51,6 +52,7 @@ public class LoopbackApplication extends Application {
private boolean mCaptureSysTraceEnabled = false;
private boolean mCaptureBugreportEnabled = false;
private boolean mCaptureWavSnippetsEnabled = false;
+ private boolean mSoundLevelCalibrationEnabled = false;
private int mNumStateCaptures = Constant.DEFAULT_NUM_CAPTURES;
public void setDefaults() {
@@ -129,7 +131,7 @@ public class LoopbackApplication extends Application {
break;
}
} else if (threadType == Constant.AUDIO_THREAD_TYPE_NATIVE) {
- //taken form OpenSLES_AndroidConfiguration.h
+ // FIXME taken from OpenSLES_AndroidConfiguration.h
switch (source) {
default:
case 0: //DEFAULT
@@ -151,6 +153,7 @@ public class LoopbackApplication extends Application {
mappedSource = 0x00; //SL_ANDROID_RECORDING_PRESET_NONE;
break;
case 6: //UNPROCESSED
+ // FIXME should use >=
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
mappedSource = 0x05; //SL_ANDROID_RECORDING_PRESET_UNPROCESSED;
} else {
@@ -174,9 +177,50 @@ public class LoopbackApplication extends Application {
return name;
}
-
void setMicSource(int micSource) { mMicSource = micSource; }
+ int mapPerformanceMode(int performanceMode) {
+ int mappedPerformanceMode = -1;
+
+ // FIXME taken from OpenSLES_AndroidConfiguration.h
+ switch (performanceMode) {
+ case 0: // NONE
+ case 1: // LATENCY
+ case 2: // LATENCY_EFFECTS
+ case 3: // POWER_SAVING
+ // FIXME should use >=
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
+ mappedPerformanceMode = performanceMode;
+ break;
+ }
+ // fall through
+ case -1:
+ default:
+ mappedPerformanceMode = -1;
+ break;
+ }
+
+ return mappedPerformanceMode;
+ }
+
+
+ int getPerformanceMode() {
+ return mPerformanceMode;
+ }
+
+ String getPerformanceModeString(int performanceMode) {
+ String name = null;
+ String[] myArray = getResources().getStringArray(R.array.performance_mode_array);
+
+ if (myArray != null && performanceMode >= -1 && performanceMode < myArray.length - 1) {
+ name = myArray[performanceMode + 1];
+ }
+ return name;
+ }
+
+
+ void setPerformanceMode(int performanceMode) { mPerformanceMode = performanceMode; }
+
int getIgnoreFirstFrames() {
return mIgnoreFirstFrames;
}
@@ -255,6 +299,10 @@ public class LoopbackApplication extends Application {
mCaptureWavSnippetsEnabled = enabled;
}
+ public void setSoundLevelCalibrationEnabled(boolean enabled) {
+ mSoundLevelCalibrationEnabled = enabled;
+ }
+
public boolean isCaptureEnabled() {
return isCaptureSysTraceEnabled() || isCaptureBugreportEnabled();
}
@@ -267,6 +315,10 @@ public class LoopbackApplication extends Application {
return mCaptureBugreportEnabled;
}
+ public boolean isSoundLevelCalibrationEnabled() {
+ return mSoundLevelCalibrationEnabled;
+ }
+
public int getNumStateCaptures() {
return mNumStateCaptures;
}
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
index 1bb2ae6..5a6735f 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/NativeAudioThread.java
@@ -55,6 +55,7 @@ public class NativeAudioThread extends Thread {
private int mMinPlayerBufferSizeInBytes = 0;
private int mMinRecorderBuffSizeInBytes = 0; // currently not used
private int mMicSource;
+ private int mPerformanceMode = -1;
private int mIgnoreFirstFrames;
private boolean mIsRequestStop = false;
@@ -86,13 +87,14 @@ public class NativeAudioThread extends Thread {
public NativeAudioThread(int samplingRate, int playerBufferInBytes, int recorderBufferInBytes,
- int micSource, int testType, int bufferTestDurationInSeconds,
+ int micSource, int performanceMode, int testType, int bufferTestDurationInSeconds,
int bufferTestWavePlotDurationInSeconds, int ignoreFirstFrames,
CaptureHolder captureHolder) {
mSamplingRate = samplingRate;
mMinPlayerBufferSizeInBytes = playerBufferInBytes;
mMinRecorderBuffSizeInBytes = recorderBufferInBytes;
mMicSource = micSource;
+ mPerformanceMode = performanceMode;
mTestType = testType;
mBufferTestDurationInSeconds = bufferTestDurationInSeconds;
mBufferTestWavePlotDurationInSeconds = bufferTestWavePlotDurationInSeconds;
@@ -101,6 +103,19 @@ public class NativeAudioThread extends Thread {
setName("Loopback_NativeAudio");
}
+ public NativeAudioThread(NativeAudioThread old) {
+ mSamplingRate = old.mSamplingRate;
+ mMinPlayerBufferSizeInBytes = old.mMinPlayerBufferSizeInBytes;
+ mMinRecorderBuffSizeInBytes = old.mMinRecorderBuffSizeInBytes;
+ mMicSource = old.mMicSource;
+ mPerformanceMode = old.mPerformanceMode;
+ mTestType = old.mTestType;
+ mBufferTestDurationInSeconds = old.mBufferTestDurationInSeconds;
+ mBufferTestWavePlotDurationInSeconds = old.mBufferTestWavePlotDurationInSeconds;
+ mIgnoreFirstFrames = old.mIgnoreFirstFrames;
+ mCaptureHolder = old.mCaptureHolder;
+ setName("Loopback_NativeAudio");
+ }
//JNI load
static {
@@ -116,6 +131,7 @@ public class NativeAudioThread extends Thread {
//jni calls
public native long slesInit(int samplingRate, int frameCount, int micSource,
+ int performanceMode,
int testType, double frequency1, ByteBuffer byteBuffer,
short[] sincTone, int maxRecordedLateCallbacks,
int ignoreFirstFrames);
@@ -173,7 +189,8 @@ public class NativeAudioThread extends Thread {
mPipeByteBuffer = new PipeByteBuffer(Constant.MAX_SHORTS);
long startTimeMs = System.currentTimeMillis();
long sles_data = slesInit(mSamplingRate,
- mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME, mMicSource, mTestType,
+ mMinPlayerBufferSizeInBytes / Constant.BYTES_PER_FRAME, mMicSource,
+ mPerformanceMode, mTestType,
mFrequency1, mPipeByteBuffer.getByteBuffer(), loopbackTone,
mBufferTestDurationInSeconds * Constant.MAX_RECORDED_LATE_CALLBACKS_PER_SECOND,
mIgnoreFirstFrames);
@@ -229,7 +246,6 @@ public class NativeAudioThread extends Thread {
log("about to destroy...");
break;
case Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD:
- //TODO adjust sound level to appropriate level before doing native buffer test
setUpGlitchDetectionThread();
long testDurationMs = mBufferTestDurationInSeconds * Constant.MILLIS_PER_SECOND;
long elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
index 9224abf..1167a25 100644
--- a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SettingsActivity.java
@@ -43,6 +43,7 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
private static final String TAG = "SettingsActivity";
private Spinner mSpinnerMicSource;
+ private Spinner mSpinnerPerformanceMode;
private Spinner mSpinnerSamplingRate;
private Spinner mSpinnerAudioThreadType;
private TextView mTextSettingsInfo;
@@ -57,6 +58,7 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
private ToggleButton mSystraceToggleButton;
private ToggleButton mBugreportToggleButton;
private ToggleButton mWavCaptureToggleButton;
+ private ToggleButton mSoundLevelCalibrationToggleButton;
ArrayAdapter<CharSequence> mAdapterSamplingRate;
@@ -81,6 +83,18 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
mSpinnerMicSource.setSelection(micSource, false);
mSpinnerMicSource.setOnItemSelectedListener(this);
+ int performanceMode = getApp().getPerformanceMode();
+ mSpinnerPerformanceMode = (Spinner) findViewById(R.id.spinnerPerformanceMode);
+ ArrayAdapter<CharSequence> adapterPerformanceMode = ArrayAdapter.createFromResource(this,
+ R.array.performance_mode_array, android.R.layout.simple_spinner_item);
+ // Specify the layout to use when the list of choices appears
+ adapterPerformanceMode.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ // Apply the adapter to the spinner
+ mSpinnerPerformanceMode.setAdapter(adapterPerformanceMode);
+ //set current value
+ mSpinnerPerformanceMode.setSelection(performanceMode + 1, false);
+ mSpinnerPerformanceMode.setOnItemSelectedListener(this);
+
int samplingRate = getApp().getSamplingRate();
//init spinner, etc
mSpinnerSamplingRate = (Spinner) findViewById(R.id.spinnerSamplingRate);
@@ -230,6 +244,11 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
mSystraceToggleButton.setChecked(getApp().isCaptureSysTraceEnabled());
mSystraceToggleButton.setOnCheckedChangeListener(this);
+ mSoundLevelCalibrationToggleButton = (ToggleButton)
+ findViewById(R.id.soundLevelCalibrationEnabledToggle);
+ mSoundLevelCalibrationToggleButton.setChecked(getApp().isSoundLevelCalibrationEnabled());
+ mSoundLevelCalibrationToggleButton.setOnCheckedChangeListener(this);
+
// Settings Picker for number of frames to ignore at the beginning
mIgnoreFirstFramesUI = (SettingsPicker) findViewById(R.id.ignoreFirstFramesSettingPicker);
mIgnoreFirstFramesUI.setMinMaxDefault(Constant.MIN_IGNORE_FIRST_FRAMES,
@@ -331,6 +350,13 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
log("mic Source:" + micSource);
refresh();
break;
+ case R.id.spinnerPerformanceMode:
+ int performanceMode = mSpinnerPerformanceMode.getSelectedItemPosition() - 1;
+ getApp().setPerformanceMode(performanceMode);
+ setSettingsHaveChanged();
+ log("performanceMode:" + performanceMode);
+ refresh();
+ break;
}
}
@@ -342,6 +368,8 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
getApp().setCaptureSysTraceEnabled(isChecked);
} else if (buttonView.getId() == mBugreportToggleButton.getId()) {
getApp().setCaptureBugreportEnabled(isChecked);
+ } else if (buttonView.getId() == mSoundLevelCalibrationToggleButton.getId()) {
+ getApp().setSoundLevelCalibrationEnabled(isChecked);
}
mNumCapturesUI.setEnabled(getApp().isCaptureEnabled() ||
getApp().isCaptureWavSnippetsEnabled());
@@ -357,22 +385,25 @@ public class SettingsActivity extends Activity implements OnItemSelectedListener
// Another interface callback
}
- public void onButtonSysTraceHelp(View view) {
+ public void onButtonHelp(View view) {
// Create a PopUpWindow with scrollable TextView
View puLayout = this.getLayoutInflater().inflate(R.layout.report_window, null);
PopupWindow popUp = new PopupWindow(puLayout, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT, true);
- // Generate report of glitch intervals and set pop up window text
TextView helpText =
(TextView) popUp.getContentView().findViewById(R.id.ReportInfo);
- helpText.setText(getResources().getString(R.string.systraceHelp));
+ if (view.getId() == R.id.buttonSystraceHelp || view.getId() == R.id.buttonBugreportHelp) {
+ helpText.setText(getResources().getString(R.string.systraceHelp));
+ } else if (view.getId() == R.id.buttonCalibrateSoundLevelHelp) {
+ helpText.setText(getResources().getString(R.string.calibrateSoundLevelHelp));
+ }
// display pop up window, dismissible with back button
popUp.showAtLocation(findViewById(R.id.settingsMainLayout), Gravity.TOP, 0, 0);
}
- /** Called when the user clicks the button */
+ /** Called when the user clicks the button */
public void onButtonClick(View view) {
getApp().computeDefaults();
refresh();
diff --git a/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SoundLevelCalibration.java b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SoundLevelCalibration.java
new file mode 100644
index 0000000..eca9a82
--- /dev/null
+++ b/LoopbackApp/app/src/main/java/org/drrickorang/loopback/SoundLevelCalibration.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 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 org.drrickorang.loopback;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+class SoundLevelCalibration {
+ private static final int SECONDS_PER_LEVEL = 1;
+ private static final int MAX_STEPS = 15; // The maximum number of levels that should be tried
+ private static final double CRITICAL_RATIO = 0.4; // Ratio of input over output amplitude at
+ // which the feedback loop neither decays nor
+ // grows (determined experimentally)
+ private static final String TAG = "SoundLevelCalibration";
+
+ private NativeAudioThread mNativeAudioThread = null;
+ private AudioManager mAudioManager;
+
+ private SoundLevelChangeListener mChangeListener;
+
+ abstract static class SoundLevelChangeListener {
+ // used to run the callback on the UI thread
+ private Handler handler = new Handler(Looper.getMainLooper());
+
+ abstract void onChange(int newLevel);
+
+ private void go(final int newLevel) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ onChange(newLevel);
+ }
+ });
+ }
+ }
+
+ SoundLevelCalibration(int samplingRate, int playerBufferSizeInBytes,
+ int recorderBufferSizeInBytes, int micSource, int performanceMode, Context context) {
+
+ // TODO: Allow capturing wave data without doing glitch detection.
+ CaptureHolder captureHolder = new CaptureHolder(0, "", false, false, false, context,
+ samplingRate);
+ // TODO: Run for less than 1 second.
+ mNativeAudioThread = new NativeAudioThread(samplingRate, playerBufferSizeInBytes,
+ recorderBufferSizeInBytes, micSource, performanceMode,
+ Constant.LOOPBACK_PLUG_AUDIO_THREAD_TEST_TYPE_BUFFER_PERIOD, SECONDS_PER_LEVEL,
+ SECONDS_PER_LEVEL, 0, captureHolder);
+ mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ // TODO: Allow stopping in the middle of calibration
+ int calibrate() {
+ final int maxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int delta = (maxLevel + MAX_STEPS - 1) / MAX_STEPS; // round up
+ int level;
+ // TODO: Use a better algorithm such as binary search.
+ for(level = maxLevel; level >= 0; level -= delta) {
+ mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, level, 0);
+ if (mChangeListener != null) {
+ mChangeListener.go(level);
+ }
+
+ mNativeAudioThread.start();
+ try {
+ mNativeAudioThread.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ double[] data = mNativeAudioThread.getWaveData();
+ mNativeAudioThread = new NativeAudioThread(mNativeAudioThread); // generate fresh thread
+ double amplitude = averageAmplitude(data);
+ Log.d(TAG, "calibrate: at sound level " + level + " volume was " + amplitude);
+
+ if (amplitude < Constant.SINE_WAVE_AMPLITUDE * CRITICAL_RATIO) {
+ Log.d(TAG, "calibrate: chose sound level " + level);
+ break;
+ }
+ }
+
+ // Return the maximum level if we can't find a proper one
+ return level != 0 ? level : maxLevel;
+ }
+
+ // TODO: Only gives accurate results for an undistorted sine wave. Check for distortion.
+ private static double averageAmplitude(double[] data) {
+ if (data == null || data.length == 0) {
+ return 0; // no data is present
+ }
+ double sumSquare = 0;
+ for (double x : data) {
+ sumSquare += x * x;
+ }
+ return Math.sqrt(2.0 * sumSquare / data.length); // amplitude of the sine wave
+ }
+
+ void setChangeListener(SoundLevelChangeListener changeListener) {
+ mChangeListener = changeListener;
+ }
+}
diff --git a/LoopbackApp/app/src/main/jni/Android.mk b/LoopbackApp/app/src/main/jni/Android.mk
index ec402fb..ef0c829 100644
--- a/LoopbackApp/app/src/main/jni/Android.mk
+++ b/LoopbackApp/app/src/main/jni/Android.mk
@@ -14,14 +14,14 @@ LOCAL_C_INCLUDES := \
frameworks/wilhelm/include
LOCAL_SHARED_LIBRARIES := \
- libutils \
- libcutils \
libOpenSLES \
- libnbaio \
- liblog
-LOCAL_PRELINK_MODULE := false
+ liblog \
+ libandroid
-LOCAL_LDFLAGS := -Wl,--hash-style=sysv
-LOCAL_CFLAGS := -DSTDC_HEADERS
+LOCAL_LDLIBS += -lOpenSLES -llog -landroid
+#LOCAL_PRELINK_MODULE := false
+
+#LOCAL_LDFLAGS += -Wl,--hash-style=sysv
+#LOCAL_CFLAGS := -DSTDC_HEADERS
include $(BUILD_SHARED_LIBRARY)
diff --git a/LoopbackApp/app/src/main/jni/audio_utils/fifo.c b/LoopbackApp/app/src/main/jni/audio_utils/fifo.c
index 2f00a7f..e00fc28 100644
--- a/LoopbackApp/app/src/main/jni/audio_utils/fifo.c
+++ b/LoopbackApp/app/src/main/jni/audio_utils/fifo.c
@@ -145,3 +145,10 @@ ssize_t audio_utils_fifo_read(struct audio_utils_fifo *fifo, void *buffer, size_
}
return availToRead;
}
+
+size_t audio_utils_fifo_availToRead(struct audio_utils_fifo *fifo) {
+ int32_t rear = android_atomic_acquire_load(&fifo->mRear);
+ int32_t front = fifo->mFront;
+ size_t availToRead = audio_utils_fifo_diff(fifo, rear, front);
+ return availToRead;
+}
diff --git a/LoopbackApp/app/src/main/jni/audio_utils/fifo.h b/LoopbackApp/app/src/main/jni/audio_utils/fifo.h
index ba4c5c6..37a9df8 100644
--- a/LoopbackApp/app/src/main/jni/audio_utils/fifo.h
+++ b/LoopbackApp/app/src/main/jni/audio_utils/fifo.h
@@ -79,6 +79,8 @@ ssize_t audio_utils_fifo_write(struct audio_utils_fifo *fifo, const void *buffer
// A negative return value indicates an error. Currently there are no errors defined.
ssize_t audio_utils_fifo_read(struct audio_utils_fifo *fifo, void *buffer, size_t count);
+size_t audio_utils_fifo_availToRead(struct audio_utils_fifo *fifo);
+
#ifdef __cplusplus
}
#endif
diff --git a/LoopbackApp/app/src/main/jni/jni_sles.c b/LoopbackApp/app/src/main/jni/jni_sles.c
index f630086..0417252 100644
--- a/LoopbackApp/app/src/main/jni/jni_sles.c
+++ b/LoopbackApp/app/src/main/jni/jni_sles.c
@@ -22,6 +22,7 @@
JNIEXPORT jlong JNICALL Java_org_drrickorang_loopback_NativeAudioThread_slesInit
(JNIEnv *env, jobject obj __unused, jint samplingRate, jint frameCount, jint micSource,
+ jint performanceMode,
jint testType, jdouble frequency1, jobject byteBuffer, jshortArray loopbackTone,
jint maxRecordedLateCallbacks, jint ignoreFirstFrames) {
@@ -33,6 +34,7 @@ JNIEXPORT jlong JNICALL Java_org_drrickorang_loopback_NativeAudioThread_slesInit
short* loopbackToneArray = (*env)->GetShortArrayElements(env, loopbackTone, 0);
if (slesInit(&pSles, samplingRate, frameCount, micSource,
+ performanceMode,
testType, frequency1, byteBufferPtr, byteBufferLength,
loopbackToneArray, maxRecordedLateCallbacks, ignoreFirstFrames) != SLES_FAIL) {
return (long) pSles;
@@ -173,4 +175,4 @@ JNICALL Java_org_drrickorang_loopback_NativeAudioThread_slesGetCaptureRank
(JNIEnv *env __unused, jobject obj __unused, jlong sles) {
sles_data * pSles = (sles_data*) (size_t) sles;
return slesGetCaptureRank(pSles);
-} \ No newline at end of file
+}
diff --git a/LoopbackApp/app/src/main/jni/jni_sles.h b/LoopbackApp/app/src/main/jni/jni_sles.h
index ee0f74d..f25bd52 100644
--- a/LoopbackApp/app/src/main/jni/jni_sles.h
+++ b/LoopbackApp/app/src/main/jni/jni_sles.h
@@ -26,7 +26,7 @@ extern "C" {
////////////////////////
////SLE
JNIEXPORT jlong JNICALL Java_org_drrickorang_loopback_NativeAudioThread_slesInit
- (JNIEnv *, jobject, jint, jint, jint, jint, jdouble, jobject byteBuffer,
+ (JNIEnv *, jobject, jint, jint, jint, jint, jint, jdouble, jobject byteBuffer,
jshortArray loopbackTone, jint maxRecordedLateCallbacks, jint ignoreFirstFrames);
JNIEXPORT jint JNICALL Java_org_drrickorang_loopback_NativeAudioThread_slesProcessNext
diff --git a/LoopbackApp/app/src/main/jni/sles.cpp b/LoopbackApp/app/src/main/jni/sles.cpp
index b49e3c8..36a096c 100644
--- a/LoopbackApp/app/src/main/jni/sles.cpp
+++ b/LoopbackApp/app/src/main/jni/sles.cpp
@@ -15,6 +15,8 @@
*/
+// FIXME taken from OpenSLES_AndroidConfiguration.h
+#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode")
////////////////////////////////////////////
/// Actual sles functions.
@@ -31,8 +33,10 @@
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
+#include <string.h>
int slesInit(sles_data ** ppSles, int samplingRate, int frameCount, int micSource,
+ int performanceMode,
int testType, double frequency1, char* byteBufferPtr, int byteBufferLength,
short* loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames) {
int status = SLES_FAIL;
@@ -49,7 +53,8 @@ int slesInit(sles_data ** ppSles, int samplingRate, int frameCount, int micSourc
{
SLES_PRINTF("creating server. Sampling rate =%d, frame count = %d",
samplingRate, frameCount);
- status = slesCreateServer(pSles, samplingRate, frameCount, micSource, testType,
+ status = slesCreateServer(pSles, samplingRate, frameCount, micSource,
+ performanceMode, testType,
frequency1, byteBufferPtr, byteBufferLength, loopbackTone,
maxRecordedLateCallbacks, ignoreFirstFrames);
SLES_PRINTF("slesCreateServer =%d", status);
@@ -230,6 +235,32 @@ static void playerCallback(SLBufferQueueItf caller __unused, void *context) {
}
if (pSles->testType == TEST_TYPE_LATENCY) {
+ // Jitter buffer should have strictly less than 2 buffers worth of data in it.
+ // This is to prevent the test itself from adding too much latency.
+ size_t discardedInputFrames = 0;
+ for (;;) {
+ size_t availToRead = audio_utils_fifo_availToRead(&pSles->fifo);
+ if (availToRead < pSles->bufSizeInFrames * 2) {
+ break;
+ }
+ ssize_t actual = audio_utils_fifo_read(&pSles->fifo, buffer, pSles->bufSizeInFrames);
+ if (actual > 0) {
+ discardedInputFrames += actual;
+ }
+ if (actual != (ssize_t) pSles->bufSizeInFrames) {
+ break;
+ }
+ }
+ if (discardedInputFrames > 0) {
+ if (pSles->totalDiscardedInputFrames > 0) {
+ __android_log_print(ANDROID_LOG_WARN, "sles_jni",
+ "Discarded an additional %zu input frames after a total of %zu input frames"
+ " had previously been discarded",
+ discardedInputFrames, pSles->totalDiscardedInputFrames);
+ }
+ pSles->totalDiscardedInputFrames += discardedInputFrames;
+ }
+
ssize_t actual = audio_utils_fifo_read(&(pSles->fifo), buffer, pSles->bufSizeInFrames);
if (actual != (ssize_t) pSles->bufSizeInFrames) {
write(1, "/", 1);
@@ -260,6 +291,7 @@ static void playerCallback(SLBufferQueueItf caller __unused, void *context) {
}
pSles->injectImpulse = 0;
+ pSles->totalDiscardedInputFrames = 0;
}
} else if (pSles->testType == TEST_TYPE_BUFFER_PERIOD) {
double twoPi = M_PI * 2;
@@ -417,6 +449,7 @@ bool updateBufferStats(bufferStats *stats, int64_t diff_in_nano, int expectedBuf
}
int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int micSource,
+ int performanceMode,
int testType, double frequency1, char *byteBufferPtr, int byteBufferLength,
short *loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames) {
int status = SLES_FAIL;
@@ -470,6 +503,7 @@ int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int mic
pSles->freeBufCount = 0; // calculated
pSles->bufSizeInBytes = 0; // calculated
pSles->injectImpulse = 300; // -i#i
+ pSles->totalDiscardedInputFrames = 0;
pSles->ignoreFirstFrames = ignoreFirstFrames;
// Storage area for the buffer queues
@@ -622,10 +656,10 @@ int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int mic
audiosnk.pFormat = NULL;
pSles->playerObject = NULL;
pSles->recorderObject = NULL;
- SLInterfaceID ids_tx[1] = {SL_IID_BUFFERQUEUE};
- SLboolean flags_tx[1] = {SL_BOOLEAN_TRUE};
+ SLInterfaceID ids_tx[2] = {SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION};
+ SLboolean flags_tx[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &(pSles->playerObject),
- &audiosrc, &audiosnk, 1, ids_tx, flags_tx);
+ &audiosrc, &audiosnk, 2, ids_tx, flags_tx);
if (SL_RESULT_CONTENT_UNSUPPORTED == result) {
fprintf(stderr, "Could not create audio player (result %x), check sample rate\n",
result);
@@ -634,6 +668,24 @@ int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int mic
goto cleanup;
}
ASSERT_EQ(SL_RESULT_SUCCESS, result);
+
+ {
+ /* Get the Android configuration interface which is explicit */
+ SLAndroidConfigurationItf configItf;
+ result = (*(pSles->playerObject))->GetInterface(pSles->playerObject,
+ SL_IID_ANDROIDCONFIGURATION, (void*)&configItf);
+ ASSERT_EQ(SL_RESULT_SUCCESS, result);
+
+ /* Use the configuration interface to configure the player before it's realized */
+ if (performanceMode != -1) {
+ SLuint32 performanceMode32 = performanceMode;
+ result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode32, sizeof(SLuint32));
+ ASSERT_EQ(SL_RESULT_SUCCESS, result);
+ }
+
+ }
+
result = (*(pSles->playerObject))->Realize(pSles->playerObject, SL_BOOLEAN_FALSE);
ASSERT_EQ(SL_RESULT_SUCCESS, result);
SLPlayItf playerPlay;
@@ -726,6 +778,12 @@ int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int mic
&presetValue, sizeof(SLuint32));
ASSERT_EQ(SL_RESULT_SUCCESS, result);
}
+ if (performanceMode != -1) {
+ SLuint32 performanceMode32 = performanceMode;
+ result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
+ &performanceMode32, sizeof(SLuint32));
+ ASSERT_EQ(SL_RESULT_SUCCESS, result);
+ }
}
diff --git a/LoopbackApp/app/src/main/jni/sles.h b/LoopbackApp/app/src/main/jni/sles.h
index b3b7e0e..c176656 100644
--- a/LoopbackApp/app/src/main/jni/sles.h
+++ b/LoopbackApp/app/src/main/jni/sles.h
@@ -67,6 +67,7 @@ typedef struct {
SLuint32 freeBufCount; // calculated
SLuint32 bufSizeInBytes; // calculated
int injectImpulse; // -i#i
+ size_t totalDiscardedInputFrames; // total number of input frames discarded
int ignoreFirstFrames;
// Storage area for the buffer queues
@@ -82,8 +83,10 @@ typedef struct {
SLuint32 freeFront; // oldest free
SLuint32 freeRear; // next to be freed
- struct audio_utils_fifo fifo; //(*)
- struct audio_utils_fifo fifo2;
+ struct audio_utils_fifo fifo; // jitter buffer between recorder and player callbacks,
+ // to mitigate unpredictable phase difference between these,
+ // or even concurrent callbacks on two CPU cores
+ struct audio_utils_fifo fifo2; // For sending data to java code (to plot it)
short *fifo2Buffer;
short *fifoBuffer;
SLAndroidSimpleBufferQueueItf recorderBufferQueue;
@@ -131,6 +134,7 @@ enum {
} SLES_STATUS_ENUM;
int slesInit(sles_data ** ppSles, int samplingRate, int frameCount, int micSource,
+ int performanceMode,
int testType, double frequency1, char* byteBufferPtr, int byteBufferLength,
short* loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames);
@@ -142,6 +146,7 @@ int slesDestroy(sles_data ** ppSles);
int slesFull(sles_data *pSles);
int slesCreateServer(sles_data *pSles, int samplingRate, int frameCount, int micSource,
+ int performanceMode,
int testType, double frequency1, char* byteBufferPtr, int byteBufferLength,
short* loopbackTone, int maxRecordedLateCallbacks, int ignoreFirstFrames);
int slesProcessNext(sles_data *pSles, double *pSamples, long maxSamples);
diff --git a/LoopbackApp/app/src/main/res/layout/main_activity.xml b/LoopbackApp/app/src/main/res/layout/main_activity.xml
index 50df2f7..072afde 100644
--- a/LoopbackApp/app/src/main/res/layout/main_activity.xml
+++ b/LoopbackApp/app/src/main/res/layout/main_activity.xml
@@ -40,6 +40,7 @@
android:layout_height="wrap_content"
android:text="@string/buttonTest_enabled"
android:drawableLeft="@drawable/ic_play_arrow"
+ android:drawableStart="@drawable/ic_play_arrow"
style="@style/TextAppearance.AppCompat.Button"
android:onClick="onButtonLatencyTest"/>
@@ -50,12 +51,24 @@
android:layout_height="wrap_content"
android:text="@string/buttonBufferTest"
android:drawableLeft="@drawable/ic_play_arrow"
+ android:drawableStart="@drawable/ic_play_arrow"
style="@style/TextAppearance.AppCompat.Button"
android:onClick="onButtonBufferTest"/>
</LinearLayout>
</HorizontalScrollView>
+ <Button
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/buttonCalibrateSoundLevel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/buttonCalibrateSoundLevel"
+ android:drawableLeft="@drawable/ic_play_arrow"
+ android:drawableStart="@drawable/ic_play_arrow"
+ style="@style/TextAppearance.AppCompat.Button"
+ android:onClick="onButtonCalibrateSoundLevel" />
+
<TextView
android:id="@+id/textInfo"
android:layout_width="wrap_content"
@@ -159,7 +172,6 @@
android:layout_height="wrap_content"
android:text=""
android:id="@+id/resultSummary"
- android:visibility="invisible"
android:textStyle="bold"/>
</LinearLayout>
diff --git a/LoopbackApp/app/src/main/res/layout/settings_activity.xml b/LoopbackApp/app/src/main/res/layout/settings_activity.xml
index 1ffbf84..8e9986a 100644
--- a/LoopbackApp/app/src/main/res/layout/settings_activity.xml
+++ b/LoopbackApp/app/src/main/res/layout/settings_activity.xml
@@ -60,6 +60,21 @@
android:background="@android:color/darker_gray"/>
<TextView
+ android:id="@+id/textPerformanceMode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/labelPerformanceMode"/>
+ <Spinner
+ android:id="@+id/spinnerPerformanceMode"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"/>
+
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray"/>
+
+ <TextView
android:id="@+id/textAudioThreadType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -202,7 +217,8 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:onClick="onButtonSysTraceHelp"
+ android:onClick="onButtonHelp"
+ android:id="@+id/buttonSystraceHelp"
android:src="@drawable/ic_help_outline"/>
</RelativeLayout>
</LinearLayout>
@@ -247,7 +263,8 @@
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:onClick="onButtonSysTraceHelp"
+ android:onClick="onButtonHelp"
+ android:id="@+id/buttonBugreportHelp"
android:src="@drawable/ic_help_outline"/>
</RelativeLayout>
</LinearLayout>
@@ -313,6 +330,52 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
+ <View
+ android:layout_width="fill_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray"/>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:padding="15dp">
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3">
+ <ToggleButton
+ android:id="@+id/soundLevelCalibrationEnabledToggle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginRight="15dp"
+ android:background="@drawable/togglebutton_state_drawable"
+ android:textOn="Enabled"
+ android:textOff="Disabled"/>
+ </RelativeLayout>
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="6">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/enableSoundLevelCalibration"/>
+ </RelativeLayout>
+ <RelativeLayout
+ android:layout_width="0dip"
+ android:layout_height="match_parent"
+ android:layout_weight="1">
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:onClick="onButtonHelp"
+ android:id="@+id/buttonCalibrateSoundLevelHelp"
+ android:src="@drawable/ic_help_outline"/>
+ </RelativeLayout>
+ </LinearLayout>
+
</LinearLayout>
</ScrollView>
</LinearLayout>
diff --git a/LoopbackApp/app/src/main/res/values/strings.xml b/LoopbackApp/app/src/main/res/values/strings.xml
index 4c84d76..bb9d69f 100644
--- a/LoopbackApp/app/src/main/res/values/strings.xml
+++ b/LoopbackApp/app/src/main/res/values/strings.xml
@@ -32,6 +32,7 @@
<string name="ReadHistTitle">Frequency vs. Recorder Buffer Period (ms) Plot</string>
<string name="WriteHistTitle">Frequency vs. Player Buffer Period (ms) Plot</string>
<string name="buttonBufferTest">Buffer Period\n&amp; Glitch Test</string>
+ <string name="buttonCalibrateSoundLevel">Calibrate Sound Level Now</string>
<string name="buttonGlitches">Glitches</string>
<string name="numGlitches">Total Number of Glitches:</string>
@@ -52,6 +53,15 @@
<item>UNPROCESSED (N or later)</item>
</string-array>
+ <string name="labelPerformanceMode">Performance Mode</string>
+ <string-array name="performance_mode_array">
+ <item>DEFAULT</item>
+ <item>NONE</item>
+ <item>LATENCY</item>
+ <item>LATENCY_EFFECTS</item>
+ <item>POWER_SAVING</item>
+ </string-array>
+
<string name="labelInfo">Test settings will appear here after the first test is run</string>
<string name="labelSettings">SETTINGS</string>
<string name="labelAbout">About</string>
@@ -139,6 +149,8 @@
<string name="enableSystrace">Systrace Captures During Test</string>
<string name="enableBugreport">BugReport Captures During Test</string>
<string name="enableWavSnippets">Wav Snippet Captures During Test</string>
+ <string name="enableSoundLevelCalibration">
+ Calibrate sound level before latency test (experimental)</string>
<string name="numCapturesSetting">Number of Systrace/BugReport and or Wav Snippets to Capture
</string>
<string name="labelIgnoreFirstFrames">
@@ -172,4 +184,10 @@
Example invocation:\n
adb shell \"sh /sdcard/Loopback/loopback_listener load am\"
</string>
+
+ <string name="calibrateSoundLevelHelp">
+ This feature is highly experimental. It will try various volume levels until it picks one
+ that it thinks is optimal. It has only been tested with the loopback plug and may fail
+ completely in open air.
+ </string>
</resources>