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