• 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.oboe.samples.hellooboe;
18 
19 import android.app.Activity;
20 import android.media.AudioManager;
21 import android.os.Build;
22 import android.os.Bundle;
23 import androidx.core.view.MotionEventCompat;
24 
25 import android.view.MotionEvent;
26 import android.view.View;
27 import android.widget.AdapterView;
28 import android.widget.ArrayAdapter;
29 import android.widget.SimpleAdapter;
30 import android.widget.Spinner;
31 import android.widget.TextView;
32 
33 import com.google.oboe.samples.audio_device.AudioDeviceListEntry;
34 import com.google.oboe.samples.audio_device.AudioDeviceSpinner;
35 
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Locale;
40 import java.util.Timer;
41 import java.util.TimerTask;
42 
43 public class MainActivity extends Activity {
44 
45     private static final String TAG = MainActivity.class.getName();
46     private static final long UPDATE_LATENCY_EVERY_MILLIS = 1000;
47     private static final Integer[] CHANNEL_COUNT_OPTIONS = {1, 2, 3, 4, 5, 6, 7, 8};
48     // Default to Stereo (OPTIONS is zero-based array so index 1 = 2 channels)
49     private static final int CHANNEL_COUNT_DEFAULT_OPTION_INDEX = 1;
50     private static final int[] BUFFER_SIZE_OPTIONS = {0, 1, 2, 4, 8};
51     private static final String[] AUDIO_API_OPTIONS = {"Unspecified", "OpenSL ES", "AAudio"};
52     // Default all other spinners to the first option on the list
53     private static final int SPINNER_DEFAULT_OPTION_INDEX = 0;
54 
55     private Spinner mAudioApiSpinner;
56     private AudioDeviceSpinner mPlaybackDeviceSpinner;
57     private Spinner mChannelCountSpinner;
58     private Spinner mBufferSizeSpinner;
59     private TextView mLatencyText;
60     private Timer mLatencyUpdater;
61 
62     /*
63      * Hook to user control to start / stop audio playback:
64      *    touch-down: start, and keeps on playing
65      *    touch-up: stop.
66      * simply pass the events to native side.
67      */
68     @Override
onTouchEvent(MotionEvent event)69     public boolean onTouchEvent(MotionEvent event) {
70         int action = MotionEventCompat.getActionMasked(event);
71         switch (action) {
72             case (MotionEvent.ACTION_DOWN):
73                 PlaybackEngine.setToneOn(true);
74                 break;
75             case (MotionEvent.ACTION_UP):
76                 PlaybackEngine.setToneOn(false);
77                 break;
78         }
79         return super.onTouchEvent(event);
80     }
81 
82     @Override
onCreate(Bundle savedInstanceState)83     protected void onCreate(Bundle savedInstanceState) {
84         super.onCreate(savedInstanceState);
85         setContentView(R.layout.activity_main);
86         mLatencyText = findViewById(R.id.latencyText);
87         setupAudioApiSpinner();
88         setupPlaybackDeviceSpinner();
89         setupChannelCountSpinner();
90         setupBufferSizeSpinner();
91 
92     }
93     /*
94     * Creating engine in onResume() and destroying in onPause() so the stream retains exclusive
95     * mode only while in focus. This allows other apps to reclaim exclusive stream mode.
96     */
97     @Override
onResume()98     protected void onResume() {
99         super.onResume();
100         PlaybackEngine.create(this);
101         setupLatencyUpdater();
102         // Return the spinner states to their default value
103         mChannelCountSpinner.setSelection(CHANNEL_COUNT_DEFAULT_OPTION_INDEX);
104         mPlaybackDeviceSpinner.setSelection(SPINNER_DEFAULT_OPTION_INDEX);
105         mBufferSizeSpinner.setSelection(SPINNER_DEFAULT_OPTION_INDEX);
106         mAudioApiSpinner.setSelection(SPINNER_DEFAULT_OPTION_INDEX);
107     }
108 
109     @Override
onPause()110     protected void onPause() {
111        if (mLatencyUpdater != null) mLatencyUpdater.cancel();
112        PlaybackEngine.delete();
113        super.onPause();
114     }
115 
setupChannelCountSpinner()116     private void setupChannelCountSpinner() {
117         mChannelCountSpinner = findViewById(R.id.channelCountSpinner);
118 
119         ArrayAdapter<Integer> channelCountAdapter = new ArrayAdapter<Integer>(this, R.layout.channel_counts_spinner, CHANNEL_COUNT_OPTIONS);
120         mChannelCountSpinner.setAdapter(channelCountAdapter);
121         mChannelCountSpinner.setSelection(CHANNEL_COUNT_DEFAULT_OPTION_INDEX);
122 
123         mChannelCountSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
124             @Override
125             public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
126                 PlaybackEngine.setChannelCount(CHANNEL_COUNT_OPTIONS[mChannelCountSpinner.getSelectedItemPosition()]);
127             }
128 
129             @Override
130             public void onNothingSelected(AdapterView<?> adapterView) {
131 
132             }
133         });
134     }
135 
setupBufferSizeSpinner()136     private void setupBufferSizeSpinner() {
137         mBufferSizeSpinner = findViewById(R.id.bufferSizeSpinner);
138         mBufferSizeSpinner.setAdapter(new SimpleAdapter(
139                 this,
140                 createBufferSizeOptionsList(), // list of buffer size options
141                 R.layout.buffer_sizes_spinner, // the xml layout
142                 new String[]{getString(R.string.buffer_size_description_key)}, // field to display
143                 new int[]{R.id.bufferSizeOption} // View to show field in
144         ));
145 
146         mBufferSizeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
147             @Override
148             public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
149                 PlaybackEngine.setBufferSizeInBursts(getBufferSizeInBursts());
150             }
151 
152             @Override
153             public void onNothingSelected(AdapterView<?> adapterView) {
154 
155             }
156         });
157     }
158 
setupPlaybackDeviceSpinner()159     private void setupPlaybackDeviceSpinner() {
160         mPlaybackDeviceSpinner = findViewById(R.id.playbackDevicesSpinner);
161 
162         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
163             mPlaybackDeviceSpinner.setDirectionType(AudioManager.GET_DEVICES_OUTPUTS);
164             mPlaybackDeviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
165                 @Override
166                 public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
167                     PlaybackEngine.setAudioDeviceId(getPlaybackDeviceId());
168                 }
169 
170                 @Override
171                 public void onNothingSelected(AdapterView<?> adapterView) {
172 
173                 }
174             });
175         }
176     }
177 
setupAudioApiSpinner()178     private void setupAudioApiSpinner() {
179         mAudioApiSpinner = findViewById(R.id.audioApiSpinner);
180         mAudioApiSpinner.setAdapter(new SimpleAdapter(
181                 this,
182                 createAudioApisOptionsList(),
183                 R.layout.audio_apis_spinner, // the xml layout
184                 new String[]{getString(R.string.audio_api_description_key)}, // field to display
185                 new int[]{R.id.audioApiOption} // View to show field in
186         ));
187 
188         mAudioApiSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
189             @Override
190             public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
191                 PlaybackEngine.setAudioApi(i);
192             }
193 
194             @Override
195             public void onNothingSelected(AdapterView<?> adapterView) {
196 
197             }
198         });
199     }
200 
getPlaybackDeviceId()201     private int getPlaybackDeviceId() {
202         return ((AudioDeviceListEntry) mPlaybackDeviceSpinner.getSelectedItem()).getId();
203     }
204 
getBufferSizeInBursts()205     private int getBufferSizeInBursts() {
206         @SuppressWarnings("unchecked")
207         HashMap<String, String> selectedOption = (HashMap<String, String>)
208                 mBufferSizeSpinner.getSelectedItem();
209 
210         String valueKey = getString(R.string.buffer_size_value_key);
211 
212         // parseInt will throw a NumberFormatException if the string doesn't contain a valid integer
213         // representation. We don't need to worry about this because the values are derived from
214         // the BUFFER_SIZE_OPTIONS int array.
215         return Integer.parseInt(selectedOption.get(valueKey));
216     }
217 
setupLatencyUpdater()218     private void setupLatencyUpdater() {
219         //Update the latency every 1s
220         TimerTask latencyUpdateTask = new TimerTask() {
221             @Override
222             public void run() {
223                 final String latencyStr;
224                 if (PlaybackEngine.isLatencyDetectionSupported()) {
225                     double latency = PlaybackEngine.getCurrentOutputLatencyMillis();
226                     if (latency >= 0) {
227                         latencyStr = String.format(Locale.getDefault(), "%.2fms", latency);
228                     } else {
229                         latencyStr = "Unknown";
230                     }
231                 } else {
232                     latencyStr = getString(R.string.only_supported_on_api_26);
233                 }
234 
235                 runOnUiThread(new Runnable() {
236                     @Override
237                     public void run() {
238                         mLatencyText.setText(getString(R.string.latency, latencyStr));
239                     }
240                 });
241             }
242         };
243         mLatencyUpdater = new Timer();
244         mLatencyUpdater.schedule(latencyUpdateTask, 0, UPDATE_LATENCY_EVERY_MILLIS);
245     }
246 
247     /**
248      * Creates a list of buffer size options which can be used to populate a SimpleAdapter.
249      * Each option has a description and a value. The description is always equal to the value,
250      * except when the value is zero as this indicates that the buffer size should be set
251      * automatically by the audio engine
252      *
253      * @return list of buffer size options
254      */
createBufferSizeOptionsList()255     private List<HashMap<String, String>> createBufferSizeOptionsList() {
256 
257         ArrayList<HashMap<String, String>> bufferSizeOptions = new ArrayList<>();
258 
259         for (int i : BUFFER_SIZE_OPTIONS) {
260             HashMap<String, String> option = new HashMap<>();
261             String strValue = String.valueOf(i);
262             String description = (i == 0) ? getString(R.string.automatic) : strValue;
263             option.put(getString(R.string.buffer_size_description_key), description);
264             option.put(getString(R.string.buffer_size_value_key), strValue);
265 
266             bufferSizeOptions.add(option);
267         }
268 
269         return bufferSizeOptions;
270     }
271 
createAudioApisOptionsList()272     private List<HashMap<String, String>> createAudioApisOptionsList() {
273 
274         ArrayList<HashMap<String, String>> audioApiOptions = new ArrayList<>();
275 
276         for (int i = 0; i < AUDIO_API_OPTIONS.length; i++) {
277             HashMap<String, String> option = new HashMap<>();
278             option.put(getString(R.string.buffer_size_description_key), AUDIO_API_OPTIONS[i]);
279             option.put(getString(R.string.buffer_size_value_key), String.valueOf(i));
280             audioApiOptions.add(option);
281         }
282         return audioApiOptions;
283     }
284 }
285