• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 android.app.Dialog;
20 import android.content.Context;
21 import android.graphics.Color;
22 import android.media.AudioDeviceCallback;
23 import android.media.AudioDeviceInfo;
24 import android.media.AudioManager;
25 import android.os.Bundle;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.ViewGroup;
30 import android.webkit.WebView;
31 import android.widget.AdapterView;
32 import android.widget.ArrayAdapter;
33 import android.widget.Spinner;
34 
35 import com.android.cts.verifier.R;
36 import com.android.cts.verifier.audio.audiolib.AudioDeviceUtils;
37 import com.android.cts.verifier.audio.audiolib.WaveScopeView;
38 
39 // MegaAudio
40 import org.hyphonate.megaaudio.common.BuilderBase;
41 import org.hyphonate.megaaudio.common.StreamBase;
42 import org.hyphonate.megaaudio.duplex.DuplexAudioManager;
43 import org.hyphonate.megaaudio.player.AudioSourceProvider;
44 import org.hyphonate.megaaudio.player.sources.SparseChannelAudioSourceProvider;
45 import org.hyphonate.megaaudio.recorder.AudioSinkProvider;
46 import org.hyphonate.megaaudio.recorder.sinks.AppCallback;
47 import org.hyphonate.megaaudio.recorder.sinks.AppCallbackAudioSinkProvider;
48 
49 class AudioLoopbackCalibrationDialog extends Dialog
50         implements OnClickListener, AppCallback, AdapterView.OnItemSelectedListener {
51     public static final String TAG = "AudioLoopbackCalibrationDialog";
52 
53     private Context mContext;
54     private AudioManager mAudioManager;
55 
56     private DuplexAudioManager mDuplexAudioManager;
57 
58     private AudioSourceProvider mLeftSineSourceProvider;
59     private AudioSourceProvider mRightSineSourceProvider;
60 
61     private AudioSinkProvider mAudioSinkProvider;
62     private AppCallback mAudioCallbackHandler;
63 
64     private boolean mPlaying;
65     private int mNumDisplayChannels;
66     private WaveScopeView mWaveView = null;
67 
68     private WebView mInfoPanel;
69 
70     Spinner mInputsSpinner;
71     Spinner mOutputsSpinner;
72 
73     AudioDeviceInfo[] mInputDevices;
74     AudioDeviceInfo[] mOutputDevices;
75 
76     AudioDeviceInfo mSelectedInputDevice;
77     AudioDeviceInfo mSelectedOutputDevice;
78 
AudioLoopbackCalibrationDialog(Context context)79     AudioLoopbackCalibrationDialog(Context context) {
80         super(context);
81 
82         mContext = context;
83 
84         mAudioManager = context.getSystemService(AudioManager.class);
85 
86         mAudioCallbackHandler = this;
87 
88         mLeftSineSourceProvider = new SparseChannelAudioSourceProvider(
89                 SparseChannelAudioSourceProvider.CHANNELMASK_LEFT);
90         mRightSineSourceProvider = new SparseChannelAudioSourceProvider(
91                 SparseChannelAudioSourceProvider.CHANNELMASK_RIGHT);
92         mAudioSinkProvider =
93                 new AppCallbackAudioSinkProvider(mAudioCallbackHandler);
94 
95         mDuplexAudioManager = new DuplexAudioManager(null, null);
96     }
97 
98     @Override
onCreate(Bundle savedInstanceState)99     public void onCreate(Bundle savedInstanceState) {
100         super.onCreate(savedInstanceState);
101 
102         setTitle(mContext.getString(R.string.audio_datapaths_calibratetitle));
103 
104         setContentView(R.layout.audio_loopback_calibration_dialog);
105         getWindow().setLayout(
106                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
107         mWaveView = (WaveScopeView) findViewById(R.id.uap_recordWaveView);
108         mWaveView.setBackgroundColor(Color.DKGRAY);
109         mWaveView.setTraceColor(Color.WHITE);
110         mWaveView.setDisplayMaxMagnitudes(true);
111         mWaveView.setDisplayLimits(true);
112         mWaveView.setDisplayZero(true);
113 
114         findViewById(R.id.audio_calibration_left).setOnClickListener(this);
115         findViewById(R.id.audio_calibration_right).setOnClickListener(this);
116         findViewById(R.id.audio_calibration_stop).setOnClickListener(this);
117         findViewById(R.id.audio_calibration_done).setOnClickListener(this);
118 
119         // Setup the Devices spinners
120         mInputsSpinner = (Spinner) findViewById(R.id.input_devices_spinner);
121         mInputsSpinner.setOnItemSelectedListener(this);
122 
123         mOutputsSpinner = (Spinner) findViewById(R.id.output_devices_spinner);
124         mOutputsSpinner.setOnItemSelectedListener(this);
125 
126         mAudioManager.registerAudioDeviceCallback(new AudioDeviceConnectionCallback(), null);
127 
128         mInfoPanel = (WebView) findViewById(R.id.audio_calibration_info);
129         mInfoPanel.loadUrl("file:///android_asset/html/AudioCalibrationInfo.html");
130     }
131 
fillAdapter(AudioDeviceInfo[] deviceInfos)132     ArrayAdapter fillAdapter(AudioDeviceInfo[] deviceInfos) {
133         ArrayAdapter arrayAdapter =
134                 new ArrayAdapter(mContext, android.R.layout.simple_spinner_item);
135         arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
136         arrayAdapter.add(mContext.getString(R.string.audio_loopback_calibrate_default));
137         if (deviceInfos != null) {
138             for (AudioDeviceInfo devInfo : deviceInfos) {
139                 String devTypeString = AudioDeviceUtils.getDeviceTypeName(devInfo.getType());
140                 String devNameString = devInfo.getAddress();
141                 StringBuilder sb = new StringBuilder();
142                 sb.append(devTypeString);
143                 // We won't filter out "meaningless" address strings
144                 // like "0" and "card=1;device=0".
145                 if (devNameString.length() != 0) {
146                     sb.append(" (").append(devNameString).append(")");
147                 }
148                 arrayAdapter.add(sb.toString());
149             }
150         }
151         return arrayAdapter;
152     }
153 
154     @Override
onStop()155     public void onStop() {
156         stopAudio();
157     }
158 
159     private static final int CHANNEL_LEFT = 0;
160     private static final int CHANNEL_RIGHT = 1;
161 
startAudio(int channel)162     void startAudio(int channel) {
163         stopAudio();
164 
165         AudioSourceProvider sourceProvider =
166                 channel == CHANNEL_LEFT ? mLeftSineSourceProvider : mRightSineSourceProvider;
167 
168         // Player
169         mDuplexAudioManager.setSources(sourceProvider, mAudioSinkProvider);
170         mDuplexAudioManager.setPlayerRouteDevice(mSelectedOutputDevice);
171         mDuplexAudioManager.setNumPlayerChannels(2);
172 
173         // Recorder
174         mDuplexAudioManager.setRecorderRouteDevice(mSelectedInputDevice);
175         mNumDisplayChannels = 2;
176         if (mSelectedInputDevice != null && AudioDeviceUtils.isMicDevice(mSelectedInputDevice)) {
177             mNumDisplayChannels = 1;
178         }
179         Log.i(TAG, "mNumDisplayChannels:" + mNumDisplayChannels);
180         mWaveView.setNumChannels(mNumDisplayChannels);
181         mDuplexAudioManager.setNumRecorderChannels(mNumDisplayChannels);
182 
183         // Open the streams.
184         // Note AudioSources and AudioSinks get allocated at this point
185         if (mDuplexAudioManager.buildStreams(BuilderBase.TYPE_OBOE, BuilderBase.TYPE_OBOE)
186                 ==  StreamBase.OK
187                 && mDuplexAudioManager.start() == StreamBase.OK) {
188             mPlaying = true;
189         } else {
190             mPlaying = false;
191         }
192     }
193 
stopAudio()194     void stopAudio() {
195         if (mPlaying) {
196             mDuplexAudioManager.stop();
197             mPlaying = false;
198         }
199     }
200 
201     //
202     // OnClickListener
203     //
onClick(View v)204     public void onClick(View v) {
205         if (v.getId() == R.id.audio_calibration_left) {
206             startAudio(CHANNEL_LEFT);
207         } else if (v.getId() == R.id.audio_calibration_right) {
208             startAudio(CHANNEL_RIGHT);
209         } else if (v.getId() == R.id.audio_calibration_stop) {
210             stopAudio();
211         } else if (v.getId() == R.id.audio_calibration_done) {
212             dismiss();
213         }
214     }
215 
216     //
217     // MegaAudio AppCallback overrides
218     //
219     @Override
onDataReady(float[] audioData, int numFrames)220     public void onDataReady(float[] audioData, int numFrames) {
221         mWaveView.setPCMFloatBuff(audioData, mNumDisplayChannels, numFrames);
222     }
223 
224     //
225     // AudioDeviceCallback overrides
226     //
227     private class AudioDeviceConnectionCallback extends AudioDeviceCallback {
stateChangeHandler()228         void stateChangeHandler() {
229             stopAudio();
230 
231             mInputDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
232             mInputsSpinner.setAdapter(fillAdapter(mInputDevices));
233 
234             mOutputDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
235             mOutputsSpinner.setAdapter(fillAdapter(mOutputDevices));
236         }
237 
238         @Override
onAudioDevicesAdded(AudioDeviceInfo[] addedDevices)239         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
240             stateChangeHandler();
241         }
242 
243         @Override
onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices)244         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
245             stateChangeHandler();
246         }
247     }
248 
249     //
250     // AdapterView.OnItemSelectedListener overrides
251     //
252     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)253     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
254         stopAudio();
255         if (parent.getId() == R.id.input_devices_spinner) {
256             if (position == 0) {
257                 mSelectedInputDevice = null;
258             } else {
259                 mSelectedInputDevice = mInputDevices[position - 1];
260             }
261         } else {
262             if (position == 0) {
263                 mSelectedOutputDevice = null;
264             } else {
265                 mSelectedOutputDevice = mOutputDevices[position - 1];
266             }
267         }
268     }
269 
270     @Override
onNothingSelected(AdapterView<?> parent)271     public void onNothingSelected(AdapterView<?> parent) {
272         // NOP
273     }
274 }
275