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