1# Copyright 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""An adapter to remotely access the audio facade on DUT.""" 6 7import os 8import tempfile 9 10 11class AudioFacadeError(Exception): 12 """Errors in audio facade.""" 13 pass 14 15 16class AudioFacadeRemoteAdapter(object): 17 """AudioFacadeRemoteAdapter is an adapter to remotely control DUT audio. 18 19 The Autotest host object representing the remote DUT, passed to this 20 class on initialization, can be accessed from its _client property. 21 22 """ 23 def __init__(self, host, remote_facade_proxy): 24 """Construct an AudioFacadeRemoteAdapter. 25 26 @param host: Host object representing a remote host. 27 @param remote_facade_proxy: RemoteFacadeProxy object. 28 29 """ 30 self._client = host 31 self._proxy = remote_facade_proxy 32 33 34 @property 35 def _audio_proxy(self): 36 """Gets the proxy to DUT audio facade. 37 38 @return XML RPC proxy to DUT audio facade. 39 40 """ 41 return self._proxy.audio 42 43 44 def playback(self, client_path, data_format, blocking=False): 45 """Playback an audio file on DUT. 46 47 @param client_path: The path to the file on DUT. 48 @param data_format: A dict containing data format including 49 file_type, sample_format, channel, and rate. 50 file_type: file type e.g. 'raw' or 'wav'. 51 sample_format: One of the keys in 52 audio_data.SAMPLE_FORMAT. 53 channel: number of channels. 54 rate: sampling rate. 55 @param blocking: Blocks this call until playback finishes. 56 57 @returns: True 58 59 """ 60 self._audio_proxy.playback( 61 client_path, data_format, blocking) 62 63 64 def stop_playback(self): 65 """Stops playback process.""" 66 self._audio_proxy.stop_playback() 67 68 69 def set_playback_file(self, path): 70 """Copies a file to client. 71 72 @param path: A path to the file. 73 74 @returns: A new path to the file on client. 75 76 """ 77 _, ext = os.path.splitext(path) 78 _, client_file_path = tempfile.mkstemp( 79 prefix='playback_', suffix=ext) 80 self._client.send_file(path, client_file_path) 81 return client_file_path 82 83 84 def start_recording(self, data_format): 85 """Starts recording an audio file on DUT. 86 87 @param data_format: A dict containing: 88 file_type: 'raw'. 89 sample_format: 'S16_LE' for 16-bit signed integer in 90 little-endian. 91 channel: channel number. 92 rate: sampling rate. 93 94 @returns: True 95 96 """ 97 self._audio_proxy.start_recording(data_format) 98 return True 99 100 101 def stop_recording(self): 102 """Stops recording on DUT. 103 104 @returns: the path to the recorded file on DUT. 105 106 @raises: AudioFacadeError if recorded path is None 107 """ 108 path = self._audio_proxy.stop_recording() 109 if not path: 110 raise AudioFacadeError( 111 'Recording does not work on DUT. ' 112 'Suggest checking messages on DUT') 113 return path 114 115 116 def start_listening(self, data_format): 117 """Starts listening horword on DUT. 118 119 @param data_format: A dict containing: 120 file_type: 'raw'. 121 sample_format: 'S16_LE' for 16-bit signed integer in 122 little-endian. 123 channel: channel number. 124 rate: sampling rate. 125 126 @returns: True 127 128 """ 129 self._audio_proxy.start_listening(data_format) 130 return True 131 132 133 def stop_listening(self): 134 """Stops listening on DUT. 135 136 @returns: the path to the recorded file on DUT. 137 138 @raises: AudioFacadeError if hotwording does not work on DUT. 139 """ 140 path = self._audio_proxy.stop_listening() 141 if not path: 142 raise AudioFacadeError('Listening does not work on DUT.') 143 return path 144 145 146 def get_recorded_file(self, remote_path, local_path): 147 """Gets a recorded file from DUT. 148 149 @param remote_path: The path to the file on DUT. 150 @param local_path: The local path for copy destination. 151 152 """ 153 self._client.get_file(remote_path, local_path) 154 155 156 def set_selected_output_volume(self, volume): 157 """Sets the selected output volume on DUT. 158 159 @param volume: the volume to be set(0-100). 160 161 """ 162 self._audio_proxy.set_selected_output_volume(volume) 163 164 165 def set_input_gain(self, gain): 166 """Sets the system capture gain. 167 168 @param gain: the capture gain in db*100 (100 = 1dB) 169 170 """ 171 self._audio_proxy.set_input_gain(gain) 172 173 174 def set_selected_node_types(self, output_node_types, input_node_types): 175 """Set selected node types. 176 177 The node types are defined in cras_utils.CRAS_NODE_TYPES. 178 179 @param output_node_types: A list of output node types. 180 None to skip setting. 181 @param input_node_types: A list of input node types. 182 None to skip setting. 183 184 """ 185 self._audio_proxy.set_selected_node_types( 186 output_node_types, input_node_types) 187 188 189 def get_selected_node_types(self): 190 """Gets the selected output and input node types on DUT. 191 192 @returns: A tuple (output_node_types, input_node_types) where each 193 field is a list of selected node types defined in 194 cras_utils.CRAS_NODE_TYPES. 195 196 """ 197 return self._audio_proxy.get_selected_node_types() 198 199 200 def get_plugged_node_types(self): 201 """Gets the plugged output and input node types on DUT. 202 203 @returns: A tuple (output_node_types, input_node_types) where each 204 field is a list of plugged node types defined in 205 cras_utils.CRAS_NODE_TYPES. 206 207 """ 208 return self._audio_proxy.get_plugged_node_types() 209 210 211 def dump_diagnostics(self, file_path): 212 """Dumps audio diagnostics results to a file. 213 214 @param file_path: The path to dump results. 215 216 @returns: True 217 218 """ 219 _, remote_path = tempfile.mkstemp( 220 prefix='audio_dump_', suffix='.txt') 221 self._audio_proxy.dump_diagnostics(remote_path) 222 self._client.get_file(remote_path, file_path) 223 return True 224 225 226 def start_counting_signal(self, signal_name): 227 """Starts counting DBus signal from Cras. 228 229 @param signal_name: Signal of interest. 230 231 """ 232 self._audio_proxy.start_counting_signal(signal_name) 233 234 235 def stop_counting_signal(self): 236 """Stops counting DBus signal from Cras. 237 238 @returns: Number of signals counted starting from last 239 start_counting_signal call. 240 241 """ 242 return self._audio_proxy.stop_counting_signal() 243 244 245 def wait_for_unexpected_nodes_changed(self, timeout_secs): 246 """Waits for unexpected nodes changed signal. 247 248 @param timeout_secs: Timeout in seconds for waiting. 249 250 """ 251 self._audio_proxy.wait_for_unexpected_nodes_changed(timeout_secs) 252 253 254 def set_chrome_active_volume(self, volume): 255 """Sets the active audio output volume using chrome.audio API. 256 257 @param volume: Volume to set (0~100). 258 259 """ 260 self._audio_proxy.set_chrome_active_volume(volume) 261 262 263 def set_chrome_mute(self, mute): 264 """Mutes the active audio output using chrome.audio API. 265 266 @param mute: True to mute. False otherwise. 267 268 """ 269 self._audio_proxy.set_chrome_mute(mute) 270 271 def check_audio_stream_at_selected_device(self): 272 """Checks the audio output is at expected node""" 273 self._audio_proxy.check_audio_stream_at_selected_device() 274 275 276 def get_chrome_active_volume_mute(self): 277 """Gets the volume state of active audio output using chrome.audio API. 278 279 @param returns: A tuple (volume, mute), where volume is 0~100, and mute 280 is True if node is muted, False otherwise. 281 282 """ 283 return self._audio_proxy.get_chrome_active_volume_mute() 284 285 286 def set_chrome_active_node_type(self, output_node_type, input_node_type): 287 """Sets active node type through chrome.audio API. 288 289 The node types are defined in cras_utils.CRAS_NODE_TYPES. 290 The current active node will be disabled first if the new active node 291 is different from the current one. 292 293 @param output_node_type: A node type defined in 294 cras_utils.CRAS_NODE_TYPES. None to skip. 295 @param input_node_type: A node type defined in 296 cras_utils.CRAS_NODE_TYPES. None to skip 297 298 """ 299 self._audio_proxy.set_chrome_active_node_type( 300 output_node_type, input_node_type) 301 302 303 def start_arc_recording(self): 304 """Starts recording using microphone app in container.""" 305 self._audio_proxy.start_arc_recording() 306 307 308 def stop_arc_recording(self): 309 """Checks the recording is stopped and gets the recorded path. 310 311 The recording duration of microphone app is fixed, so this method just 312 asks Cros device to copy the recorded result from container to a path 313 on Cros device. 314 315 @returns: Path to the recorded file on DUT. 316 317 """ 318 return self._audio_proxy.stop_arc_recording() 319 320 321 def set_arc_playback_file(self, path): 322 """Copies the file from server to Cros host and into container. 323 324 @param path: Path to the file on server. 325 326 @returns: Path to the file in container on Cros host. 327 328 """ 329 client_file_path = self.set_playback_file(path) 330 return self._audio_proxy.set_arc_playback_file(client_file_path) 331 332 333 def start_arc_playback(self, path): 334 """Starts playback through ARC on Cros host. 335 336 @param path: Path to the file in container on Cros host. 337 338 """ 339 self._audio_proxy.start_arc_playback(path) 340 341 342 def stop_arc_playback(self): 343 """Stops playback through ARC on Cros host.""" 344 self._audio_proxy.stop_arc_playback() 345