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