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.Manifest; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.os.Build; 23 import android.os.Bundle; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.support.annotation.NonNull; 27 import android.support.v4.app.ActivityCompat; 28 import android.support.v4.content.ContextCompat; 29 import android.util.Log; 30 import android.view.View; 31 import android.widget.Button; 32 import android.widget.TextView; 33 import android.widget.Toast; 34 35 import java.io.File; 36 import java.io.FileOutputStream; 37 import java.io.IOException; 38 import java.io.OutputStreamWriter; 39 import java.io.Writer; 40 41 /** 42 * Activity to measure latency on a full duplex stream. 43 */ 44 public class AnalyzerActivity extends TestInputActivity { 45 46 private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 1001; 47 48 public static final String KEY_IN_SHARING = "in_sharing"; 49 public static final String KEY_OUT_SHARING = "out_sharing"; 50 public static final String VALUE_SHARING_EXCLUSIVE = "exclusive"; 51 public static final String VALUE_SHARING_SHARED = "shared"; 52 53 public static final String KEY_IN_PERF = "in_perf"; 54 public static final String KEY_OUT_PERF = "out_perf"; 55 public static final String VALUE_PERF_LOW_LATENCY = "lowlat"; 56 public static final String VALUE_PERF_POWERSAVE = "powersave"; 57 public static final String VALUE_PERF_NONE = "none"; 58 59 public static final String KEY_IN_CHANNELS = "in_channels"; 60 public static final String KEY_OUT_CHANNELS = "out_channels"; 61 public static final int VALUE_DEFAULT_CHANNELS = 2; 62 63 public static final String KEY_SAMPLE_RATE = "sample_rate"; 64 public static final int VALUE_DEFAULT_SAMPLE_RATE = 48000; 65 66 protected static final String KEY_FILE_NAME = "file"; 67 protected static final String KEY_BUFFER_BURSTS = "buffer_bursts"; 68 69 public static final String VALUE_UNSPECIFIED = "unspecified"; 70 public static final String KEY_IN_API = "in_api"; 71 public static final String KEY_OUT_API = "out_api"; 72 public static final String VALUE_API_AAUDIO = "aaudio"; 73 public static final String VALUE_API_OPENSLES = "opensles"; 74 75 AudioOutputTester mAudioOutTester; 76 protected BufferSizeView mBufferSizeView; 77 protected String mResultFileName; 78 private String mTestResults; 79 80 // Note that these string must match the enum result_code in LatencyAnalyzer.h resultCodeToString(int resultCode)81 String resultCodeToString(int resultCode) { 82 switch (resultCode) { 83 case 0: 84 return "OK"; 85 case -99: 86 return "ERROR_NOISY"; 87 case -98: 88 return "ERROR_VOLUME_TOO_LOW"; 89 case -97: 90 return "ERROR_VOLUME_TOO_HIGH"; 91 case -96: 92 return "ERROR_CONFIDENCE"; 93 case -95: 94 return "ERROR_INVALID_STATE"; 95 case -94: 96 return "ERROR_GLITCHES"; 97 case -93: 98 return "ERROR_NO_LOCK"; 99 default: 100 return "UNKNOWN"; 101 } 102 } 103 getAnalyzerState()104 public native int getAnalyzerState(); isAnalyzerDone()105 public native boolean isAnalyzerDone(); getMeasuredResult()106 public native int getMeasuredResult(); getResetCount()107 public native int getResetCount(); 108 109 @NonNull getCommonTestReport()110 protected String getCommonTestReport() { 111 StringBuffer report = new StringBuffer(); 112 // Add some extra information for the remote tester. 113 report.append("build.fingerprint = " + Build.FINGERPRINT + "\n"); 114 try { 115 PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); 116 report.append(String.format("test.version = %s\n", pinfo.versionName)); 117 report.append(String.format("test.version.code = %d\n", pinfo.versionCode)); 118 } catch (PackageManager.NameNotFoundException e) { 119 } 120 report.append("time.millis = " + System.currentTimeMillis() + "\n"); 121 122 // INPUT 123 report.append(mAudioInputTester.actualConfiguration.dump()); 124 AudioStreamBase inStream = mAudioInputTester.getCurrentAudioStream(); 125 report.append(String.format("in.burst.frames = %d\n", inStream.getFramesPerBurst())); 126 report.append(String.format("in.xruns = %d\n", inStream.getXRunCount())); 127 128 // OUTPUT 129 report.append(mAudioOutTester.actualConfiguration.dump()); 130 AudioStreamBase outStream = mAudioOutTester.getCurrentAudioStream(); 131 report.append(String.format("out.burst.frames = %d\n", outStream.getFramesPerBurst())); 132 int bufferSize = outStream.getBufferSizeInFrames(); 133 report.append(String.format("out.buffer.size.frames = %d\n", bufferSize)); 134 int bufferCapacity = outStream.getBufferCapacityInFrames(); 135 report.append(String.format("out.buffer.capacity.frames = %d\n", bufferCapacity)); 136 report.append(String.format("out.xruns = %d\n", outStream.getXRunCount())); 137 138 return report.toString(); 139 } 140 141 @Override onCreate(Bundle savedInstanceState)142 protected void onCreate(Bundle savedInstanceState) { 143 super.onCreate(savedInstanceState); 144 mAudioOutTester = addAudioOutputTester(); 145 mBufferSizeView = (BufferSizeView) findViewById(R.id.buffer_size_view); 146 } 147 148 @Override resetConfiguration()149 protected void resetConfiguration() { 150 super.resetConfiguration(); 151 mAudioOutTester.reset(); 152 153 StreamContext streamContext = getFirstInputStreamContext(); 154 if (streamContext != null) { 155 if (streamContext.configurationView != null) { 156 streamContext.configurationView.setFormat(StreamConfiguration.AUDIO_FORMAT_PCM_FLOAT); 157 streamContext.configurationView.setFormatConversionAllowed(true); 158 } 159 } 160 streamContext = getFirstOutputStreamContext(); 161 if (streamContext != null) { 162 if (streamContext.configurationView != null) { 163 streamContext.configurationView.setFormat(StreamConfiguration.AUDIO_FORMAT_PCM_FLOAT); 164 streamContext.configurationView.setFormatConversionAllowed(true); 165 } 166 } 167 } 168 169 @Override openAudio()170 public void openAudio() throws IOException { 171 super.openAudio(); 172 if (mBufferSizeView != null) { 173 mBufferSizeView.onStreamOpened((OboeAudioStream) mAudioOutTester.getCurrentAudioStream()); 174 } 175 } 176 onStreamClosed()177 public void onStreamClosed() { 178 Toast.makeText(getApplicationContext(), 179 "Stream was closed or disconnected!", 180 Toast.LENGTH_SHORT) 181 .show(); 182 stopAudioTest(); 183 } 184 stopAudioTest()185 public void stopAudioTest() { 186 } 187 getApiFromText(String text)188 private int getApiFromText(String text) { 189 if (VALUE_API_AAUDIO.equals(text)) { 190 return StreamConfiguration.NATIVE_API_AAUDIO; 191 } else if (VALUE_API_OPENSLES.equals(text)) { 192 return StreamConfiguration.NATIVE_API_OPENSLES; 193 } else { 194 return StreamConfiguration.NATIVE_API_UNSPECIFIED; 195 } 196 } 197 getPerfFromText(String text)198 private int getPerfFromText(String text) { 199 if (VALUE_PERF_NONE.equals(text)) { 200 return StreamConfiguration.PERFORMANCE_MODE_NONE; 201 } else if (VALUE_PERF_POWERSAVE.equals(text)) { 202 return StreamConfiguration.PERFORMANCE_MODE_POWER_SAVING; 203 } else { 204 return StreamConfiguration.PERFORMANCE_MODE_LOW_LATENCY; 205 } 206 } 207 getSharingFromText(String text)208 private int getSharingFromText(String text) { 209 if (VALUE_SHARING_SHARED.equals(text)) { 210 return StreamConfiguration.SHARING_MODE_SHARED; 211 } else { 212 return StreamConfiguration.SHARING_MODE_EXCLUSIVE; 213 } 214 } 215 configureStreamsFromBundle(Bundle bundle)216 void configureStreamsFromBundle(Bundle bundle) { 217 // Configure settings 218 StreamConfiguration requestedInConfig = mAudioInputTester.requestedConfiguration; 219 StreamConfiguration requestedOutConfig = mAudioOutTester.requestedConfiguration; 220 221 requestedInConfig.reset(); 222 requestedOutConfig.reset(); 223 224 // OpenSL ES or AAudio API 225 String text = bundle.getString(KEY_IN_API, VALUE_UNSPECIFIED); 226 int audioApi = getApiFromText(text); 227 requestedInConfig.setNativeApi(audioApi); 228 text = bundle.getString(KEY_OUT_API, VALUE_UNSPECIFIED); 229 audioApi = getApiFromText(text); 230 requestedOutConfig.setNativeApi(audioApi); 231 232 // channnels 233 int inChannels = bundle.getInt(KEY_IN_CHANNELS, VALUE_DEFAULT_CHANNELS); 234 requestedInConfig.setChannelCount(inChannels); 235 int outChannels = bundle.getInt(KEY_OUT_CHANNELS, VALUE_DEFAULT_CHANNELS); 236 requestedOutConfig.setChannelCount(outChannels); 237 238 // performance mode 239 text = bundle.getString(KEY_IN_PERF, VALUE_PERF_LOW_LATENCY); 240 int perfMode = getPerfFromText(text); 241 requestedInConfig.setPerformanceMode(perfMode); 242 text = bundle.getString(KEY_OUT_PERF, VALUE_PERF_LOW_LATENCY); 243 perfMode = getPerfFromText(text); 244 requestedOutConfig.setPerformanceMode(perfMode); 245 246 int sampleRate = bundle.getInt(KEY_SAMPLE_RATE, VALUE_DEFAULT_SAMPLE_RATE); 247 requestedInConfig.setSampleRate(sampleRate); 248 requestedOutConfig.setSampleRate(sampleRate); 249 250 text = bundle.getString(KEY_IN_SHARING, VALUE_SHARING_EXCLUSIVE); 251 int sharingMode = getSharingFromText(text); 252 requestedInConfig.setSharingMode(sharingMode); 253 text = bundle.getString(KEY_OUT_SHARING, VALUE_SHARING_EXCLUSIVE); 254 sharingMode = getSharingFromText(text); 255 requestedOutConfig.setSharingMode(sharingMode); 256 } 257 writeTestResultIfPermitted(String resultString)258 void writeTestResultIfPermitted(String resultString) { 259 // Here, thisActivity is the current activity 260 if (ContextCompat.checkSelfPermission(this, 261 Manifest.permission.WRITE_EXTERNAL_STORAGE) 262 != PackageManager.PERMISSION_GRANTED) { 263 mTestResults = resultString; 264 ActivityCompat.requestPermissions(this, 265 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 266 MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE); 267 } else { 268 // Permission has already been granted 269 writeTestResult(resultString); 270 } 271 } 272 maybeWriteTestResult(String resultString)273 void maybeWriteTestResult(String resultString) { 274 if (mResultFileName == null) return; 275 writeTestResultIfPermitted(resultString); 276 } 277 278 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)279 public void onRequestPermissionsResult(int requestCode, 280 String[] permissions, 281 int[] grantResults) { 282 switch (requestCode) { 283 case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: { 284 // If request is cancelled, the result arrays are empty. 285 if (grantResults.length > 0 286 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 287 writeTestResult(mTestResults); 288 } else { 289 showToast("Writing external storage needed for test results."); 290 } 291 return; 292 } 293 } 294 } 295 writeTestInBackground(final String resultString)296 private void writeTestInBackground(final String resultString) { 297 new Thread() { 298 public void run() { 299 writeTestResult(resultString); 300 } 301 }.start(); 302 } 303 304 // Run this in a background thread. writeTestResult(String resultString)305 private void writeTestResult(String resultString) { 306 File resultFile = new File(mResultFileName); 307 Writer writer = null; 308 try { 309 writer = new OutputStreamWriter(new FileOutputStream(resultFile)); 310 writer.write(resultString); 311 } catch ( 312 IOException e) { 313 e.printStackTrace(); 314 showErrorToast(" writing result file. " + e.getMessage()); 315 } finally { 316 if (writer != null) { 317 try { 318 writer.close(); 319 } catch (IOException e) { 320 e.printStackTrace(); 321 } 322 } 323 } 324 325 mResultFileName = null; 326 } 327 328 329 } 330