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