• 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 framework for audio tests using chameleon."""
6
7import logging
8from contextlib import contextmanager
9
10from autotest_lib.client.cros.chameleon import audio_widget
11from autotest_lib.client.cros.chameleon import audio_widget_arc
12from autotest_lib.client.cros.chameleon import audio_widget_link
13from autotest_lib.server.cros.bluetooth import bluetooth_device
14from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
15
16class AudioPort(object):
17    """
18    This class abstracts an audio port in audio test framework. A port is
19    identified by its host and interface. Available hosts and interfaces
20    are listed in chameleon_audio_ids.
21
22    Properties:
23        port_id: The port id defined in chameleon_audio_ids.
24        host: The host of this audio port, e.g. 'Chameleon', 'Cros',
25              'Peripheral'.
26        interface: The interface of this audio port, e.g. 'HDMI', 'Headphone'.
27        role: The role of this audio port, that is, 'source' or
28              'sink'. Note that bidirectional interface like 3.5mm
29              jack is separated to two interfaces 'Headphone' and
30             'External Mic'.
31
32    """
33    def __init__(self, port_id):
34        """Initialize an AudioPort with port id string.
35
36        @param port_id: A port id string defined in chameleon_audio_ids.
37
38        """
39        logging.debug('Creating AudioPort with port_id: %s', port_id)
40        self.port_id = port_id
41        self.host = ids.get_host(port_id)
42        self.interface = ids.get_interface(port_id)
43        self.role = ids.get_role(port_id)
44        logging.debug('Created AudioPort: %s', self)
45
46
47    def __str__(self):
48        """String representation of audio port.
49
50        @returns: The string representation of audio port which is composed by
51                  host, interface, and role.
52
53        """
54        return '( %s | %s | %s )' % (
55                self.host, self.interface, self.role)
56
57
58class AudioLinkFactoryError(Exception):
59    """Error in AudioLinkFactory."""
60    pass
61
62
63class AudioLinkFactory(object):
64    """
65    This class provides method to create link that connects widgets.
66    This is used by AudioWidgetFactory when user wants to create binder for
67    widgets.
68
69    Properties:
70        _audio_bus_links: A dict containing mapping from index number
71                          to object of AudioBusLink's subclass.
72        _audio_board: An AudioBoard object to access Chameleon
73                      audio board functionality.
74
75    """
76
77    # Maps pair of widgets to widget link of different type.
78    LINK_TABLE = {
79        (ids.CrosIds.HDMI, ids.ChameleonIds.HDMI):
80                audio_widget_link.HDMIWidgetLink,
81        (ids.CrosIds.HEADPHONE, ids.ChameleonIds.LINEIN):
82                audio_widget_link.AudioBusToChameleonLink,
83        (ids.ChameleonIds.LINEOUT, ids.CrosIds.EXTERNAL_MIC):
84                audio_widget_link.AudioBusToCrosLink,
85        (ids.ChameleonIds.LINEOUT, ids.PeripheralIds.SPEAKER):
86                audio_widget_link.AudioBusChameleonToPeripheralLink,
87        (ids.PeripheralIds.MIC, ids.ChameleonIds.LINEIN):
88                audio_widget_link.AudioBusToChameleonLink,
89        (ids.PeripheralIds.BLUETOOTH_DATA_RX,
90         ids.ChameleonIds.LINEIN):
91                audio_widget_link.AudioBusToChameleonLink,
92        (ids.ChameleonIds.LINEOUT,
93         ids.PeripheralIds.BLUETOOTH_DATA_TX):
94                audio_widget_link.AudioBusChameleonToPeripheralLink,
95        (ids.CrosIds.BLUETOOTH_HEADPHONE,
96         ids.PeripheralIds.BLUETOOTH_DATA_RX):
97                audio_widget_link.BluetoothHeadphoneWidgetLink,
98        (ids.PeripheralIds.BLUETOOTH_DATA_TX,
99         ids.CrosIds.BLUETOOTH_MIC):
100                audio_widget_link.BluetoothMicWidgetLink,
101        (ids.CrosIds.USBOUT, ids.ChameleonIds.USBIN):
102                audio_widget_link.USBToChameleonWidgetLink,
103        (ids.ChameleonIds.USBOUT, ids.CrosIds.USBIN):
104                audio_widget_link.USBToCrosWidgetLink,
105        # TODO(cychiang): Add link for other widget pairs.
106    }
107
108    def __init__(self, cros_host):
109        """Initializes an AudioLinkFactory.
110
111        @param cros_host: A CrosHost object to access Cros device.
112
113        """
114        # There are two audio buses on audio board. Initializes these links
115        # to None. They may be changed to objects of AudioBusLink's subclass.
116        self._audio_bus_links = {1: None, 2: None}
117        self._cros_host = cros_host
118        self._chameleon_board = cros_host.chameleon
119        self._audio_board = self._chameleon_board.get_audio_board()
120        self._bluetooth_device = None
121        self._usb_ctrl = None
122
123
124    def _acquire_audio_bus_index(self):
125        """Acquires an available audio bus index that is not occupied yet.
126
127        @returns: A number.
128
129        @raises: AudioLinkFactoryError if there is no available
130                 audio bus.
131        """
132        for index, bus in self._audio_bus_links.iteritems():
133            if not (bus and bus.occupied):
134                return index
135
136        raise AudioLinkFactoryError('No available audio bus')
137
138
139    def create_link(self, source, sink):
140        """Creates a widget link for two audio widgets.
141
142        @param source: An AudioWidget.
143        @param sink: An AudioWidget.
144
145        @returns: An object of WidgetLink's subclass.
146
147        @raises: AudioLinkFactoryError if there is no link between
148            source and sink.
149
150        """
151        # Finds the available link types from LINK_TABLE.
152        link_type = self.LINK_TABLE.get((source.port_id, sink.port_id), None)
153        if not link_type:
154            raise AudioLinkFactoryError(
155                    'No supported link between %s and %s' % (
156                            source.port_id, sink.port_id))
157
158        # There is only one dedicated HDMI cable, just use it.
159        if link_type == audio_widget_link.HDMIWidgetLink:
160            link = audio_widget_link.HDMIWidgetLink(self._cros_host)
161
162        # Acquires audio bus if there is available bus.
163        # Creates a bus of AudioBusLink's subclass that is more
164        # specific than AudioBusLink.
165        # Controls this link using AudioBus object obtained from AudioBoard
166        # object.
167        elif issubclass(link_type, audio_widget_link.AudioBusLink):
168            bus_index = self._acquire_audio_bus_index()
169            link = link_type(self._audio_board.get_audio_bus(bus_index))
170            self._audio_bus_links[bus_index] = link
171        elif issubclass(link_type, audio_widget_link.BluetoothWidgetLink):
172            # To connect bluetooth adapter on Cros device to bluetooth module on
173            # chameleon board, we need to access bluetooth adapter on Cros host
174            # using BluetoothDevice, and access bluetooth module on
175            # audio board using BluetoothController.
176
177            # Initializes a BluetoothDevice object if needed. And reuse this
178            # object for future bluetooth link usage.
179            if not self._bluetooth_device:
180                self._bluetooth_device = bluetooth_device.BluetoothDevice(
181                        self._cros_host)
182            link = link_type(
183                    self._bluetooth_device,
184                    self._chameleon_board.get_bluetooth_ref_controller(),
185                    self._chameleon_board.get_bluetooth_a2dp_sink().GetLocalBluetoothAddress())
186        elif issubclass(link_type, audio_widget_link.USBWidgetLink):
187            # Aside from managing connection between USB audio gadget driver on
188            # Chameleon with Cros device, USBWidgetLink also handles changing
189            # the gadget driver's configurations, through the USBController that
190            # is passed to it at initialization.
191            if not self._usb_ctrl:
192                self._usb_ctrl = self._chameleon_board.get_usb_controller()
193
194            link = link_type(self._usb_ctrl)
195        else:
196            raise NotImplementedError('Link %s is not implemented' % link_type)
197
198        return link
199
200
201class AudioWidgetFactoryError(Exception):
202    """Error in AudioWidgetFactory."""
203    pass
204
205
206class AudioWidgetFactory(object):
207    """
208    This class provides methods to create widgets and binder of widgets.
209    User can use binder to setup audio paths. User can use widgets to control
210    record/playback on different ports on Cros device or Chameleon.
211
212    Properties:
213        _audio_facade: An AudioFacadeRemoteAdapter to access Cros device audio
214                       functionality. This is created by the
215                       'factory' argument passed to the constructor.
216        _display_facade: A DisplayFacadeRemoteAdapter to access Cros device
217                         display functionality. This is created by the
218                         'factory' argument passed to the constructor.
219        _system_facade: A SystemFacadeRemoteAdapter to access Cros device
220                         system functionality. This is created by the
221                         'factory' argument passed to the constructor.
222        _chameleon_board: A ChameleonBoard object to access Chameleon
223                          functionality.
224        _link_factory: An AudioLinkFactory that creates link for widgets.
225
226    """
227    def __init__(self, factory, cros_host):
228        """Initializes a AudioWidgetFactory
229
230        @param factory: A facade factory to access Cros device functionality.
231                        Currently only audio facade is used, but we can access
232                        other functionalities including display and video by
233                        facades created by this facade factory.
234        @param cros_host: A CrosHost object to access Cros device.
235
236        """
237        self._audio_facade = factory.create_audio_facade()
238        self._display_facade = factory.create_display_facade()
239        self._system_facade = factory.create_system_facade()
240        self._usb_facade = factory.create_usb_facade()
241        self._cros_host = cros_host
242        self._chameleon_board = cros_host.chameleon
243        self._link_factory = AudioLinkFactory(cros_host)
244
245
246    def create_widget(self, port_id, use_arc=False):
247        """Creates a AudioWidget given port id string.
248
249        @param port_id: A port id string defined in chameleon_audio_ids.
250        @param use_arc: For Cros widget, select if audio path exercises ARC.
251                        Currently only input widget is supported.
252
253        @returns: An AudioWidget that is actually a
254                  (Chameleon/Cros/Peripheral)(Input/Output)Widget.
255
256        """
257        def _create_chameleon_handler(audio_port):
258            """Creates a ChameleonWidgetHandler for a given AudioPort.
259
260            @param audio_port: An AudioPort object.
261
262            @returns: A Chameleon(Input/Output)WidgetHandler depending on
263                      role of audio_port.
264
265            """
266            if audio_port.role == 'sink':
267                if audio_port.port_id == ids.ChameleonIds.HDMI:
268                    return audio_widget.ChameleonHDMIInputWidgetHandler(
269                            self._chameleon_board, audio_port.interface,
270                            self._display_facade)
271                else:
272                    return audio_widget.ChameleonInputWidgetHandler(
273                            self._chameleon_board, audio_port.interface)
274            else:
275                if audio_port.port_id == ids.ChameleonIds.LINEOUT:
276                    return audio_widget.ChameleonLineOutOutputWidgetHandler(
277                            self._chameleon_board, audio_port.interface)
278                else:
279                    return audio_widget.ChameleonOutputWidgetHandler(
280                            self._chameleon_board, audio_port.interface)
281
282        def _create_cros_handler(audio_port):
283            """Creates a CrosWidgetHandler for a given AudioPort.
284
285            @param audio_port: An AudioPort object.
286
287            @returns: A Cros(Input/Output)WidgetHandler depending on
288                      role of audio_port.
289
290            """
291            is_usb = audio_port.port_id in [
292                    ids.CrosIds.USBIN, ids.CrosIds.USBOUT
293            ]
294            is_internal_mic = audio_port.port_id == ids.CrosIds.INTERNAL_MIC
295            is_hotwording = audio_port.port_id == ids.CrosIds.HOTWORDING
296
297            if audio_port.role == 'sink':
298                if use_arc:
299                    return audio_widget_arc.CrosInputWidgetARCHandler(
300                            self._audio_facade)
301                elif is_usb:
302                    return audio_widget.CrosUSBInputWidgetHandler(
303                            self._audio_facade)
304                elif is_hotwording:
305                    return audio_widget.CrosHotwordingWidgetHandler(
306                            self._audio_facade, self._system_facade)
307                else:
308                    return audio_widget.CrosInputWidgetHandler(
309                            self._audio_facade)
310            else:
311                if use_arc:
312                    return audio_widget_arc.CrosOutputWidgetARCHandler(
313                            self._audio_facade)
314                return audio_widget.CrosOutputWidgetHandler(
315                        self._audio_facade)
316
317        def _create_audio_widget(audio_port, handler):
318            """Creates an AudioWidget for given AudioPort using WidgetHandler.
319
320            Creates an AudioWidget with the role of audio_port. Put
321            the widget handler into the widget so the widget can handle
322            action requests.
323
324            @param audio_port: An AudioPort object.
325            @param handler: A WidgetHandler object.
326
327            @returns: An Audio(Input/Output)Widget depending on
328                      role of audio_port.
329
330            @raises: AudioWidgetFactoryError if fail to create widget.
331
332            """
333            if audio_port.host in ['Chameleon', 'Cros']:
334                if audio_port.role == 'sink':
335                    return audio_widget.AudioInputWidget(audio_port, handler)
336                else:
337                    return audio_widget.AudioOutputWidget(audio_port, handler)
338            elif audio_port.host == 'Peripheral':
339                return audio_widget.PeripheralWidget(audio_port, handler)
340            else:
341                raise AudioWidgetFactoryError(
342                        'The host %s is not valid' % audio_port.host)
343
344
345        audio_port = AudioPort(port_id)
346        if audio_port.host == 'Chameleon':
347            handler = _create_chameleon_handler(audio_port)
348        elif audio_port.host == 'Cros':
349            handler = _create_cros_handler(audio_port)
350        elif audio_port.host == 'Peripheral':
351            handler = audio_widget.PeripheralWidgetHandler()
352
353        return _create_audio_widget(audio_port, handler)
354
355
356    def _create_widget_binder(self, source, sink):
357        """Creates a WidgetBinder for two AudioWidgets.
358
359        @param source: An AudioWidget.
360        @param sink: An AudioWidget.
361
362        @returns: A WidgetBinder object.
363
364        """
365        return audio_widget_link.WidgetBinder(
366                source, self._link_factory.create_link(source, sink), sink)
367
368
369    def create_binder(self, *widgets):
370        """Creates a WidgetBinder or a WidgetChainBinder for AudioWidgets.
371
372        @param widgets: A list of widgets that should be linked in a chain.
373
374        @returns: A WidgetBinder for two widgets. A WidgetBinderChain object
375                  for three or more widgets.
376
377        """
378        if len(widgets) == 2:
379            return self._create_widget_binder(widgets[0], widgets[1])
380        binders = []
381        for index in xrange(len(widgets) - 1):
382            binders.append(
383                    self._create_widget_binder(
384                            widgets[index],  widgets[index + 1]))
385
386        return audio_widget_link.WidgetBinderChain(binders)
387
388
389@contextmanager
390def bind_widgets(binder):
391    """Context manager for widget binders.
392
393    Connects widgets in the beginning. Disconnects widgets and releases binder
394    in the end.
395
396    @param binder: A WidgetBinder object or a WidgetBinderChain object.
397                   If binder is None, then do nothing. This is for test user's
398                   convenience to reuse test logic among paths using binder
399                   and paths not using binder.
400
401    E.g. with bind_widgets(binder):
402             do something on widget.
403
404    """
405    if not binder:
406        yield
407    else:
408        try:
409            binder.connect()
410            yield
411        finally:
412            binder.disconnect()
413            binder.release()
414