1 /* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.sample.oboe.manualtest; 18 19 import android.os.Bundle; 20 import android.os.Handler; 21 import android.os.Looper; 22 import android.view.View; 23 import android.widget.Button; 24 import android.widget.TextView; 25 26 import java.io.IOException; 27 28 /** 29 * Activity to measure the number of glitches. 30 */ 31 public class GlitchActivity extends AnalyzerActivity { 32 private TextView mAnalyzerTextView; 33 private Button mStartButton; 34 private Button mStopButton; 35 private Button mShareButton; 36 37 // These must match the values in LatencyAnalyzer.h 38 final static int STATE_IDLE = 0; 39 final static int STATE_IMMUNE = 1; 40 final static int STATE_WAITING_FOR_SIGNAL = 2; 41 final static int STATE_WAITING_FOR_LOCK = 3; 42 final static int STATE_LOCKED = 4; 43 final static int STATE_GLITCHING = 5; 44 String mLastGlitchReport; 45 getStateFrameCount(int state)46 native int getStateFrameCount(int state); getGlitchCount()47 native int getGlitchCount(); getSignalToNoiseDB()48 native double getSignalToNoiseDB(); getPeakAmplitude()49 native double getPeakAmplitude(); 50 51 // Note that these strings must match the enum result_code in LatencyAnalyzer.h stateToString(int resultCode)52 String stateToString(int resultCode) { 53 switch (resultCode) { 54 case STATE_IDLE: 55 return "IDLE"; 56 case STATE_IMMUNE: 57 return "IMMUNE"; 58 case STATE_WAITING_FOR_SIGNAL: 59 return "WAITING_FOR_SIGNAL"; 60 case STATE_WAITING_FOR_LOCK: 61 return "WAITING_FOR_LOCK"; 62 case STATE_LOCKED: 63 return "LOCKED"; 64 case STATE_GLITCHING: 65 return "GLITCHING"; 66 default: 67 return "UNKNOWN"; 68 } 69 } 70 71 // Periodically query for glitches from the native detector. 72 protected class GlitchSniffer { 73 public static final int SNIFFER_UPDATE_PERIOD_MSEC = 100; 74 public static final int SNIFFER_UPDATE_DELAY_MSEC = 200; 75 76 private long mTimeAtStart; 77 private long mTimeOfLastGlitch; 78 private double mSecondsWithoutGlitches; 79 private double mMaxSecondsWithoutGlitches; 80 private int mLastGlitchCount; 81 private int mLastUnlockedFrames; 82 private int mLastLockedFrames; 83 private int mLastGlitchFrames; 84 85 private int mStartResetCount; 86 private int mLastResetCount; 87 private int mPreviousState; 88 89 private double mSignalToNoiseDB; 90 private double mPeakAmplitude; 91 private Handler mHandler = new Handler(Looper.getMainLooper()); // UI thread 92 private volatile boolean mEnabled = true; 93 startSniffer()94 private void startSniffer() { 95 long now = System.currentTimeMillis(); 96 mTimeAtStart = now; 97 mTimeOfLastGlitch = now; 98 mLastUnlockedFrames = 0; 99 mLastLockedFrames = 0; 100 mLastGlitchFrames = 0; 101 mSecondsWithoutGlitches = 0.0; 102 mMaxSecondsWithoutGlitches = 0.0; 103 mLastGlitchCount = 0; 104 mStartResetCount = mLastResetCount; 105 // Start the initial runnable task by posting through the handler 106 mEnabled = true; 107 mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC); 108 } 109 stopSniffer()110 private void stopSniffer() { 111 mEnabled = false; 112 if (mHandler != null) { 113 mHandler.removeCallbacks(runnableCode); 114 } 115 116 runOnUiThread(new Runnable() { 117 @Override 118 public void run() { 119 updateStatusText(); 120 } 121 }); 122 } 123 124 private Runnable runnableCode = new Runnable() { 125 @Override 126 public void run() { 127 int state = getAnalyzerState(); 128 mSignalToNoiseDB = getSignalToNoiseDB(); 129 mPeakAmplitude = getPeakAmplitude(); 130 mPreviousState = state; 131 132 long now = System.currentTimeMillis(); 133 int glitchCount = getGlitchCount(); 134 int resetCount = getResetCount(); 135 mLastUnlockedFrames = getStateFrameCount(STATE_WAITING_FOR_LOCK); 136 int lockedFrames = getStateFrameCount(STATE_LOCKED); 137 int glitchFrames = getStateFrameCount(STATE_GLITCHING); 138 139 if (glitchFrames > mLastGlitchFrames || glitchCount > mLastGlitchCount) { 140 mTimeOfLastGlitch = now; 141 mSecondsWithoutGlitches = 0.0; 142 onGlitchDetected(); 143 } else if (lockedFrames > mLastLockedFrames) { 144 mSecondsWithoutGlitches = (now - mTimeOfLastGlitch) / 1000.0; 145 } 146 147 if (resetCount > mLastResetCount) { 148 mLastResetCount = resetCount; 149 } 150 151 if (mSecondsWithoutGlitches > mMaxSecondsWithoutGlitches) { 152 mMaxSecondsWithoutGlitches = mSecondsWithoutGlitches; 153 } 154 155 mLastGlitchCount = glitchCount; 156 mLastGlitchFrames = glitchFrames; 157 mLastLockedFrames = lockedFrames; 158 mLastResetCount = resetCount; 159 160 updateStatusText(); 161 162 // Reschedule so this task repeats 163 if (mEnabled) { 164 mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC); 165 } 166 } 167 }; 168 getCurrentStatusReport()169 String getCurrentStatusReport() { 170 long now = System.currentTimeMillis(); 171 double totalSeconds = (now - mTimeAtStart) / 1000.0; 172 173 StringBuffer message = new StringBuffer(); 174 message.append("state = " + stateToString(mPreviousState) + "\n"); 175 message.append(String.format("unlocked.frames = %d\n", mLastUnlockedFrames)); 176 message.append(String.format("locked.frames = %d\n", mLastLockedFrames)); 177 message.append(String.format("glitch.frames = %d\n", mLastGlitchFrames)); 178 message.append(String.format("reset.count = %d\n", mLastResetCount - mStartResetCount)); 179 message.append(String.format("peak.amplitude = %8.6f\n", mPeakAmplitude)); 180 if (mLastLockedFrames > 0) { 181 message.append(String.format("signal.noise.ratio.db = %5.1f\n", mSignalToNoiseDB)); 182 } 183 message.append(String.format("time.total = %8.2f seconds\n", totalSeconds)); 184 if (mLastLockedFrames > 0) { 185 message.append(String.format("time.no.glitches = %8.2f\n", mSecondsWithoutGlitches)); 186 message.append(String.format("max.time.no.glitches = %8.2f\n", 187 mMaxSecondsWithoutGlitches)); 188 message.append(String.format("glitch.count = %d\n", mLastGlitchCount)); 189 } 190 return message.toString(); 191 } 192 getShortReport()193 public String getShortReport() { 194 String resultText = "#glitches = " + getLastGlitchCount() 195 + ", #resets = " + getLastResetCount() 196 + ", max no glitch = " + getMaxSecondsWithNoGlitch() + " secs\n"; 197 resultText += String.format("SNR = %5.1f db", mSignalToNoiseDB); 198 resultText += ", #locked = " + mLastLockedFrames; 199 return resultText; 200 } 201 updateStatusText()202 private void updateStatusText() { 203 mLastGlitchReport = getCurrentStatusReport(); 204 setAnalyzerText(mLastGlitchReport); 205 } 206 getMaxSecondsWithNoGlitch()207 public double getMaxSecondsWithNoGlitch() { 208 return mMaxSecondsWithoutGlitches; 209 } 210 getLastGlitchCount()211 public int getLastGlitchCount() { 212 return mLastGlitchCount; 213 } getLastResetCount()214 public int getLastResetCount() { 215 return mLastResetCount; 216 } 217 } 218 219 // Called on UI thread onGlitchDetected()220 protected void onGlitchDetected() { 221 } 222 223 private GlitchSniffer mGlitchSniffer = new GlitchSniffer(); 224 setAnalyzerText(String s)225 private void setAnalyzerText(String s) { 226 mAnalyzerTextView.setText(s); 227 } 228 229 /** 230 * Set tolerance to deviations from expected value. 231 * The normalized value will be converted in the native code. 232 * @param tolerance normalized between 0.0 and 1.0 233 */ setTolerance(float tolerance)234 public native void setTolerance(float tolerance); 235 236 @Override onCreate(Bundle savedInstanceState)237 protected void onCreate(Bundle savedInstanceState) { 238 super.onCreate(savedInstanceState); 239 mStartButton = (Button) findViewById(R.id.button_start); 240 mStopButton = (Button) findViewById(R.id.button_stop); 241 mStopButton.setEnabled(false); 242 mShareButton = (Button) findViewById(R.id.button_share); 243 mShareButton.setEnabled(false); 244 mAnalyzerTextView = (TextView) findViewById(R.id.text_status); 245 updateEnabledWidgets(); 246 hideSettingsViews(); 247 // TODO hide sample rate menu 248 StreamContext streamContext = getFirstInputStreamContext(); 249 if (streamContext != null) { 250 if (streamContext.configurationView != null) { 251 streamContext.configurationView.hideSampleRateMenu(); 252 } 253 } 254 } 255 256 @Override onStart()257 protected void onStart() { 258 super.onStart(); 259 setActivityType(ACTIVITY_GLITCHES); 260 mStartButton.setEnabled(true); 261 mStopButton.setEnabled(false); 262 mShareButton.setEnabled(false); 263 } 264 265 @Override onStop()266 protected void onStop() { 267 stopAudioTest(); 268 super.onStop(); 269 } 270 271 // Called on UI thread onStartAudioTest(View view)272 public void onStartAudioTest(View view) throws IOException { 273 startAudioTest(); 274 mStartButton.setEnabled(false); 275 mStopButton.setEnabled(true); 276 mShareButton.setEnabled(false); 277 keepScreenOn(true); 278 } 279 startAudioTest()280 public void startAudioTest() throws IOException { 281 openAudio(); 282 startAudio(); 283 mGlitchSniffer.startSniffer(); 284 onTestBegan(); 285 } 286 onCancel(View view)287 public void onCancel(View view) { 288 stopAudioTest(); 289 onTestFinished(); 290 } 291 292 // Called on UI thread onStopAudioTest(View view)293 public void onStopAudioTest(View view) { 294 stopAudioTest(); 295 onTestFinished(); 296 keepScreenOn(false); 297 } 298 299 // Must be called on UI thread. onTestBegan()300 public void onTestBegan() { 301 } 302 303 // Must be called on UI thread. onTestFinished()304 public void onTestFinished() { 305 mStartButton.setEnabled(true); 306 mStopButton.setEnabled(false); 307 mShareButton.setEnabled(true); 308 } 309 stopAudioTest()310 public void stopAudioTest() { 311 mGlitchSniffer.stopSniffer(); 312 stopAudio(); 313 closeAudio(); 314 } 315 316 @Override isOutput()317 boolean isOutput() { 318 return false; 319 } 320 321 @Override setupEffects(int sessionId)322 public void setupEffects(int sessionId) { 323 } 324 getMaxSecondsWithNoGlitch()325 public double getMaxSecondsWithNoGlitch() { 326 return mGlitchSniffer.getMaxSecondsWithNoGlitch(); 327 } 328 getShortReport()329 public String getShortReport() { 330 return mGlitchSniffer.getShortReport(); 331 } 332 333 @Override getWaveTag()334 String getWaveTag() { 335 return "glitches"; 336 } 337 } 338