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