1*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Li"""Facade to access the audio-related functionality.""" 6*9c5db199SXin Li 7*9c5db199SXin Liimport functools 8*9c5db199SXin Liimport glob 9*9c5db199SXin Liimport logging 10*9c5db199SXin Liimport numpy as np 11*9c5db199SXin Liimport os 12*9c5db199SXin Liimport tempfile 13*9c5db199SXin Li 14*9c5db199SXin Lifrom autotest_lib.client.cros import constants 15*9c5db199SXin Lifrom autotest_lib.client.cros.audio import audio_helper 16*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cmd_utils 17*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cras_dbus_utils 18*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cras_utils 19*9c5db199SXin Lifrom autotest_lib.client.cros.audio import alsa_utils 20*9c5db199SXin Lifrom autotest_lib.client.cros.multimedia import audio_extension_handler 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Liclass AudioFacadeLocalError(Exception): 24*9c5db199SXin Li """Error in AudioFacadeLocal.""" 25*9c5db199SXin Li pass 26*9c5db199SXin Li 27*9c5db199SXin Li 28*9c5db199SXin Lidef check_arc_resource(func): 29*9c5db199SXin Li """Decorator function for ARC related functions in AudioFacadeLocal.""" 30*9c5db199SXin Li @functools.wraps(func) 31*9c5db199SXin Li def wrapper(instance, *args, **kwargs): 32*9c5db199SXin Li """Wrapper for the methods to check _arc_resource. 33*9c5db199SXin Li 34*9c5db199SXin Li @param instance: Object instance. 35*9c5db199SXin Li 36*9c5db199SXin Li @raises: AudioFacadeLocalError if there is no ARC resource. 37*9c5db199SXin Li 38*9c5db199SXin Li """ 39*9c5db199SXin Li if not instance._arc_resource: 40*9c5db199SXin Li raise AudioFacadeLocalError('There is no ARC resource.') 41*9c5db199SXin Li return func(instance, *args, **kwargs) 42*9c5db199SXin Li return wrapper 43*9c5db199SXin Li 44*9c5db199SXin Li 45*9c5db199SXin Lidef file_contains_all_zeros(path): 46*9c5db199SXin Li """Reads a file and checks whether the file contains all zeros.""" 47*9c5db199SXin Li with open(path, 'rb') as f: 48*9c5db199SXin Li binary = f.read() 49*9c5db199SXin Li # Assume data is in 16 bit signed int format. The real format 50*9c5db199SXin Li # does not matter though since we only care if there is nonzero data. 51*9c5db199SXin Li np_array = np.fromstring(binary, dtype='<i2') 52*9c5db199SXin Li return not np.any(np_array) 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Liclass AudioFacadeLocal(object): 56*9c5db199SXin Li """Facede to access the audio-related functionality. 57*9c5db199SXin Li 58*9c5db199SXin Li The methods inside this class only accept Python native types. 59*9c5db199SXin Li 60*9c5db199SXin Li """ 61*9c5db199SXin Li _CAPTURE_DATA_FORMATS = [ 62*9c5db199SXin Li dict(file_type='raw', sample_format='S16_LE', 63*9c5db199SXin Li channel=1, rate=48000), 64*9c5db199SXin Li dict(file_type='raw', sample_format='S16_LE', 65*9c5db199SXin Li channel=2, rate=48000)] 66*9c5db199SXin Li 67*9c5db199SXin Li _PLAYBACK_DATA_FORMAT = dict( 68*9c5db199SXin Li file_type='raw', sample_format='S16_LE', channel=2, rate=48000) 69*9c5db199SXin Li 70*9c5db199SXin Li _LISTEN_DATA_FORMATS = [ 71*9c5db199SXin Li dict(file_type='raw', sample_format='S16_LE', 72*9c5db199SXin Li channel=1, rate=16000)] 73*9c5db199SXin Li 74*9c5db199SXin Li def __init__(self, resource, arc_resource=None): 75*9c5db199SXin Li """Initializes an audio facade. 76*9c5db199SXin Li 77*9c5db199SXin Li @param resource: A FacadeResource object. 78*9c5db199SXin Li @param arc_resource: An ArcResource object. 79*9c5db199SXin Li 80*9c5db199SXin Li """ 81*9c5db199SXin Li self._resource = resource 82*9c5db199SXin Li self._listener = None 83*9c5db199SXin Li self._recorders = {} 84*9c5db199SXin Li self._player = None 85*9c5db199SXin Li self._counter = None 86*9c5db199SXin Li self._loaded_extension_handler = None 87*9c5db199SXin Li self._arc_resource = arc_resource 88*9c5db199SXin Li 89*9c5db199SXin Li 90*9c5db199SXin Li @property 91*9c5db199SXin Li def _extension_handler(self): 92*9c5db199SXin Li """Multimedia test extension handler.""" 93*9c5db199SXin Li if not self._loaded_extension_handler: 94*9c5db199SXin Li extension = self._resource.get_extension( 95*9c5db199SXin Li constants.AUDIO_TEST_EXTENSION) 96*9c5db199SXin Li logging.debug('Loaded extension: %s', extension) 97*9c5db199SXin Li self._loaded_extension_handler = ( 98*9c5db199SXin Li audio_extension_handler.AudioExtensionHandler(extension)) 99*9c5db199SXin Li return self._loaded_extension_handler 100*9c5db199SXin Li 101*9c5db199SXin Li 102*9c5db199SXin Li def get_audio_availability(self): 103*9c5db199SXin Li """Returns the availability of chrome.audio API. 104*9c5db199SXin Li 105*9c5db199SXin Li @returns: True if chrome.audio exists 106*9c5db199SXin Li """ 107*9c5db199SXin Li return self._extension_handler.get_audio_api_availability() 108*9c5db199SXin Li 109*9c5db199SXin Li 110*9c5db199SXin Li def get_audio_devices(self): 111*9c5db199SXin Li """Returns the audio devices from chrome.audio API. 112*9c5db199SXin Li 113*9c5db199SXin Li @returns: Checks docstring of get_audio_devices of AudioExtensionHandler. 114*9c5db199SXin Li 115*9c5db199SXin Li """ 116*9c5db199SXin Li return self._extension_handler.get_audio_devices() 117*9c5db199SXin Li 118*9c5db199SXin Li 119*9c5db199SXin Li def set_chrome_active_volume(self, volume): 120*9c5db199SXin Li """Sets the active audio output volume using chrome.audio API. 121*9c5db199SXin Li 122*9c5db199SXin Li @param volume: Volume to set (0~100). 123*9c5db199SXin Li 124*9c5db199SXin Li """ 125*9c5db199SXin Li self._extension_handler.set_active_volume(volume) 126*9c5db199SXin Li 127*9c5db199SXin Li 128*9c5db199SXin Li def set_chrome_active_input_gain(self, gain): 129*9c5db199SXin Li """Sets the active audio input gain using chrome.audio API. 130*9c5db199SXin Li 131*9c5db199SXin Li @param volume: Gain to set (0~100). 132*9c5db199SXin Li 133*9c5db199SXin Li """ 134*9c5db199SXin Li self._extension_handler.set_active_input_gain(gain) 135*9c5db199SXin Li 136*9c5db199SXin Li 137*9c5db199SXin Li def set_chrome_mute(self, mute): 138*9c5db199SXin Li """Mutes the active audio output using chrome.audio API. 139*9c5db199SXin Li 140*9c5db199SXin Li @param mute: True to mute. False otherwise. 141*9c5db199SXin Li 142*9c5db199SXin Li """ 143*9c5db199SXin Li self._extension_handler.set_mute(mute) 144*9c5db199SXin Li 145*9c5db199SXin Li 146*9c5db199SXin Li def get_chrome_active_volume_mute(self): 147*9c5db199SXin Li """Gets the volume state of active audio output using chrome.audio API. 148*9c5db199SXin Li 149*9c5db199SXin Li @param returns: A tuple (volume, mute), where volume is 0~100, and mute 150*9c5db199SXin Li is True if node is muted, False otherwise. 151*9c5db199SXin Li 152*9c5db199SXin Li """ 153*9c5db199SXin Li return self._extension_handler.get_active_volume_mute() 154*9c5db199SXin Li 155*9c5db199SXin Li 156*9c5db199SXin Li def set_chrome_active_node_type(self, output_node_type, input_node_type): 157*9c5db199SXin Li """Sets active node type through chrome.audio API. 158*9c5db199SXin Li 159*9c5db199SXin Li The node types are defined in cras_utils.CRAS_NODE_TYPES. 160*9c5db199SXin Li The current active node will be disabled first if the new active node 161*9c5db199SXin Li is different from the current one. 162*9c5db199SXin Li 163*9c5db199SXin Li @param output_node_type: A node type defined in 164*9c5db199SXin Li cras_utils.CRAS_NODE_TYPES. None to skip. 165*9c5db199SXin Li @param input_node_type: A node type defined in 166*9c5db199SXin Li cras_utils.CRAS_NODE_TYPES. None to skip 167*9c5db199SXin Li 168*9c5db199SXin Li """ 169*9c5db199SXin Li if output_node_type: 170*9c5db199SXin Li node_id = cras_utils.get_node_id_from_node_type( 171*9c5db199SXin Li output_node_type, False) 172*9c5db199SXin Li self._extension_handler.set_active_node_id(node_id) 173*9c5db199SXin Li if input_node_type: 174*9c5db199SXin Li node_id = cras_utils.get_node_id_from_node_type( 175*9c5db199SXin Li input_node_type, True) 176*9c5db199SXin Li self._extension_handler.set_active_node_id(node_id) 177*9c5db199SXin Li 178*9c5db199SXin Li 179*9c5db199SXin Li def check_audio_stream_at_selected_device(self): 180*9c5db199SXin Li """Checks the audio output is at expected node""" 181*9c5db199SXin Li output_device_name = cras_utils.get_selected_output_device_name() 182*9c5db199SXin Li output_device_type = cras_utils.get_selected_output_device_type() 183*9c5db199SXin Li logging.info("Output device name is %s", output_device_name) 184*9c5db199SXin Li logging.info("Output device type is %s", output_device_type) 185*9c5db199SXin Li alsa_utils.check_audio_stream_at_selected_device(output_device_name, 186*9c5db199SXin Li output_device_type) 187*9c5db199SXin Li 188*9c5db199SXin Li 189*9c5db199SXin Li def cleanup(self): 190*9c5db199SXin Li """Clean up the temporary files.""" 191*9c5db199SXin Li for path in glob.glob('/tmp/playback_*'): 192*9c5db199SXin Li os.unlink(path) 193*9c5db199SXin Li 194*9c5db199SXin Li for path in glob.glob('/tmp/capture_*'): 195*9c5db199SXin Li os.unlink(path) 196*9c5db199SXin Li 197*9c5db199SXin Li for path in glob.glob('/tmp/listen_*'): 198*9c5db199SXin Li os.unlink(path) 199*9c5db199SXin Li 200*9c5db199SXin Li if self._recorders: 201*9c5db199SXin Li for _, recorder in self._recorders: 202*9c5db199SXin Li recorder.cleanup() 203*9c5db199SXin Li self._recorders.clear() 204*9c5db199SXin Li 205*9c5db199SXin Li if self._player: 206*9c5db199SXin Li self._player.cleanup() 207*9c5db199SXin Li if self._listener: 208*9c5db199SXin Li self._listener.cleanup() 209*9c5db199SXin Li 210*9c5db199SXin Li if self._arc_resource: 211*9c5db199SXin Li self._arc_resource.cleanup() 212*9c5db199SXin Li 213*9c5db199SXin Li 214*9c5db199SXin Li def playback(self, file_path, data_format, blocking=False, node_type=None, 215*9c5db199SXin Li block_size=None): 216*9c5db199SXin Li """Playback a file. 217*9c5db199SXin Li 218*9c5db199SXin Li @param file_path: The path to the file. 219*9c5db199SXin Li @param data_format: A dict containing data format including 220*9c5db199SXin Li file_type, sample_format, channel, and rate. 221*9c5db199SXin Li file_type: file type e.g. 'raw' or 'wav'. 222*9c5db199SXin Li sample_format: One of the keys in 223*9c5db199SXin Li audio_data.SAMPLE_FORMAT. 224*9c5db199SXin Li channel: number of channels. 225*9c5db199SXin Li rate: sampling rate. 226*9c5db199SXin Li @param blocking: Blocks this call until playback finishes. 227*9c5db199SXin Li @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES 228*9c5db199SXin Li that we like to pin at. None to have the playback on 229*9c5db199SXin Li active selected device. 230*9c5db199SXin Li @param block_size: The number for frames per callback. 231*9c5db199SXin Li 232*9c5db199SXin Li @returns: True. 233*9c5db199SXin Li 234*9c5db199SXin Li @raises: AudioFacadeLocalError if data format is not supported. 235*9c5db199SXin Li 236*9c5db199SXin Li """ 237*9c5db199SXin Li logging.info('AudioFacadeLocal playback file: %r. format: %r', 238*9c5db199SXin Li file_path, data_format) 239*9c5db199SXin Li 240*9c5db199SXin Li if data_format != self._PLAYBACK_DATA_FORMAT: 241*9c5db199SXin Li raise AudioFacadeLocalError( 242*9c5db199SXin Li 'data format %r is not supported' % data_format) 243*9c5db199SXin Li 244*9c5db199SXin Li device_id = None 245*9c5db199SXin Li if node_type: 246*9c5db199SXin Li device_id = int(cras_utils.get_device_id_from_node_type( 247*9c5db199SXin Li node_type, False)) 248*9c5db199SXin Li 249*9c5db199SXin Li self._player = Player() 250*9c5db199SXin Li self._player.start(file_path, blocking, device_id, block_size) 251*9c5db199SXin Li 252*9c5db199SXin Li return True 253*9c5db199SXin Li 254*9c5db199SXin Li 255*9c5db199SXin Li def stop_playback(self): 256*9c5db199SXin Li """Stops playback process.""" 257*9c5db199SXin Li self._player.stop() 258*9c5db199SXin Li 259*9c5db199SXin Li 260*9c5db199SXin Li def start_recording(self, data_format, node_type=None, block_size=None): 261*9c5db199SXin Li """Starts recording an audio file. 262*9c5db199SXin Li 263*9c5db199SXin Li Currently the format specified in _CAPTURE_DATA_FORMATS is the only 264*9c5db199SXin Li formats. 265*9c5db199SXin Li 266*9c5db199SXin Li @param data_format: A dict containing: 267*9c5db199SXin Li file_type: 'raw'. 268*9c5db199SXin Li sample_format: 'S16_LE' for 16-bit signed integer in 269*9c5db199SXin Li little-endian. 270*9c5db199SXin Li channel: channel number. 271*9c5db199SXin Li rate: sampling rate. 272*9c5db199SXin Li @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES 273*9c5db199SXin Li that we like to pin at. None to have the recording 274*9c5db199SXin Li from active selected device. 275*9c5db199SXin Li @param block_size: The number for frames per callback. 276*9c5db199SXin Li 277*9c5db199SXin Li @returns: True 278*9c5db199SXin Li 279*9c5db199SXin Li @raises: AudioFacadeLocalError if data format is not supported, no 280*9c5db199SXin Li active selected node or the specified node is occupied. 281*9c5db199SXin Li 282*9c5db199SXin Li """ 283*9c5db199SXin Li logging.info('AudioFacadeLocal record format: %r', data_format) 284*9c5db199SXin Li 285*9c5db199SXin Li if data_format not in self._CAPTURE_DATA_FORMATS: 286*9c5db199SXin Li raise AudioFacadeLocalError( 287*9c5db199SXin Li 'data format %r is not supported' % data_format) 288*9c5db199SXin Li 289*9c5db199SXin Li if node_type is None: 290*9c5db199SXin Li device_id = None 291*9c5db199SXin Li node_type = cras_utils.get_selected_input_device_type() 292*9c5db199SXin Li if node_type is None: 293*9c5db199SXin Li raise AudioFacadeLocalError('No active selected input node.') 294*9c5db199SXin Li else: 295*9c5db199SXin Li device_id = int(cras_utils.get_device_id_from_node_type( 296*9c5db199SXin Li node_type, True)) 297*9c5db199SXin Li 298*9c5db199SXin Li if node_type in self._recorders: 299*9c5db199SXin Li raise AudioFacadeLocalError( 300*9c5db199SXin Li 'Node %s is already ocuppied' % node_type) 301*9c5db199SXin Li 302*9c5db199SXin Li self._recorders[node_type] = Recorder() 303*9c5db199SXin Li self._recorders[node_type].start(data_format, device_id, block_size) 304*9c5db199SXin Li 305*9c5db199SXin Li return True 306*9c5db199SXin Li 307*9c5db199SXin Li 308*9c5db199SXin Li def stop_recording(self, node_type=None): 309*9c5db199SXin Li """Stops recording an audio file. 310*9c5db199SXin Li @param node_type: A Cras node type defined in cras_utils.CRAS_NODE_TYPES 311*9c5db199SXin Li that we like to pin at. None to have the recording 312*9c5db199SXin Li from active selected device. 313*9c5db199SXin Li 314*9c5db199SXin Li @returns: The path to the recorded file. 315*9c5db199SXin Li None if capture device is not functional. 316*9c5db199SXin Li 317*9c5db199SXin Li @raises: AudioFacadeLocalError if no recording is started on 318*9c5db199SXin Li corresponding node. 319*9c5db199SXin Li """ 320*9c5db199SXin Li if node_type is None: 321*9c5db199SXin Li device_id = None 322*9c5db199SXin Li node_type = cras_utils.get_selected_input_device_type() 323*9c5db199SXin Li if node_type is None: 324*9c5db199SXin Li raise AudioFacadeLocalError('No active selected input node.') 325*9c5db199SXin Li else: 326*9c5db199SXin Li device_id = int(cras_utils.get_device_id_from_node_type( 327*9c5db199SXin Li node_type, True)) 328*9c5db199SXin Li 329*9c5db199SXin Li 330*9c5db199SXin Li if node_type not in self._recorders: 331*9c5db199SXin Li raise AudioFacadeLocalError( 332*9c5db199SXin Li 'No recording is started on node %s' % node_type) 333*9c5db199SXin Li 334*9c5db199SXin Li recorder = self._recorders[node_type] 335*9c5db199SXin Li recorder.stop() 336*9c5db199SXin Li del self._recorders[node_type] 337*9c5db199SXin Li 338*9c5db199SXin Li file_path = recorder.file_path 339*9c5db199SXin Li if file_contains_all_zeros(recorder.file_path): 340*9c5db199SXin Li logging.error('Recorded file contains all zeros. ' 341*9c5db199SXin Li 'Capture device is not functional') 342*9c5db199SXin Li return None 343*9c5db199SXin Li 344*9c5db199SXin Li return file_path 345*9c5db199SXin Li 346*9c5db199SXin Li 347*9c5db199SXin Li def start_listening(self, data_format): 348*9c5db199SXin Li """Starts listening to hotword for a given format. 349*9c5db199SXin Li 350*9c5db199SXin Li Currently the format specified in _CAPTURE_DATA_FORMATS is the only 351*9c5db199SXin Li formats. 352*9c5db199SXin Li 353*9c5db199SXin Li @param data_format: A dict containing: 354*9c5db199SXin Li file_type: 'raw'. 355*9c5db199SXin Li sample_format: 'S16_LE' for 16-bit signed integer in 356*9c5db199SXin Li little-endian. 357*9c5db199SXin Li channel: channel number. 358*9c5db199SXin Li rate: sampling rate. 359*9c5db199SXin Li 360*9c5db199SXin Li 361*9c5db199SXin Li @returns: True 362*9c5db199SXin Li 363*9c5db199SXin Li @raises: AudioFacadeLocalError if data format is not supported. 364*9c5db199SXin Li 365*9c5db199SXin Li """ 366*9c5db199SXin Li logging.info('AudioFacadeLocal record format: %r', data_format) 367*9c5db199SXin Li 368*9c5db199SXin Li if data_format not in self._LISTEN_DATA_FORMATS: 369*9c5db199SXin Li raise AudioFacadeLocalError( 370*9c5db199SXin Li 'data format %r is not supported' % data_format) 371*9c5db199SXin Li 372*9c5db199SXin Li self._listener = Listener() 373*9c5db199SXin Li self._listener.start(data_format) 374*9c5db199SXin Li 375*9c5db199SXin Li return True 376*9c5db199SXin Li 377*9c5db199SXin Li 378*9c5db199SXin Li def stop_listening(self): 379*9c5db199SXin Li """Stops listening to hotword. 380*9c5db199SXin Li 381*9c5db199SXin Li @returns: The path to the recorded file. 382*9c5db199SXin Li None if hotwording is not functional. 383*9c5db199SXin Li 384*9c5db199SXin Li """ 385*9c5db199SXin Li self._listener.stop() 386*9c5db199SXin Li if file_contains_all_zeros(self._listener.file_path): 387*9c5db199SXin Li logging.error('Recorded file contains all zeros. ' 388*9c5db199SXin Li 'Hotwording device is not functional') 389*9c5db199SXin Li return None 390*9c5db199SXin Li return self._listener.file_path 391*9c5db199SXin Li 392*9c5db199SXin Li 393*9c5db199SXin Li def set_selected_output_volume(self, volume): 394*9c5db199SXin Li """Sets the selected output volume. 395*9c5db199SXin Li 396*9c5db199SXin Li @param volume: the volume to be set(0-100). 397*9c5db199SXin Li 398*9c5db199SXin Li """ 399*9c5db199SXin Li cras_utils.set_selected_output_node_volume(volume) 400*9c5db199SXin Li 401*9c5db199SXin Li 402*9c5db199SXin Li def set_selected_node_types(self, output_node_types, input_node_types): 403*9c5db199SXin Li """Set selected node types. 404*9c5db199SXin Li 405*9c5db199SXin Li The node types are defined in cras_utils.CRAS_NODE_TYPES. 406*9c5db199SXin Li 407*9c5db199SXin Li @param output_node_types: A list of output node types. 408*9c5db199SXin Li None to skip setting. 409*9c5db199SXin Li @param input_node_types: A list of input node types. 410*9c5db199SXin Li None to skip setting. 411*9c5db199SXin Li 412*9c5db199SXin Li """ 413*9c5db199SXin Li cras_utils.set_selected_node_types(output_node_types, input_node_types) 414*9c5db199SXin Li 415*9c5db199SXin Li 416*9c5db199SXin Li def get_selected_node_types(self): 417*9c5db199SXin Li """Gets the selected output and input node types. 418*9c5db199SXin Li 419*9c5db199SXin Li @returns: A tuple (output_node_types, input_node_types) where each 420*9c5db199SXin Li field is a list of selected node types defined in 421*9c5db199SXin Li cras_utils.CRAS_NODE_TYPES. 422*9c5db199SXin Li 423*9c5db199SXin Li """ 424*9c5db199SXin Li return cras_utils.get_selected_node_types() 425*9c5db199SXin Li 426*9c5db199SXin Li 427*9c5db199SXin Li def get_plugged_node_types(self): 428*9c5db199SXin Li """Gets the plugged output and input node types. 429*9c5db199SXin Li 430*9c5db199SXin Li @returns: A tuple (output_node_types, input_node_types) where each 431*9c5db199SXin Li field is a list of plugged node types defined in 432*9c5db199SXin Li cras_utils.CRAS_NODE_TYPES. 433*9c5db199SXin Li 434*9c5db199SXin Li """ 435*9c5db199SXin Li return cras_utils.get_plugged_node_types() 436*9c5db199SXin Li 437*9c5db199SXin Li 438*9c5db199SXin Li def dump_diagnostics(self, file_path): 439*9c5db199SXin Li """Dumps audio diagnostics results to a file. 440*9c5db199SXin Li 441*9c5db199SXin Li @param file_path: The path to dump results. 442*9c5db199SXin Li 443*9c5db199SXin Li """ 444*9c5db199SXin Li audio_helper.dump_audio_diagnostics(file_path) 445*9c5db199SXin Li 446*9c5db199SXin Li 447*9c5db199SXin Li def start_counting_signal(self, signal_name): 448*9c5db199SXin Li """Starts counting DBus signal from Cras. 449*9c5db199SXin Li 450*9c5db199SXin Li @param signal_name: Signal of interest. 451*9c5db199SXin Li 452*9c5db199SXin Li """ 453*9c5db199SXin Li if self._counter: 454*9c5db199SXin Li raise AudioFacadeLocalError('There is an ongoing counting.') 455*9c5db199SXin Li self._counter = cras_dbus_utils.CrasDBusBackgroundSignalCounter() 456*9c5db199SXin Li self._counter.start(signal_name) 457*9c5db199SXin Li 458*9c5db199SXin Li 459*9c5db199SXin Li def stop_counting_signal(self): 460*9c5db199SXin Li """Stops counting DBus signal from Cras. 461*9c5db199SXin Li 462*9c5db199SXin Li @returns: Number of signals starting from last start_counting_signal 463*9c5db199SXin Li call. 464*9c5db199SXin Li 465*9c5db199SXin Li """ 466*9c5db199SXin Li if not self._counter: 467*9c5db199SXin Li raise AudioFacadeLocalError('Should start counting signal first') 468*9c5db199SXin Li result = self._counter.stop() 469*9c5db199SXin Li self._counter = None 470*9c5db199SXin Li return result 471*9c5db199SXin Li 472*9c5db199SXin Li 473*9c5db199SXin Li def wait_for_unexpected_nodes_changed(self, timeout_secs): 474*9c5db199SXin Li """Waits for unexpected nodes changed signal. 475*9c5db199SXin Li 476*9c5db199SXin Li @param timeout_secs: Timeout in seconds for waiting. 477*9c5db199SXin Li 478*9c5db199SXin Li """ 479*9c5db199SXin Li cras_dbus_utils.wait_for_unexpected_nodes_changed(timeout_secs) 480*9c5db199SXin Li 481*9c5db199SXin Li 482*9c5db199SXin Li def get_noise_cancellation_supported(self): 483*9c5db199SXin Li """Gets whether the device supports Noise Cancellation. 484*9c5db199SXin Li 485*9c5db199SXin Li @returns: True is supported; False otherwise. 486*9c5db199SXin Li 487*9c5db199SXin Li """ 488*9c5db199SXin Li return cras_utils.get_noise_cancellation_supported() 489*9c5db199SXin Li 490*9c5db199SXin Li 491*9c5db199SXin Li def set_bypass_block_noise_cancellation(self, bypass): 492*9c5db199SXin Li """Sets CRAS to bypass the blocking logic of Noise Cancellation. 493*9c5db199SXin Li 494*9c5db199SXin Li @param bypass: True for bypass; False for un-bypass. 495*9c5db199SXin Li 496*9c5db199SXin Li """ 497*9c5db199SXin Li cras_utils.set_bypass_block_noise_cancellation(bypass) 498*9c5db199SXin Li 499*9c5db199SXin Li 500*9c5db199SXin Li def set_noise_cancellation_enabled(self, enabled): 501*9c5db199SXin Li """Sets the state to enable or disable Noise Cancellation. 502*9c5db199SXin Li 503*9c5db199SXin Li @param enabled: True to enable; False to disable. 504*9c5db199SXin Li 505*9c5db199SXin Li """ 506*9c5db199SXin Li cras_utils.set_noise_cancellation_enabled(enabled) 507*9c5db199SXin Li 508*9c5db199SXin Li @check_arc_resource 509*9c5db199SXin Li def start_arc_recording(self): 510*9c5db199SXin Li """Starts recording using microphone app in container.""" 511*9c5db199SXin Li self._arc_resource.microphone.start_microphone_app() 512*9c5db199SXin Li 513*9c5db199SXin Li 514*9c5db199SXin Li @check_arc_resource 515*9c5db199SXin Li def stop_arc_recording(self): 516*9c5db199SXin Li """Checks the recording is stopped and gets the recorded path. 517*9c5db199SXin Li 518*9c5db199SXin Li The recording duration of microphone app is fixed, so this method just 519*9c5db199SXin Li copies the recorded result from container to a path on Cros device. 520*9c5db199SXin Li 521*9c5db199SXin Li """ 522*9c5db199SXin Li _, file_path = tempfile.mkstemp(prefix='capture_', suffix='.amr-nb') 523*9c5db199SXin Li self._arc_resource.microphone.stop_microphone_app(file_path) 524*9c5db199SXin Li return file_path 525*9c5db199SXin Li 526*9c5db199SXin Li 527*9c5db199SXin Li @check_arc_resource 528*9c5db199SXin Li def set_arc_playback_file(self, file_path): 529*9c5db199SXin Li """Copies the audio file to be played into container. 530*9c5db199SXin Li 531*9c5db199SXin Li User should call this method to put the file into container before 532*9c5db199SXin Li calling start_arc_playback. 533*9c5db199SXin Li 534*9c5db199SXin Li @param file_path: Path to the file to be played on Cros host. 535*9c5db199SXin Li 536*9c5db199SXin Li @returns: Path to the file in container. 537*9c5db199SXin Li 538*9c5db199SXin Li """ 539*9c5db199SXin Li return self._arc_resource.play_music.set_playback_file(file_path) 540*9c5db199SXin Li 541*9c5db199SXin Li 542*9c5db199SXin Li @check_arc_resource 543*9c5db199SXin Li def start_arc_playback(self, path): 544*9c5db199SXin Li """Start playback through Play Music app. 545*9c5db199SXin Li 546*9c5db199SXin Li Before calling this method, user should call set_arc_playback_file to 547*9c5db199SXin Li put the file into container. 548*9c5db199SXin Li 549*9c5db199SXin Li @param path: Path to the file in container. 550*9c5db199SXin Li 551*9c5db199SXin Li """ 552*9c5db199SXin Li self._arc_resource.play_music.start_playback(path) 553*9c5db199SXin Li 554*9c5db199SXin Li 555*9c5db199SXin Li @check_arc_resource 556*9c5db199SXin Li def stop_arc_playback(self): 557*9c5db199SXin Li """Stop playback through Play Music app.""" 558*9c5db199SXin Li self._arc_resource.play_music.stop_playback() 559*9c5db199SXin Li 560*9c5db199SXin Li 561*9c5db199SXin Liclass RecorderError(Exception): 562*9c5db199SXin Li """Error in Recorder.""" 563*9c5db199SXin Li pass 564*9c5db199SXin Li 565*9c5db199SXin Li 566*9c5db199SXin Liclass Recorder(object): 567*9c5db199SXin Li """The class to control recording subprocess. 568*9c5db199SXin Li 569*9c5db199SXin Li Properties: 570*9c5db199SXin Li file_path: The path to recorded file. It should be accessed after 571*9c5db199SXin Li stop() is called. 572*9c5db199SXin Li 573*9c5db199SXin Li """ 574*9c5db199SXin Li def __init__(self): 575*9c5db199SXin Li """Initializes a Recorder.""" 576*9c5db199SXin Li _, self.file_path = tempfile.mkstemp(prefix='capture_', suffix='.raw') 577*9c5db199SXin Li self._capture_subprocess = None 578*9c5db199SXin Li 579*9c5db199SXin Li 580*9c5db199SXin Li def start(self, data_format, pin_device, block_size): 581*9c5db199SXin Li """Starts recording. 582*9c5db199SXin Li 583*9c5db199SXin Li Starts recording subprocess. It can be stopped by calling stop(). 584*9c5db199SXin Li 585*9c5db199SXin Li @param data_format: A dict containing: 586*9c5db199SXin Li file_type: 'raw'. 587*9c5db199SXin Li sample_format: 'S16_LE' for 16-bit signed integer in 588*9c5db199SXin Li little-endian. 589*9c5db199SXin Li channel: channel number. 590*9c5db199SXin Li rate: sampling rate. 591*9c5db199SXin Li @param pin_device: A integer of device id to record from. 592*9c5db199SXin Li @param block_size: The number for frames per callback. 593*9c5db199SXin Li """ 594*9c5db199SXin Li self._capture_subprocess = cmd_utils.popen( 595*9c5db199SXin Li cras_utils.capture_cmd( 596*9c5db199SXin Li capture_file=self.file_path, duration=None, 597*9c5db199SXin Li channels=data_format['channel'], 598*9c5db199SXin Li rate=data_format['rate'], 599*9c5db199SXin Li pin_device=pin_device, block_size=block_size)) 600*9c5db199SXin Li 601*9c5db199SXin Li 602*9c5db199SXin Li def stop(self): 603*9c5db199SXin Li """Stops recording subprocess.""" 604*9c5db199SXin Li if self._capture_subprocess.poll() is None: 605*9c5db199SXin Li self._capture_subprocess.terminate() 606*9c5db199SXin Li else: 607*9c5db199SXin Li raise RecorderError( 608*9c5db199SXin Li 'Recording process was terminated unexpectedly.') 609*9c5db199SXin Li 610*9c5db199SXin Li 611*9c5db199SXin Li def cleanup(self): 612*9c5db199SXin Li """Cleanup the resources. 613*9c5db199SXin Li 614*9c5db199SXin Li Terminates the recording process if needed. 615*9c5db199SXin Li 616*9c5db199SXin Li """ 617*9c5db199SXin Li if self._capture_subprocess and self._capture_subprocess.poll() is None: 618*9c5db199SXin Li self._capture_subprocess.terminate() 619*9c5db199SXin Li 620*9c5db199SXin Li 621*9c5db199SXin Liclass PlayerError(Exception): 622*9c5db199SXin Li """Error in Player.""" 623*9c5db199SXin Li pass 624*9c5db199SXin Li 625*9c5db199SXin Li 626*9c5db199SXin Liclass Player(object): 627*9c5db199SXin Li """The class to control audio playback subprocess. 628*9c5db199SXin Li 629*9c5db199SXin Li Properties: 630*9c5db199SXin Li file_path: The path to the file to play. 631*9c5db199SXin Li 632*9c5db199SXin Li """ 633*9c5db199SXin Li def __init__(self): 634*9c5db199SXin Li """Initializes a Player.""" 635*9c5db199SXin Li self._playback_subprocess = None 636*9c5db199SXin Li 637*9c5db199SXin Li 638*9c5db199SXin Li def start(self, file_path, blocking, pin_device, block_size): 639*9c5db199SXin Li """Starts playing. 640*9c5db199SXin Li 641*9c5db199SXin Li Starts playing subprocess. It can be stopped by calling stop(). 642*9c5db199SXin Li 643*9c5db199SXin Li @param file_path: The path to the file. 644*9c5db199SXin Li @param blocking: Blocks this call until playback finishes. 645*9c5db199SXin Li @param pin_device: A integer of device id to play on. 646*9c5db199SXin Li @param block_size: The number for frames per callback. 647*9c5db199SXin Li 648*9c5db199SXin Li """ 649*9c5db199SXin Li self._playback_subprocess = cras_utils.playback( 650*9c5db199SXin Li blocking, playback_file=file_path, pin_device=pin_device, 651*9c5db199SXin Li block_size=block_size) 652*9c5db199SXin Li 653*9c5db199SXin Li 654*9c5db199SXin Li def stop(self): 655*9c5db199SXin Li """Stops playback subprocess.""" 656*9c5db199SXin Li cmd_utils.kill_or_log_returncode(self._playback_subprocess) 657*9c5db199SXin Li 658*9c5db199SXin Li 659*9c5db199SXin Li def cleanup(self): 660*9c5db199SXin Li """Cleanup the resources. 661*9c5db199SXin Li 662*9c5db199SXin Li Terminates the playback process if needed. 663*9c5db199SXin Li 664*9c5db199SXin Li """ 665*9c5db199SXin Li self.stop() 666*9c5db199SXin Li 667*9c5db199SXin Li 668*9c5db199SXin Liclass ListenerError(Exception): 669*9c5db199SXin Li """Error in Listener.""" 670*9c5db199SXin Li pass 671*9c5db199SXin Li 672*9c5db199SXin Li 673*9c5db199SXin Liclass Listener(object): 674*9c5db199SXin Li """The class to control listening subprocess. 675*9c5db199SXin Li 676*9c5db199SXin Li Properties: 677*9c5db199SXin Li file_path: The path to recorded file. It should be accessed after 678*9c5db199SXin Li stop() is called. 679*9c5db199SXin Li 680*9c5db199SXin Li """ 681*9c5db199SXin Li def __init__(self): 682*9c5db199SXin Li """Initializes a Listener.""" 683*9c5db199SXin Li _, self.file_path = tempfile.mkstemp(prefix='listen_', suffix='.raw') 684*9c5db199SXin Li self._capture_subprocess = None 685*9c5db199SXin Li 686*9c5db199SXin Li 687*9c5db199SXin Li def start(self, data_format): 688*9c5db199SXin Li """Starts listening. 689*9c5db199SXin Li 690*9c5db199SXin Li Starts listening subprocess. It can be stopped by calling stop(). 691*9c5db199SXin Li 692*9c5db199SXin Li @param data_format: A dict containing: 693*9c5db199SXin Li file_type: 'raw'. 694*9c5db199SXin Li sample_format: 'S16_LE' for 16-bit signed integer in 695*9c5db199SXin Li little-endian. 696*9c5db199SXin Li channel: channel number. 697*9c5db199SXin Li rate: sampling rate. 698*9c5db199SXin Li 699*9c5db199SXin Li @raises: ListenerError: If listening subprocess is terminated 700*9c5db199SXin Li unexpectedly. 701*9c5db199SXin Li 702*9c5db199SXin Li """ 703*9c5db199SXin Li self._capture_subprocess = cmd_utils.popen( 704*9c5db199SXin Li cras_utils.listen_cmd( 705*9c5db199SXin Li capture_file=self.file_path, duration=None, 706*9c5db199SXin Li channels=data_format['channel'], 707*9c5db199SXin Li rate=data_format['rate'])) 708*9c5db199SXin Li 709*9c5db199SXin Li 710*9c5db199SXin Li def stop(self): 711*9c5db199SXin Li """Stops listening subprocess.""" 712*9c5db199SXin Li if self._capture_subprocess.poll() is None: 713*9c5db199SXin Li self._capture_subprocess.terminate() 714*9c5db199SXin Li else: 715*9c5db199SXin Li raise ListenerError( 716*9c5db199SXin Li 'Listening process was terminated unexpectedly.') 717*9c5db199SXin Li 718*9c5db199SXin Li 719*9c5db199SXin Li def cleanup(self): 720*9c5db199SXin Li """Cleanup the resources. 721*9c5db199SXin Li 722*9c5db199SXin Li Terminates the listening process if needed. 723*9c5db199SXin Li 724*9c5db199SXin Li """ 725*9c5db199SXin Li if self._capture_subprocess and self._capture_subprocess.poll() is None: 726*9c5db199SXin Li self._capture_subprocess.terminate() 727