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