1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2013 The Chromium Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li"""This module provides cras audio utilities.""" 7*9c5db199SXin Li 8*9c5db199SXin Liimport logging 9*9c5db199SXin Liimport re 10*9c5db199SXin Liimport subprocess 11*9c5db199SXin Li 12*9c5db199SXin Lifrom autotest_lib.client.bin import utils 13*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cmd_utils 14*9c5db199SXin Li 15*9c5db199SXin Li_CRAS_TEST_CLIENT = '/usr/bin/cras_test_client' 16*9c5db199SXin Li 17*9c5db199SXin Li 18*9c5db199SXin Liclass CrasUtilsError(Exception): 19*9c5db199SXin Li """Error in CrasUtils.""" 20*9c5db199SXin Li pass 21*9c5db199SXin Li 22*9c5db199SXin Li 23*9c5db199SXin Lidef dump_audio_thread(): 24*9c5db199SXin Li """Dumps audio thread info. 25*9c5db199SXin Li 26*9c5db199SXin Li @returns: A list of cras audio information. 27*9c5db199SXin Li """ 28*9c5db199SXin Li proc = subprocess.Popen([_CRAS_TEST_CLIENT, '--dump_a'], 29*9c5db199SXin Li stdout=subprocess.PIPE) 30*9c5db199SXin Li 31*9c5db199SXin Li output, err = proc.communicate() 32*9c5db199SXin Li if err: 33*9c5db199SXin Li raise CrasUtilsError(err) 34*9c5db199SXin Li return output.decode().splitlines() 35*9c5db199SXin Li 36*9c5db199SXin Li 37*9c5db199SXin Lidef get_audio_thread_summary(): 38*9c5db199SXin Li """Gets stream summary info. 39*9c5db199SXin Li 40*9c5db199SXin Li @returns: A list of stream summary information. 41*9c5db199SXin Li """ 42*9c5db199SXin Li 43*9c5db199SXin Li lines = dump_audio_thread() 44*9c5db199SXin Li return [l for l in lines if l.startswith('Summary:')] 45*9c5db199SXin Li 46*9c5db199SXin Li 47*9c5db199SXin Lidef playback(blocking=True, stdin=None, *args, **kargs): 48*9c5db199SXin Li """A helper function to execute the playback_cmd. 49*9c5db199SXin Li 50*9c5db199SXin Li @param blocking: Blocks this call until playback finishes. 51*9c5db199SXin Li @param stdin: the standard input of playback process 52*9c5db199SXin Li @param args: args passed to playback_cmd. 53*9c5db199SXin Li @param kargs: kargs passed to playback_cmd. 54*9c5db199SXin Li 55*9c5db199SXin Li @returns: The process running the playback command. Note that if the 56*9c5db199SXin Li blocking parameter is true, this will return a finished process. 57*9c5db199SXin Li """ 58*9c5db199SXin Li process = cmd_utils.popen(playback_cmd(*args, **kargs), stdin=stdin) 59*9c5db199SXin Li if blocking: 60*9c5db199SXin Li cmd_utils.wait_and_check_returncode(process) 61*9c5db199SXin Li return process 62*9c5db199SXin Li 63*9c5db199SXin Li 64*9c5db199SXin Lidef capture(*args, **kargs): 65*9c5db199SXin Li """A helper function to execute the capture_cmd. 66*9c5db199SXin Li 67*9c5db199SXin Li @param args: args passed to capture_cmd. 68*9c5db199SXin Li @param kargs: kargs passed to capture_cmd. 69*9c5db199SXin Li 70*9c5db199SXin Li """ 71*9c5db199SXin Li cmd_utils.execute(capture_cmd(*args, **kargs)) 72*9c5db199SXin Li 73*9c5db199SXin Li 74*9c5db199SXin Lidef playback_cmd(playback_file, block_size=None, duration=None, 75*9c5db199SXin Li pin_device=None, channels=2, rate=48000): 76*9c5db199SXin Li """Gets a command to playback a file with given settings. 77*9c5db199SXin Li 78*9c5db199SXin Li @param playback_file: the name of the file to play. '-' indicates to 79*9c5db199SXin Li playback raw audio from the stdin. 80*9c5db199SXin Li @param pin_device: the device id to playback on. 81*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency). 82*9c5db199SXin Li @param duration: seconds to playback. 83*9c5db199SXin Li @param channels: number of channels. 84*9c5db199SXin Li @param rate: the sampling rate. 85*9c5db199SXin Li 86*9c5db199SXin Li @returns: The command args put in a list of strings. 87*9c5db199SXin Li 88*9c5db199SXin Li """ 89*9c5db199SXin Li args = [_CRAS_TEST_CLIENT] 90*9c5db199SXin Li args += ['--playback_file', playback_file] 91*9c5db199SXin Li if pin_device is not None: 92*9c5db199SXin Li args += ['--pin_device', str(pin_device)] 93*9c5db199SXin Li if block_size is not None: 94*9c5db199SXin Li args += ['--block_size', str(block_size)] 95*9c5db199SXin Li if duration is not None: 96*9c5db199SXin Li args += ['--duration', str(duration)] 97*9c5db199SXin Li args += ['--num_channels', str(channels)] 98*9c5db199SXin Li args += ['--rate', str(rate)] 99*9c5db199SXin Li return args 100*9c5db199SXin Li 101*9c5db199SXin Li 102*9c5db199SXin Lidef capture_cmd(capture_file, block_size=None, duration=10, 103*9c5db199SXin Li sample_format='S16_LE', 104*9c5db199SXin Li pin_device=None, channels=1, rate=48000): 105*9c5db199SXin Li """Gets a command to capture the audio into the file with given settings. 106*9c5db199SXin Li 107*9c5db199SXin Li @param capture_file: the name of file the audio to be stored in. 108*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency). 109*9c5db199SXin Li @param duration: seconds to record. If it is None, duration is not set, 110*9c5db199SXin Li and command will keep capturing audio until it is 111*9c5db199SXin Li terminated. 112*9c5db199SXin Li @param sample_format: the sample format; 113*9c5db199SXin Li possible choices: 'S16_LE', 'S24_LE', and 'S32_LE' 114*9c5db199SXin Li default to S16_LE: signed 16 bits/sample, 115*9c5db199SXin Li little endian 116*9c5db199SXin Li @param pin_device: the device id to record from. 117*9c5db199SXin Li @param channels: number of channels. 118*9c5db199SXin Li @param rate: the sampling rate. 119*9c5db199SXin Li 120*9c5db199SXin Li @returns: The command args put in a list of strings. 121*9c5db199SXin Li 122*9c5db199SXin Li """ 123*9c5db199SXin Li args = [_CRAS_TEST_CLIENT] 124*9c5db199SXin Li args += ['--capture_file', capture_file] 125*9c5db199SXin Li if pin_device is not None: 126*9c5db199SXin Li args += ['--pin_device', str(pin_device)] 127*9c5db199SXin Li if block_size is not None: 128*9c5db199SXin Li args += ['--block_size', str(block_size)] 129*9c5db199SXin Li if duration is not None: 130*9c5db199SXin Li args += ['--duration', str(duration)] 131*9c5db199SXin Li args += ['--num_channels', str(channels)] 132*9c5db199SXin Li args += ['--rate', str(rate)] 133*9c5db199SXin Li args += ['--format', str(sample_format)] 134*9c5db199SXin Li return args 135*9c5db199SXin Li 136*9c5db199SXin Li 137*9c5db199SXin Lidef listen_cmd( 138*9c5db199SXin Li capture_file, block_size=None, duration=10, channels=1, rate=48000): 139*9c5db199SXin Li """Gets a command to listen on hotword and record audio into the file with 140*9c5db199SXin Li given settings. 141*9c5db199SXin Li 142*9c5db199SXin Li @param capture_file: the name of file the audio to be stored in. 143*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency). 144*9c5db199SXin Li @param duration: seconds to record. If it is None, duration is not set, 145*9c5db199SXin Li and command will keep capturing audio until it is 146*9c5db199SXin Li terminated. 147*9c5db199SXin Li @param channels: number of channels. 148*9c5db199SXin Li @param rate: the sampling rate. 149*9c5db199SXin Li 150*9c5db199SXin Li @returns: The command args put in a list of strings. 151*9c5db199SXin Li 152*9c5db199SXin Li """ 153*9c5db199SXin Li args = [_CRAS_TEST_CLIENT] 154*9c5db199SXin Li args += ['--listen_for_hotword', capture_file] 155*9c5db199SXin Li if block_size is not None: 156*9c5db199SXin Li args += ['--block_size', str(block_size)] 157*9c5db199SXin Li if duration is not None: 158*9c5db199SXin Li args += ['--duration', str(duration)] 159*9c5db199SXin Li args += ['--num_channels', str(channels)] 160*9c5db199SXin Li args += ['--rate', str(rate)] 161*9c5db199SXin Li return args 162*9c5db199SXin Li 163*9c5db199SXin Li 164*9c5db199SXin Lidef loopback(*args, **kargs): 165*9c5db199SXin Li """A helper function to execute loopback_cmd. 166*9c5db199SXin Li 167*9c5db199SXin Li @param args: args passed to loopback_cmd. 168*9c5db199SXin Li @param kargs: kargs passed to loopback_cmd. 169*9c5db199SXin Li 170*9c5db199SXin Li """ 171*9c5db199SXin Li 172*9c5db199SXin Li cmd_utils.execute(loopback_cmd(*args, **kargs)) 173*9c5db199SXin Li 174*9c5db199SXin Li 175*9c5db199SXin Lidef loopback_cmd(output_file, duration=10, channels=2, rate=48000): 176*9c5db199SXin Li """Gets a command to record the loopback. 177*9c5db199SXin Li 178*9c5db199SXin Li @param output_file: The name of the file the loopback to be stored in. 179*9c5db199SXin Li @param channels: The number of channels of the recorded audio. 180*9c5db199SXin Li @param duration: seconds to record. 181*9c5db199SXin Li @param rate: the sampling rate. 182*9c5db199SXin Li 183*9c5db199SXin Li @returns: The command args put in a list of strings. 184*9c5db199SXin Li 185*9c5db199SXin Li """ 186*9c5db199SXin Li args = [_CRAS_TEST_CLIENT] 187*9c5db199SXin Li args += ['--loopback_file', output_file] 188*9c5db199SXin Li args += ['--duration_seconds', str(duration)] 189*9c5db199SXin Li args += ['--num_channels', str(channels)] 190*9c5db199SXin Li args += ['--rate', str(rate)] 191*9c5db199SXin Li return args 192*9c5db199SXin Li 193*9c5db199SXin Li 194*9c5db199SXin Lidef get_cras_nodes_cmd(): 195*9c5db199SXin Li """Gets a command to query the nodes from Cras. 196*9c5db199SXin Li 197*9c5db199SXin Li @returns: The command to query nodes information from Cras using dbus-send. 198*9c5db199SXin Li 199*9c5db199SXin Li """ 200*9c5db199SXin Li return ('dbus-send --system --type=method_call --print-reply ' 201*9c5db199SXin Li '--dest=org.chromium.cras /org/chromium/cras ' 202*9c5db199SXin Li 'org.chromium.cras.Control.GetNodes') 203*9c5db199SXin Li 204*9c5db199SXin Li 205*9c5db199SXin Lidef set_system_volume(volume): 206*9c5db199SXin Li """Set the system volume. 207*9c5db199SXin Li 208*9c5db199SXin Li @param volume: the system output vlume to be set(0 - 100). 209*9c5db199SXin Li 210*9c5db199SXin Li """ 211*9c5db199SXin Li get_cras_control_interface().SetOutputVolume(volume) 212*9c5db199SXin Li 213*9c5db199SXin Li 214*9c5db199SXin Lidef set_node_volume(node_id, volume): 215*9c5db199SXin Li """Set the volume of the given output node. 216*9c5db199SXin Li 217*9c5db199SXin Li @param node_id: the id of the output node to be set the volume. 218*9c5db199SXin Li @param volume: the volume to be set(0-100). 219*9c5db199SXin Li 220*9c5db199SXin Li """ 221*9c5db199SXin Li get_cras_control_interface().SetOutputNodeVolume(node_id, volume) 222*9c5db199SXin Li 223*9c5db199SXin Li 224*9c5db199SXin Lidef get_cras_control_interface(private=False): 225*9c5db199SXin Li """Gets Cras DBus control interface. 226*9c5db199SXin Li 227*9c5db199SXin Li @param private: Set to True to use a new instance for dbus.SystemBus 228*9c5db199SXin Li instead of the shared instance. 229*9c5db199SXin Li 230*9c5db199SXin Li @returns: A dBus.Interface object with Cras Control interface. 231*9c5db199SXin Li 232*9c5db199SXin Li @raises: ImportError if this is not called on Cros device. 233*9c5db199SXin Li 234*9c5db199SXin Li """ 235*9c5db199SXin Li try: 236*9c5db199SXin Li import dbus 237*9c5db199SXin Li except ImportError as e: 238*9c5db199SXin Li logging.exception( 239*9c5db199SXin Li 'Can not import dbus: %s. This method should only be ' 240*9c5db199SXin Li 'called on Cros device.', e) 241*9c5db199SXin Li raise 242*9c5db199SXin Li bus = dbus.SystemBus(private=private) 243*9c5db199SXin Li cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras') 244*9c5db199SXin Li return dbus.Interface(cras_object, 'org.chromium.cras.Control') 245*9c5db199SXin Li 246*9c5db199SXin Li 247*9c5db199SXin Lidef get_cras_nodes(): 248*9c5db199SXin Li """Gets nodes information from Cras. 249*9c5db199SXin Li 250*9c5db199SXin Li @returns: A dict containing information of each node. 251*9c5db199SXin Li 252*9c5db199SXin Li """ 253*9c5db199SXin Li return get_cras_control_interface().GetNodes() 254*9c5db199SXin Li 255*9c5db199SXin Li 256*9c5db199SXin Lidef get_selected_nodes(): 257*9c5db199SXin Li """Gets selected output nodes and input nodes. 258*9c5db199SXin Li 259*9c5db199SXin Li @returns: A tuple (output_nodes, input_nodes) where each 260*9c5db199SXin Li field is a list of selected node IDs returned from Cras DBus API. 261*9c5db199SXin Li Note that there may be multiple output/input nodes being selected 262*9c5db199SXin Li at the same time. 263*9c5db199SXin Li 264*9c5db199SXin Li """ 265*9c5db199SXin Li output_nodes = [] 266*9c5db199SXin Li input_nodes = [] 267*9c5db199SXin Li nodes = get_cras_nodes() 268*9c5db199SXin Li for node in nodes: 269*9c5db199SXin Li if node['Active']: 270*9c5db199SXin Li if node['IsInput']: 271*9c5db199SXin Li input_nodes.append(node['Id']) 272*9c5db199SXin Li else: 273*9c5db199SXin Li output_nodes.append(node['Id']) 274*9c5db199SXin Li return (output_nodes, input_nodes) 275*9c5db199SXin Li 276*9c5db199SXin Li 277*9c5db199SXin Lidef set_selected_output_node_volume(volume): 278*9c5db199SXin Li """Sets the selected output node volume. 279*9c5db199SXin Li 280*9c5db199SXin Li @param volume: the volume to be set (0-100). 281*9c5db199SXin Li 282*9c5db199SXin Li """ 283*9c5db199SXin Li selected_output_node_ids, _ = get_selected_nodes() 284*9c5db199SXin Li for node_id in selected_output_node_ids: 285*9c5db199SXin Li set_node_volume(node_id, volume) 286*9c5db199SXin Li 287*9c5db199SXin Li 288*9c5db199SXin Lidef get_active_stream_count(): 289*9c5db199SXin Li """Gets the number of active streams. 290*9c5db199SXin Li 291*9c5db199SXin Li @returns: The number of active streams. 292*9c5db199SXin Li 293*9c5db199SXin Li """ 294*9c5db199SXin Li return int(get_cras_control_interface().GetNumberOfActiveStreams()) 295*9c5db199SXin Li 296*9c5db199SXin Li 297*9c5db199SXin Lidef set_system_mute(is_mute): 298*9c5db199SXin Li """Sets the system mute switch. 299*9c5db199SXin Li 300*9c5db199SXin Li @param is_mute: Set True to mute the system playback. 301*9c5db199SXin Li 302*9c5db199SXin Li """ 303*9c5db199SXin Li get_cras_control_interface().SetOutputMute(is_mute) 304*9c5db199SXin Li 305*9c5db199SXin Li 306*9c5db199SXin Lidef set_capture_mute(is_mute): 307*9c5db199SXin Li """Sets the capture mute switch. 308*9c5db199SXin Li 309*9c5db199SXin Li @param is_mute: Set True to mute the capture. 310*9c5db199SXin Li 311*9c5db199SXin Li """ 312*9c5db199SXin Li get_cras_control_interface().SetInputMute(is_mute) 313*9c5db199SXin Li 314*9c5db199SXin Li 315*9c5db199SXin Lidef node_type_is_plugged(node_type, nodes_info): 316*9c5db199SXin Li """Determine if there is any node of node_type plugged. 317*9c5db199SXin Li 318*9c5db199SXin Li This method is used in the AudioLoopbackDongleLabel class, where the 319*9c5db199SXin Li call is executed on autotest server. Use get_cras_nodes instead if 320*9c5db199SXin Li the call can be executed on Cros device. 321*9c5db199SXin Li 322*9c5db199SXin Li Since Cras only reports the plugged node in GetNodes, we can 323*9c5db199SXin Li parse the return value to see if there is any node with the given type. 324*9c5db199SXin Li For example, if INTERNAL_MIC is of intereset, the pattern we are 325*9c5db199SXin Li looking for is: 326*9c5db199SXin Li 327*9c5db199SXin Li dict entry( 328*9c5db199SXin Li string "Type" 329*9c5db199SXin Li variant string "INTERNAL_MIC" 330*9c5db199SXin Li ) 331*9c5db199SXin Li 332*9c5db199SXin Li @param node_type: A str representing node type defined in CRAS_NODE_TYPES. 333*9c5db199SXin Li @param nodes_info: A str containing output of command get_nodes_cmd. 334*9c5db199SXin Li 335*9c5db199SXin Li @returns: True if there is any node of node_type plugged. False otherwise. 336*9c5db199SXin Li 337*9c5db199SXin Li """ 338*9c5db199SXin Li match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type, 339*9c5db199SXin Li nodes_info) 340*9c5db199SXin Li return True if match else False 341*9c5db199SXin Li 342*9c5db199SXin Li 343*9c5db199SXin Li# Cras node types reported from Cras DBus control API. 344*9c5db199SXin LiCRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB', 345*9c5db199SXin Li 'BLUETOOTH', 'LINEOUT', 'UNKNOWN', 'ALSA_LOOPBACK'] 346*9c5db199SXin LiCRAS_INPUT_NODE_TYPES = [ 347*9c5db199SXin Li 'MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH', 'POST_DSP_DELAYED_LOOPBACK', 348*9c5db199SXin Li 'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN', 'KEYBOARD_MIC', 349*9c5db199SXin Li 'HOTWORD', 'FRONT_MIC', 'REAR_MIC', 'ECHO_REFERENCE' 350*9c5db199SXin Li] 351*9c5db199SXin LiCRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES 352*9c5db199SXin Li 353*9c5db199SXin Li 354*9c5db199SXin Lidef get_filtered_node_types(callback): 355*9c5db199SXin Li """Returns the pair of filtered output node types and input node types. 356*9c5db199SXin Li 357*9c5db199SXin Li @param callback: A callback function which takes a node as input parameter 358*9c5db199SXin Li and filter the node based on its return value. 359*9c5db199SXin Li 360*9c5db199SXin Li @returns: A tuple (output_node_types, input_node_types) where each 361*9c5db199SXin Li field is a list of node types defined in CRAS_NODE_TYPES, 362*9c5db199SXin Li and their 'attribute_name' is True. 363*9c5db199SXin Li 364*9c5db199SXin Li """ 365*9c5db199SXin Li output_node_types = [] 366*9c5db199SXin Li input_node_types = [] 367*9c5db199SXin Li nodes = get_cras_nodes() 368*9c5db199SXin Li for node in nodes: 369*9c5db199SXin Li if callback(node): 370*9c5db199SXin Li node_type = str(node['Type']) 371*9c5db199SXin Li if node_type not in CRAS_NODE_TYPES: 372*9c5db199SXin Li logging.warning('node type %s is not in known CRAS_NODE_TYPES', 373*9c5db199SXin Li node_type) 374*9c5db199SXin Li if node['IsInput']: 375*9c5db199SXin Li input_node_types.append(node_type) 376*9c5db199SXin Li else: 377*9c5db199SXin Li output_node_types.append(node_type) 378*9c5db199SXin Li return (output_node_types, input_node_types) 379*9c5db199SXin Li 380*9c5db199SXin Li 381*9c5db199SXin Lidef get_selected_node_types(): 382*9c5db199SXin Li """Returns the pair of active output node types and input node types. 383*9c5db199SXin Li 384*9c5db199SXin Li @returns: A tuple (output_node_types, input_node_types) where each 385*9c5db199SXin Li field is a list of selected node types defined in CRAS_NODE_TYPES. 386*9c5db199SXin Li 387*9c5db199SXin Li """ 388*9c5db199SXin Li def is_selected(node): 389*9c5db199SXin Li """Checks if a node is selected. 390*9c5db199SXin Li 391*9c5db199SXin Li A node is selected if its Active attribute is True. 392*9c5db199SXin Li 393*9c5db199SXin Li @returns: True is a node is selected, False otherwise. 394*9c5db199SXin Li 395*9c5db199SXin Li """ 396*9c5db199SXin Li return node['Active'] 397*9c5db199SXin Li 398*9c5db199SXin Li return get_filtered_node_types(is_selected) 399*9c5db199SXin Li 400*9c5db199SXin Li 401*9c5db199SXin Lidef get_selected_input_device_name(): 402*9c5db199SXin Li """Returns the device name of the active input node. 403*9c5db199SXin Li 404*9c5db199SXin Li @returns: device name string. E.g. kbl_r5514_5663_max: :0,1 405*9c5db199SXin Li """ 406*9c5db199SXin Li nodes = get_cras_nodes() 407*9c5db199SXin Li for node in nodes: 408*9c5db199SXin Li if node['Active'] and node['IsInput']: 409*9c5db199SXin Li return node['DeviceName'] 410*9c5db199SXin Li return None 411*9c5db199SXin Li 412*9c5db199SXin Li 413*9c5db199SXin Lidef get_selected_input_device_type(): 414*9c5db199SXin Li """Returns the device type of the active input node. 415*9c5db199SXin Li 416*9c5db199SXin Li @returns: device type string. E.g. INTERNAL_MICROPHONE 417*9c5db199SXin Li """ 418*9c5db199SXin Li nodes = get_cras_nodes() 419*9c5db199SXin Li for node in nodes: 420*9c5db199SXin Li if node['Active'] and node['IsInput']: 421*9c5db199SXin Li return node['Type'] 422*9c5db199SXin Li return None 423*9c5db199SXin Li 424*9c5db199SXin Li 425*9c5db199SXin Lidef get_selected_output_device_name(): 426*9c5db199SXin Li """Returns the device name of the active output node. 427*9c5db199SXin Li 428*9c5db199SXin Li @returns: device name string. E.g. mtk-rt5650: :0,0 429*9c5db199SXin Li """ 430*9c5db199SXin Li nodes = get_cras_nodes() 431*9c5db199SXin Li for node in nodes: 432*9c5db199SXin Li if node['Active'] and not node['IsInput']: 433*9c5db199SXin Li return node['DeviceName'] 434*9c5db199SXin Li return None 435*9c5db199SXin Li 436*9c5db199SXin Li 437*9c5db199SXin Lidef get_selected_output_device_type(): 438*9c5db199SXin Li """Returns the device type of the active output node. 439*9c5db199SXin Li 440*9c5db199SXin Li @returns: device type string. E.g. INTERNAL_SPEAKER 441*9c5db199SXin Li """ 442*9c5db199SXin Li nodes = get_cras_nodes() 443*9c5db199SXin Li for node in nodes: 444*9c5db199SXin Li if node['Active'] and not node['IsInput']: 445*9c5db199SXin Li return node['Type'] 446*9c5db199SXin Li return None 447*9c5db199SXin Li 448*9c5db199SXin Li 449*9c5db199SXin Lidef get_plugged_node_types(): 450*9c5db199SXin Li """Returns the pair of plugged output node types and input node types. 451*9c5db199SXin Li 452*9c5db199SXin Li @returns: A tuple (output_node_types, input_node_types) where each 453*9c5db199SXin Li field is a list of plugged node types defined in CRAS_NODE_TYPES. 454*9c5db199SXin Li 455*9c5db199SXin Li """ 456*9c5db199SXin Li def is_plugged(node): 457*9c5db199SXin Li """Checks if a node is plugged and is not unknown node. 458*9c5db199SXin Li 459*9c5db199SXin Li Cras DBus API only reports plugged node, so every node reported by Cras 460*9c5db199SXin Li DBus API is plugged. However, we filter out UNKNOWN node here because 461*9c5db199SXin Li the existence of unknown node depends on the number of redundant 462*9c5db199SXin Li playback/record audio device created on audio card. Also, the user of 463*9c5db199SXin Li Cras will ignore unknown nodes. 464*9c5db199SXin Li 465*9c5db199SXin Li @returns: True if a node is plugged and is not an UNKNOWN node. 466*9c5db199SXin Li 467*9c5db199SXin Li """ 468*9c5db199SXin Li return node['Type'] != 'UNKNOWN' 469*9c5db199SXin Li 470*9c5db199SXin Li return get_filtered_node_types(is_plugged) 471*9c5db199SXin Li 472*9c5db199SXin Li 473*9c5db199SXin Lidef set_selected_node_types(output_node_types, input_node_types): 474*9c5db199SXin Li """Sets selected node types. 475*9c5db199SXin Li 476*9c5db199SXin Li @param output_node_types: A list of output node types. None to skip setting. 477*9c5db199SXin Li @param input_node_types: A list of input node types. None to skip setting. 478*9c5db199SXin Li 479*9c5db199SXin Li """ 480*9c5db199SXin Li if output_node_types is not None and len(output_node_types) == 1: 481*9c5db199SXin Li set_single_selected_output_node(output_node_types[0]) 482*9c5db199SXin Li elif output_node_types: 483*9c5db199SXin Li set_selected_output_nodes(output_node_types) 484*9c5db199SXin Li if input_node_types is not None and len(input_node_types) == 1: 485*9c5db199SXin Li set_single_selected_input_node(input_node_types[0]) 486*9c5db199SXin Li elif input_node_types: 487*9c5db199SXin Li set_selected_input_nodes(input_node_types) 488*9c5db199SXin Li 489*9c5db199SXin Li 490*9c5db199SXin Lidef set_single_selected_output_node(node_type): 491*9c5db199SXin Li """Sets one selected output node. 492*9c5db199SXin Li 493*9c5db199SXin Li Note that Chrome UI uses SetActiveOutputNode of Cras DBus API 494*9c5db199SXin Li to select one output node. 495*9c5db199SXin Li 496*9c5db199SXin Li @param node_type: A node type. 497*9c5db199SXin Li 498*9c5db199SXin Li @returns: True if the output node type is found and set active. 499*9c5db199SXin Li """ 500*9c5db199SXin Li nodes = get_cras_nodes() 501*9c5db199SXin Li for node in nodes: 502*9c5db199SXin Li if node['IsInput']: 503*9c5db199SXin Li continue 504*9c5db199SXin Li if node['Type'] == node_type: 505*9c5db199SXin Li set_active_output_node(node['Id']) 506*9c5db199SXin Li return True 507*9c5db199SXin Li return False 508*9c5db199SXin Li 509*9c5db199SXin Li 510*9c5db199SXin Lidef set_single_selected_input_node(node_type): 511*9c5db199SXin Li """Sets one selected input node. 512*9c5db199SXin Li 513*9c5db199SXin Li Note that Chrome UI uses SetActiveInputNode of Cras DBus API 514*9c5db199SXin Li to select one input node. 515*9c5db199SXin Li 516*9c5db199SXin Li @param node_type: A node type. 517*9c5db199SXin Li 518*9c5db199SXin Li @returns: True if the input node type is found and set active. 519*9c5db199SXin Li """ 520*9c5db199SXin Li nodes = get_cras_nodes() 521*9c5db199SXin Li for node in nodes: 522*9c5db199SXin Li if not node['IsInput']: 523*9c5db199SXin Li continue 524*9c5db199SXin Li if node['Type'] == node_type: 525*9c5db199SXin Li set_active_input_node(node['Id']) 526*9c5db199SXin Li return True 527*9c5db199SXin Li return False 528*9c5db199SXin Li 529*9c5db199SXin Li 530*9c5db199SXin Lidef set_selected_output_nodes(types): 531*9c5db199SXin Li """Sets selected output node types. 532*9c5db199SXin Li 533*9c5db199SXin Li Note that Chrome UI uses SetActiveOutputNode of Cras DBus API 534*9c5db199SXin Li to select one output node. Here we use add/remove active output node 535*9c5db199SXin Li to support multiple nodes. 536*9c5db199SXin Li 537*9c5db199SXin Li @param types: A list of output node types. 538*9c5db199SXin Li 539*9c5db199SXin Li """ 540*9c5db199SXin Li nodes = get_cras_nodes() 541*9c5db199SXin Li for node in nodes: 542*9c5db199SXin Li if node['IsInput']: 543*9c5db199SXin Li continue 544*9c5db199SXin Li if node['Type'] in types: 545*9c5db199SXin Li add_active_output_node(node['Id']) 546*9c5db199SXin Li elif node['Active']: 547*9c5db199SXin Li remove_active_output_node(node['Id']) 548*9c5db199SXin Li 549*9c5db199SXin Li 550*9c5db199SXin Lidef set_selected_input_nodes(types): 551*9c5db199SXin Li """Sets selected input node types. 552*9c5db199SXin Li 553*9c5db199SXin Li Note that Chrome UI uses SetActiveInputNode of Cras DBus API 554*9c5db199SXin Li to select one input node. Here we use add/remove active input node 555*9c5db199SXin Li to support multiple nodes. 556*9c5db199SXin Li 557*9c5db199SXin Li @param types: A list of input node types. 558*9c5db199SXin Li 559*9c5db199SXin Li """ 560*9c5db199SXin Li nodes = get_cras_nodes() 561*9c5db199SXin Li for node in nodes: 562*9c5db199SXin Li if not node['IsInput']: 563*9c5db199SXin Li continue 564*9c5db199SXin Li if node['Type'] in types: 565*9c5db199SXin Li add_active_input_node(node['Id']) 566*9c5db199SXin Li elif node['Active']: 567*9c5db199SXin Li remove_active_input_node(node['Id']) 568*9c5db199SXin Li 569*9c5db199SXin Li 570*9c5db199SXin Lidef set_active_input_node(node_id): 571*9c5db199SXin Li """Sets one active input node. 572*9c5db199SXin Li 573*9c5db199SXin Li @param node_id: node id. 574*9c5db199SXin Li 575*9c5db199SXin Li """ 576*9c5db199SXin Li get_cras_control_interface().SetActiveInputNode(node_id) 577*9c5db199SXin Li 578*9c5db199SXin Li 579*9c5db199SXin Lidef set_active_output_node(node_id): 580*9c5db199SXin Li """Sets one active output node. 581*9c5db199SXin Li 582*9c5db199SXin Li @param node_id: node id. 583*9c5db199SXin Li 584*9c5db199SXin Li """ 585*9c5db199SXin Li get_cras_control_interface().SetActiveOutputNode(node_id) 586*9c5db199SXin Li 587*9c5db199SXin Li 588*9c5db199SXin Lidef add_active_output_node(node_id): 589*9c5db199SXin Li """Adds an active output node. 590*9c5db199SXin Li 591*9c5db199SXin Li @param node_id: node id. 592*9c5db199SXin Li 593*9c5db199SXin Li """ 594*9c5db199SXin Li get_cras_control_interface().AddActiveOutputNode(node_id) 595*9c5db199SXin Li 596*9c5db199SXin Li 597*9c5db199SXin Lidef add_active_input_node(node_id): 598*9c5db199SXin Li """Adds an active input node. 599*9c5db199SXin Li 600*9c5db199SXin Li @param node_id: node id. 601*9c5db199SXin Li 602*9c5db199SXin Li """ 603*9c5db199SXin Li get_cras_control_interface().AddActiveInputNode(node_id) 604*9c5db199SXin Li 605*9c5db199SXin Li 606*9c5db199SXin Lidef remove_active_output_node(node_id): 607*9c5db199SXin Li """Removes an active output node. 608*9c5db199SXin Li 609*9c5db199SXin Li @param node_id: node id. 610*9c5db199SXin Li 611*9c5db199SXin Li """ 612*9c5db199SXin Li get_cras_control_interface().RemoveActiveOutputNode(node_id) 613*9c5db199SXin Li 614*9c5db199SXin Li 615*9c5db199SXin Lidef remove_active_input_node(node_id): 616*9c5db199SXin Li """Removes an active input node. 617*9c5db199SXin Li 618*9c5db199SXin Li @param node_id: node id. 619*9c5db199SXin Li 620*9c5db199SXin Li """ 621*9c5db199SXin Li get_cras_control_interface().RemoveActiveInputNode(node_id) 622*9c5db199SXin Li 623*9c5db199SXin Li 624*9c5db199SXin Lidef get_node_id_from_node_type(node_type, is_input): 625*9c5db199SXin Li """Gets node id from node type. 626*9c5db199SXin Li 627*9c5db199SXin Li @param types: A node type defined in CRAS_NODE_TYPES. 628*9c5db199SXin Li @param is_input: True if the node is input. False otherwise. 629*9c5db199SXin Li 630*9c5db199SXin Li @returns: A string for node id. 631*9c5db199SXin Li 632*9c5db199SXin Li @raises: CrasUtilsError: if unique node id can not be found. 633*9c5db199SXin Li 634*9c5db199SXin Li """ 635*9c5db199SXin Li nodes = get_cras_nodes() 636*9c5db199SXin Li find_ids = [] 637*9c5db199SXin Li for node in nodes: 638*9c5db199SXin Li if node['Type'] == node_type and node['IsInput'] == is_input: 639*9c5db199SXin Li find_ids.append(node['Id']) 640*9c5db199SXin Li if len(find_ids) != 1: 641*9c5db199SXin Li raise CrasUtilsError( 642*9c5db199SXin Li 'Can not find unique node id from node type %s' % node_type) 643*9c5db199SXin Li return find_ids[0] 644*9c5db199SXin Li 645*9c5db199SXin Li 646*9c5db199SXin Lidef get_device_id_of(node_id): 647*9c5db199SXin Li """Gets the device id of the node id. 648*9c5db199SXin Li 649*9c5db199SXin Li The conversion logic is replicated from the CRAS's type definition at 650*9c5db199SXin Li third_party/adhd/cras/src/common/cras_types.h. 651*9c5db199SXin Li 652*9c5db199SXin Li @param node_id: A string for node id. 653*9c5db199SXin Li 654*9c5db199SXin Li @returns: A string for device id. 655*9c5db199SXin Li 656*9c5db199SXin Li @raise: CrasUtilsError: if device id is invalid. 657*9c5db199SXin Li """ 658*9c5db199SXin Li device_id = str(int(node_id) >> 32) 659*9c5db199SXin Li if device_id == "0": 660*9c5db199SXin Li raise CrasUtilsError('Got invalid device_id: 0') 661*9c5db199SXin Li return device_id 662*9c5db199SXin Li 663*9c5db199SXin Li 664*9c5db199SXin Lidef get_device_id_from_node_type(node_type, is_input): 665*9c5db199SXin Li """Gets device id from node type. 666*9c5db199SXin Li 667*9c5db199SXin Li @param types: A node type defined in CRAS_NODE_TYPES. 668*9c5db199SXin Li @param is_input: True if the node is input. False otherwise. 669*9c5db199SXin Li 670*9c5db199SXin Li @returns: A string for device id. 671*9c5db199SXin Li 672*9c5db199SXin Li """ 673*9c5db199SXin Li node_id = get_node_id_from_node_type(node_type, is_input) 674*9c5db199SXin Li return get_device_id_of(node_id) 675*9c5db199SXin Li 676*9c5db199SXin Li 677*9c5db199SXin Lidef get_active_node_volume(): 678*9c5db199SXin Li """Returns volume from active node. 679*9c5db199SXin Li 680*9c5db199SXin Li @returns: int for volume 681*9c5db199SXin Li 682*9c5db199SXin Li @raises: CrasUtilsError: if node volume cannot be found. 683*9c5db199SXin Li """ 684*9c5db199SXin Li nodes = get_cras_nodes() 685*9c5db199SXin Li for node in nodes: 686*9c5db199SXin Li if node['Active'] == 1 and node['IsInput'] == 0: 687*9c5db199SXin Li return int(node['NodeVolume']) 688*9c5db199SXin Li raise CrasUtilsError('Cannot find active node volume from nodes.') 689*9c5db199SXin Li 690*9c5db199SXin Li 691*9c5db199SXin Lidef get_active_output_node_max_supported_channels(): 692*9c5db199SXin Li """Returns max supported channels from active output node. 693*9c5db199SXin Li 694*9c5db199SXin Li @returns: int for max supported channels. 695*9c5db199SXin Li 696*9c5db199SXin Li @raises: CrasUtilsError: if node cannot be found. 697*9c5db199SXin Li """ 698*9c5db199SXin Li nodes = get_cras_nodes() 699*9c5db199SXin Li for node in nodes: 700*9c5db199SXin Li if node['Active'] == 1 and node['IsInput'] == 0: 701*9c5db199SXin Li return int(node['MaxSupportedChannels']) 702*9c5db199SXin Li raise CrasUtilsError('Cannot find active output node.') 703*9c5db199SXin Li 704*9c5db199SXin Li 705*9c5db199SXin Lidef get_noise_cancellation_supported(): 706*9c5db199SXin Li """Gets whether the device supports Noise Cancellation. 707*9c5db199SXin Li 708*9c5db199SXin Li @returns: True is supported; False otherwise. 709*9c5db199SXin Li """ 710*9c5db199SXin Li return bool(get_cras_control_interface().IsNoiseCancellationSupported()) 711*9c5db199SXin Li 712*9c5db199SXin Li 713*9c5db199SXin Lidef set_bypass_block_noise_cancellation(bypass): 714*9c5db199SXin Li """Sets CRAS to bypass the blocking logic of Noise Cancellation. 715*9c5db199SXin Li 716*9c5db199SXin Li @param bypass: True for bypass; False for un-bypass. 717*9c5db199SXin Li """ 718*9c5db199SXin Li get_cras_control_interface().SetBypassBlockNoiseCancellation(bypass) 719*9c5db199SXin Li 720*9c5db199SXin Li 721*9c5db199SXin Lidef set_noise_cancellation_enabled(enabled): 722*9c5db199SXin Li """Sets the state to enable or disable Noise Cancellation. 723*9c5db199SXin Li 724*9c5db199SXin Li @param enabled: True to enable; False to disable. 725*9c5db199SXin Li """ 726*9c5db199SXin Li get_cras_control_interface().SetNoiseCancellationEnabled(enabled) 727*9c5db199SXin Li 728*9c5db199SXin Li 729*9c5db199SXin Lidef set_floss_enabled(enabled): 730*9c5db199SXin Li """Sets whether CRAS stack expects to use Floss. 731*9c5db199SXin Li 732*9c5db199SXin Li @param enabled: True for Floss, False for Bluez. 733*9c5db199SXin Li """ 734*9c5db199SXin Li get_cras_control_interface().SetFlossEnabled(enabled) 735*9c5db199SXin Li 736*9c5db199SXin Li 737*9c5db199SXin Liclass CrasTestClient(object): 738*9c5db199SXin Li """An object to perform cras_test_client functions.""" 739*9c5db199SXin Li 740*9c5db199SXin Li BLOCK_SIZE = None 741*9c5db199SXin Li PIN_DEVICE = None 742*9c5db199SXin Li SAMPLE_FORMAT = 'S16_LE' 743*9c5db199SXin Li DURATION = 10 744*9c5db199SXin Li CHANNELS = 2 745*9c5db199SXin Li RATE = 48000 746*9c5db199SXin Li 747*9c5db199SXin Li 748*9c5db199SXin Li def __init__(self): 749*9c5db199SXin Li self._proc = None 750*9c5db199SXin Li self._capturing_proc = None 751*9c5db199SXin Li self._playing_proc = None 752*9c5db199SXin Li self._capturing_msg = 'capturing audio file' 753*9c5db199SXin Li self._playing_msg = 'playing audio file' 754*9c5db199SXin Li self._wbs_cmd = '%s --set_wbs_enabled ' % _CRAS_TEST_CLIENT 755*9c5db199SXin Li self._enable_wbs_cmd = ('%s 1' % self._wbs_cmd).split() 756*9c5db199SXin Li self._disable_wbs_cmd = ('%s 0' % self._wbs_cmd).split() 757*9c5db199SXin Li self._info_cmd = [_CRAS_TEST_CLIENT,] 758*9c5db199SXin Li self._select_input_cmd = '%s --select_input ' % _CRAS_TEST_CLIENT 759*9c5db199SXin Li 760*9c5db199SXin Li 761*9c5db199SXin Li def start_subprocess(self, proc, proc_cmd, filename, proc_msg): 762*9c5db199SXin Li """Start a capture or play subprocess 763*9c5db199SXin Li 764*9c5db199SXin Li @param proc: the process 765*9c5db199SXin Li @param proc_cmd: the process command and its arguments 766*9c5db199SXin Li @param filename: the file name to capture or play 767*9c5db199SXin Li @param proc_msg: the message to display in logging 768*9c5db199SXin Li 769*9c5db199SXin Li @returns: True if the process is started successfully 770*9c5db199SXin Li """ 771*9c5db199SXin Li if proc is None: 772*9c5db199SXin Li try: 773*9c5db199SXin Li self._proc = subprocess.Popen(proc_cmd) 774*9c5db199SXin Li logging.debug('Start %s %s on the DUT', proc_msg, filename) 775*9c5db199SXin Li except Exception as e: 776*9c5db199SXin Li logging.error('Failed to popen: %s (%s)', proc_msg, e) 777*9c5db199SXin Li return False 778*9c5db199SXin Li else: 779*9c5db199SXin Li logging.error('cannot run the command twice: %s', proc_msg) 780*9c5db199SXin Li return False 781*9c5db199SXin Li return True 782*9c5db199SXin Li 783*9c5db199SXin Li 784*9c5db199SXin Li def stop_subprocess(self, proc, proc_msg): 785*9c5db199SXin Li """Stop a subprocess 786*9c5db199SXin Li 787*9c5db199SXin Li @param proc: the process to stop 788*9c5db199SXin Li @param proc_msg: the message to display in logging 789*9c5db199SXin Li 790*9c5db199SXin Li @returns: True if the process is stopped successfully 791*9c5db199SXin Li """ 792*9c5db199SXin Li if proc is None: 793*9c5db199SXin Li logging.error('cannot run stop %s before starting it.', proc_msg) 794*9c5db199SXin Li return False 795*9c5db199SXin Li 796*9c5db199SXin Li proc.terminate() 797*9c5db199SXin Li try: 798*9c5db199SXin Li utils.poll_for_condition( 799*9c5db199SXin Li condition=lambda: proc.poll() is not None, 800*9c5db199SXin Li exception=CrasUtilsError, 801*9c5db199SXin Li timeout=10, 802*9c5db199SXin Li sleep_interval=0.5, 803*9c5db199SXin Li desc='Waiting for subprocess to terminate') 804*9c5db199SXin Li except Exception: 805*9c5db199SXin Li logging.warning('Killing subprocess due to timeout') 806*9c5db199SXin Li proc.kill() 807*9c5db199SXin Li proc.wait() 808*9c5db199SXin Li 809*9c5db199SXin Li logging.debug('stop %s on the DUT', proc_msg) 810*9c5db199SXin Li return True 811*9c5db199SXin Li 812*9c5db199SXin Li 813*9c5db199SXin Li def start_capturing_subprocess(self, capture_file, block_size=BLOCK_SIZE, 814*9c5db199SXin Li duration=DURATION, pin_device=PIN_DEVICE, 815*9c5db199SXin Li sample_format=SAMPLE_FORMAT, 816*9c5db199SXin Li channels=CHANNELS, rate=RATE): 817*9c5db199SXin Li """Start capturing in a subprocess. 818*9c5db199SXin Li 819*9c5db199SXin Li @param capture_file: the name of file the audio to be stored in 820*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency) 821*9c5db199SXin Li @param duration: seconds to record. If it is None, duration is not set, 822*9c5db199SXin Li and will keep capturing audio until terminated 823*9c5db199SXin Li @param sample_format: the sample format 824*9c5db199SXin Li @param pin_device: the device id to record from 825*9c5db199SXin Li @param channels: number of channels 826*9c5db199SXin Li @param rate: the sampling rate 827*9c5db199SXin Li 828*9c5db199SXin Li @returns: True if the process is started successfully 829*9c5db199SXin Li """ 830*9c5db199SXin Li proc_cmd = capture_cmd(capture_file, block_size=block_size, 831*9c5db199SXin Li duration=duration, sample_format=sample_format, 832*9c5db199SXin Li pin_device=pin_device, channels=channels, 833*9c5db199SXin Li rate=rate) 834*9c5db199SXin Li result = self.start_subprocess(self._capturing_proc, proc_cmd, 835*9c5db199SXin Li capture_file, self._capturing_msg) 836*9c5db199SXin Li if result: 837*9c5db199SXin Li self._capturing_proc = self._proc 838*9c5db199SXin Li return result 839*9c5db199SXin Li 840*9c5db199SXin Li 841*9c5db199SXin Li def stop_capturing_subprocess(self): 842*9c5db199SXin Li """Stop the capturing subprocess.""" 843*9c5db199SXin Li result = self.stop_subprocess(self._capturing_proc, self._capturing_msg) 844*9c5db199SXin Li if result: 845*9c5db199SXin Li self._capturing_proc = None 846*9c5db199SXin Li return result 847*9c5db199SXin Li 848*9c5db199SXin Li 849*9c5db199SXin Li def start_playing_subprocess(self, audio_file, block_size=BLOCK_SIZE, 850*9c5db199SXin Li duration=DURATION, pin_device=PIN_DEVICE, 851*9c5db199SXin Li channels=CHANNELS, rate=RATE): 852*9c5db199SXin Li """Start playing the audio file in a subprocess. 853*9c5db199SXin Li 854*9c5db199SXin Li @param audio_file: the name of audio file to play 855*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency) 856*9c5db199SXin Li @param duration: seconds to play. If it is None, duration is not set, 857*9c5db199SXin Li and will keep playing audio until terminated 858*9c5db199SXin Li @param pin_device: the device id to play to 859*9c5db199SXin Li @param channels: number of channels 860*9c5db199SXin Li @param rate: the sampling rate 861*9c5db199SXin Li 862*9c5db199SXin Li @returns: True if the process is started successfully 863*9c5db199SXin Li """ 864*9c5db199SXin Li proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device, 865*9c5db199SXin Li channels, rate) 866*9c5db199SXin Li result = self.start_subprocess(self._playing_proc, proc_cmd, 867*9c5db199SXin Li audio_file, self._playing_msg) 868*9c5db199SXin Li if result: 869*9c5db199SXin Li self._playing_proc = self._proc 870*9c5db199SXin Li return result 871*9c5db199SXin Li 872*9c5db199SXin Li 873*9c5db199SXin Li def stop_playing_subprocess(self): 874*9c5db199SXin Li """Stop the playing subprocess.""" 875*9c5db199SXin Li result = self.stop_subprocess(self._playing_proc, self._playing_msg) 876*9c5db199SXin Li if result: 877*9c5db199SXin Li self._playing_proc = None 878*9c5db199SXin Li return result 879*9c5db199SXin Li 880*9c5db199SXin Li 881*9c5db199SXin Li def play(self, audio_file, block_size=BLOCK_SIZE, duration=DURATION, 882*9c5db199SXin Li pin_device=PIN_DEVICE, channels=CHANNELS, rate=RATE): 883*9c5db199SXin Li """Play the audio file. 884*9c5db199SXin Li 885*9c5db199SXin Li This method will get blocked until it has completed playing back. 886*9c5db199SXin Li If you do not want to get blocked, use start_playing_subprocess() 887*9c5db199SXin Li above instead. 888*9c5db199SXin Li 889*9c5db199SXin Li @param audio_file: the name of audio file to play 890*9c5db199SXin Li @param block_size: the number of frames per callback(dictates latency) 891*9c5db199SXin Li @param duration: seconds to play. If it is None, duration is not set, 892*9c5db199SXin Li and will keep playing audio until terminated 893*9c5db199SXin Li @param pin_device: the device id to play to 894*9c5db199SXin Li @param channels: number of channels 895*9c5db199SXin Li @param rate: the sampling rate 896*9c5db199SXin Li 897*9c5db199SXin Li @returns: True if the process is started successfully 898*9c5db199SXin Li """ 899*9c5db199SXin Li proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device, 900*9c5db199SXin Li channels, rate) 901*9c5db199SXin Li try: 902*9c5db199SXin Li self._proc = subprocess.call(proc_cmd) 903*9c5db199SXin Li logging.debug('call "%s" on the DUT', proc_cmd) 904*9c5db199SXin Li except Exception as e: 905*9c5db199SXin Li logging.error('Failed to call: %s (%s)', proc_cmd, e) 906*9c5db199SXin Li return False 907*9c5db199SXin Li return True 908*9c5db199SXin Li 909*9c5db199SXin Li 910*9c5db199SXin Li def enable_wbs(self, value): 911*9c5db199SXin Li """Enable or disable wideband speech (wbs) per the value. 912*9c5db199SXin Li 913*9c5db199SXin Li @param value: True to enable wbs. 914*9c5db199SXin Li 915*9c5db199SXin Li @returns: True if the operation succeeds. 916*9c5db199SXin Li """ 917*9c5db199SXin Li cmd = self._enable_wbs_cmd if value else self._disable_wbs_cmd 918*9c5db199SXin Li logging.debug('call "%s" on the DUT', cmd) 919*9c5db199SXin Li if subprocess.call(cmd): 920*9c5db199SXin Li logging.error('Failed to call: %s (%s)', cmd) 921*9c5db199SXin Li return False 922*9c5db199SXin Li return True 923*9c5db199SXin Li 924*9c5db199SXin Li 925*9c5db199SXin Li def select_input_device(self, device_name): 926*9c5db199SXin Li """Select the audio input device. 927*9c5db199SXin Li 928*9c5db199SXin Li @param device_name: the name of the Bluetooth peer device 929*9c5db199SXin Li 930*9c5db199SXin Li @returns: True if the operation succeeds. 931*9c5db199SXin Li """ 932*9c5db199SXin Li logging.debug('to select input device for device_name: %s', device_name) 933*9c5db199SXin Li try: 934*9c5db199SXin Li info = subprocess.check_output(self._info_cmd) 935*9c5db199SXin Li logging.debug('info: %s', info) 936*9c5db199SXin Li except Exception as e: 937*9c5db199SXin Li logging.error('Failed to call: %s (%s)', self._info_cmd, e) 938*9c5db199SXin Li return False 939*9c5db199SXin Li 940*9c5db199SXin Li flag_input_nodes = False 941*9c5db199SXin Li audio_input_node = None 942*9c5db199SXin Li for line in info.decode().splitlines(): 943*9c5db199SXin Li if 'Input Nodes' in line: 944*9c5db199SXin Li flag_input_nodes = True 945*9c5db199SXin Li elif 'Attached clients' in line: 946*9c5db199SXin Li flag_input_nodes = False 947*9c5db199SXin Li 948*9c5db199SXin Li if flag_input_nodes: 949*9c5db199SXin Li if device_name in line: 950*9c5db199SXin Li audio_input_node = line.split()[1] 951*9c5db199SXin Li logging.debug('%s', audio_input_node) 952*9c5db199SXin Li break 953*9c5db199SXin Li 954*9c5db199SXin Li if audio_input_node is None: 955*9c5db199SXin Li logging.error('Failed to find audio input node: %s', device_name) 956*9c5db199SXin Li return False 957*9c5db199SXin Li 958*9c5db199SXin Li select_input_cmd = (self._select_input_cmd + audio_input_node).split() 959*9c5db199SXin Li if subprocess.call(select_input_cmd): 960*9c5db199SXin Li logging.error('Failed to call: %s (%s)', select_input_cmd, e) 961*9c5db199SXin Li return False 962*9c5db199SXin Li 963*9c5db199SXin Li logging.debug('call "%s" on the DUT', select_input_cmd) 964*9c5db199SXin Li return True 965*9c5db199SXin Li 966*9c5db199SXin Li 967*9c5db199SXin Li def set_player_playback_status(self, status): 968*9c5db199SXin Li """Set playback status for the registered media player. 969*9c5db199SXin Li 970*9c5db199SXin Li @param status: playback status in string. 971*9c5db199SXin Li 972*9c5db199SXin Li """ 973*9c5db199SXin Li try: 974*9c5db199SXin Li get_cras_control_interface().SetPlayerPlaybackStatus(status) 975*9c5db199SXin Li except Exception as e: 976*9c5db199SXin Li logging.error('Failed to set player playback status: %s', e) 977*9c5db199SXin Li return False 978*9c5db199SXin Li 979*9c5db199SXin Li return True 980*9c5db199SXin Li 981*9c5db199SXin Li 982*9c5db199SXin Li def set_player_position(self, position): 983*9c5db199SXin Li """Set media position for the registered media player. 984*9c5db199SXin Li 985*9c5db199SXin Li @param position: position in micro seconds. 986*9c5db199SXin Li 987*9c5db199SXin Li """ 988*9c5db199SXin Li try: 989*9c5db199SXin Li get_cras_control_interface().SetPlayerPosition(position) 990*9c5db199SXin Li except Exception as e: 991*9c5db199SXin Li logging.error('Failed to set player position: %s', e) 992*9c5db199SXin Li return False 993*9c5db199SXin Li 994*9c5db199SXin Li return True 995*9c5db199SXin Li 996*9c5db199SXin Li 997*9c5db199SXin Li def set_player_metadata(self, metadata): 998*9c5db199SXin Li """Set title, artist, and album for the registered media player. 999*9c5db199SXin Li 1000*9c5db199SXin Li @param metadata: dictionary of media metadata. 1001*9c5db199SXin Li 1002*9c5db199SXin Li """ 1003*9c5db199SXin Li try: 1004*9c5db199SXin Li get_cras_control_interface().SetPlayerMetadata(metadata) 1005*9c5db199SXin Li except Exception as e: 1006*9c5db199SXin Li logging.error('Failed to set player metadata: %s', e) 1007*9c5db199SXin Li return False 1008*9c5db199SXin Li 1009*9c5db199SXin Li return True 1010*9c5db199SXin Li 1011*9c5db199SXin Li 1012*9c5db199SXin Li def _encode_length_for_dbus(self, length): 1013*9c5db199SXin Li """Encode length as Int64 for |SetPlayerMetadata|.""" 1014*9c5db199SXin Li try: 1015*9c5db199SXin Li import dbus 1016*9c5db199SXin Li except ImportError as e: 1017*9c5db199SXin Li logging.exception( 1018*9c5db199SXin Li 'Can not import dbus: %s. This method should only be ' 1019*9c5db199SXin Li 'called on Cros device.', e) 1020*9c5db199SXin Li raise 1021*9c5db199SXin Li 1022*9c5db199SXin Li length_variant = dbus.types.Int64(length, variant_level=1) 1023*9c5db199SXin Li return dbus.Dictionary({'length': length_variant}, signature='sv') 1024*9c5db199SXin Li 1025*9c5db199SXin Li def set_player_length(self, length): 1026*9c5db199SXin Li """Set metadata length for the registered media player. 1027*9c5db199SXin Li 1028*9c5db199SXin Li Media length is a part of metadata information. However, without 1029*9c5db199SXin Li specify its type to int64. dbus-python will guess the variant type to 1030*9c5db199SXin Li be int32 by default. Separate it from the metadata function to help 1031*9c5db199SXin Li prepare the data differently. 1032*9c5db199SXin Li 1033*9c5db199SXin Li @param length: Integer value that will be encoded for dbus. 1034*9c5db199SXin Li 1035*9c5db199SXin Li """ 1036*9c5db199SXin Li try: 1037*9c5db199SXin Li length_dbus = self._encode_length_for_dbus(length) 1038*9c5db199SXin Li get_cras_control_interface().SetPlayerMetadata(length_dbus) 1039*9c5db199SXin Li except Exception as e: 1040*9c5db199SXin Li logging.error('Failed to set player length: %s', e) 1041*9c5db199SXin Li return False 1042*9c5db199SXin Li 1043*9c5db199SXin Li return True 1044