blob: 78a0520073812506793b743a2329f95dc01d9c31 [file] [log] [blame]
# Copyright 2015 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 level control for audio widgets."""
from autotest_lib.client.cros.chameleon import chameleon_audio_ids as ids
class _AudioLevel(object):
"""Audio signal level on audio widgets."""
# Line level signal on consumer equipment is typically -10 dBV, or
# 0.316 Volt RMS.
LINE_LEVEL = 'Line level'
# Mic level signal on microphone is typically -60 dBV, or
# 1 mV RMS.
MIC_LEVEL = 'Mic level'
# Digital signal, e.g., USB, HDMI. is not subjected to bias level or
# full swing constraints. The signal is guranteed to be transmitted to the
# other end without noise introduced on the path.
# Signal level is relative to full swing of data width.
# E.g. 2^12 is 1/8 of maximum amplitude, that is, 2^15 - 1, of signed
# 16 bit data format.
# TODO(cychiang) Check if we need to do level scaling for digital signal.
DIGITAL = 'Digital'
_LEVEL_TABLE = {
# Chameleon audio ports.
ids.ChameleonIds.HDMI: _AudioLevel.DIGITAL,
ids.ChameleonIds.LINEIN: _AudioLevel.LINE_LEVEL,
ids.ChameleonIds.LINEOUT: _AudioLevel.LINE_LEVEL,
# Cros audio ports.
ids.CrosIds.HDMI: _AudioLevel.DIGITAL,
ids.CrosIds.HEADPHONE: _AudioLevel.LINE_LEVEL,
ids.CrosIds.EXTERNAL_MIC: _AudioLevel.MIC_LEVEL,
ids.CrosIds.SPEAKER: _AudioLevel.LINE_LEVEL,
ids.CrosIds.INTERNAL_MIC: _AudioLevel.MIC_LEVEL,
# Peripheral audio ports.
ids.PeripheralIds.SPEAKER: _AudioLevel.LINE_LEVEL,
ids.PeripheralIds.MIC: _AudioLevel.MIC_LEVEL,
}
class AudioLevelError(Exception):
"""Error in _AudioLevel"""
pass
def get_level(port_id):
"""Gets audio level by port id.
@param port_id: An audio port id defined in ids.
@returns: An audio level defined in _AudioLevel.
"""
if port_id not in _LEVEL_TABLE:
raise AudioLevelError('Level of port %s is not defined.' % port_id)
return _LEVEL_TABLE[port_id]
class _AudioScale(object):
"""Audio scales used by level controller.
The scales are determined by experiment.
"""
DOWN_SCALE = 0.0033
UP_SCALE = 300.0
NO_SCALE = None
class LevelControllerError(Exception):
"""Error in LevelController."""
pass
class LevelController(object):
"""The controller which sets scale between widgets of different levels.
Specifically, there are two cases:
1. ChameleonIds.LINEOUT -> CrosIds.EXTERNAL_MIC: Chameleon scales down
signal before playback.
2. PeripheralIds.MIC -> ChameleonIds.LINEIN: Chameleon scales up signal
after recording.
"""
def __init__(self, source, sink):
"""Initializes a LevelController.
@param source: An AudioWidget for source.
@param sink: An AudioWidget for sink.
"""
self._source = source
self._sink = sink
def _get_needed_scale(self):
"""Gets the needed scale for _source and _sink to balance the level.
@returns: A scale defined in _AudioScale.
"""
source_level = get_level(self._source.port_id)
sink_level = get_level(self._sink.port_id)
if (source_level == _AudioLevel.LINE_LEVEL and
sink_level == _AudioLevel.MIC_LEVEL):
return _AudioScale.DOWN_SCALE
elif (source_level == _AudioLevel.MIC_LEVEL and
sink_level == _AudioLevel.LINE_LEVEL):
return _AudioScale.UP_SCALE
return _AudioScale.NO_SCALE
def _scale_source(self):
"""Sets scale of _source widget."""
self._source.handler.scale = self._get_needed_scale()
self._sink.handler.scale = _AudioScale.NO_SCALE
def _scale_sink(self):
"""Sets scale of _sink widget."""
self._source.handler.scale = _AudioScale.NO_SCALE
self._sink.handler.scale = self._get_needed_scale()
def reset(self):
"""Resets scale of both _source and _sink."""
self._source.handler.scale = _AudioScale.NO_SCALE
self._sink.handler.scale = _AudioScale.NO_SCALE
def _support_scale(self, widget):
"""Checks if a widget supports scale.
@param widget: An AudioWidget.
@returns: True if the widget supports scale. False otherwise.
"""
# Currently, only Chameleon supports scale.
return widget.audio_port.host == 'Chameleon'
def set_scale(self):
"""Sets scale of either _source or _sink to balance the level.
@raises: LevelControllerError if neither _source nor _sink supports
scale.
"""
if self._support_scale(self._source):
self._scale_source()
elif self._support_scale(self._sink):
self._scale_sink()
elif self._get_needed_scale != _AudioScale.NO_SCALE:
raise LevelControllerError(
'Widgets %s and %s do not support scale' % (
self._source.audio_port, self._sink.audio_port))