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