1# Copyright 2014 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""This module provides the audio widgets used in audio tests.""" 6 7import abc 8import copy 9import logging 10import os 11import tempfile 12 13from autotest_lib.client.cros.audio import audio_data 14from autotest_lib.client.cros.audio import audio_test_data 15from autotest_lib.client.cros.audio import cras_configs 16from autotest_lib.client.cros.audio import sox_utils 17from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids 18from autotest_lib.client.cros.chameleon import chameleon_port_finder 19 20 21_CHAMELEON_FILE_PATH = os.path.join(os.path.dirname(__file__)) 22 23class AudioWidget(object): 24 """ 25 This class abstracts an audio widget in audio test framework. A widget 26 is identified by its audio port. The handler passed in at __init__ will 27 handle action on the audio widget. 28 29 Properties: 30 audio_port: The AudioPort this AudioWidget resides in. 31 handler: The handler that handles audio action on the widget. It is 32 actually a (Chameleon/Cros)(Input/Output)WidgetHandler object. 33 34 """ 35 def __init__(self, audio_port, handler): 36 """Initializes an AudioWidget on a AudioPort. 37 38 @param audio_port: An AudioPort object. 39 @param handler: A WidgetHandler object which handles action on the widget. 40 41 """ 42 self.audio_port = audio_port 43 self.handler = handler 44 45 46 @property 47 def port_id(self): 48 """Port id of this audio widget. 49 50 @returns: A string. The port id defined in chameleon_audio_ids for this 51 audio widget. 52 """ 53 return self.audio_port.port_id 54 55 56class AudioInputWidget(AudioWidget): 57 """ 58 This class abstracts an audio input widget. This class provides the audio 59 action that is available on an input audio port. 60 61 Properties: 62 _remote_rec_path: The path to the recorded file on the remote host. 63 _rec_binary: The recorded binary data. 64 _rec_format: The recorded data format. A dict containing 65 file_type: 'raw' or 'wav'. 66 sample_format: 'S32_LE' for 32-bit signed integer in 67 little-endian. Refer to aplay manpage for 68 other formats. 69 channel: channel number. 70 rate: sampling rate. 71 72 _channel_map: A list containing current channel map. Checks docstring 73 of channel_map method for details. 74 75 """ 76 def __init__(self, *args, **kwargs): 77 """Initializes an AudioInputWidget.""" 78 super(AudioInputWidget, self).__init__(*args, **kwargs) 79 self._remote_rec_path = None 80 self._rec_binary = None 81 self._rec_format = None 82 self._channel_map = None 83 self._init_channel_map_without_link() 84 85 86 def start_recording(self): 87 """Starts recording.""" 88 self._remote_rec_path = None 89 self._rec_binary = None 90 self._rec_format = None 91 self.handler.start_recording() 92 93 94 def stop_recording(self): 95 """Stops recording.""" 96 self._remote_rec_path, self._rec_format = self.handler.stop_recording() 97 98 99 def start_listening(self): 100 """Starts listening.""" 101 self._remote_rec_path = None 102 self._rec_binary = None 103 self._rec_format = None 104 self.handler.start_listening() 105 106 107 def stop_listening(self): 108 """Stops listening.""" 109 self._remote_rec_path, self._rec_format = self.handler.stop_listening() 110 111 112 def read_recorded_binary(self): 113 """Gets recorded file from handler and fills _rec_binary.""" 114 self._rec_binary = self.handler.get_recorded_binary( 115 self._remote_rec_path, self._rec_format) 116 117 118 def save_file(self, file_path): 119 """Saves recorded data to a file. 120 121 @param file_path: The path to save the file. 122 123 """ 124 with open(file_path, 'wb') as f: 125 logging.debug('Saving recorded raw file to %s', file_path) 126 f.write(self._rec_binary) 127 128 wav_file_path = file_path + '.wav' 129 logging.debug('Saving recorded wav file to %s', wav_file_path) 130 sox_utils.convert_raw_file( 131 path_src=file_path, 132 channels_src=self._channel, 133 rate_src=self._sampling_rate, 134 bits_src=self._sample_size_bits, 135 path_dst=wav_file_path) 136 137 138 def get_binary(self): 139 """Gets recorded binary data. 140 141 @returns: The recorded binary data. 142 143 """ 144 return self._rec_binary 145 146 147 @property 148 def data_format(self): 149 """The recorded data format. 150 151 @returns: The recorded data format. 152 153 """ 154 return self._rec_format 155 156 157 @property 158 def channel_map(self): 159 """The recorded data channel map. 160 161 @returns: The recorded channel map. A list containing channel mapping. 162 E.g. [1, 0, None, None, None, None, None, None] means 163 channel 0 of recorded data should be mapped to channel 1 of 164 data played to the recorder. Channel 1 of recorded data should 165 be mapped to channel 0 of data played to recorder. 166 Channel 2 to 7 of recorded data should be ignored. 167 168 """ 169 return self._channel_map 170 171 172 @channel_map.setter 173 def channel_map(self, new_channel_map): 174 """Sets channel map. 175 176 @param new_channel_map: A list containing new channel map. 177 178 """ 179 self._channel_map = copy.deepcopy(new_channel_map) 180 181 182 def _init_channel_map_without_link(self): 183 """Initializes channel map without WidgetLink. 184 185 WidgetLink sets channel map to a sink widget when the link combines 186 a source widget to a sink widget. For simple cases like internal 187 microphone on Cros device, or Mic port on Chameleon, the audio signal 188 is over the air, so we do not use link to combine the source to 189 the sink. We just set a default channel map in this case. 190 191 """ 192 if self.port_id in [ids.ChameleonIds.MIC, ids.CrosIds.INTERNAL_MIC]: 193 self._channel_map = [0] 194 195 196 @property 197 def _sample_size_bytes(self): 198 """Gets sample size in bytes of recorded data.""" 199 return audio_data.SAMPLE_FORMATS[ 200 self._rec_format['sample_format']]['size_bytes'] 201 202 203 @property 204 def _sample_size_bits(self): 205 """Gets sample size in bits of recorded data.""" 206 return self._sample_size_bytes * 8 207 208 209 @property 210 def _channel(self): 211 """Gets number of channels of recorded data.""" 212 return self._rec_format['channel'] 213 214 215 @property 216 def _sampling_rate(self): 217 """Gets sampling rate of recorded data.""" 218 return self._rec_format['rate'] 219 220 221 def remove_head(self, duration_secs): 222 """Removes a duration of recorded data from head. 223 224 @param duration_secs: The duration in seconds to be removed from head. 225 226 """ 227 offset = int(self._sampling_rate * duration_secs * 228 self._sample_size_bytes * self._channel) 229 self._rec_binary = self._rec_binary[offset:] 230 231 232 def lowpass_filter(self, frequency): 233 """Passes the recorded data to a lowpass filter. 234 235 @param frequency: The 3dB frequency of lowpass filter. 236 237 """ 238 with tempfile.NamedTemporaryFile( 239 prefix='original_') as original_file: 240 with tempfile.NamedTemporaryFile( 241 prefix='filtered_') as filtered_file: 242 243 original_file.write(self._rec_binary) 244 original_file.flush() 245 246 sox_utils.lowpass_filter( 247 original_file.name, self._channel, 248 self._sample_size_bits, self._sampling_rate, 249 filtered_file.name, frequency) 250 251 self._rec_binary = filtered_file.read() 252 253 254class AudioOutputWidget(AudioWidget): 255 """ 256 This class abstracts an audio output widget. This class provides the audio 257 action that is available on an output audio port. 258 259 """ 260 def __init__(self, *args, **kwargs): 261 """Initializes an AudioOutputWidget.""" 262 super(AudioOutputWidget, self).__init__(*args, **kwargs) 263 self._remote_playback_path = None 264 265 266 def set_playback_data(self, test_data): 267 """Sets data to play. 268 269 Sets the data to play in the handler and gets the remote file path. 270 271 @param test_data: An AudioTestData object. 272 273 @returns: path to the remote playback data 274 275 """ 276 self._remote_playback_path = self.handler.set_playback_data(test_data) 277 278 return self._remote_playback_path 279 280 def start_playback(self, blocking=False): 281 """Starts playing audio specified in previous set_playback_data call. 282 283 @param blocking: Blocks this call until playback finishes. 284 285 """ 286 self.handler.start_playback(self._remote_playback_path, blocking) 287 288 def start_playback_with_path(self, remote_playback_path, blocking=False): 289 """Starts playing audio specified in previous set_playback_data call 290 and the remote_playback_path returned by set_playback_data function. 291 292 @param remote_playback_path: Path returned by set_playback_data. 293 @param blocking: Blocks this call until playback finishes. 294 295 """ 296 self.handler.start_playback(remote_playback_path, blocking) 297 298 299 def stop_playback(self): 300 """Stops playing audio.""" 301 self.handler.stop_playback() 302 303 304class WidgetHandler(object): 305 """This class abstracts handler for basic actions on widget.""" 306 __metaclass__ = abc.ABCMeta 307 308 @abc.abstractmethod 309 def plug(self): 310 """Plug this widget.""" 311 pass 312 313 314 @abc.abstractmethod 315 def unplug(self): 316 """Unplug this widget.""" 317 pass 318 319 320class ChameleonWidgetHandler(WidgetHandler): 321 """ 322 This class abstracts a Chameleon audio widget handler. 323 324 Properties: 325 interface: A string that represents the interface name on 326 Chameleon, e.g. 'HDMI', 'LineIn', 'LineOut'. 327 scale: The scale is the scaling factor to be applied on the data of the 328 widget before playing or after recording. 329 _chameleon_board: A ChameleonBoard object to control Chameleon. 330 _port: A ChameleonPort object to control port on Chameleon. 331 332 """ 333 # The mic port on chameleon has a small gain. We need to scale 334 # the recorded value up, otherwise, the recorded value will be 335 # too small and will be falsely judged as not meaningful in the 336 # processing, even when the recorded audio is clear. 337 _DEFAULT_MIC_SCALE = 50.0 338 339 def __init__(self, chameleon_board, interface): 340 """Initializes a ChameleonWidgetHandler. 341 342 @param chameleon_board: A ChameleonBoard object. 343 @param interface: A string that represents the interface name on 344 Chameleon, e.g. 'HDMI', 'LineIn', 'LineOut'. 345 346 """ 347 self.interface = interface 348 self._chameleon_board = chameleon_board 349 self._port = self._find_port(interface) 350 self.scale = None 351 self._init_scale_without_link() 352 353 354 @abc.abstractmethod 355 def _find_port(self, interface): 356 """Finds the port by interface.""" 357 pass 358 359 360 def plug(self): 361 """Plugs this widget.""" 362 self._port.plug() 363 364 365 def unplug(self): 366 """Unplugs this widget.""" 367 self._port.unplug() 368 369 370 def _init_scale_without_link(self): 371 """Initializes scale for widget handler not used with link. 372 373 Audio widget link sets scale when it connects two audio widgets. 374 For audio widget not used with link, e.g. Mic on Chameleon, we set 375 a default scale here. 376 377 """ 378 if self.interface == 'Mic': 379 self.scale = self._DEFAULT_MIC_SCALE 380 381 382class ChameleonInputWidgetHandler(ChameleonWidgetHandler): 383 """ 384 This class abstracts a Chameleon audio input widget handler. 385 386 """ 387 def start_recording(self): 388 """Starts recording.""" 389 self._port.start_capturing_audio() 390 391 392 def stop_recording(self): 393 """Stops recording. 394 395 Gets remote recorded path and format from Chameleon. The format can 396 then be used in get_recorded_binary() 397 398 @returns: A tuple (remote_path, data_format) for recorded data. 399 Refer to stop_capturing_audio call of ChameleonAudioInput. 400 401 """ 402 return self._port.stop_capturing_audio() 403 404 405 def get_recorded_binary(self, remote_path, record_format): 406 """Gets remote recorded file binary. 407 408 Reads file from Chameleon host and handles scale if needed. 409 410 @param remote_path: The path to the recorded file on Chameleon. 411 @param record_format: The recorded data format. A dict containing 412 file_type: 'raw' or 'wav'. 413 sample_format: 'S32_LE' for 32-bit signed integer in 414 little-endian. Refer to aplay manpage for 415 other formats. 416 channel: channel number. 417 rate: sampling rate. 418 419 @returns: The recorded binary. 420 421 """ 422 with tempfile.NamedTemporaryFile(prefix='recorded_') as f: 423 self._chameleon_board.host.get_file(remote_path, f.name) 424 425 # Handles scaling using audio_test_data. 426 test_data = audio_test_data.AudioTestData(record_format, f.name) 427 converted_test_data = test_data.convert(record_format, self.scale) 428 try: 429 return converted_test_data.get_binary() 430 finally: 431 converted_test_data.delete() 432 433 434 def _find_port(self, interface): 435 """Finds a Chameleon audio port by interface(port name). 436 437 @param interface: string, the interface. e.g: HDMI. 438 439 @returns: A ChameleonPort object. 440 441 @raises: ValueError if port is not connected. 442 443 """ 444 finder = chameleon_port_finder.ChameleonAudioInputFinder( 445 self._chameleon_board) 446 chameleon_port = finder.find_port(interface) 447 if not chameleon_port: 448 raise ValueError( 449 'Port %s is not connected to Chameleon' % interface) 450 return chameleon_port 451 452 453class ChameleonHDMIInputWidgetHandlerError(Exception): 454 """Error in ChameleonHDMIInputWidgetHandler.""" 455 456 457class ChameleonHDMIInputWidgetHandler(ChameleonInputWidgetHandler): 458 """This class abstracts a Chameleon HDMI audio input widget handler.""" 459 _EDID_FILE_PATH = os.path.join( 460 _CHAMELEON_FILE_PATH, 'test_data/edids/HDMI_DELL_U2410.txt') 461 462 def __init__(self, chameleon_board, interface, display_facade): 463 """Initializes a ChameleonHDMIInputWidgetHandler. 464 465 @param chameleon_board: Pass to ChameleonInputWidgetHandler. 466 @param interface: Pass to ChameleonInputWidgetHandler. 467 @param display_facade: A DisplayFacadeRemoteAdapter to access 468 Cros device display functionality. 469 470 """ 471 super(ChameleonHDMIInputWidgetHandler, self).__init__( 472 chameleon_board, interface) 473 self._display_facade = display_facade 474 self._hdmi_video_port = None 475 476 self._find_video_port() 477 478 479 def _find_video_port(self): 480 """Finds HDMI as a video port.""" 481 finder = chameleon_port_finder.ChameleonVideoInputFinder( 482 self._chameleon_board, self._display_facade) 483 self._hdmi_video_port = finder.find_port(self.interface) 484 if not self._hdmi_video_port: 485 raise ChameleonHDMIInputWidgetHandlerError( 486 'Can not find HDMI port, perhaps HDMI is not connected?') 487 488 489 def set_edid_for_audio(self): 490 """Sets the EDID suitable for audio test.""" 491 self._hdmi_video_port.set_edid_from_file(self._EDID_FILE_PATH) 492 493 494 def restore_edid(self): 495 """Restores the original EDID.""" 496 self._hdmi_video_port.restore_edid() 497 498 499class ChameleonOutputWidgetHandler(ChameleonWidgetHandler): 500 """ 501 This class abstracts a Chameleon audio output widget handler. 502 503 """ 504 def __init__(self, *args, **kwargs): 505 """Initializes an ChameleonOutputWidgetHandler.""" 506 super(ChameleonOutputWidgetHandler, self).__init__(*args, **kwargs) 507 self._test_data_for_chameleon_format = None 508 509 510 def set_playback_data(self, test_data): 511 """Sets data to play. 512 513 Handles scale if needed. Creates a path and sends the scaled data to 514 Chameleon at that path. 515 516 @param test_data: An AudioTestData object. 517 518 @return: The remote data path on Chameleon. 519 520 """ 521 self._test_data_for_chameleon_format = test_data.data_format 522 return self._scale_and_send_playback_data(test_data) 523 524 525 def _scale_and_send_playback_data(self, test_data): 526 """Sets data to play on Chameleon. 527 528 Creates a path and sends the scaled test data to Chameleon at that path. 529 530 @param test_data: An AudioTestData object. 531 532 @return: The remote data path on Chameleon. 533 534 """ 535 test_data_for_chameleon = test_data.convert( 536 self._test_data_for_chameleon_format, self.scale) 537 538 try: 539 with tempfile.NamedTemporaryFile(prefix='audio_') as f: 540 self._chameleon_board.host.send_file( 541 test_data_for_chameleon.path, f.name) 542 return f.name 543 finally: 544 test_data_for_chameleon.delete() 545 546 547 def start_playback(self, path, blocking=False): 548 """Starts playback. 549 550 @param path: The path to the file to play on Chameleon. 551 @param blocking: Blocks this call until playback finishes. 552 553 """ 554 if blocking: 555 raise NotImplementedError( 556 'Blocking playback on chameleon is not supported') 557 558 self._port.start_playing_audio( 559 path, self._test_data_for_chameleon_format) 560 561 562 def stop_playback(self): 563 """Stops playback.""" 564 self._port.stop_playing_audio() 565 566 567 def _find_port(self, interface): 568 """Finds a Chameleon audio port by interface(port name). 569 570 @param interface: string, the interface. e.g: LineOut. 571 572 @returns: A ChameleonPort object. 573 574 @raises: ValueError if port is not connected. 575 576 """ 577 finder = chameleon_port_finder.ChameleonAudioOutputFinder( 578 self._chameleon_board) 579 chameleon_port = finder.find_port(interface) 580 if not chameleon_port: 581 raise ValueError( 582 'Port %s is not connected to Chameleon' % interface) 583 return chameleon_port 584 585 586class ChameleonLineOutOutputWidgetHandler(ChameleonOutputWidgetHandler): 587 """ 588 This class abstracts a Chameleon usb audio output widget handler. 589 590 """ 591 592 _DEFAULT_DATA_FORMAT = dict(file_type='raw', 593 sample_format='S32_LE', 594 channel=8, 595 rate=48000) 596 597 def set_playback_data(self, test_data): 598 """Sets data to play. 599 600 Handles scale if needed. Creates a path and sends the scaled data to 601 Chameleon at that path. 602 603 @param test_data: An AudioTestData object. 604 605 @return: The remote data path on Chameleon. 606 607 """ 608 self._test_data_for_chameleon_format = self._DEFAULT_DATA_FORMAT 609 return self._scale_and_send_playback_data(test_data) 610 611 612 613class CrosWidgetHandler(WidgetHandler): 614 """ 615 This class abstracts a Cros device audio widget handler. 616 617 Properties: 618 _audio_facade: An AudioFacadeRemoteAdapter to access Cros device 619 audio functionality. 620 _plug_handler: A PlugHandler for performing plug and unplug. 621 622 """ 623 def __init__(self, audio_facade, plug_handler): 624 """Initializes a CrosWidgetHandler. 625 626 @param audio_facade: An AudioFacadeRemoteAdapter to access Cros device 627 audio functionality. 628 @param plug_handler: A PlugHandler object for plug and unplug. 629 630 """ 631 self._audio_facade = audio_facade 632 self._plug_handler = plug_handler 633 634 635 def plug(self): 636 """Plugs this widget.""" 637 logging.info('CrosWidgetHandler: plug') 638 self._plug_handler.plug() 639 640 641 def unplug(self): 642 """Unplugs this widget.""" 643 logging.info('CrosWidgetHandler: unplug') 644 self._plug_handler.unplug() 645 646 647class PlugHandler(object): 648 """This class abstracts plug/unplug action for widgets on Cros device. 649 650 This class will be used by CrosWidgetHandler when performinng plug/unplug. 651 652 """ 653 def __init__(self): 654 """Initializes a PlugHandler.""" 655 656 657 def plug(self): 658 """Plugs in the widget/device.""" 659 raise NotImplementedError('plug() not implemented.') 660 661 662 def unplug(self): 663 """Unplugs the widget/device.""" 664 raise NotImplementedError('unplug() not implemented.') 665 666 667class DummyPlugHandler(PlugHandler): 668 """A dummy class that does not do anything for plug() or unplug(). 669 670 This class can be used by Cros widgets that have alternative ways of 671 performing plug and unplug. 672 673 """ 674 675 def plug(self): 676 """Does nothing for plug.""" 677 logging.info('DummyPlugHandler: plug') 678 679 680 def unplug(self): 681 """Does nothing for unplug.""" 682 logging.info('DummyPlugHandler: unplug') 683 684 685class JackPluggerPlugHandler(PlugHandler): 686 """This class abstracts plug/unplug action with motor on Cros device. 687 688 Properties: 689 _jack_plugger: A JackPlugger object to access the jack plugger robot 690 691 """ 692 693 def __init__(self, jack_plugger): 694 """Initializes a JackPluggerPlugHandler. 695 696 @param jack_plugger: A JackPlugger object 697 """ 698 self._jack_plugger = jack_plugger 699 700 701 def plug(self): 702 """plugs in the jack to the cros device.""" 703 self._jack_plugger.plug() 704 705 706 def unplug(self): 707 """Unplugs the jack from the cros device.""" 708 self._jack_plugger.unplug() 709 710 711class CrosInputWidgetHandlerError(Exception): 712 """Error in CrosInputWidgetHandler.""" 713 714 715class CrosInputWidgetHandler(CrosWidgetHandler): 716 """ 717 This class abstracts a Cros device audio input widget handler. 718 719 """ 720 _DEFAULT_DATA_FORMAT = dict(file_type='raw', 721 sample_format='S16_LE', 722 channel=1, 723 rate=48000) 724 725 def start_recording(self): 726 """Starts recording audio.""" 727 self._audio_facade.start_recording(self._DEFAULT_DATA_FORMAT) 728 729 730 def stop_recording(self): 731 """Stops recording audio. 732 733 @returns: 734 A tuple (remote_path, format). 735 remote_path: The path to the recorded file on Cros device. 736 format: A dict containing: 737 file_type: 'raw'. 738 sample_format: 'S16_LE' for 16-bit signed integer in 739 little-endian. 740 channel: channel number. 741 rate: sampling rate. 742 743 """ 744 return self._audio_facade.stop_recording(), self._DEFAULT_DATA_FORMAT 745 746 747 def get_recorded_binary(self, remote_path, record_format): 748 """Gets remote recorded file binary. 749 750 Gets and reads recorded file from Cros device. 751 752 @param remote_path: The path to the recorded file on Cros device. 753 @param record_format: The recorded data format. A dict containing 754 file_type: 'raw' or 'wav'. 755 sample_format: 'S32_LE' for 32-bit signed integer in 756 little-endian. Refer to aplay manpage for 757 other formats. 758 channel: channel number. 759 rate: sampling rate. 760 761 @returns: The recorded binary. 762 763 @raises: CrosInputWidgetHandlerError if record_format is not correct. 764 """ 765 if record_format != self._DEFAULT_DATA_FORMAT: 766 raise CrosInputWidgetHandlerError( 767 'Record format %r is not valid' % record_format) 768 769 with tempfile.NamedTemporaryFile(prefix='recorded_') as f: 770 self._audio_facade.get_recorded_file(remote_path, f.name) 771 return open(f.name).read() 772 773 774class CrosUSBInputWidgetHandler(CrosInputWidgetHandler): 775 """ 776 This class abstracts a Cros device audio input widget handler. 777 778 """ 779 _DEFAULT_DATA_FORMAT = dict(file_type='raw', 780 sample_format='S16_LE', 781 channel=2, 782 rate=48000) 783 784 785class CrosIntMicInputWidgetHandler(CrosInputWidgetHandler): 786 """ 787 This class abstracts a Cros device audio input widget handler on int mic. 788 789 """ 790 def __init__(self, audio_facade, plug_handler, system_facade): 791 """Initializes a CrosWidgetHandler. 792 793 @param audio_facade: An AudioFacadeRemoteAdapter to access Cros device 794 audio functionality. 795 @param plug_handler: A PlugHandler object for plug and unplug. 796 @param system_facade: A SystemFacadeRemoteAdapter to access Cros device 797 audio functionality. 798 799 """ 800 super(CrosIntMicInputWidgetHandler, self).__init__( 801 audio_facade, plug_handler) 802 self._system_facade = system_facade 803 804 805 def set_proper_gain(self): 806 """Sets a proper gain. 807 808 On some boards, the default gain is too high. It relies on automatic 809 gain control in application level to adjust the gain. Since there is no 810 automatic gain control in the test, we set a proper gain before 811 recording. 812 813 """ 814 board = self._system_facade.get_current_board() 815 proper_gain = cras_configs.get_proper_internal_mic_gain(board) 816 817 if proper_gain is None: 818 logging.debug('No proper gain for %s', board) 819 return 820 821 logging.debug('Set gain to %f dB on internal mic for %s ', 822 proper_gain / 100, board) 823 self._audio_facade.set_input_gain(proper_gain) 824 825 826 def start_recording(self): 827 """Starts recording audio with proper gain.""" 828 self.set_proper_gain() 829 self._audio_facade.start_recording(self._DEFAULT_DATA_FORMAT) 830 831 832class CrosHotwordingWidgetHandler(CrosInputWidgetHandler): 833 """ 834 This class abstracts a Cros device audio input widget handler on hotwording. 835 836 """ 837 _DEFAULT_DATA_FORMAT = dict(file_type='raw', 838 sample_format='S16_LE', 839 channel=1, 840 rate=16000) 841 842 def __init__(self, audio_facade, plug_handler, system_facade): 843 """Initializes a CrosWidgetHandler. 844 845 @param audio_facade: An AudioFacadeRemoteAdapter to access Cros device 846 audio functionality. 847 @param plug_handler: A PlugHandler object for plug and unplug. 848 @param system_facade: A SystemFacadeRemoteAdapter to access Cros device 849 system functionality. 850 851 """ 852 super(CrosHotwordingWidgetHandler, self).__init__( 853 audio_facade, plug_handler) 854 self._system_facade = system_facade 855 856 857 def start_listening(self): 858 """Start listening to hotword.""" 859 self._audio_facade.start_listening(self._DEFAULT_DATA_FORMAT) 860 861 862 def stop_listening(self): 863 """Stops listening to hotword.""" 864 return self._audio_facade.stop_listening(), self._DEFAULT_DATA_FORMAT 865 866 867class CrosOutputWidgetHandlerError(Exception): 868 """The error in CrosOutputWidgetHandler.""" 869 pass 870 871 872class CrosOutputWidgetHandler(CrosWidgetHandler): 873 """ 874 This class abstracts a Cros device audio output widget handler. 875 876 """ 877 _DEFAULT_DATA_FORMAT = dict(file_type='raw', 878 sample_format='S16_LE', 879 channel=2, 880 rate=48000) 881 882 def set_playback_data(self, test_data): 883 """Sets data to play. 884 885 @param test_data: An AudioTestData object. 886 887 @returns: The remote file path on Cros device. 888 889 """ 890 # TODO(cychiang): Do format conversion on Cros device if this is 891 # needed. 892 if test_data.data_format != self._DEFAULT_DATA_FORMAT: 893 raise CrosOutputWidgetHandlerError( 894 'File format conversion for cros device is not supported.') 895 return self._audio_facade.set_playback_file(test_data.path) 896 897 898 def start_playback(self, path, blocking=False): 899 """Starts playing audio. 900 901 @param path: The path to the file to play on Cros device. 902 @param blocking: Blocks this call until playback finishes. 903 904 """ 905 self._audio_facade.playback(path, self._DEFAULT_DATA_FORMAT, blocking) 906 907 908 def stop_playback(self): 909 """Stops playing audio.""" 910 self._audio_facade.stop_playback() 911 912 913class PeripheralWidgetHandler(object): 914 """ 915 This class abstracts an action handler on peripheral. 916 Currently, as there is no action to take on the peripheral speaker and mic, 917 this class serves as a place-holder. 918 919 """ 920 pass 921 922 923class PeripheralWidget(AudioWidget): 924 """ 925 This class abstracts a peripheral widget which only acts passively like 926 peripheral speaker or microphone, or acts transparently like bluetooth 927 module on audio board which relays the audio siganl between Chameleon board 928 and Cros device. This widget does not provide playback/record function like 929 AudioOutputWidget or AudioInputWidget. The main purpose of this class is 930 an identifier to find the correct AudioWidgetLink to do the real work. 931 """ 932 pass 933