Home | History | Annotate | Download | only in chameleon
      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 
      7 import abc
      8 import copy
      9 import logging
     10 import os
     11 import tempfile
     12 
     13 from autotest_lib.client.cros.audio import audio_data
     14 from autotest_lib.client.cros.audio import audio_test_data
     15 from autotest_lib.client.cros.audio import cras_configs
     16 from autotest_lib.client.cros.audio import sox_utils
     17 from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
     18 from autotest_lib.client.cros.chameleon import chameleon_port_finder
     19 
     20 
     21 _CHAMELEON_FILE_PATH = os.path.join(os.path.dirname(__file__))
     22 
     23 class 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 
     56 class 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 
    241 class 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 
    278 class 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 
    294 class 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 
    356 class 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 
    427 class ChameleonHDMIInputWidgetHandlerError(Exception):
    428     """Error in ChameleonHDMIInputWidgetHandler."""
    429 
    430 
    431 class 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 
    473 class 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 
    560 class 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 
    587 class 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 
    621 class 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 
    641 class 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 
    659 class 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 
    685 class CrosInputWidgetHandlerError(Exception):
    686     """Error in CrosInputWidgetHandler."""
    687 
    688 
    689 class 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 
    748 class 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 
    759 class 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 
    806 class CrosOutputWidgetHandlerError(Exception):
    807     """The error in CrosOutputWidgetHandler."""
    808     pass
    809 
    810 
    811 class 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 
    852 class 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 
    862 class 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