# Copyright 2014 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """An adapter to remotely access the audio facade on DUT.""" import os import tempfile class AudioFacadeError(Exception): """Errors in audio facade.""" pass class AudioFacadeRemoteAdapter(object): """AudioFacadeRemoteAdapter is an adapter to remotely control DUT audio. The Autotest host object representing the remote DUT, passed to this class on initialization, can be accessed from its _client property. """ def __init__(self, host, remote_facade_proxy): """Construct an AudioFacadeRemoteAdapter. @param host: Host object representing a remote host. @param remote_facade_proxy: RemoteFacadeProxy object. """ self._client = host self._proxy = remote_facade_proxy @property def _audio_proxy(self): """Gets the proxy to DUT audio facade. @return XML RPC proxy to DUT audio facade. """ return self._proxy.audio def playback(self, client_path, data_format, blocking=False, node_type=None, block_size=None): """Playback an audio file on DUT. @param client_path: The path to the file on DUT. @param data_format: A dict containing data format including file_type, sample_format, channel, and rate. file_type: file type e.g. 'raw' or 'wav'. sample_format: One of the keys in audio_data.SAMPLE_FORMAT. channel: number of channels. rate: sampling rate. @param blocking: Blocks this call until playback finishes. @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES that we like to pin at. None to have the playback on active selected device. @param block_size: The number for frames per callback. """ self._audio_proxy.playback( client_path, data_format, blocking, node_type, block_size) def stop_playback(self): """Stops playback process.""" self._audio_proxy.stop_playback() def set_playback_file(self, path): """Copies a file to client. @param path: A path to the file. @returns: A new path to the file on client. """ _, ext = os.path.splitext(path) _, client_file_path = tempfile.mkstemp( prefix='playback_', suffix=ext) self._client.send_file(path, client_file_path) return client_file_path def start_recording(self, data_format, node_type=None, block_size=None): """Starts recording an audio file on DUT. @param data_format: A dict containing: file_type: 'raw'. sample_format: 'S16_LE' for 16-bit signed integer in little-endian. channel: channel number. rate: sampling rate. @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES that we like to pin at. None to have the recording from active selected device. @param block_size: The number for frames per callback. @returns: True """ self._audio_proxy.start_recording(data_format, node_type, block_size) return True def stop_recording(self, node_type=None): """Stops recording on DUT. @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES that we like to stop recording from. None to stop the recording from active selected device. @returns: the path to the recorded file on DUT. @raises: AudioFacadeError if recorded path is None """ path = self._audio_proxy.stop_recording(node_type) if not path: raise AudioFacadeError( 'Recording does not work on DUT. ' 'Suggest checking messages on DUT') return path def start_listening(self, data_format): """Starts listening horword on DUT. @param data_format: A dict containing: file_type: 'raw'. sample_format: 'S16_LE' for 16-bit signed integer in little-endian. channel: channel number. rate: sampling rate. @returns: True """ self._audio_proxy.start_listening(data_format) return True def stop_listening(self): """Stops listening on DUT. @returns: the path to the recorded file on DUT. @raises: AudioFacadeError if hotwording does not work on DUT. """ path = self._audio_proxy.stop_listening() if not path: raise AudioFacadeError('Listening does not work on DUT.') return path def get_recorded_file(self, remote_path, local_path): """Gets a recorded file from DUT. @param remote_path: The path to the file on DUT. @param local_path: The local path for copy destination. """ self._client.get_file(remote_path, local_path) def set_selected_output_volume(self, volume): """Sets the selected output volume on DUT. @param volume: the volume to be set(0-100). """ self._audio_proxy.set_selected_output_volume(volume) def set_input_gain(self, gain): """Sets the system capture gain. @param gain: the capture gain in db*100 (100 = 1dB) """ self._audio_proxy.set_input_gain(gain) def set_selected_node_types(self, output_node_types, input_node_types): """Set selected node types. The node types are defined in cras_utils.CRAS_NODE_TYPES. @param output_node_types: A list of output node types. None to skip setting. @param input_node_types: A list of input node types. None to skip setting. """ self._audio_proxy.set_selected_node_types( output_node_types, input_node_types) def get_selected_node_types(self): """Gets the selected output and input node types on DUT. @returns: A tuple (output_node_types, input_node_types) where each field is a list of selected node types defined in cras_utils.CRAS_NODE_TYPES. """ return self._audio_proxy.get_selected_node_types() def get_plugged_node_types(self): """Gets the plugged output and input node types on DUT. @returns: A tuple (output_node_types, input_node_types) where each field is a list of plugged node types defined in cras_utils.CRAS_NODE_TYPES. """ return self._audio_proxy.get_plugged_node_types() def dump_diagnostics(self, file_path): """Dumps audio diagnostics results to a file. @param file_path: The path to dump results. @returns: True """ _, remote_path = tempfile.mkstemp( prefix='audio_dump_', suffix='.txt') self._audio_proxy.dump_diagnostics(remote_path) self._client.get_file(remote_path, file_path) return True def start_counting_signal(self, signal_name): """Starts counting DBus signal from Cras. @param signal_name: Signal of interest. """ self._audio_proxy.start_counting_signal(signal_name) def stop_counting_signal(self): """Stops counting DBus signal from Cras. @returns: Number of signals counted starting from last start_counting_signal call. """ return self._audio_proxy.stop_counting_signal() def wait_for_unexpected_nodes_changed(self, timeout_secs): """Waits for unexpected nodes changed signal. @param timeout_secs: Timeout in seconds for waiting. """ self._audio_proxy.wait_for_unexpected_nodes_changed(timeout_secs) def get_chrome_audio_availablity(self): """Gets if the chrome.audio API is ready. @returns: chrome.audio is ready or not. """ return self._audio_proxy.get_audio_availability() def set_chrome_active_volume(self, volume): """Sets the active audio output volume using chrome.audio API. @param volume: Volume to set (0~100). """ self._audio_proxy.set_chrome_active_volume(volume) def set_chrome_mute(self, mute): """Mutes the active audio output using chrome.audio API. @param mute: True to mute. False otherwise. """ self._audio_proxy.set_chrome_mute(mute) def check_audio_stream_at_selected_device(self): """Checks the audio output is at expected node""" self._audio_proxy.check_audio_stream_at_selected_device() def get_chrome_active_volume_mute(self): """Gets the volume state of active audio output using chrome.audio API. @param returns: A tuple (volume, mute), where volume is 0~100, and mute is True if node is muted, False otherwise. """ return self._audio_proxy.get_chrome_active_volume_mute() def set_chrome_active_node_type(self, output_node_type, input_node_type): """Sets active node type through chrome.audio API. The node types are defined in cras_utils.CRAS_NODE_TYPES. The current active node will be disabled first if the new active node is different from the current one. @param output_node_type: A node type defined in cras_utils.CRAS_NODE_TYPES. None to skip. @param input_node_type: A node type defined in cras_utils.CRAS_NODE_TYPES. None to skip """ self._audio_proxy.set_chrome_active_node_type( output_node_type, input_node_type) def start_arc_recording(self): """Starts recording using microphone app in container.""" self._audio_proxy.start_arc_recording() def stop_arc_recording(self): """Checks the recording is stopped and gets the recorded path. The recording duration of microphone app is fixed, so this method just asks Cros device to copy the recorded result from container to a path on Cros device. @returns: Path to the recorded file on DUT. """ return self._audio_proxy.stop_arc_recording() def set_arc_playback_file(self, path): """Copies the file from server to Cros host and into container. @param path: Path to the file on server. @returns: Path to the file in container on Cros host. """ client_file_path = self.set_playback_file(path) return self._audio_proxy.set_arc_playback_file(client_file_path) def start_arc_playback(self, path): """Starts playback through ARC on Cros host. @param path: Path to the file in container on Cros host. """ self._audio_proxy.start_arc_playback(path) def stop_arc_playback(self): """Stops playback through ARC on Cros host.""" self._audio_proxy.stop_arc_playback()