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.mobileer.oboetester; 18 19 import android.app.Activity; 20 import android.os.Bundle; 21 import android.view.View; 22 import android.widget.Button; 23 import android.widget.TextView; 24 25 import java.io.IOException; 26 27 /** 28 * Activity to measure the number of glitches. 29 */ 30 public class GlitchActivity extends AnalyzerActivity { 31 private TextView mAnalyzerTextView; 32 private Button mStartButton; 33 private Button mStopButton; 34 private Button mShareButton; 35 36 // These must match the values in LatencyAnalyzer.h 37 final static int STATE_IDLE = 0; 38 final static int STATE_IMMUNE = 1; 39 final static int STATE_WAITING_FOR_SIGNAL = 2; 40 final static int STATE_WAITING_FOR_LOCK = 3; 41 final static int STATE_LOCKED = 4; 42 final static int STATE_GLITCHING = 5; 43 String mLastGlitchReport; 44 private int mInputChannel; 45 private int mOutputChannel; 46 getStateFrameCount(int state)47 native int getStateFrameCount(int state); getGlitchCount()48 native int getGlitchCount(); getSignalToNoiseDB()49 native double getSignalToNoiseDB(); getPeakAmplitude()50 native double getPeakAmplitude(); getSineAmplitude()51 native double getSineAmplitude(); 52 53 private GlitchSniffer mGlitchSniffer; 54 private NativeSniffer mNativeSniffer = createNativeSniffer(); 55 createNativeSniffer()56 synchronized NativeSniffer createNativeSniffer() { 57 if (mGlitchSniffer == null) { 58 mGlitchSniffer = new GlitchSniffer(this); 59 } 60 return mGlitchSniffer; 61 } 62 63 // Note that these strings must match the enum result_code in LatencyAnalyzer.h stateToString(int resultCode)64 String stateToString(int resultCode) { 65 switch (resultCode) { 66 case STATE_IDLE: 67 return "IDLE"; 68 case STATE_IMMUNE: 69 return "IMMUNE"; 70 case STATE_WAITING_FOR_SIGNAL: 71 return "WAITING_FOR_SIGNAL"; 72 case STATE_WAITING_FOR_LOCK: 73 return "WAITING_FOR_LOCK"; 74 case STATE_LOCKED: 75 return "LOCKED"; 76 case STATE_GLITCHING: 77 return "GLITCHING"; 78 default: 79 return "UNKNOWN"; 80 } 81 } 82 83 // Periodically query for glitches from the native detector. 84 protected class GlitchSniffer extends NativeSniffer { 85 86 private long mTimeAtStart; 87 private long mTimeOfLastGlitch; 88 private double mSecondsWithoutGlitches; 89 private double mMaxSecondsWithoutGlitches; 90 private int mLastGlitchCount; 91 private int mLastUnlockedFrames; 92 private int mLastLockedFrames; 93 private int mLastGlitchFrames; 94 95 private int mStartResetCount; 96 private int mLastResetCount; 97 private int mPreviousState; 98 99 private double mSignalToNoiseDB; 100 private double mPeakAmplitude; 101 private double mSineAmplitude; 102 GlitchSniffer(Activity activity)103 public GlitchSniffer(Activity activity) { 104 super(activity); 105 } 106 107 @Override startSniffer()108 public void startSniffer() { 109 long now = System.currentTimeMillis(); 110 mTimeAtStart = now; 111 mTimeOfLastGlitch = now; 112 mLastUnlockedFrames = 0; 113 mLastLockedFrames = 0; 114 mLastGlitchFrames = 0; 115 mSecondsWithoutGlitches = 0.0; 116 mMaxSecondsWithoutGlitches = 0.0; 117 mLastGlitchCount = 0; 118 mStartResetCount = mLastResetCount; 119 super.startSniffer(); 120 } 121 run()122 public void run() { 123 int state = getAnalyzerState(); 124 mSignalToNoiseDB = getSignalToNoiseDB(); 125 mPeakAmplitude = getPeakAmplitude(); 126 mSineAmplitude = getSineAmplitude(); 127 int glitchCount = getGlitchCount(); 128 if (state != mPreviousState) { 129 if ((state == STATE_WAITING_FOR_SIGNAL || state == STATE_WAITING_FOR_LOCK) 130 && glitchCount == 0) { // did not previously lock 131 GlitchActivity.this.giveAdvice("Try raising volume!"); 132 } else { 133 GlitchActivity.this.giveAdvice(null); 134 } 135 } 136 mPreviousState = state; 137 138 long now = System.currentTimeMillis(); 139 int resetCount = getResetCount(); 140 mLastUnlockedFrames = getStateFrameCount(STATE_WAITING_FOR_LOCK); 141 int lockedFrames = getStateFrameCount(STATE_LOCKED); 142 int glitchFrames = getStateFrameCount(STATE_GLITCHING); 143 144 if (glitchFrames > mLastGlitchFrames || glitchCount > mLastGlitchCount) { 145 mTimeOfLastGlitch = now; 146 mSecondsWithoutGlitches = 0.0; 147 if (glitchCount > mLastGlitchCount) { 148 onGlitchDetected(); 149 } 150 } else if (lockedFrames > mLastLockedFrames) { 151 mSecondsWithoutGlitches = (now - mTimeOfLastGlitch) / 1000.0; 152 } 153 154 if (resetCount > mLastResetCount) { 155 mLastResetCount = resetCount; 156 } 157 158 if (mSecondsWithoutGlitches > mMaxSecondsWithoutGlitches) { 159 mMaxSecondsWithoutGlitches = mSecondsWithoutGlitches; 160 } 161 162 mLastGlitchCount = glitchCount; 163 mLastGlitchFrames = glitchFrames; 164 mLastLockedFrames = lockedFrames; 165 mLastResetCount = resetCount; 166 167 reschedule(); 168 } 169 getCurrentStatusReport()170 private String getCurrentStatusReport() { 171 long now = System.currentTimeMillis(); 172 double totalSeconds = (now - mTimeAtStart) / 1000.0; 173 174 StringBuffer message = new StringBuffer(); 175 message.append("state = " + stateToString(mPreviousState) + "\n"); 176 message.append(String.format("unlocked.frames = %d\n", mLastUnlockedFrames)); 177 message.append(String.format("locked.frames = %d\n", mLastLockedFrames)); 178 message.append(String.format("glitch.frames = %d\n", mLastGlitchFrames)); 179 message.append(String.format("reset.count = %d\n", mLastResetCount - mStartResetCount)); 180 message.append(String.format("peak.amplitude = %8.6f\n", mPeakAmplitude)); 181 message.append(String.format("sine.amplitude = %8.6f\n", mSineAmplitude)); 182 if (mLastLockedFrames > 0) { 183 message.append(String.format("signal.noise.ratio.db = %5.1f\n", mSignalToNoiseDB)); 184 } 185 message.append(String.format("time.total = %4.2f seconds\n", totalSeconds)); 186 if (mLastLockedFrames > 0) { 187 message.append(String.format("time.no.glitches = %4.2f\n", mSecondsWithoutGlitches)); 188 message.append(String.format("max.time.no.glitches = %4.2f\n", 189 mMaxSecondsWithoutGlitches)); 190 message.append(String.format("glitch.count = %d\n", mLastGlitchCount)); 191 } 192 return message.toString(); 193 } 194 195 @Override getShortReport()196 public String getShortReport() { 197 String resultText = "#glitches = " + getLastGlitchCount() 198 + ", #resets = " + getLastResetCount() 199 + ", max no glitch = " + getMaxSecondsWithNoGlitch() + " secs\n"; 200 resultText += String.format("SNR = %5.1f db", mSignalToNoiseDB); 201 resultText += ", #locked = " + mLastLockedFrames; 202 return resultText; 203 } 204 205 @Override updateStatusText()206 public void updateStatusText() { 207 mLastGlitchReport = getCurrentStatusReport(); 208 setAnalyzerText(mLastGlitchReport); 209 } 210 getMaxSecondsWithNoGlitch()211 public double getMaxSecondsWithNoGlitch() { 212 return mMaxSecondsWithoutGlitches; 213 } 214 getLastGlitchCount()215 public int getLastGlitchCount() { 216 return mLastGlitchCount; 217 } getLastResetCount()218 public int getLastResetCount() { 219 return mLastResetCount; 220 } 221 } 222 giveAdvice(String s)223 public void giveAdvice(String s) { 224 } 225 226 // Called on UI thread onGlitchDetected()227 protected void onGlitchDetected() { 228 } 229 setAnalyzerText(String s)230 protected void setAnalyzerText(String s) { 231 mAnalyzerTextView.setText(s); 232 } 233 234 /** 235 * Set tolerance to deviations from expected value. 236 * The normalized value will be converted in the native code. 237 * @param tolerance normalized between 0.0 and 1.0 238 */ setTolerance(float tolerance)239 public native void setTolerance(float tolerance); 240 setInputChannel(int channel)241 public void setInputChannel(int channel) { 242 mInputChannel = channel; 243 setInputChannelNative(channel); 244 } 245 setOutputChannel(int channel)246 public void setOutputChannel(int channel) { 247 mOutputChannel = channel; 248 setOutputChannelNative(channel); 249 } 250 getInputChannel()251 public int getInputChannel() { 252 return mInputChannel; 253 } 254 getOutputChannel()255 public int getOutputChannel() { 256 return mOutputChannel; 257 } 258 setInputChannelNative(int channel)259 public native void setInputChannelNative(int channel); 260 setOutputChannelNative(int channel)261 public native void setOutputChannelNative(int channel); 262 263 @Override onCreate(Bundle savedInstanceState)264 protected void onCreate(Bundle savedInstanceState) { 265 super.onCreate(savedInstanceState); 266 mStartButton = (Button) findViewById(R.id.button_start); 267 mStopButton = (Button) findViewById(R.id.button_stop); 268 mStopButton.setEnabled(false); 269 mShareButton = (Button) findViewById(R.id.button_share); 270 mShareButton.setEnabled(false); 271 mAnalyzerTextView = (TextView) findViewById(R.id.text_status); 272 updateEnabledWidgets(); 273 hideSettingsViews(); 274 // TODO hide sample rate menu 275 StreamContext streamContext = getFirstInputStreamContext(); 276 if (streamContext != null) { 277 if (streamContext.configurationView != null) { 278 streamContext.configurationView.hideSampleRateMenu(); 279 } 280 } 281 } 282 283 @Override getActivityType()284 int getActivityType() { 285 return ACTIVITY_GLITCHES; 286 } 287 288 @Override onStart()289 protected void onStart() { 290 super.onStart(); 291 setInputChannel(0); 292 setOutputChannel(0); 293 } 294 295 @Override onStop()296 protected void onStop() { 297 if (!isBackgroundEnabled()) { 298 stopAudioTest(); 299 } 300 super.onStop(); 301 } 302 303 @Override onDestroy()304 protected void onDestroy() { 305 if (isBackgroundEnabled()) { 306 stopAudioTest(); 307 } 308 super.onDestroy(); 309 } 310 311 // Called on UI thread onStartAudioTest(View view)312 public void onStartAudioTest(View view) { 313 try { 314 openStartAudioTestUI(); 315 } catch (IOException e) { 316 showErrorToast(e.getMessage()); 317 } 318 } 319 openStartAudioTestUI()320 protected void openStartAudioTestUI() throws IOException { 321 openAudio(); 322 startAudioTest(); 323 mStartButton.setEnabled(false); 324 mStopButton.setEnabled(true); 325 mShareButton.setEnabled(false); 326 keepScreenOn(true); 327 } 328 startAudioTest()329 public void startAudioTest() throws IOException { 330 startAudio(); 331 mNativeSniffer.startSniffer(); 332 onTestBegan(); 333 } 334 onCancel(View view)335 public void onCancel(View view) { 336 stopAudioTest(); 337 onTestFinished(); 338 } 339 340 // Called on UI thread onStopAudioTest(View view)341 public void onStopAudioTest(View view) { 342 stopAudioTest(); 343 onTestFinished(); 344 mStartButton.setEnabled(true); 345 mStopButton.setEnabled(false); 346 mShareButton.setEnabled(false); 347 keepScreenOn(false); 348 } 349 350 // Must be called on UI thread. onTestBegan()351 public void onTestBegan() { 352 } 353 354 // Must be called on UI thread. onTestFinished()355 public void onTestFinished() { 356 mStartButton.setEnabled(true); 357 mStopButton.setEnabled(false); 358 mShareButton.setEnabled(true); 359 } 360 stopAudioTest()361 public void stopAudioTest() { 362 mNativeSniffer.stopSniffer(); 363 stopAudio(); 364 closeAudio(); 365 } 366 stopTest()367 public void stopTest() { 368 stopAudio(); 369 } 370 371 @Override isOutput()372 boolean isOutput() { 373 return false; 374 } 375 getMaxSecondsWithNoGlitch()376 public double getMaxSecondsWithNoGlitch() { 377 return mGlitchSniffer.getMaxSecondsWithNoGlitch(); 378 } 379 getShortReport()380 public String getShortReport() { 381 return mNativeSniffer.getShortReport(); 382 } 383 384 @Override getWaveTag()385 String getWaveTag() { 386 return "glitches"; 387 } 388 } 389