1 /* 2 * Copyright (C) 2015 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.android.cts.verifier.audio; 18 19 import com.android.cts.verifier.PassFailButtons; 20 import com.android.cts.verifier.R; 21 import com.android.compatibility.common.util.ReportLog; 22 import com.android.compatibility.common.util.ResultType; 23 import com.android.compatibility.common.util.ResultUnit; 24 import android.content.Context; 25 26 import android.media.AudioDeviceCallback; 27 import android.media.AudioDeviceInfo; 28 import android.media.AudioManager; 29 import android.media.AudioTrack; 30 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Message; 34 35 import android.util.Log; 36 37 import android.view.View; 38 import android.view.View.OnClickListener; 39 40 import android.widget.Button; 41 import android.widget.TextView; 42 import android.widget.SeekBar; 43 import android.widget.LinearLayout; 44 import android.widget.ProgressBar; 45 46 /** 47 * Tests Audio Device roundtrip latency by using a loopback plug. 48 */ 49 public class AudioLoopbackActivity extends PassFailButtons.Activity { 50 private static final String TAG = "AudioLoopbackActivity"; 51 52 public static final int BYTES_PER_FRAME = 2; 53 54 NativeAudioThread nativeAudioThread = null; 55 56 private int mSamplingRate = 44100; 57 private int mMinBufferSizeInFrames = 0; 58 private static final double CONFIDENCE_THRESHOLD = 0.6; 59 private Correlation mCorrelation = new Correlation(); 60 61 // TODO: remove this when no longer necessary 62 private int mNumFramesToIgnore = mSamplingRate / 10; // ignore first 100 ms 63 64 OnBtnClickListener mBtnClickListener = new OnBtnClickListener(); 65 Context mContext; 66 67 Button mHeadsetPortYes; 68 Button mHeadsetPortNo; 69 70 Button mLoopbackPlugReady; 71 TextView mAudioLevelText; 72 SeekBar mAudioLevelSeekbar; 73 LinearLayout mLinearLayout; 74 Button mTestButton; 75 TextView mResultText; 76 ProgressBar mProgressBar; 77 78 int mMaxLevel; 79 private class OnBtnClickListener implements OnClickListener { 80 @Override onClick(View v)81 public void onClick(View v) { 82 switch (v.getId()) { 83 case R.id.audio_loopback_plug_ready_btn: 84 Log.i(TAG, "audio loopback plug ready"); 85 //enable all the other views. 86 enableLayout(true); 87 break; 88 case R.id.audio_loopback_test_btn: 89 Log.i(TAG, "audio loopback test"); 90 startAudioTest(); 91 break; 92 case R.id.audio_general_headset_yes: 93 Log.i(TAG, "User confirms Headset Port existence"); 94 mLoopbackPlugReady.setEnabled(true); 95 recordHeasetPortFound(true); 96 mHeadsetPortYes.setEnabled(false); 97 mHeadsetPortNo.setEnabled(false); 98 break; 99 case R.id.audio_general_headset_no: 100 Log.i(TAG, "User denies Headset Port existence"); 101 recordHeasetPortFound(false); 102 getPassButton().setEnabled(true); 103 mHeadsetPortYes.setEnabled(false); 104 mHeadsetPortNo.setEnabled(false); 105 break; 106 } 107 } 108 } 109 110 @Override onCreate(Bundle savedInstanceState)111 protected void onCreate(Bundle savedInstanceState) { 112 super.onCreate(savedInstanceState); 113 setContentView(R.layout.audio_loopback_activity); 114 115 mContext = this; 116 117 mHeadsetPortYes = (Button)findViewById(R.id.audio_general_headset_yes); 118 mHeadsetPortYes.setOnClickListener(mBtnClickListener); 119 mHeadsetPortNo = (Button)findViewById(R.id.audio_general_headset_no); 120 mHeadsetPortNo.setOnClickListener(mBtnClickListener); 121 122 mLoopbackPlugReady = (Button)findViewById(R.id.audio_loopback_plug_ready_btn); 123 mLoopbackPlugReady.setOnClickListener(mBtnClickListener); 124 mLoopbackPlugReady.setEnabled(false); 125 mLinearLayout = (LinearLayout)findViewById(R.id.audio_loopback_layout); 126 mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text); 127 mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar); 128 mTestButton =(Button)findViewById(R.id.audio_loopback_test_btn); 129 mTestButton.setOnClickListener(mBtnClickListener); 130 mResultText = (TextView)findViewById(R.id.audio_loopback_results_text); 131 mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar); 132 showWait(false); 133 134 enableLayout(false); //disabled all content 135 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 136 mMaxLevel = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 137 mAudioLevelSeekbar.setMax(mMaxLevel); 138 am.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0); 139 refreshLevel(); 140 141 mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 142 @Override 143 public void onStopTrackingTouch(SeekBar seekBar) { 144 } 145 146 @Override 147 public void onStartTrackingTouch(SeekBar seekBar) { 148 } 149 150 @Override 151 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 152 153 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 154 am.setStreamVolume(AudioManager.STREAM_MUSIC, 155 progress, 0); 156 refreshLevel(); 157 Log.i(TAG,"Changed stream volume to: " + progress); 158 } 159 }); 160 161 setPassFailButtonClickListeners(); 162 getPassButton().setEnabled(false); 163 setInfoResources(R.string.audio_loopback_test, R.string.audio_loopback_info, -1); 164 } 165 166 /** 167 * refresh Audio Level seekbar and text 168 */ refreshLevel()169 private void refreshLevel() { 170 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 171 172 int currentLevel = am.getStreamVolume(AudioManager.STREAM_MUSIC); 173 mAudioLevelSeekbar.setProgress(currentLevel); 174 175 String levelText = String.format("%s: %d/%d", 176 getResources().getString(R.string.audio_loopback_level_text), 177 currentLevel, mMaxLevel); 178 mAudioLevelText.setText(levelText); 179 } 180 181 /** 182 * enable test ui elements 183 */ enableLayout(boolean enable)184 private void enableLayout(boolean enable) { 185 for (int i = 0; i<mLinearLayout.getChildCount(); i++) { 186 View view = mLinearLayout.getChildAt(i); 187 view.setEnabled(enable); 188 } 189 } 190 191 /** 192 * show active progress bar 193 */ showWait(boolean show)194 private void showWait(boolean show) { 195 if (show) { 196 mProgressBar.setVisibility(View.VISIBLE) ; 197 } else { 198 mProgressBar.setVisibility(View.INVISIBLE) ; 199 } 200 } 201 202 /** 203 * Start the loopback audio test 204 */ startAudioTest()205 private void startAudioTest() { 206 getPassButton().setEnabled(false); 207 208 //get system defaults for sampling rate, buffers. 209 AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE); 210 String value = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER); 211 mMinBufferSizeInFrames = Integer.parseInt(value); 212 213 int minBufferSizeInBytes = BYTES_PER_FRAME * mMinBufferSizeInFrames; 214 215 mSamplingRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); 216 217 Log.i(TAG, String.format("startAudioTest sr:%d , buffer:%d frames", 218 mSamplingRate, mMinBufferSizeInFrames)); 219 220 nativeAudioThread = new NativeAudioThread(); 221 if (nativeAudioThread != null) { 222 nativeAudioThread.setMessageHandler(mMessageHandler); 223 nativeAudioThread.mSessionId = 0; 224 nativeAudioThread.setParams(mSamplingRate, 225 minBufferSizeInBytes, 226 minBufferSizeInBytes, 227 0x03 /*voice recognition*/, 228 mNumFramesToIgnore); 229 nativeAudioThread.start(); 230 231 try { 232 Thread.sleep(200); 233 } catch (InterruptedException e) { 234 e.printStackTrace(); 235 } 236 237 nativeAudioThread.runTest(); 238 239 } 240 } 241 242 /** 243 * handler for messages from audio thread 244 */ 245 private Handler mMessageHandler = new Handler() { 246 public void handleMessage(Message msg) { 247 super.handleMessage(msg); 248 switch(msg.what) { 249 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED: 250 Log.v(TAG,"got message native rec started!!"); 251 showWait(true); 252 mResultText.setText("Test Running..."); 253 break; 254 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR: 255 Log.v(TAG,"got message native rec can't start!!"); 256 showWait(false); 257 mResultText.setText("Test Error."); 258 break; 259 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE: 260 case NativeAudioThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS: 261 if (nativeAudioThread != null) { 262 Log.v(TAG,"Finished recording."); 263 double [] waveData = nativeAudioThread.getWaveData(); 264 mCorrelation.computeCorrelation(waveData, mSamplingRate); 265 mResultText.setText(String.format( 266 "Test Finished\nLatency:%.2f ms\nConfidence: %.2f", 267 mCorrelation.mEstimatedLatencyMs, 268 mCorrelation.mEstimatedLatencyConfidence)); 269 270 recordTestResults(); 271 if (mCorrelation.mEstimatedLatencyConfidence >= CONFIDENCE_THRESHOLD) { 272 getPassButton().setEnabled(true); 273 } 274 275 //close 276 if (nativeAudioThread != null) { 277 nativeAudioThread.isRunning = false; 278 try { 279 nativeAudioThread.finish(); 280 nativeAudioThread.join(); 281 } catch (InterruptedException e) { 282 e.printStackTrace(); 283 } 284 nativeAudioThread = null; 285 } 286 showWait(false); 287 } 288 break; 289 default: 290 break; 291 } 292 } 293 }; 294 295 /** 296 * Store test results in log 297 */ recordTestResults()298 private void recordTestResults() { 299 300 getReportLog().addValue( 301 "Estimated Latency", 302 mCorrelation.mEstimatedLatencyMs, 303 ResultType.LOWER_BETTER, 304 ResultUnit.MS); 305 306 getReportLog().addValue( 307 "Confidence", 308 mCorrelation.mEstimatedLatencyConfidence, 309 ResultType.HIGHER_BETTER, 310 ResultUnit.NONE); 311 312 int audioLevel = mAudioLevelSeekbar.getProgress(); 313 getReportLog().addValue( 314 "Audio Level", 315 audioLevel, 316 ResultType.NEUTRAL, 317 ResultUnit.NONE); 318 319 getReportLog().addValue( 320 "Frames Buffer Size", 321 mMinBufferSizeInFrames, 322 ResultType.NEUTRAL, 323 ResultUnit.NONE); 324 325 getReportLog().addValue( 326 "Sampling Rate", 327 mSamplingRate, 328 ResultType.NEUTRAL, 329 ResultUnit.NONE); 330 331 Log.v(TAG,"Results Recorded"); 332 } 333 recordHeasetPortFound(boolean found)334 private void recordHeasetPortFound(boolean found) { 335 getReportLog().addValue( 336 "User Reported Headset Port", 337 found ? 1.0 : 0, 338 ResultType.NEUTRAL, 339 ResultUnit.NONE); 340 } 341 } 342