• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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.content.Context;
21 import android.media.AudioDeviceInfo;
22 import android.media.AudioManager;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.WindowManager;
29 import android.widget.Button;
30 import android.widget.CheckBox;
31 import android.widget.Toast;
32 
33 import java.io.IOException;
34 import java.util.ArrayList;
35 
36 /**
37  * Base class for other Activities.
38  */
39 abstract class TestAudioActivity extends Activity {
40     public static final String TAG = "OboeTester";
41 
42     protected static final int FADER_PROGRESS_MAX = 1000;
43 
44     public static final int AUDIO_STATE_OPEN = 0;
45     public static final int AUDIO_STATE_STARTED = 1;
46     public static final int AUDIO_STATE_PAUSED = 2;
47     public static final int AUDIO_STATE_STOPPED = 3;
48     public static final int AUDIO_STATE_CLOSING = 4;
49     public static final int AUDIO_STATE_CLOSED = 5;
50 
51     public static final int COLOR_ACTIVE = 0xFFD0D0A0;
52     public static final int COLOR_IDLE = 0xFFD0D0D0;
53 
54     // Pass the activity index to native so it can know how to respond to the start and stop calls.
55     // WARNING - must match definitions in NativeAudioContext.h ActivityType
56     public static final int ACTIVITY_TEST_OUTPUT = 0;
57     public static final int ACTIVITY_TEST_INPUT = 1;
58     public static final int ACTIVITY_TAP_TO_TONE = 2;
59     public static final int ACTIVITY_RECORD_PLAY = 3;
60     public static final int ACTIVITY_ECHO = 4;
61     public static final int ACTIVITY_RT_LATENCY = 5;
62     public static final int ACTIVITY_GLITCHES = 6;
63     public static final int ACTIVITY_TEST_DISCONNECT = 7;
64     public static final int ACTIVITY_DATA_PATHS = 8;
65 
66     private int mAudioState = AUDIO_STATE_CLOSED;
67     protected String audioManagerSampleRate;
68     protected int audioManagerFramesPerBurst;
69     protected ArrayList<StreamContext> mStreamContexts;
70     private Button mOpenButton;
71     private Button mStartButton;
72     private Button mPauseButton;
73     private Button mStopButton;
74     private Button mCloseButton;
75     private MyStreamSniffer mStreamSniffer;
76     private CheckBox mCallbackReturnStopBox;
77     private int mSampleRate;
78     private boolean mScoStarted;
79     private int mSingleTestIndex = -1;
80 
getTestName()81     public String getTestName() {
82         return "TestAudio";
83     }
84 
85     public static class StreamContext {
86         StreamConfigurationView configurationView;
87         AudioStreamTester tester;
88 
isInput()89         boolean isInput() {
90             return tester.getCurrentAudioStream().isInput();
91         }
92     }
93 
94     // Periodically query the status of the streams.
95     protected class MyStreamSniffer {
96         public static final int SNIFFER_UPDATE_PERIOD_MSEC = 150;
97         public static final int SNIFFER_UPDATE_DELAY_MSEC = 300;
98 
99         private Handler mHandler;
100 
101         // Display status info for the stream.
102         private Runnable runnableCode = new Runnable() {
103             @Override
104             public void run() {
105                 boolean streamClosed = false;
106                 boolean gotViews = false;
107                 for (StreamContext streamContext : mStreamContexts) {
108                     AudioStreamBase.StreamStatus status = streamContext.tester.getCurrentAudioStream().getStreamStatus();
109                     AudioStreamBase.DoubleStatistics latencyStatistics =
110                             streamContext.tester.getCurrentAudioStream().getLatencyStatistics();
111                     if (streamContext.configurationView != null) {
112                         // Handler runs this on the main UI thread.
113                         int framesPerBurst = streamContext.tester.getCurrentAudioStream().getFramesPerBurst();
114                         status.framesPerCallback = getFramesPerCallback();
115                         String msg = "";
116                         msg += "timestamp.latency = " + latencyStatistics.dump() + "\n";
117                         msg += status.dump(framesPerBurst);
118                         streamContext.configurationView.setStatusText(msg);
119                         updateStreamDisplay();
120                         gotViews = true;
121                     }
122 
123                     streamClosed = streamClosed || (status.state >= 12);
124                 }
125 
126                 if (streamClosed) {
127                     onStreamClosed();
128                 } else {
129                     // Repeat this runnable code block again.
130                     if (gotViews) {
131                         mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_PERIOD_MSEC);
132                     }
133                 }
134             }
135         };
136 
startStreamSniffer()137         private void startStreamSniffer() {
138             stopStreamSniffer();
139             mHandler = new Handler(Looper.getMainLooper());
140             // Start the initial runnable task by posting through the handler
141             mHandler.postDelayed(runnableCode, SNIFFER_UPDATE_DELAY_MSEC);
142         }
143 
stopStreamSniffer()144         private void stopStreamSniffer() {
145             if (mHandler != null) {
146                 mHandler.removeCallbacks(runnableCode);
147             }
148         }
149 
150     }
151 
onStreamClosed()152     public void onStreamClosed() {
153     }
154 
inflateActivity()155     protected abstract void inflateActivity();
156 
updateStreamDisplay()157     void updateStreamDisplay() {
158     }
159 
160     @Override
onCreate(Bundle savedInstanceState)161     protected void onCreate(Bundle savedInstanceState) {
162         super.onCreate(savedInstanceState);
163         inflateActivity();
164         findAudioCommon();
165     }
166 
hideSettingsViews()167     public void hideSettingsViews() {
168         for (StreamContext streamContext : mStreamContexts) {
169             if (streamContext.configurationView != null) {
170                 streamContext.configurationView.hideSettingsView();
171             }
172         }
173     }
174 
getActivityType()175     abstract int getActivityType();
176 
setSingleTestIndex(int testIndex)177     public void setSingleTestIndex(int testIndex) {
178         mSingleTestIndex = testIndex;
179     }
getSingleTestIndex()180     public int getSingleTestIndex() {
181         return mSingleTestIndex;
182     }
183 
184     @Override
onStart()185     protected void onStart() {
186         super.onStart();
187         resetConfiguration();
188         setActivityType(getActivityType());
189     }
190 
resetConfiguration()191     protected void resetConfiguration() {
192     }
193 
194     @Override
onStop()195     protected void onStop() {
196         Log.i(TAG, "onStop() called so stop the test =========================");
197         onStopTest();
198         super.onStop();
199     }
200 
201     @Override
onDestroy()202     protected void onDestroy() {
203         mAudioState = AUDIO_STATE_CLOSED;
204         super.onDestroy();
205     }
206 
updateEnabledWidgets()207     protected void updateEnabledWidgets() {
208         if (mOpenButton != null) {
209             mOpenButton.setBackgroundColor(mAudioState == AUDIO_STATE_OPEN ? COLOR_ACTIVE : COLOR_IDLE);
210             mStartButton.setBackgroundColor(mAudioState == AUDIO_STATE_STARTED ? COLOR_ACTIVE : COLOR_IDLE);
211             mPauseButton.setBackgroundColor(mAudioState == AUDIO_STATE_PAUSED ? COLOR_ACTIVE : COLOR_IDLE);
212             mStopButton.setBackgroundColor(mAudioState == AUDIO_STATE_STOPPED ? COLOR_ACTIVE : COLOR_IDLE);
213             mCloseButton.setBackgroundColor(mAudioState == AUDIO_STATE_CLOSED ? COLOR_ACTIVE : COLOR_IDLE);
214         }
215         setConfigViewsEnabled(mAudioState == AUDIO_STATE_CLOSED);
216     }
217 
setConfigViewsEnabled(boolean b)218     private void setConfigViewsEnabled(boolean b) {
219         for (StreamContext streamContext : mStreamContexts) {
220             if (streamContext.configurationView != null) {
221                 streamContext.configurationView.setChildrenEnabled(b);
222             }
223         }
224     }
225 
isOutput()226     abstract boolean isOutput();
227 
clearStreamContexts()228     public void clearStreamContexts() {
229         mStreamContexts.clear();
230     }
231 
addOutputStreamContext()232     public StreamContext addOutputStreamContext() {
233         StreamContext streamContext = new StreamContext();
234         streamContext.tester = AudioOutputTester.getInstance();
235         streamContext.configurationView = (StreamConfigurationView)
236                 findViewById(R.id.outputStreamConfiguration);
237         if (streamContext.configurationView == null) {
238             streamContext.configurationView = (StreamConfigurationView)
239                     findViewById(R.id.streamConfiguration);
240         }
241         if (streamContext.configurationView != null) {
242             streamContext.configurationView.setOutput(true);
243             streamContext.configurationView.setRequestedConfiguration(streamContext.tester.requestedConfiguration);
244             streamContext.configurationView.setActualConfiguration(streamContext.tester.actualConfiguration);
245         }
246         mStreamContexts.add(streamContext);
247         return streamContext;
248     }
249 
250 
addAudioOutputTester()251     public AudioOutputTester addAudioOutputTester() {
252         StreamContext streamContext = addOutputStreamContext();
253         return (AudioOutputTester) streamContext.tester;
254     }
255 
addInputStreamContext()256     public StreamContext addInputStreamContext() {
257         StreamContext streamContext = new StreamContext();
258         streamContext.tester = AudioInputTester.getInstance();
259         streamContext.configurationView = (StreamConfigurationView)
260                 findViewById(R.id.inputStreamConfiguration);
261         if (streamContext.configurationView == null) {
262             streamContext.configurationView = (StreamConfigurationView)
263                     findViewById(R.id.streamConfiguration);
264         }
265         if (streamContext.configurationView != null) {
266             streamContext.configurationView.setOutput(false);
267             streamContext.configurationView.setRequestedConfiguration(streamContext.tester.requestedConfiguration);
268             streamContext.configurationView.setActualConfiguration(streamContext.tester.actualConfiguration);
269         }
270         streamContext.tester = AudioInputTester.getInstance();
271         mStreamContexts.add(streamContext);
272         return streamContext;
273     }
274 
addAudioInputTester()275     public AudioInputTester addAudioInputTester() {
276         StreamContext streamContext = addInputStreamContext();
277         return (AudioInputTester) streamContext.tester;
278     }
279 
updateStreamConfigurationViews()280     void updateStreamConfigurationViews() {
281         for (StreamContext streamContext : mStreamContexts) {
282             if (streamContext.configurationView != null) {
283                 streamContext.configurationView.updateDisplay();
284             }
285         }
286     }
287 
getFirstInputStreamContext()288     StreamContext getFirstInputStreamContext() {
289         for (StreamContext streamContext : mStreamContexts) {
290             if (streamContext.isInput())
291                 return streamContext;
292         }
293         return null;
294     }
295 
getFirstOutputStreamContext()296     StreamContext getFirstOutputStreamContext() {
297         for (StreamContext streamContext : mStreamContexts) {
298             if (!streamContext.isInput())
299                 return streamContext;
300         }
301         return null;
302     }
303 
findAudioCommon()304     protected void findAudioCommon() {
305         mOpenButton = (Button) findViewById(R.id.button_open);
306         if (mOpenButton != null) {
307             mStartButton = (Button) findViewById(R.id.button_start);
308             mPauseButton = (Button) findViewById(R.id.button_pause);
309             mStopButton = (Button) findViewById(R.id.button_stop);
310             mCloseButton = (Button) findViewById(R.id.button_close);
311         }
312         mStreamContexts = new ArrayList<StreamContext>();
313 
314         queryNativeAudioParameters();
315 
316         mCallbackReturnStopBox = (CheckBox) findViewById(R.id.callbackReturnStop);
317         if (mCallbackReturnStopBox != null) {
318             mCallbackReturnStopBox.setOnClickListener(new View.OnClickListener() {
319                 @Override
320                 public void onClick(View v) {
321                     OboeAudioStream.setCallbackReturnStop(mCallbackReturnStopBox.isChecked());
322                 }
323             });
324         }
325         OboeAudioStream.setCallbackReturnStop(false);
326 
327         mStreamSniffer = new MyStreamSniffer();
328     }
329 
queryNativeAudioParameters()330     private void queryNativeAudioParameters() {
331         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
332         audioManagerSampleRate = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
333         String audioManagerFramesPerBurstText = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
334         audioManagerFramesPerBurst = Integer.parseInt(audioManagerFramesPerBurstText);
335     }
336 
setupEffects(int sessionId)337     abstract public void setupEffects(int sessionId);
338 
showErrorToast(String message)339     protected void showErrorToast(String message) {
340         showToast("Error: " + message);
341     }
342 
showToast(final String message)343     protected void showToast(final String message) {
344         runOnUiThread(new Runnable() {
345             @Override
346             public void run() {
347                 Toast.makeText(TestAudioActivity.this,
348                         message,
349                         Toast.LENGTH_SHORT).show();
350             }
351         });
352     }
353 
openAudio(View view)354     public void openAudio(View view) {
355         try {
356             openAudio();
357         } catch (Exception e) {
358             showErrorToast(e.getMessage());
359         }
360     }
361 
startAudio(View view)362     public void startAudio(View view) {
363         Log.i(TAG, "startAudio() called =======================================");
364         try {
365             startAudio();
366         } catch (Exception e) {
367             showErrorToast(e.getMessage());
368         }
369         keepScreenOn(true);
370     }
371 
keepScreenOn(boolean on)372     protected void keepScreenOn(boolean on) {
373         if (on) {
374             getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
375         } else {
376             getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
377         }
378     }
379 
stopAudio(View view)380     public void stopAudio(View view) {
381         stopAudio();
382         keepScreenOn(false);
383     }
384 
pauseAudio(View view)385     public void pauseAudio(View view) {
386         pauseAudio();
387         keepScreenOn(false);
388     }
389 
closeAudio(View view)390     public void closeAudio(View view) {
391         closeAudio();
392     }
393 
getSampleRate()394     public int getSampleRate() {
395         return mSampleRate;
396     }
397 
openAudio()398     public void openAudio() throws IOException {
399         closeAudio();
400 
401         int sampleRate = 0;
402 
403         // Open output streams then open input streams.
404         // This is so that the capacity of input stream can be expanded to
405         // match the burst size of the output for full duplex.
406         for (StreamContext streamContext : mStreamContexts) {
407             if (!streamContext.isInput()) {
408                 openStreamContext(streamContext);
409                 int streamSampleRate = streamContext.tester.actualConfiguration.getSampleRate();
410                 if (sampleRate == 0) {
411                     sampleRate = streamSampleRate;
412                 }
413             }
414         }
415         for (StreamContext streamContext : mStreamContexts) {
416             if (streamContext.isInput()) {
417                 if (sampleRate != 0) {
418                     streamContext.tester.requestedConfiguration.setSampleRate(sampleRate);
419                 }
420                 openStreamContext(streamContext);
421             }
422         }
423         updateEnabledWidgets();
424         mStreamSniffer.startStreamSniffer();
425     }
426 
427     /**
428      * @param deviceId
429      * @return true if the device is TYPE_BLUETOOTH_SCO
430      */
isScoDevice(int deviceId)431     boolean isScoDevice(int deviceId) {
432         if (deviceId == 0) return false; // Unspecified
433         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
434         final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
435         for (AudioDeviceInfo device : devices) {
436             if (device.getId() == deviceId) {
437                 return device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
438             }
439         }
440         return false;
441     }
442 
openStreamContext(StreamContext streamContext)443     private void openStreamContext(StreamContext streamContext) throws IOException {
444         StreamConfiguration requestedConfig = streamContext.tester.requestedConfiguration;
445         StreamConfiguration actualConfig = streamContext.tester.actualConfiguration;
446         requestedConfig.setFramesPerBurst(audioManagerFramesPerBurst);
447 
448         // Start Bluetooth SCO if needed.
449         if (isScoDevice(requestedConfig.getDeviceId()) && !mScoStarted) {
450             startBluetoothSco();
451             mScoStarted = true;
452         }
453 
454         streamContext.tester.open(); // OPEN the stream
455 
456         mSampleRate = actualConfig.getSampleRate();
457         mAudioState = AUDIO_STATE_OPEN;
458         int sessionId = actualConfig.getSessionId();
459         if (sessionId > 0) {
460             setupEffects(sessionId);
461         }
462         if (streamContext.configurationView != null) {
463             streamContext.configurationView.updateDisplay();
464         }
465     }
466 
467     // Native methods
startNative()468     private native int startNative();
pauseNative()469     private native int pauseNative();
stopNative()470     private native int stopNative();
setActivityType(int activityType)471     protected native void setActivityType(int activityType);
getFramesPerCallback()472     private native int getFramesPerCallback();
473 
startAudio()474     public void startAudio() throws IOException {
475         int result = startNative();
476         if (result < 0) {
477             showErrorToast("Start failed with " + result);
478             throw new IOException("startNative returned " + result);
479         } else {
480             for (StreamContext streamContext : mStreamContexts) {
481                 StreamConfigurationView configView = streamContext.configurationView;
482                 if (configView != null) {
483                     configView.updateDisplay();
484                 }
485             }
486             mAudioState = AUDIO_STATE_STARTED;
487             updateEnabledWidgets();
488         }
489     }
490 
toastPauseError(int result)491     protected void toastPauseError(int result) {
492         showErrorToast("Pause failed with " + result);
493     }
494 
pauseAudio()495     public void pauseAudio() {
496         int result = pauseNative();
497         if (result < 0) {
498             toastPauseError(result);
499         } else {
500             mAudioState = AUDIO_STATE_PAUSED;
501             updateEnabledWidgets();
502         }
503     }
504 
stopAudio()505     public void stopAudio() {
506         int result = stopNative();
507         if (result < 0) {
508             showErrorToast("Stop failed with " + result);
509         } else {
510             mAudioState = AUDIO_STATE_STOPPED;
511             updateEnabledWidgets();
512         }
513     }
514 
runTest()515     public void runTest() {}
516 
517     // This should only be called from UI events such as onStop or a button press.
onStopTest()518     public void onStopTest() {
519         stopTest();
520     }
521 
stopTest()522     public void stopTest() {
523         stopAudio();
524         closeAudio();
525     }
526 
stopAudioQuiet()527     public void stopAudioQuiet() {
528         stopNative();
529         mAudioState = AUDIO_STATE_STOPPED;
530         updateEnabledWidgets();
531     }
532 
533     // Make synchronized so we don't close from two streams at the same time.
closeAudio()534     public synchronized void closeAudio() {
535         if (mAudioState >= AUDIO_STATE_CLOSING) {
536             Log.d(TAG, "closeAudio() already closing");
537             return;
538         }
539         mAudioState = AUDIO_STATE_CLOSING;
540 
541         mStreamSniffer.stopStreamSniffer();
542         // Close output streams first because legacy callbacks may still be active
543         // and an output stream may be calling the input stream.
544         for (StreamContext streamContext : mStreamContexts) {
545             if (!streamContext.isInput()) {
546                 streamContext.tester.close();
547             }
548         }
549         for (StreamContext streamContext : mStreamContexts) {
550             if (streamContext.isInput()) {
551                 streamContext.tester.close();
552             }
553         }
554 
555         if (mScoStarted) {
556             stopBluetoothSco();
557             mScoStarted = false;
558         }
559 
560         mAudioState = AUDIO_STATE_CLOSED;
561         updateEnabledWidgets();
562     }
563 
startBluetoothSco()564     void startBluetoothSco() {
565         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
566         myAudioMgr.startBluetoothSco();
567     }
568 
stopBluetoothSco()569     void stopBluetoothSco() {
570         AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
571         myAudioMgr.stopBluetoothSco();
572     }
573 
574 }
575