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