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