• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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