blob: e9c1c4c0ddef2349f8a18fdcbdb155f89f242d36 [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module provides the framework for audio tests using chameleon."""
import logging
from contextlib import contextmanager
from autotest_lib.client.cros.audio import audio_helper
from autotest_lib.client.cros.chameleon import audio_widget
from autotest_lib.client.cros.chameleon import audio_widget_arc
from autotest_lib.client.cros.chameleon import audio_widget_link
from autotest_lib.server.cros.bluetooth import bluetooth_device
from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
from autotest_lib.client.cros.chameleon import chameleon_info
class AudioPort(object):
"""
This class abstracts an audio port in audio test framework. A port is
identified by its host and interface. Available hosts and interfaces
are listed in chameleon_audio_ids.
Properties:
port_id: The port id defined in chameleon_audio_ids.
host: The host of this audio port, e.g. 'Chameleon', 'Cros',
'Peripheral'.
interface: The interface of this audio port, e.g. 'HDMI', 'Headphone'.
role: The role of this audio port, that is, 'source' or
'sink'. Note that bidirectional interface like 3.5mm
jack is separated to two interfaces 'Headphone' and
'External Mic'.
"""
def __init__(self, port_id):
"""Initialize an AudioPort with port id string.
@param port_id: A port id string defined in chameleon_audio_ids.
"""
logging.debug('Creating AudioPort with port_id: %s', port_id)
self.port_id = port_id
self.host = ids.get_host(port_id)
self.interface = ids.get_interface(port_id)
self.role = ids.get_role(port_id)
logging.debug('Created AudioPort: %s', self)
def __str__(self):
"""String representation of audio port.
@returns: The string representation of audio port which is composed by
host, interface, and role.
"""
return '( %s | %s | %s )' % (
self.host, self.interface, self.role)
class AudioLinkFactoryError(Exception):
"""Error in AudioLinkFactory."""
pass
class AudioLinkFactory(object):
"""
This class provides method to create link that connects widgets.
This is used by AudioWidgetFactory when user wants to create binder for
widgets.
Properties:
_audio_bus_links: A dict containing mapping from index number
to object of AudioBusLink's subclass.
_audio_board: An AudioBoard object to access Chameleon
audio board functionality.
"""
# Maps pair of widgets to widget link of different type.
LINK_TABLE = {
(ids.CrosIds.HDMI, ids.ChameleonIds.HDMI):
audio_widget_link.HDMIWidgetLink,
(ids.CrosIds.HEADPHONE, ids.ChameleonIds.LINEIN):
audio_widget_link.AudioBusToChameleonLink,
(ids.ChameleonIds.LINEOUT, ids.CrosIds.EXTERNAL_MIC):
audio_widget_link.AudioBusToCrosLink,
(ids.ChameleonIds.LINEOUT, ids.PeripheralIds.SPEAKER):
audio_widget_link.AudioBusChameleonToPeripheralLink,
(ids.PeripheralIds.MIC, ids.ChameleonIds.LINEIN):
audio_widget_link.AudioBusToChameleonLink,
(ids.PeripheralIds.BLUETOOTH_DATA_RX,
ids.ChameleonIds.LINEIN):
audio_widget_link.AudioBusToChameleonLink,
(ids.ChameleonIds.LINEOUT,
ids.PeripheralIds.BLUETOOTH_DATA_TX):
audio_widget_link.AudioBusChameleonToPeripheralLink,
(ids.CrosIds.BLUETOOTH_HEADPHONE,
ids.PeripheralIds.BLUETOOTH_DATA_RX):
audio_widget_link.BluetoothHeadphoneWidgetLink,
(ids.PeripheralIds.BLUETOOTH_DATA_TX,
ids.CrosIds.BLUETOOTH_MIC):
audio_widget_link.BluetoothMicWidgetLink,
(ids.CrosIds.USBOUT, ids.ChameleonIds.USBIN):
audio_widget_link.USBToChameleonWidgetLink,
(ids.ChameleonIds.USBOUT, ids.CrosIds.USBIN):
audio_widget_link.USBToCrosWidgetLink,
# TODO(cychiang): Add link for other widget pairs.
}
def __init__(self, cros_host):
"""Initializes an AudioLinkFactory.
@param cros_host: A CrosHost object to access Cros device.
"""
# There are two audio buses on audio board. Initializes these links
# to None. They may be changed to objects of AudioBusLink's subclass.
self._audio_bus_links = {1: None, 2: None}
self._cros_host = cros_host
self._chameleon_board = cros_host.chameleon
self._audio_board = self._chameleon_board.get_audio_board()
self._bluetooth_device = None
self._usb_ctrl = None
def _acquire_audio_bus_index(self):
"""Acquires an available audio bus index that is not occupied yet.
@returns: A number.
@raises: AudioLinkFactoryError if there is no available
audio bus.
"""
for index, bus in self._audio_bus_links.iteritems():
if not (bus and bus.occupied):
return index
raise AudioLinkFactoryError('No available audio bus')
def create_link(self, source, sink):
"""Creates a widget link for two audio widgets.
@param source: An AudioWidget.
@param sink: An AudioWidget.
@returns: An object of WidgetLink's subclass.
@raises: AudioLinkFactoryError if there is no link between
source and sink.
"""
# Finds the available link types from LINK_TABLE.
link_type = self.LINK_TABLE.get((source.port_id, sink.port_id), None)
if not link_type:
raise AudioLinkFactoryError(
'No supported link between %s and %s' % (
source.port_id, sink.port_id))
# There is only one dedicated HDMI cable, just use it.
if link_type == audio_widget_link.HDMIWidgetLink:
link = audio_widget_link.HDMIWidgetLink(self._cros_host)
# Acquires audio bus if there is available bus.
# Creates a bus of AudioBusLink's subclass that is more
# specific than AudioBusLink.
# Controls this link using AudioBus object obtained from AudioBoard
# object.
elif issubclass(link_type, audio_widget_link.AudioBusLink):
bus_index = self._acquire_audio_bus_index()
link = link_type(self._audio_board.get_audio_bus(bus_index))
self._audio_bus_links[bus_index] = link
elif issubclass(link_type, audio_widget_link.BluetoothWidgetLink):
# To connect bluetooth adapter on Cros device to bluetooth module on
# chameleon board, we need to access bluetooth adapter on Cros host
# using BluetoothDevice, and access bluetooth module on
# audio board using BluetoothController. Finally, the MAC address
# of bluetooth module is queried through chameleon_info because
# it is not probeable on Chameleon board.
# Initializes a BluetoothDevice object if needed. And reuse this
# object for future bluetooth link usage.
if not self._bluetooth_device:
self._bluetooth_device = bluetooth_device.BluetoothDevice(
self._cros_host)
link = link_type(
self._bluetooth_device,
self._audio_board.get_bluetooth_controller(),
chameleon_info.get_bluetooth_mac_address(
self._chameleon_board))
elif issubclass(link_type, audio_widget_link.USBWidgetLink):
# Aside from managing connection between USB audio gadget driver on
# Chameleon with Cros device, USBWidgetLink also handles changing
# the gadget driver's configurations, through the USBController that
# is passed to it at initialization.
if not self._usb_ctrl:
self._usb_ctrl = self._chameleon_board.get_usb_controller()
link = link_type(self._usb_ctrl)
else:
raise NotImplementedError('Link %s is not implemented' % link_type)
return link
class AudioWidgetFactoryError(Exception):
"""Error in AudioWidgetFactory."""
pass
class AudioWidgetFactory(object):
"""
This class provides methods to create widgets and binder of widgets.
User can use binder to setup audio paths. User can use widgets to control
record/playback on different ports on Cros device or Chameleon.
Properties:
_audio_facade: An AudioFacadeRemoteAdapter to access Cros device audio
functionality. This is created by the
'factory' argument passed to the constructor.
_display_facade: A DisplayFacadeRemoteAdapter to access Cros device
display functionality. This is created by the
'factory' argument passed to the constructor.
_system_facade: A SystemFacadeRemoteAdapter to access Cros device
system functionality. This is created by the
'factory' argument passed to the constructor.
_chameleon_board: A ChameleonBoard object to access Chameleon
functionality.
_link_factory: An AudioLinkFactory that creates link for widgets.
"""
def __init__(self, factory, cros_host):
"""Initializes a AudioWidgetFactory
@param factory: A facade factory to access Cros device functionality.
Currently only audio facade is used, but we can access
other functionalities including display and video by
facades created by this facade factory.
@param cros_host: A CrosHost object to access Cros device.
"""
self._audio_facade = factory.create_audio_facade()
self._display_facade = factory.create_display_facade()
self._system_facade = factory.create_system_facade()
self._usb_facade = factory.create_usb_facade()
self._cros_host = cros_host
self._chameleon_board = cros_host.chameleon
self._link_factory = AudioLinkFactory(cros_host)
def create_widget(self, port_id, use_arc=False):
"""Creates a AudioWidget given port id string.
@param port_id: A port id string defined in chameleon_audio_ids.
@param use_arc: For Cros widget, select if audio path exercises ARC.
Currently only input widget is supported.
@returns: An AudioWidget that is actually a
(Chameleon/Cros/Peripheral)(Input/Output)Widget.
"""
def _create_chameleon_handler(audio_port):
"""Creates a ChameleonWidgetHandler for a given AudioPort.
@param audio_port: An AudioPort object.
@returns: A Chameleon(Input/Output)WidgetHandler depending on
role of audio_port.
"""
if audio_port.role == 'sink':
if audio_port.port_id == ids.ChameleonIds.HDMI:
return audio_widget.ChameleonHDMIInputWidgetHandler(
self._chameleon_board, audio_port.interface,
self._display_facade)
else:
return audio_widget.ChameleonInputWidgetHandler(
self._chameleon_board, audio_port.interface)
else:
if audio_port.port_id == ids.ChameleonIds.LINEOUT:
return audio_widget.ChameleonLineOutOutputWidgetHandler(
self._chameleon_board, audio_port.interface)
else:
return audio_widget.ChameleonOutputWidgetHandler(
self._chameleon_board, audio_port.interface)
def _create_cros_handler(audio_port):
"""Creates a CrosWidgetHandler for a given AudioPort.
@param audio_port: An AudioPort object.
@returns: A Cros(Input/Output)WidgetHandler depending on
role of audio_port.
"""
is_usb = audio_port.port_id in [ids.CrosIds.USBIN,
ids.CrosIds.USBOUT]
is_audio_jack = audio_port.port_id in [ids.CrosIds.HEADPHONE,
ids.CrosIds.EXTERNAL_MIC]
is_internal_mic = audio_port.port_id == ids.CrosIds.INTERNAL_MIC
# Determines the plug handler to be used.
# By default, the plug handler is DummyPlugHandler.
# If the port uses audio jack, and there is jack plugger available
# through audio board, then JackPluggerPlugHandler should be used.
audio_board = self._chameleon_board.get_audio_board()
if audio_board:
jack_plugger = audio_board.get_jack_plugger()
else:
jack_plugger = None
if jack_plugger and is_audio_jack:
plug_handler = audio_widget.JackPluggerPlugHandler(jack_plugger)
else:
plug_handler = audio_widget.DummyPlugHandler()
if audio_port.role == 'sink':
if use_arc:
return audio_widget_arc.CrosInputWidgetARCHandler(
self._audio_facade, plug_handler)
elif is_usb:
return audio_widget.CrosUSBInputWidgetHandler(
self._audio_facade, plug_handler)
elif is_internal_mic:
return audio_widget.CrosIntMicInputWidgetHandler(
self._audio_facade, plug_handler,
self._system_facade)
else:
return audio_widget.CrosInputWidgetHandler(
self._audio_facade, plug_handler)
else:
if use_arc:
return audio_widget_arc.CrosOutputWidgetARCHandler(
self._audio_facade, plug_handler)
return audio_widget.CrosOutputWidgetHandler(self._audio_facade,
plug_handler)
def _create_audio_widget(audio_port, handler):
"""Creates an AudioWidget for given AudioPort using WidgetHandler.
Creates an AudioWidget with the role of audio_port. Put
the widget handler into the widget so the widget can handle
action requests.
@param audio_port: An AudioPort object.
@param handler: A WidgetHandler object.
@returns: An Audio(Input/Output)Widget depending on
role of audio_port.
@raises: AudioWidgetFactoryError if fail to create widget.
"""
if audio_port.host in ['Chameleon', 'Cros']:
if audio_port.role == 'sink':
return audio_widget.AudioInputWidget(audio_port, handler)
else:
return audio_widget.AudioOutputWidget(audio_port, handler)
elif audio_port.host == 'Peripheral':
return audio_widget.PeripheralWidget(audio_port, handler)
else:
raise AudioWidgetFactoryError(
'The host %s is not valid' % audio_port.host)
audio_port = AudioPort(port_id)
if audio_port.host == 'Chameleon':
handler = _create_chameleon_handler(audio_port)
elif audio_port.host == 'Cros':
handler = _create_cros_handler(audio_port)
elif audio_port.host == 'Peripheral':
handler = audio_widget.PeripheralWidgetHandler()
return _create_audio_widget(audio_port, handler)
def _create_widget_binder(self, source, sink):
"""Creates a WidgetBinder for two AudioWidgets.
@param source: An AudioWidget.
@param sink: An AudioWidget.
@returns: A WidgetBinder object.
"""
return audio_widget_link.WidgetBinder(
source, self._link_factory.create_link(source, sink), sink)
def create_binder(self, *widgets):
"""Creates a WidgetBinder or a WidgetChainBinder for AudioWidgets.
@param widgets: A list of widgets that should be linked in a chain.
@returns: A WidgetBinder for two widgets. A WidgetBinderChain object
for three or more widgets.
"""
if len(widgets) == 2:
return self._create_widget_binder(widgets[0], widgets[1])
binders = []
for index in xrange(len(widgets) - 1):
binders.append(
self._create_widget_binder(
widgets[index], widgets[index + 1]))
return audio_widget_link.WidgetBinderChain(binders)
@contextmanager
def bind_widgets(binder):
"""Context manager for widget binders.
Connects widgets in the beginning. Disconnects widgets and releases binder
in the end.
@param binder: A WidgetBinder object or a WidgetBinderChain object.
If binder is None, then do nothing. This is for test user's
convenience to reuse test logic among paths using binder
and paths not using binder.
E.g. with bind_widgets(binder):
do something on widget.
"""
if not binder:
yield
else:
try:
binder.connect()
yield
finally:
binder.disconnect()
binder.release()