You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
11 KiB
311 lines
11 KiB
# 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.
|
|
|
|
"""Handler for audio extension functionality."""
|
|
|
|
import logging
|
|
|
|
from autotest_lib.client.bin import utils
|
|
from autotest_lib.client.cros.multimedia import facade_resource
|
|
|
|
class AudioExtensionHandlerError(Exception):
|
|
"""Class for exceptions thrown from the AudioExtensionHandler"""
|
|
pass
|
|
|
|
|
|
class AudioExtensionHandler(object):
|
|
"""Wrapper around test extension that uses chrome.audio API to get audio
|
|
device information
|
|
"""
|
|
def __init__(self, extension):
|
|
"""Initializes an AudioExtensionHandler.
|
|
|
|
@param extension: Extension got from telemetry chrome wrapper.
|
|
|
|
"""
|
|
self._extension = extension
|
|
self._check_api_available()
|
|
|
|
|
|
def _check_api_available(self):
|
|
"""Checks chrome.audio is available.
|
|
|
|
@raises: AudioExtensionHandlerError if extension is not available.
|
|
|
|
"""
|
|
success = utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"chrome.audio") != None),
|
|
expected_value=True)
|
|
if not success:
|
|
raise AudioExtensionHandlerError('chrome.audio is not available.')
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def get_audio_api_availability(self):
|
|
"""Gets whether the chrome.audio is available."""
|
|
return self._extension.EvaluateJavaScript("chrome.audio") != None
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def get_audio_devices(self, device_filter=None):
|
|
"""Gets the audio device info from Chrome audio API.
|
|
|
|
@param device_filter: Filter for returned device nodes.
|
|
An optional dict that can have the following properties:
|
|
string array streamTypes
|
|
Restricts stream types that returned devices can have.
|
|
It should contain "INPUT" for result to include input
|
|
devices, and "OUTPUT" for results to include output devices.
|
|
If not set, returned devices will not be filtered by the
|
|
stream type.
|
|
|
|
boolean isActive
|
|
If true, only active devices will be included in the result.
|
|
If false, only inactive devices will be included in the
|
|
result.
|
|
|
|
The filter param defaults to {}, requests all available audio
|
|
devices.
|
|
|
|
@returns: An array of audioDeviceInfo.
|
|
Each audioDeviceInfo dict
|
|
contains these key-value pairs:
|
|
string id
|
|
The unique identifier of the audio device.
|
|
|
|
string stableDeviceId
|
|
The stable identifier of the audio device.
|
|
|
|
string streamType
|
|
"INPUT" if the device is an input audio device,
|
|
"OUTPUT" if the device is an output audio device.
|
|
|
|
string displayName
|
|
The user-friendly name (e.g. "Bose Amplifier").
|
|
|
|
string deviceName
|
|
The devuce name
|
|
|
|
boolean isActive
|
|
True if this is the current active device.
|
|
|
|
boolean isMuted
|
|
True if this is muted.
|
|
|
|
long level
|
|
The output volume or input gain.
|
|
|
|
"""
|
|
def filter_to_str(device_filter):
|
|
"""Converts python dict device filter to JS object string.
|
|
|
|
@param device_filter: Device filter dict.
|
|
|
|
@returns: Device filter as a srting representation of a
|
|
JavaScript object.
|
|
|
|
"""
|
|
return str(device_filter or {}).replace('True', 'true').replace(
|
|
'False', 'false')
|
|
|
|
self._extension.ExecuteJavaScript('window.__audio_devices = null;')
|
|
self._extension.ExecuteJavaScript(
|
|
"chrome.audio.getDevices(%s, function(devices) {"
|
|
"window.__audio_devices = devices;})"
|
|
% filter_to_str(device_filter))
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__audio_devices") != None),
|
|
expected_value=True)
|
|
return self._extension.EvaluateJavaScript("window.__audio_devices")
|
|
|
|
|
|
def _get_active_id_for_stream_type(self, stream_type):
|
|
"""Gets active node id of the specified stream type.
|
|
|
|
Assume there is only one active node.
|
|
|
|
@param stream_type: 'INPUT' to get the active input device,
|
|
'OUTPUT' to get the active output device.
|
|
|
|
@returns: A string for the active device id.
|
|
|
|
@raises: AudioExtensionHandlerError if active id is not unique.
|
|
|
|
"""
|
|
nodes = self.get_audio_devices(
|
|
{'streamTypes': [stream_type], 'isActive': True})
|
|
if len(nodes) != 1:
|
|
logging.error(
|
|
'Node info contains multiple active nodes: %s', nodes)
|
|
raise AudioExtensionHandlerError('Active id should be unique')
|
|
|
|
return nodes[0]['id']
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def set_active_volume(self, volume):
|
|
"""Sets the active audio output volume using chrome.audio API.
|
|
|
|
This method also unmutes the node.
|
|
|
|
@param volume: Volume to set (0~100).
|
|
|
|
"""
|
|
output_id = self._get_active_id_for_stream_type('OUTPUT')
|
|
logging.debug('output_id: %s', output_id)
|
|
|
|
self.set_mute(False)
|
|
|
|
self._extension.ExecuteJavaScript('window.__set_volume_done = null;')
|
|
self._extension.ExecuteJavaScript(
|
|
"""
|
|
chrome.audio.setProperties(
|
|
'%s',
|
|
{level: %s},
|
|
function() {window.__set_volume_done = true;});
|
|
"""
|
|
% (output_id, volume))
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__set_volume_done") != None),
|
|
expected_value=True)
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def set_active_input_gain(self, gain):
|
|
"""Sets the active audio input gain using chrome.audio API.
|
|
|
|
@param gain: Gain to set (0~100).
|
|
|
|
"""
|
|
input_id = self._get_active_id_for_stream_type('INPUT')
|
|
logging.debug('input_id: %s', input_id)
|
|
|
|
self._extension.ExecuteJavaScript('window.__set_volume_done = null;')
|
|
self._extension.ExecuteJavaScript(
|
|
"""
|
|
chrome.audio.setProperties(
|
|
'%s',
|
|
{level: %s},
|
|
function() {window.__set_volume_done = true;});
|
|
"""
|
|
% (input_id, gain))
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__set_volume_done") != None),
|
|
expected_value=True)
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def set_mute(self, mute):
|
|
"""Mutes the audio output using chrome.audio API.
|
|
|
|
@param mute: True to mute. False otherwise.
|
|
|
|
"""
|
|
is_muted_string = 'true' if mute else 'false'
|
|
|
|
self._extension.ExecuteJavaScript('window.__set_mute_done = null;')
|
|
|
|
self._extension.ExecuteJavaScript(
|
|
"""
|
|
chrome.audio.setMute(
|
|
'OUTPUT', %s,
|
|
function() {window.__set_mute_done = true;});
|
|
"""
|
|
% (is_muted_string))
|
|
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__set_mute_done") != None),
|
|
expected_value=True)
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def get_mute(self):
|
|
"""Determines whether audio output is muted.
|
|
|
|
@returns Whether audio output is muted.
|
|
|
|
"""
|
|
self._extension.ExecuteJavaScript('window.__output_muted = null;')
|
|
self._extension.ExecuteJavaScript(
|
|
"chrome.audio.getMute('OUTPUT', function(isMute) {"
|
|
"window.__output_muted = isMute;})")
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__output_muted") != None),
|
|
expected_value=True)
|
|
return self._extension.EvaluateJavaScript("window.__output_muted")
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def get_active_volume_mute(self):
|
|
"""Gets the volume state of active audio output using chrome.audio API.
|
|
|
|
@param returns: A tuple (volume, mute), where volume is 0~100, and mute
|
|
is True if node is muted, False otherwise.
|
|
|
|
"""
|
|
nodes = self.get_audio_devices(
|
|
{'streamTypes': ['OUTPUT'], 'isActive': True})
|
|
if len(nodes) != 1:
|
|
logging.error('Node info contains multiple active nodes: %s', nodes)
|
|
raise AudioExtensionHandlerError('Active id should be unique')
|
|
|
|
return (nodes[0]['level'], self.get_mute())
|
|
|
|
|
|
@facade_resource.retry_chrome_call
|
|
def set_active_node_id(self, node_id):
|
|
"""Sets the active node by node id.
|
|
|
|
The current active node will be disabled first if the new active node
|
|
is different from the current one.
|
|
|
|
@param node_id: Node id obtained from cras_utils.get_cras_nodes.
|
|
Chrome.audio also uses this id to specify input/output
|
|
nodes.
|
|
Note that node id returned by cras_utils.get_cras_nodes
|
|
is a number, while chrome.audio API expects a string.
|
|
|
|
@raises AudioExtensionHandlerError if there is no such id.
|
|
|
|
"""
|
|
nodes = self.get_audio_devices({})
|
|
target_node = None
|
|
for node in nodes:
|
|
if node['id'] == str(node_id):
|
|
target_node = node
|
|
break
|
|
|
|
if not target_node:
|
|
logging.error('Node %s not found.', node_id)
|
|
raise AudioExtensionHandlerError('Node id not found')
|
|
|
|
if target_node['isActive']:
|
|
logging.debug('Node %s is already active.', node_id)
|
|
return
|
|
|
|
logging.debug('Setting active id to %s', node_id)
|
|
|
|
self._extension.ExecuteJavaScript('window.__set_active_done = null;')
|
|
|
|
is_input = target_node['streamType'] == 'INPUT'
|
|
stream_type = 'input' if is_input else 'output'
|
|
self._extension.ExecuteJavaScript(
|
|
"""
|
|
chrome.audio.setActiveDevices(
|
|
{'%s': ['%s']},
|
|
function() {window.__set_active_done = true;});
|
|
"""
|
|
% (stream_type, node_id))
|
|
|
|
utils.wait_for_value(
|
|
lambda: (self._extension.EvaluateJavaScript(
|
|
"window.__set_active_done") != None),
|
|
expected_value=True)
|