diff options
author | Glenn Kasten <gkasten@google.com> | 2017-02-28 16:16:14 -0800 |
---|---|---|
committer | Glenn Kasten <gkasten@google.com> | 2017-02-28 16:19:03 -0800 |
commit | 9e72ade37314f00216313b2cdcbbf5c16c5252e8 (patch) | |
tree | 42eb0f6b66ca33c60f7ab70b84f4a77cba634ed4 /LoopbackApp/app | |
parent | 144dd00dcb045631df54ec7c755288a1dadeddc3 (diff) | |
download | drrickorang-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')
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& 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> |