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