blob: def6c24f31e593e077811dad8945b0eeade6f0e9 [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 link between audio widgets."""
import abc
import logging
from autotest_lib.client.cros.chameleon import audio_level
from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
class WidgetBinderError(Exception):
"""Error in WidgetBinder."""
pass
class WidgetBinder(object):
"""
This class abstracts the binding controls between two audio widgets.
________ __________________ ______
| | | link | | |
| source |------->| input output |------->| sink |
|________| |__________________| |______|
Properties:
_source: An AudioWidget object. The audio source. This should be
an output widget.
_sink: An AudioWidget object. The audio sink. This should be an
input widget.
_link: An WidgetLink object to link source and sink.
_connected: True if this binder is connected.
_level_controller: A LevelController to set scale and balance levels of
source and sink.
"""
def __init__(self, source, link, sink):
"""Initializes a WidgetBinder.
After initialization, the binder is not connected, but the link
is occupied until it is released.
After connection, the channel map of link will be set to the sink
widget, and it will remains the same until the sink widget is connected
to a different link. This is to make sure sink widget knows the channel
map of recorded data even after link is disconnected or released.
@param source: An AudioWidget object for audio source.
@param link: A WidgetLink object to connect source and sink.
@param sink: An AudioWidget object for audio sink.
"""
self._source = source
self._link = link
self._sink = sink
self._connected = False
self._link.occupied = True
self._level_controller = audio_level.LevelController(
self._source, self._sink)
def connect(self):
"""Connects source and sink to link."""
if self._connected:
return
logging.info('Connecting %s to %s', self._source.audio_port,
self._sink.audio_port)
self._link.plug_input(self._source)
self._link.plug_output(self._sink)
self._connected = True
# Sets channel map of link to the sink widget so
# sink widget knows the channel map of recorded data.
self._sink.channel_map = self._link.channel_map
self._level_controller.set_scale()
def disconnect(self):
"""Disconnects source and sink from link."""
if not self._connected:
return
logging.info('Disconnecting %s to %s', self._source.audio_port,
self._sink.audio_port)
self._link.unplug_input(self._source)
self._link.unplug_output(self._sink)
self._connected = False
self._level_controller.reset()
def release(self):
"""Releases the link used by this binder.
@raises: WidgetBinderError if this binder is still connected.
"""
if self._connected:
raise WidgetBinderError('Can not release while connected')
self._link.occupied = False
class WidgetLinkError(Exception):
"""Error in WidgetLink."""
pass
class WidgetLink(object):
"""
This class abstracts the link between two audio widgets.
Properties:
name: A string. The link name.
occupied: True if this widget is occupied by a widget binder.
channel_map: A list containing current channel map. Checks docstring
of channel_map method of AudioInputWidget for details.
"""
__metaclass__ = abc.ABCMeta
def __init__(self):
self.name = 'Unknown'
self.occupied = False
self.channel_map = None
def _check_widget_id(self, port_id, widget):
"""Checks that the port id of a widget is expected.
@param port_id: An id defined in chameleon_audio_ids.
@param widget: An AudioWidget object.
@raises: WidgetLinkError if the port id of widget is not expected.
"""
if widget.audio_port.port_id != port_id:
raise WidgetLinkError(
'Link %s expects a %s widget, but gets a %s widget' % (
self.name, port_id, widget.audio_port.port_id))
def _check_widget_role(self, role, widget):
"""Checks that the role of a widget is expected.
@param role: 'source' or 'sink'.
@param widget: An AudioWidget object.
@raises: WidgetLinkError if the role of widget is not expected.
"""
if widget.audio_port.role != role:
raise WidgetLinkError(
'Link %s expects a %s widget, but gets a %s widget' % (
self.name, role, widget.audio_port.role))
@abc.abstractmethod
def plug_input(self, widget):
"""Plugs input of this link to the widget.
@param widget: An AudioWidget object.
"""
pass
@abc.abstractmethod
def unplug_input(self, widget):
"""Unplugs input of this link from the widget.
@param widget: An AudioWidget object.
"""
pass
@abc.abstractmethod
def plug_output(self, widget):
"""Plugs output of this link to the widget.
@param widget: An AudioWidget object.
"""
pass
@abc.abstractmethod
def unplug_output(self, widget):
"""Unplugs output of this link from the widget.
@param widget: An AudioWidget object.
"""
pass
class AudioBusLink(WidgetLink):
"""The abstraction of widget link using audio bus on audio board.
This class handles two tasks.
1. Audio bus routing.
2. 3.5mm 4-ring audio cable plugging/unplugging between audio board
and Cros device using motor in audio box.
Note that in the configuration where there is no audio box, assume that
audio board and Cros device are always connected by 3.5mm 4-ring audio
cable and there is no need to plug/unplug the cable.
Note that audio jack is shared by headphone and external microphone on
Cros device. So plugging/unplugging headphone widget will also affect
external microphone. This should be handled outside of this class
when we need to support complicated test case.
Properties:
_audio_bus: An AudioBus object.
"""
def __init__(self, audio_bus, jack_plugger):
"""Initializes an AudioBusLink.
@param audio_bus: An AudioBus object.
@param jack_plugger: An AudioJackPlugger object if there is an audio
jack plugger on audio board.
A DummyAudioJackPlugger object if there is no
jack plugger on audio board.
"""
super(AudioBusLink, self).__init__()
self._audio_bus = audio_bus
self._jack_plugger = jack_plugger
logging.debug('Create an AudioBusLink with bus index %d',
audio_bus.bus_index)
def plug_input(self, widget):
"""Plugs input of audio bus to the widget.
@param widget: An AudioWidget object.
"""
self._check_widget_role('source', widget)
if widget.audio_port.host == 'Cros':
self._jack_plugger.plug()
self._audio_bus.connect(widget.audio_port.port_id)
logging.info(
'Plugged audio board bus %d input to %s',
self._audio_bus.bus_index, widget.audio_port)
def unplug_input(self, widget):
"""Unplugs input of audio bus from the widget.
@param widget: An AudioWidget object.
"""
self._check_widget_role('source', widget)
if widget.audio_port.host == 'Cros':
self._jack_plugger.unplug()
self._audio_bus.disconnect(widget.audio_port.port_id)
logging.info(
'Unplugged audio board bus %d input from %s',
self._audio_bus.bus_index, widget.audio_port)
def plug_output(self, widget):
"""Plugs output of audio bus to the widget.
@param widget: An AudioWidget object.
"""
self._check_widget_role('sink', widget)
if widget.audio_port.host == 'Cros':
self._jack_plugger.plug()
self._audio_bus.connect(widget.audio_port.port_id)
logging.info(
'Plugged audio board bus %d output to %s',
self._audio_bus.bus_index, widget.audio_port)
def unplug_output(self, widget):
"""Unplugs output of audio bus from the widget.
@param widget: An AudioWidget object.
"""
self._check_widget_role('sink', widget)
if widget.audio_port.host == 'Cros':
self._jack_plugger.unplug()
self._audio_bus.disconnect(widget.audio_port.port_id)
logging.info(
'Unplugged audio board bus %d output from %s',
self._audio_bus.bus_index, widget.audio_port)
class AudioBusToChameleonLink(AudioBusLink):
"""The abstraction for bus on audio board that is connected to Chameleon."""
# This is the default channel map for 2-channel data recorded on
# Chameleon through audio board.
_DEFAULT_CHANNEL_MAP = [1, 0, None, None, None, None, None, None]
def __init__(self, *args, **kwargs):
super(AudioBusToChameleonLink, self).__init__(
*args, **kwargs)
self.name = ('Audio board bus %s to Chameleon' %
self._audio_bus.bus_index)
self.channel_map = self._DEFAULT_CHANNEL_MAP
logging.debug(
'Create an AudioBusToChameleonLink named %s with '
'channel map %r', self.name, self.channel_map)
class AudioBusChameleonToPeripheralLink(AudioBusLink):
"""The abstraction for audio bus connecting Chameleon to peripheral."""
# This is the channel map which maps 2-channel data at peripehral speaker
# to 8 channel data at Chameleon.
# The left channel at speaker comes from the second channel at Chameleon.
# The right channel at speaker comes from the first channel at Chameleon.
# Other channels at Chameleon are neglected.
_DEFAULT_CHANNEL_MAP = [1, 0]
def __init__(self, *args, **kwargs):
super(AudioBusChameleonToPeripheralLink, self).__init__(
*args, **kwargs)
self.name = 'Audio board bus %s to peripheral' % self._audio_bus.bus_index
self.channel_map = self._DEFAULT_CHANNEL_MAP
logging.debug(
'Create an AudioBusToPeripheralLink named %s with '
'channel map %r', self.name, self.channel_map)
class AudioBusToCrosLink(AudioBusLink):
"""The abstraction for audio bus that is connected to Cros device."""
# This is the default channel map for 1-channel data recorded on
# Cros device.
_DEFAULT_CHANNEL_MAP = [0]
def __init__(self, *args, **kwargs):
super(AudioBusToCrosLink, self).__init__(
*args, **kwargs)
self.name = 'Audio board bus %s to Cros' % self._audio_bus.bus_index
self.channel_map = self._DEFAULT_CHANNEL_MAP
logging.debug(
'Create an AudioBusToCrosLink named %s with '
'channel map %r', self.name, self.channel_map)
class HDMIWidgetLink(WidgetLink):
"""The abstraction for HDMI cable."""
# This is the default channel map for 2-channel data recorded on
# Chameleon through HDMI cable.
_DEFAULT_CHANNEL_MAP = [1, 0, None, None, None, None, None, None]
def __init__(self):
super(HDMIWidgetLink, self).__init__()
self.name = 'HDMI cable'
self.channel_map = self._DEFAULT_CHANNEL_MAP
logging.debug(
'Create an HDMIWidgetLink. Do nothing because HDMI cable'
' is dedicated')
def plug_input(self, widget):
"""Plugs input of HDMI cable to the widget using widget handler.
@param widget: An AudioWidget object.
"""
self._check_widget_id(ids.CrosIds.HDMI, widget)
logging.info(
'Plug HDMI cable input. Do nothing because HDMI cable should '
'always be physically plugged to Cros device')
def unplug_input(self, widget):
"""Unplugs input of HDMI cable from the widget using widget handler.
@param widget_handler: A WidgetHandler object.
"""
self._check_widget_id(ids.CrosIds.HDMI, widget)
logging.info(
'Unplug HDMI cable input. Do nothing because HDMI cable should '
'always be physically plugged to Cros device')
def plug_output(self, widget):
"""Plugs output of HDMI cable to the widget using widget handler.
@param widget: An AudioWidget object.
@raises: WidgetLinkError if widget handler interface is not HDMI.
"""
self._check_widget_id(ids.ChameleonIds.HDMI, widget)
# HDMI plugging emulation is done on Chameleon port.
logging.info(
'Plug HDMI cable output. This is emulated on Chameleon port')
widget.handler.plug()
def unplug_output(self, widget):
"""Unplugs output of HDMI cable from the widget using widget handler.
@param widget: An AudioWidget object.
@raises: WidgetLinkError if widget handler interface is not HDMI.
"""
self._check_widget_id(ids.ChameleonIds.HDMI, widget)
# HDMI plugging emulation is done on Chameleon port.
logging.info(
'Unplug HDMI cable output. This is emulated on Chameleon port')
widget.handler.unplug()