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