xref: /aosp_15_r20/external/autotest/client/cros/audio/cras_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""This module provides cras audio utilities."""
7*9c5db199SXin Li
8*9c5db199SXin Liimport logging
9*9c5db199SXin Liimport re
10*9c5db199SXin Liimport subprocess
11*9c5db199SXin Li
12*9c5db199SXin Lifrom autotest_lib.client.bin import utils
13*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cmd_utils
14*9c5db199SXin Li
15*9c5db199SXin Li_CRAS_TEST_CLIENT = '/usr/bin/cras_test_client'
16*9c5db199SXin Li
17*9c5db199SXin Li
18*9c5db199SXin Liclass CrasUtilsError(Exception):
19*9c5db199SXin Li    """Error in CrasUtils."""
20*9c5db199SXin Li    pass
21*9c5db199SXin Li
22*9c5db199SXin Li
23*9c5db199SXin Lidef dump_audio_thread():
24*9c5db199SXin Li    """Dumps audio thread info.
25*9c5db199SXin Li
26*9c5db199SXin Li    @returns: A list of cras audio information.
27*9c5db199SXin Li    """
28*9c5db199SXin Li    proc = subprocess.Popen([_CRAS_TEST_CLIENT, '--dump_a'],
29*9c5db199SXin Li                            stdout=subprocess.PIPE)
30*9c5db199SXin Li
31*9c5db199SXin Li    output, err = proc.communicate()
32*9c5db199SXin Li    if err:
33*9c5db199SXin Li        raise CrasUtilsError(err)
34*9c5db199SXin Li    return output.decode().splitlines()
35*9c5db199SXin Li
36*9c5db199SXin Li
37*9c5db199SXin Lidef get_audio_thread_summary():
38*9c5db199SXin Li    """Gets stream summary info.
39*9c5db199SXin Li
40*9c5db199SXin Li    @returns: A list of stream summary information.
41*9c5db199SXin Li    """
42*9c5db199SXin Li
43*9c5db199SXin Li    lines = dump_audio_thread()
44*9c5db199SXin Li    return [l for l in lines if l.startswith('Summary:')]
45*9c5db199SXin Li
46*9c5db199SXin Li
47*9c5db199SXin Lidef playback(blocking=True, stdin=None, *args, **kargs):
48*9c5db199SXin Li    """A helper function to execute the playback_cmd.
49*9c5db199SXin Li
50*9c5db199SXin Li    @param blocking: Blocks this call until playback finishes.
51*9c5db199SXin Li    @param stdin: the standard input of playback process
52*9c5db199SXin Li    @param args: args passed to playback_cmd.
53*9c5db199SXin Li    @param kargs: kargs passed to playback_cmd.
54*9c5db199SXin Li
55*9c5db199SXin Li    @returns: The process running the playback command. Note that if the
56*9c5db199SXin Li              blocking parameter is true, this will return a finished process.
57*9c5db199SXin Li    """
58*9c5db199SXin Li    process = cmd_utils.popen(playback_cmd(*args, **kargs), stdin=stdin)
59*9c5db199SXin Li    if blocking:
60*9c5db199SXin Li        cmd_utils.wait_and_check_returncode(process)
61*9c5db199SXin Li    return process
62*9c5db199SXin Li
63*9c5db199SXin Li
64*9c5db199SXin Lidef capture(*args, **kargs):
65*9c5db199SXin Li    """A helper function to execute the capture_cmd.
66*9c5db199SXin Li
67*9c5db199SXin Li    @param args: args passed to capture_cmd.
68*9c5db199SXin Li    @param kargs: kargs passed to capture_cmd.
69*9c5db199SXin Li
70*9c5db199SXin Li    """
71*9c5db199SXin Li    cmd_utils.execute(capture_cmd(*args, **kargs))
72*9c5db199SXin Li
73*9c5db199SXin Li
74*9c5db199SXin Lidef playback_cmd(playback_file, block_size=None, duration=None,
75*9c5db199SXin Li                 pin_device=None, channels=2, rate=48000):
76*9c5db199SXin Li    """Gets a command to playback a file with given settings.
77*9c5db199SXin Li
78*9c5db199SXin Li    @param playback_file: the name of the file to play. '-' indicates to
79*9c5db199SXin Li                          playback raw audio from the stdin.
80*9c5db199SXin Li    @param pin_device: the device id to playback on.
81*9c5db199SXin Li    @param block_size: the number of frames per callback(dictates latency).
82*9c5db199SXin Li    @param duration: seconds to playback.
83*9c5db199SXin Li    @param channels: number of channels.
84*9c5db199SXin Li    @param rate: the sampling rate.
85*9c5db199SXin Li
86*9c5db199SXin Li    @returns: The command args put in a list of strings.
87*9c5db199SXin Li
88*9c5db199SXin Li    """
89*9c5db199SXin Li    args = [_CRAS_TEST_CLIENT]
90*9c5db199SXin Li    args += ['--playback_file', playback_file]
91*9c5db199SXin Li    if pin_device is not None:
92*9c5db199SXin Li        args += ['--pin_device', str(pin_device)]
93*9c5db199SXin Li    if block_size is not None:
94*9c5db199SXin Li        args += ['--block_size', str(block_size)]
95*9c5db199SXin Li    if duration is not None:
96*9c5db199SXin Li        args += ['--duration', str(duration)]
97*9c5db199SXin Li    args += ['--num_channels', str(channels)]
98*9c5db199SXin Li    args += ['--rate', str(rate)]
99*9c5db199SXin Li    return args
100*9c5db199SXin Li
101*9c5db199SXin Li
102*9c5db199SXin Lidef capture_cmd(capture_file, block_size=None, duration=10,
103*9c5db199SXin Li                sample_format='S16_LE',
104*9c5db199SXin Li                pin_device=None, channels=1, rate=48000):
105*9c5db199SXin Li    """Gets a command to capture the audio into the file with given settings.
106*9c5db199SXin Li
107*9c5db199SXin Li    @param capture_file: the name of file the audio to be stored in.
108*9c5db199SXin Li    @param block_size: the number of frames per callback(dictates latency).
109*9c5db199SXin Li    @param duration: seconds to record. If it is None, duration is not set,
110*9c5db199SXin Li                     and command will keep capturing audio until it is
111*9c5db199SXin Li                     terminated.
112*9c5db199SXin Li    @param sample_format: the sample format;
113*9c5db199SXin Li                          possible choices: 'S16_LE', 'S24_LE', and 'S32_LE'
114*9c5db199SXin Li                          default to S16_LE: signed 16 bits/sample,
115*9c5db199SXin Li                                             little endian
116*9c5db199SXin Li    @param pin_device: the device id to record from.
117*9c5db199SXin Li    @param channels: number of channels.
118*9c5db199SXin Li    @param rate: the sampling rate.
119*9c5db199SXin Li
120*9c5db199SXin Li    @returns: The command args put in a list of strings.
121*9c5db199SXin Li
122*9c5db199SXin Li    """
123*9c5db199SXin Li    args = [_CRAS_TEST_CLIENT]
124*9c5db199SXin Li    args += ['--capture_file', capture_file]
125*9c5db199SXin Li    if pin_device is not None:
126*9c5db199SXin Li        args += ['--pin_device', str(pin_device)]
127*9c5db199SXin Li    if block_size is not None:
128*9c5db199SXin Li        args += ['--block_size', str(block_size)]
129*9c5db199SXin Li    if duration is not None:
130*9c5db199SXin Li        args += ['--duration', str(duration)]
131*9c5db199SXin Li    args += ['--num_channels', str(channels)]
132*9c5db199SXin Li    args += ['--rate', str(rate)]
133*9c5db199SXin Li    args += ['--format', str(sample_format)]
134*9c5db199SXin Li    return args
135*9c5db199SXin Li
136*9c5db199SXin Li
137*9c5db199SXin Lidef listen_cmd(
138*9c5db199SXin Li        capture_file, block_size=None, duration=10, channels=1, rate=48000):
139*9c5db199SXin Li    """Gets a command to listen on hotword and record audio into the file with
140*9c5db199SXin Li       given settings.
141*9c5db199SXin Li
142*9c5db199SXin Li    @param capture_file: the name of file the audio to be stored in.
143*9c5db199SXin Li    @param block_size: the number of frames per callback(dictates latency).
144*9c5db199SXin Li    @param duration: seconds to record. If it is None, duration is not set,
145*9c5db199SXin Li                     and command will keep capturing audio until it is
146*9c5db199SXin Li                     terminated.
147*9c5db199SXin Li    @param channels: number of channels.
148*9c5db199SXin Li    @param rate: the sampling rate.
149*9c5db199SXin Li
150*9c5db199SXin Li    @returns: The command args put in a list of strings.
151*9c5db199SXin Li
152*9c5db199SXin Li    """
153*9c5db199SXin Li    args = [_CRAS_TEST_CLIENT]
154*9c5db199SXin Li    args += ['--listen_for_hotword', capture_file]
155*9c5db199SXin Li    if block_size is not None:
156*9c5db199SXin Li        args += ['--block_size', str(block_size)]
157*9c5db199SXin Li    if duration is not None:
158*9c5db199SXin Li        args += ['--duration', str(duration)]
159*9c5db199SXin Li    args += ['--num_channels', str(channels)]
160*9c5db199SXin Li    args += ['--rate', str(rate)]
161*9c5db199SXin Li    return args
162*9c5db199SXin Li
163*9c5db199SXin Li
164*9c5db199SXin Lidef loopback(*args, **kargs):
165*9c5db199SXin Li    """A helper function to execute loopback_cmd.
166*9c5db199SXin Li
167*9c5db199SXin Li    @param args: args passed to loopback_cmd.
168*9c5db199SXin Li    @param kargs: kargs passed to loopback_cmd.
169*9c5db199SXin Li
170*9c5db199SXin Li    """
171*9c5db199SXin Li
172*9c5db199SXin Li    cmd_utils.execute(loopback_cmd(*args, **kargs))
173*9c5db199SXin Li
174*9c5db199SXin Li
175*9c5db199SXin Lidef loopback_cmd(output_file, duration=10, channels=2, rate=48000):
176*9c5db199SXin Li    """Gets a command to record the loopback.
177*9c5db199SXin Li
178*9c5db199SXin Li    @param output_file: The name of the file the loopback to be stored in.
179*9c5db199SXin Li    @param channels: The number of channels of the recorded audio.
180*9c5db199SXin Li    @param duration: seconds to record.
181*9c5db199SXin Li    @param rate: the sampling rate.
182*9c5db199SXin Li
183*9c5db199SXin Li    @returns: The command args put in a list of strings.
184*9c5db199SXin Li
185*9c5db199SXin Li    """
186*9c5db199SXin Li    args = [_CRAS_TEST_CLIENT]
187*9c5db199SXin Li    args += ['--loopback_file', output_file]
188*9c5db199SXin Li    args += ['--duration_seconds', str(duration)]
189*9c5db199SXin Li    args += ['--num_channels', str(channels)]
190*9c5db199SXin Li    args += ['--rate', str(rate)]
191*9c5db199SXin Li    return args
192*9c5db199SXin Li
193*9c5db199SXin Li
194*9c5db199SXin Lidef get_cras_nodes_cmd():
195*9c5db199SXin Li    """Gets a command to query the nodes from Cras.
196*9c5db199SXin Li
197*9c5db199SXin Li    @returns: The command to query nodes information from Cras using dbus-send.
198*9c5db199SXin Li
199*9c5db199SXin Li    """
200*9c5db199SXin Li    return ('dbus-send --system --type=method_call --print-reply '
201*9c5db199SXin Li            '--dest=org.chromium.cras /org/chromium/cras '
202*9c5db199SXin Li            'org.chromium.cras.Control.GetNodes')
203*9c5db199SXin Li
204*9c5db199SXin Li
205*9c5db199SXin Lidef set_system_volume(volume):
206*9c5db199SXin Li    """Set the system volume.
207*9c5db199SXin Li
208*9c5db199SXin Li    @param volume: the system output vlume to be set(0 - 100).
209*9c5db199SXin Li
210*9c5db199SXin Li    """
211*9c5db199SXin Li    get_cras_control_interface().SetOutputVolume(volume)
212*9c5db199SXin Li
213*9c5db199SXin Li
214*9c5db199SXin Lidef set_node_volume(node_id, volume):
215*9c5db199SXin Li    """Set the volume of the given output node.
216*9c5db199SXin Li
217*9c5db199SXin Li    @param node_id: the id of the output node to be set the volume.
218*9c5db199SXin Li    @param volume: the volume to be set(0-100).
219*9c5db199SXin Li
220*9c5db199SXin Li    """
221*9c5db199SXin Li    get_cras_control_interface().SetOutputNodeVolume(node_id, volume)
222*9c5db199SXin Li
223*9c5db199SXin Li
224*9c5db199SXin Lidef get_cras_control_interface(private=False):
225*9c5db199SXin Li    """Gets Cras DBus control interface.
226*9c5db199SXin Li
227*9c5db199SXin Li    @param private: Set to True to use a new instance for dbus.SystemBus
228*9c5db199SXin Li                    instead of the shared instance.
229*9c5db199SXin Li
230*9c5db199SXin Li    @returns: A dBus.Interface object with Cras Control interface.
231*9c5db199SXin Li
232*9c5db199SXin Li    @raises: ImportError if this is not called on Cros device.
233*9c5db199SXin Li
234*9c5db199SXin Li    """
235*9c5db199SXin Li    try:
236*9c5db199SXin Li        import dbus
237*9c5db199SXin Li    except ImportError as e:
238*9c5db199SXin Li        logging.exception(
239*9c5db199SXin Li                'Can not import dbus: %s. This method should only be '
240*9c5db199SXin Li                'called on Cros device.', e)
241*9c5db199SXin Li        raise
242*9c5db199SXin Li    bus = dbus.SystemBus(private=private)
243*9c5db199SXin Li    cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras')
244*9c5db199SXin Li    return dbus.Interface(cras_object, 'org.chromium.cras.Control')
245*9c5db199SXin Li
246*9c5db199SXin Li
247*9c5db199SXin Lidef get_cras_nodes():
248*9c5db199SXin Li    """Gets nodes information from Cras.
249*9c5db199SXin Li
250*9c5db199SXin Li    @returns: A dict containing information of each node.
251*9c5db199SXin Li
252*9c5db199SXin Li    """
253*9c5db199SXin Li    return get_cras_control_interface().GetNodes()
254*9c5db199SXin Li
255*9c5db199SXin Li
256*9c5db199SXin Lidef get_selected_nodes():
257*9c5db199SXin Li    """Gets selected output nodes and input nodes.
258*9c5db199SXin Li
259*9c5db199SXin Li    @returns: A tuple (output_nodes, input_nodes) where each
260*9c5db199SXin Li              field is a list of selected node IDs returned from Cras DBus API.
261*9c5db199SXin Li              Note that there may be multiple output/input nodes being selected
262*9c5db199SXin Li              at the same time.
263*9c5db199SXin Li
264*9c5db199SXin Li    """
265*9c5db199SXin Li    output_nodes = []
266*9c5db199SXin Li    input_nodes = []
267*9c5db199SXin Li    nodes = get_cras_nodes()
268*9c5db199SXin Li    for node in nodes:
269*9c5db199SXin Li        if node['Active']:
270*9c5db199SXin Li            if node['IsInput']:
271*9c5db199SXin Li                input_nodes.append(node['Id'])
272*9c5db199SXin Li            else:
273*9c5db199SXin Li                output_nodes.append(node['Id'])
274*9c5db199SXin Li    return (output_nodes, input_nodes)
275*9c5db199SXin Li
276*9c5db199SXin Li
277*9c5db199SXin Lidef set_selected_output_node_volume(volume):
278*9c5db199SXin Li    """Sets the selected output node volume.
279*9c5db199SXin Li
280*9c5db199SXin Li    @param volume: the volume to be set (0-100).
281*9c5db199SXin Li
282*9c5db199SXin Li    """
283*9c5db199SXin Li    selected_output_node_ids, _ = get_selected_nodes()
284*9c5db199SXin Li    for node_id in selected_output_node_ids:
285*9c5db199SXin Li        set_node_volume(node_id, volume)
286*9c5db199SXin Li
287*9c5db199SXin Li
288*9c5db199SXin Lidef get_active_stream_count():
289*9c5db199SXin Li    """Gets the number of active streams.
290*9c5db199SXin Li
291*9c5db199SXin Li    @returns: The number of active streams.
292*9c5db199SXin Li
293*9c5db199SXin Li    """
294*9c5db199SXin Li    return int(get_cras_control_interface().GetNumberOfActiveStreams())
295*9c5db199SXin Li
296*9c5db199SXin Li
297*9c5db199SXin Lidef set_system_mute(is_mute):
298*9c5db199SXin Li    """Sets the system mute switch.
299*9c5db199SXin Li
300*9c5db199SXin Li    @param is_mute: Set True to mute the system playback.
301*9c5db199SXin Li
302*9c5db199SXin Li    """
303*9c5db199SXin Li    get_cras_control_interface().SetOutputMute(is_mute)
304*9c5db199SXin Li
305*9c5db199SXin Li
306*9c5db199SXin Lidef set_capture_mute(is_mute):
307*9c5db199SXin Li    """Sets the capture mute switch.
308*9c5db199SXin Li
309*9c5db199SXin Li    @param is_mute: Set True to mute the capture.
310*9c5db199SXin Li
311*9c5db199SXin Li    """
312*9c5db199SXin Li    get_cras_control_interface().SetInputMute(is_mute)
313*9c5db199SXin Li
314*9c5db199SXin Li
315*9c5db199SXin Lidef node_type_is_plugged(node_type, nodes_info):
316*9c5db199SXin Li    """Determine if there is any node of node_type plugged.
317*9c5db199SXin Li
318*9c5db199SXin Li    This method is used in the AudioLoopbackDongleLabel class, where the
319*9c5db199SXin Li    call is executed on autotest server. Use get_cras_nodes instead if
320*9c5db199SXin Li    the call can be executed on Cros device.
321*9c5db199SXin Li
322*9c5db199SXin Li    Since Cras only reports the plugged node in GetNodes, we can
323*9c5db199SXin Li    parse the return value to see if there is any node with the given type.
324*9c5db199SXin Li    For example, if INTERNAL_MIC is of intereset, the pattern we are
325*9c5db199SXin Li    looking for is:
326*9c5db199SXin Li
327*9c5db199SXin Li    dict entry(
328*9c5db199SXin Li       string "Type"
329*9c5db199SXin Li       variant             string "INTERNAL_MIC"
330*9c5db199SXin Li    )
331*9c5db199SXin Li
332*9c5db199SXin Li    @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
333*9c5db199SXin Li    @param nodes_info: A str containing output of command get_nodes_cmd.
334*9c5db199SXin Li
335*9c5db199SXin Li    @returns: True if there is any node of node_type plugged. False otherwise.
336*9c5db199SXin Li
337*9c5db199SXin Li    """
338*9c5db199SXin Li    match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
339*9c5db199SXin Li                      nodes_info)
340*9c5db199SXin Li    return True if match else False
341*9c5db199SXin Li
342*9c5db199SXin Li
343*9c5db199SXin Li# Cras node types reported from Cras DBus control API.
344*9c5db199SXin LiCRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
345*9c5db199SXin Li                          'BLUETOOTH', 'LINEOUT', 'UNKNOWN', 'ALSA_LOOPBACK']
346*9c5db199SXin LiCRAS_INPUT_NODE_TYPES = [
347*9c5db199SXin Li        'MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH', 'POST_DSP_DELAYED_LOOPBACK',
348*9c5db199SXin Li        'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN', 'KEYBOARD_MIC',
349*9c5db199SXin Li        'HOTWORD', 'FRONT_MIC', 'REAR_MIC', 'ECHO_REFERENCE'
350*9c5db199SXin Li]
351*9c5db199SXin LiCRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
352*9c5db199SXin Li
353*9c5db199SXin Li
354*9c5db199SXin Lidef get_filtered_node_types(callback):
355*9c5db199SXin Li    """Returns the pair of filtered output node types and input node types.
356*9c5db199SXin Li
357*9c5db199SXin Li    @param callback: A callback function which takes a node as input parameter
358*9c5db199SXin Li                     and filter the node based on its return value.
359*9c5db199SXin Li
360*9c5db199SXin Li    @returns: A tuple (output_node_types, input_node_types) where each
361*9c5db199SXin Li              field is a list of node types defined in CRAS_NODE_TYPES,
362*9c5db199SXin Li              and their 'attribute_name' is True.
363*9c5db199SXin Li
364*9c5db199SXin Li    """
365*9c5db199SXin Li    output_node_types = []
366*9c5db199SXin Li    input_node_types = []
367*9c5db199SXin Li    nodes = get_cras_nodes()
368*9c5db199SXin Li    for node in nodes:
369*9c5db199SXin Li        if callback(node):
370*9c5db199SXin Li            node_type = str(node['Type'])
371*9c5db199SXin Li            if node_type not in CRAS_NODE_TYPES:
372*9c5db199SXin Li                logging.warning('node type %s is not in known CRAS_NODE_TYPES',
373*9c5db199SXin Li                                node_type)
374*9c5db199SXin Li            if node['IsInput']:
375*9c5db199SXin Li                input_node_types.append(node_type)
376*9c5db199SXin Li            else:
377*9c5db199SXin Li                output_node_types.append(node_type)
378*9c5db199SXin Li    return (output_node_types, input_node_types)
379*9c5db199SXin Li
380*9c5db199SXin Li
381*9c5db199SXin Lidef get_selected_node_types():
382*9c5db199SXin Li    """Returns the pair of active output node types and input node types.
383*9c5db199SXin Li
384*9c5db199SXin Li    @returns: A tuple (output_node_types, input_node_types) where each
385*9c5db199SXin Li              field is a list of selected node types defined in CRAS_NODE_TYPES.
386*9c5db199SXin Li
387*9c5db199SXin Li    """
388*9c5db199SXin Li    def is_selected(node):
389*9c5db199SXin Li        """Checks if a node is selected.
390*9c5db199SXin Li
391*9c5db199SXin Li        A node is selected if its Active attribute is True.
392*9c5db199SXin Li
393*9c5db199SXin Li        @returns: True is a node is selected, False otherwise.
394*9c5db199SXin Li
395*9c5db199SXin Li        """
396*9c5db199SXin Li        return node['Active']
397*9c5db199SXin Li
398*9c5db199SXin Li    return get_filtered_node_types(is_selected)
399*9c5db199SXin Li
400*9c5db199SXin Li
401*9c5db199SXin Lidef get_selected_input_device_name():
402*9c5db199SXin Li    """Returns the device name of the active input node.
403*9c5db199SXin Li
404*9c5db199SXin Li    @returns: device name string. E.g. kbl_r5514_5663_max: :0,1
405*9c5db199SXin Li    """
406*9c5db199SXin Li    nodes = get_cras_nodes()
407*9c5db199SXin Li    for node in nodes:
408*9c5db199SXin Li        if node['Active'] and node['IsInput']:
409*9c5db199SXin Li            return node['DeviceName']
410*9c5db199SXin Li    return None
411*9c5db199SXin Li
412*9c5db199SXin Li
413*9c5db199SXin Lidef get_selected_input_device_type():
414*9c5db199SXin Li    """Returns the device type of the active input node.
415*9c5db199SXin Li
416*9c5db199SXin Li    @returns: device type string. E.g. INTERNAL_MICROPHONE
417*9c5db199SXin Li    """
418*9c5db199SXin Li    nodes = get_cras_nodes()
419*9c5db199SXin Li    for node in nodes:
420*9c5db199SXin Li        if node['Active'] and node['IsInput']:
421*9c5db199SXin Li            return node['Type']
422*9c5db199SXin Li    return None
423*9c5db199SXin Li
424*9c5db199SXin Li
425*9c5db199SXin Lidef get_selected_output_device_name():
426*9c5db199SXin Li    """Returns the device name of the active output node.
427*9c5db199SXin Li
428*9c5db199SXin Li    @returns: device name string. E.g. mtk-rt5650: :0,0
429*9c5db199SXin Li    """
430*9c5db199SXin Li    nodes = get_cras_nodes()
431*9c5db199SXin Li    for node in nodes:
432*9c5db199SXin Li        if node['Active'] and not node['IsInput']:
433*9c5db199SXin Li            return node['DeviceName']
434*9c5db199SXin Li    return None
435*9c5db199SXin Li
436*9c5db199SXin Li
437*9c5db199SXin Lidef get_selected_output_device_type():
438*9c5db199SXin Li    """Returns the device type of the active output node.
439*9c5db199SXin Li
440*9c5db199SXin Li    @returns: device type string. E.g. INTERNAL_SPEAKER
441*9c5db199SXin Li    """
442*9c5db199SXin Li    nodes = get_cras_nodes()
443*9c5db199SXin Li    for node in nodes:
444*9c5db199SXin Li        if node['Active'] and not node['IsInput']:
445*9c5db199SXin Li            return node['Type']
446*9c5db199SXin Li    return None
447*9c5db199SXin Li
448*9c5db199SXin Li
449*9c5db199SXin Lidef get_plugged_node_types():
450*9c5db199SXin Li    """Returns the pair of plugged output node types and input node types.
451*9c5db199SXin Li
452*9c5db199SXin Li    @returns: A tuple (output_node_types, input_node_types) where each
453*9c5db199SXin Li              field is a list of plugged node types defined in CRAS_NODE_TYPES.
454*9c5db199SXin Li
455*9c5db199SXin Li    """
456*9c5db199SXin Li    def is_plugged(node):
457*9c5db199SXin Li        """Checks if a node is plugged and is not unknown node.
458*9c5db199SXin Li
459*9c5db199SXin Li        Cras DBus API only reports plugged node, so every node reported by Cras
460*9c5db199SXin Li        DBus API is plugged. However, we filter out UNKNOWN node here because
461*9c5db199SXin Li        the existence of unknown node depends on the number of redundant
462*9c5db199SXin Li        playback/record audio device created on audio card. Also, the user of
463*9c5db199SXin Li        Cras will ignore unknown nodes.
464*9c5db199SXin Li
465*9c5db199SXin Li        @returns: True if a node is plugged and is not an UNKNOWN node.
466*9c5db199SXin Li
467*9c5db199SXin Li        """
468*9c5db199SXin Li        return node['Type'] != 'UNKNOWN'
469*9c5db199SXin Li
470*9c5db199SXin Li    return get_filtered_node_types(is_plugged)
471*9c5db199SXin Li
472*9c5db199SXin Li
473*9c5db199SXin Lidef set_selected_node_types(output_node_types, input_node_types):
474*9c5db199SXin Li    """Sets selected node types.
475*9c5db199SXin Li
476*9c5db199SXin Li    @param output_node_types: A list of output node types. None to skip setting.
477*9c5db199SXin Li    @param input_node_types: A list of input node types. None to skip setting.
478*9c5db199SXin Li
479*9c5db199SXin Li    """
480*9c5db199SXin Li    if output_node_types is not None and len(output_node_types) == 1:
481*9c5db199SXin Li        set_single_selected_output_node(output_node_types[0])
482*9c5db199SXin Li    elif output_node_types:
483*9c5db199SXin Li        set_selected_output_nodes(output_node_types)
484*9c5db199SXin Li    if input_node_types is not None and len(input_node_types) == 1:
485*9c5db199SXin Li        set_single_selected_input_node(input_node_types[0])
486*9c5db199SXin Li    elif input_node_types:
487*9c5db199SXin Li        set_selected_input_nodes(input_node_types)
488*9c5db199SXin Li
489*9c5db199SXin Li
490*9c5db199SXin Lidef set_single_selected_output_node(node_type):
491*9c5db199SXin Li    """Sets one selected output node.
492*9c5db199SXin Li
493*9c5db199SXin Li    Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
494*9c5db199SXin Li    to select one output node.
495*9c5db199SXin Li
496*9c5db199SXin Li    @param node_type: A node type.
497*9c5db199SXin Li
498*9c5db199SXin Li    @returns: True if the output node type is found and set active.
499*9c5db199SXin Li    """
500*9c5db199SXin Li    nodes = get_cras_nodes()
501*9c5db199SXin Li    for node in nodes:
502*9c5db199SXin Li        if node['IsInput']:
503*9c5db199SXin Li            continue
504*9c5db199SXin Li        if node['Type'] == node_type:
505*9c5db199SXin Li            set_active_output_node(node['Id'])
506*9c5db199SXin Li            return True
507*9c5db199SXin Li    return False
508*9c5db199SXin Li
509*9c5db199SXin Li
510*9c5db199SXin Lidef set_single_selected_input_node(node_type):
511*9c5db199SXin Li    """Sets one selected input node.
512*9c5db199SXin Li
513*9c5db199SXin Li    Note that Chrome UI uses SetActiveInputNode of Cras DBus API
514*9c5db199SXin Li    to select one input node.
515*9c5db199SXin Li
516*9c5db199SXin Li    @param node_type: A node type.
517*9c5db199SXin Li
518*9c5db199SXin Li    @returns: True if the input node type is found and set active.
519*9c5db199SXin Li    """
520*9c5db199SXin Li    nodes = get_cras_nodes()
521*9c5db199SXin Li    for node in nodes:
522*9c5db199SXin Li        if not node['IsInput']:
523*9c5db199SXin Li            continue
524*9c5db199SXin Li        if node['Type'] == node_type:
525*9c5db199SXin Li            set_active_input_node(node['Id'])
526*9c5db199SXin Li            return True
527*9c5db199SXin Li    return False
528*9c5db199SXin Li
529*9c5db199SXin Li
530*9c5db199SXin Lidef set_selected_output_nodes(types):
531*9c5db199SXin Li    """Sets selected output node types.
532*9c5db199SXin Li
533*9c5db199SXin Li    Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
534*9c5db199SXin Li    to select one output node. Here we use add/remove active output node
535*9c5db199SXin Li    to support multiple nodes.
536*9c5db199SXin Li
537*9c5db199SXin Li    @param types: A list of output node types.
538*9c5db199SXin Li
539*9c5db199SXin Li    """
540*9c5db199SXin Li    nodes = get_cras_nodes()
541*9c5db199SXin Li    for node in nodes:
542*9c5db199SXin Li        if node['IsInput']:
543*9c5db199SXin Li            continue
544*9c5db199SXin Li        if node['Type'] in types:
545*9c5db199SXin Li            add_active_output_node(node['Id'])
546*9c5db199SXin Li        elif node['Active']:
547*9c5db199SXin Li            remove_active_output_node(node['Id'])
548*9c5db199SXin Li
549*9c5db199SXin Li
550*9c5db199SXin Lidef set_selected_input_nodes(types):
551*9c5db199SXin Li    """Sets selected input node types.
552*9c5db199SXin Li
553*9c5db199SXin Li    Note that Chrome UI uses SetActiveInputNode of Cras DBus API
554*9c5db199SXin Li    to select one input node. Here we use add/remove active input node
555*9c5db199SXin Li    to support multiple nodes.
556*9c5db199SXin Li
557*9c5db199SXin Li    @param types: A list of input node types.
558*9c5db199SXin Li
559*9c5db199SXin Li    """
560*9c5db199SXin Li    nodes = get_cras_nodes()
561*9c5db199SXin Li    for node in nodes:
562*9c5db199SXin Li        if not node['IsInput']:
563*9c5db199SXin Li            continue
564*9c5db199SXin Li        if node['Type'] in types:
565*9c5db199SXin Li            add_active_input_node(node['Id'])
566*9c5db199SXin Li        elif node['Active']:
567*9c5db199SXin Li            remove_active_input_node(node['Id'])
568*9c5db199SXin Li
569*9c5db199SXin Li
570*9c5db199SXin Lidef set_active_input_node(node_id):
571*9c5db199SXin Li    """Sets one active input node.
572*9c5db199SXin Li
573*9c5db199SXin Li    @param node_id: node id.
574*9c5db199SXin Li
575*9c5db199SXin Li    """
576*9c5db199SXin Li    get_cras_control_interface().SetActiveInputNode(node_id)
577*9c5db199SXin Li
578*9c5db199SXin Li
579*9c5db199SXin Lidef set_active_output_node(node_id):
580*9c5db199SXin Li    """Sets one active output node.
581*9c5db199SXin Li
582*9c5db199SXin Li    @param node_id: node id.
583*9c5db199SXin Li
584*9c5db199SXin Li    """
585*9c5db199SXin Li    get_cras_control_interface().SetActiveOutputNode(node_id)
586*9c5db199SXin Li
587*9c5db199SXin Li
588*9c5db199SXin Lidef add_active_output_node(node_id):
589*9c5db199SXin Li    """Adds an active output node.
590*9c5db199SXin Li
591*9c5db199SXin Li    @param node_id: node id.
592*9c5db199SXin Li
593*9c5db199SXin Li    """
594*9c5db199SXin Li    get_cras_control_interface().AddActiveOutputNode(node_id)
595*9c5db199SXin Li
596*9c5db199SXin Li
597*9c5db199SXin Lidef add_active_input_node(node_id):
598*9c5db199SXin Li    """Adds an active input node.
599*9c5db199SXin Li
600*9c5db199SXin Li    @param node_id: node id.
601*9c5db199SXin Li
602*9c5db199SXin Li    """
603*9c5db199SXin Li    get_cras_control_interface().AddActiveInputNode(node_id)
604*9c5db199SXin Li
605*9c5db199SXin Li
606*9c5db199SXin Lidef remove_active_output_node(node_id):
607*9c5db199SXin Li    """Removes an active output node.
608*9c5db199SXin Li
609*9c5db199SXin Li    @param node_id: node id.
610*9c5db199SXin Li
611*9c5db199SXin Li    """
612*9c5db199SXin Li    get_cras_control_interface().RemoveActiveOutputNode(node_id)
613*9c5db199SXin Li
614*9c5db199SXin Li
615*9c5db199SXin Lidef remove_active_input_node(node_id):
616*9c5db199SXin Li    """Removes an active input node.
617*9c5db199SXin Li
618*9c5db199SXin Li    @param node_id: node id.
619*9c5db199SXin Li
620*9c5db199SXin Li    """
621*9c5db199SXin Li    get_cras_control_interface().RemoveActiveInputNode(node_id)
622*9c5db199SXin Li
623*9c5db199SXin Li
624*9c5db199SXin Lidef get_node_id_from_node_type(node_type, is_input):
625*9c5db199SXin Li    """Gets node id from node type.
626*9c5db199SXin Li
627*9c5db199SXin Li    @param types: A node type defined in CRAS_NODE_TYPES.
628*9c5db199SXin Li    @param is_input: True if the node is input. False otherwise.
629*9c5db199SXin Li
630*9c5db199SXin Li    @returns: A string for node id.
631*9c5db199SXin Li
632*9c5db199SXin Li    @raises: CrasUtilsError: if unique node id can not be found.
633*9c5db199SXin Li
634*9c5db199SXin Li    """
635*9c5db199SXin Li    nodes = get_cras_nodes()
636*9c5db199SXin Li    find_ids = []
637*9c5db199SXin Li    for node in nodes:
638*9c5db199SXin Li        if node['Type'] == node_type and node['IsInput'] == is_input:
639*9c5db199SXin Li            find_ids.append(node['Id'])
640*9c5db199SXin Li    if len(find_ids) != 1:
641*9c5db199SXin Li        raise CrasUtilsError(
642*9c5db199SXin Li                'Can not find unique node id from node type %s' % node_type)
643*9c5db199SXin Li    return find_ids[0]
644*9c5db199SXin Li
645*9c5db199SXin Li
646*9c5db199SXin Lidef get_device_id_of(node_id):
647*9c5db199SXin Li    """Gets the device id of the node id.
648*9c5db199SXin Li
649*9c5db199SXin Li    The conversion logic is replicated from the CRAS's type definition at
650*9c5db199SXin Li    third_party/adhd/cras/src/common/cras_types.h.
651*9c5db199SXin Li
652*9c5db199SXin Li    @param node_id: A string for node id.
653*9c5db199SXin Li
654*9c5db199SXin Li    @returns: A string for device id.
655*9c5db199SXin Li
656*9c5db199SXin Li    @raise: CrasUtilsError: if device id is invalid.
657*9c5db199SXin Li    """
658*9c5db199SXin Li    device_id = str(int(node_id) >> 32)
659*9c5db199SXin Li    if device_id == "0":
660*9c5db199SXin Li        raise CrasUtilsError('Got invalid device_id: 0')
661*9c5db199SXin Li    return device_id
662*9c5db199SXin Li
663*9c5db199SXin Li
664*9c5db199SXin Lidef get_device_id_from_node_type(node_type, is_input):
665*9c5db199SXin Li    """Gets device id from node type.
666*9c5db199SXin Li
667*9c5db199SXin Li    @param types: A node type defined in CRAS_NODE_TYPES.
668*9c5db199SXin Li    @param is_input: True if the node is input. False otherwise.
669*9c5db199SXin Li
670*9c5db199SXin Li    @returns: A string for device id.
671*9c5db199SXin Li
672*9c5db199SXin Li    """
673*9c5db199SXin Li    node_id = get_node_id_from_node_type(node_type, is_input)
674*9c5db199SXin Li    return get_device_id_of(node_id)
675*9c5db199SXin Li
676*9c5db199SXin Li
677*9c5db199SXin Lidef get_active_node_volume():
678*9c5db199SXin Li    """Returns volume from active node.
679*9c5db199SXin Li
680*9c5db199SXin Li    @returns: int for volume
681*9c5db199SXin Li
682*9c5db199SXin Li    @raises: CrasUtilsError: if node volume cannot be found.
683*9c5db199SXin Li    """
684*9c5db199SXin Li    nodes = get_cras_nodes()
685*9c5db199SXin Li    for node in nodes:
686*9c5db199SXin Li        if node['Active'] == 1 and node['IsInput'] == 0:
687*9c5db199SXin Li            return int(node['NodeVolume'])
688*9c5db199SXin Li    raise CrasUtilsError('Cannot find active node volume from nodes.')
689*9c5db199SXin Li
690*9c5db199SXin Li
691*9c5db199SXin Lidef get_active_output_node_max_supported_channels():
692*9c5db199SXin Li    """Returns max supported channels from active output node.
693*9c5db199SXin Li
694*9c5db199SXin Li    @returns: int for max supported channels.
695*9c5db199SXin Li
696*9c5db199SXin Li    @raises: CrasUtilsError: if node cannot be found.
697*9c5db199SXin Li    """
698*9c5db199SXin Li    nodes = get_cras_nodes()
699*9c5db199SXin Li    for node in nodes:
700*9c5db199SXin Li        if node['Active'] == 1 and node['IsInput'] == 0:
701*9c5db199SXin Li            return int(node['MaxSupportedChannels'])
702*9c5db199SXin Li    raise CrasUtilsError('Cannot find active output node.')
703*9c5db199SXin Li
704*9c5db199SXin Li
705*9c5db199SXin Lidef get_noise_cancellation_supported():
706*9c5db199SXin Li    """Gets whether the device supports Noise Cancellation.
707*9c5db199SXin Li
708*9c5db199SXin Li    @returns: True is supported; False otherwise.
709*9c5db199SXin Li    """
710*9c5db199SXin Li    return bool(get_cras_control_interface().IsNoiseCancellationSupported())
711*9c5db199SXin Li
712*9c5db199SXin Li
713*9c5db199SXin Lidef set_bypass_block_noise_cancellation(bypass):
714*9c5db199SXin Li    """Sets CRAS to bypass the blocking logic of Noise Cancellation.
715*9c5db199SXin Li
716*9c5db199SXin Li    @param bypass: True for bypass; False for un-bypass.
717*9c5db199SXin Li    """
718*9c5db199SXin Li    get_cras_control_interface().SetBypassBlockNoiseCancellation(bypass)
719*9c5db199SXin Li
720*9c5db199SXin Li
721*9c5db199SXin Lidef set_noise_cancellation_enabled(enabled):
722*9c5db199SXin Li    """Sets the state to enable or disable Noise Cancellation.
723*9c5db199SXin Li
724*9c5db199SXin Li    @param enabled: True to enable; False to disable.
725*9c5db199SXin Li    """
726*9c5db199SXin Li    get_cras_control_interface().SetNoiseCancellationEnabled(enabled)
727*9c5db199SXin Li
728*9c5db199SXin Li
729*9c5db199SXin Lidef set_floss_enabled(enabled):
730*9c5db199SXin Li    """Sets whether CRAS stack expects to use Floss.
731*9c5db199SXin Li
732*9c5db199SXin Li    @param enabled: True for Floss, False for Bluez.
733*9c5db199SXin Li    """
734*9c5db199SXin Li    get_cras_control_interface().SetFlossEnabled(enabled)
735*9c5db199SXin Li
736*9c5db199SXin Li
737*9c5db199SXin Liclass CrasTestClient(object):
738*9c5db199SXin Li    """An object to perform cras_test_client functions."""
739*9c5db199SXin Li
740*9c5db199SXin Li    BLOCK_SIZE = None
741*9c5db199SXin Li    PIN_DEVICE = None
742*9c5db199SXin Li    SAMPLE_FORMAT = 'S16_LE'
743*9c5db199SXin Li    DURATION = 10
744*9c5db199SXin Li    CHANNELS = 2
745*9c5db199SXin Li    RATE = 48000
746*9c5db199SXin Li
747*9c5db199SXin Li
748*9c5db199SXin Li    def __init__(self):
749*9c5db199SXin Li        self._proc = None
750*9c5db199SXin Li        self._capturing_proc = None
751*9c5db199SXin Li        self._playing_proc = None
752*9c5db199SXin Li        self._capturing_msg = 'capturing audio file'
753*9c5db199SXin Li        self._playing_msg = 'playing audio file'
754*9c5db199SXin Li        self._wbs_cmd = '%s --set_wbs_enabled ' % _CRAS_TEST_CLIENT
755*9c5db199SXin Li        self._enable_wbs_cmd = ('%s 1' % self._wbs_cmd).split()
756*9c5db199SXin Li        self._disable_wbs_cmd = ('%s 0' % self._wbs_cmd).split()
757*9c5db199SXin Li        self._info_cmd = [_CRAS_TEST_CLIENT,]
758*9c5db199SXin Li        self._select_input_cmd = '%s --select_input ' % _CRAS_TEST_CLIENT
759*9c5db199SXin Li
760*9c5db199SXin Li
761*9c5db199SXin Li    def start_subprocess(self, proc, proc_cmd, filename, proc_msg):
762*9c5db199SXin Li        """Start a capture or play subprocess
763*9c5db199SXin Li
764*9c5db199SXin Li        @param proc: the process
765*9c5db199SXin Li        @param proc_cmd: the process command and its arguments
766*9c5db199SXin Li        @param filename: the file name to capture or play
767*9c5db199SXin Li        @param proc_msg: the message to display in logging
768*9c5db199SXin Li
769*9c5db199SXin Li        @returns: True if the process is started successfully
770*9c5db199SXin Li        """
771*9c5db199SXin Li        if proc is None:
772*9c5db199SXin Li            try:
773*9c5db199SXin Li                self._proc = subprocess.Popen(proc_cmd)
774*9c5db199SXin Li                logging.debug('Start %s %s on the DUT', proc_msg, filename)
775*9c5db199SXin Li            except Exception as e:
776*9c5db199SXin Li                logging.error('Failed to popen: %s (%s)', proc_msg, e)
777*9c5db199SXin Li                return False
778*9c5db199SXin Li        else:
779*9c5db199SXin Li            logging.error('cannot run the command twice: %s', proc_msg)
780*9c5db199SXin Li            return False
781*9c5db199SXin Li        return True
782*9c5db199SXin Li
783*9c5db199SXin Li
784*9c5db199SXin Li    def stop_subprocess(self, proc, proc_msg):
785*9c5db199SXin Li        """Stop a subprocess
786*9c5db199SXin Li
787*9c5db199SXin Li        @param proc: the process to stop
788*9c5db199SXin Li        @param proc_msg: the message to display in logging
789*9c5db199SXin Li
790*9c5db199SXin Li        @returns: True if the process is stopped successfully
791*9c5db199SXin Li        """
792*9c5db199SXin Li        if proc is None:
793*9c5db199SXin Li            logging.error('cannot run stop %s before starting it.', proc_msg)
794*9c5db199SXin Li            return False
795*9c5db199SXin Li
796*9c5db199SXin Li        proc.terminate()
797*9c5db199SXin Li        try:
798*9c5db199SXin Li            utils.poll_for_condition(
799*9c5db199SXin Li                    condition=lambda: proc.poll() is not None,
800*9c5db199SXin Li                    exception=CrasUtilsError,
801*9c5db199SXin Li                    timeout=10,
802*9c5db199SXin Li                    sleep_interval=0.5,
803*9c5db199SXin Li                    desc='Waiting for subprocess to terminate')
804*9c5db199SXin Li        except Exception:
805*9c5db199SXin Li            logging.warning('Killing subprocess due to timeout')
806*9c5db199SXin Li            proc.kill()
807*9c5db199SXin Li            proc.wait()
808*9c5db199SXin Li
809*9c5db199SXin Li        logging.debug('stop %s on the DUT', proc_msg)
810*9c5db199SXin Li        return True
811*9c5db199SXin Li
812*9c5db199SXin Li
813*9c5db199SXin Li    def start_capturing_subprocess(self, capture_file, block_size=BLOCK_SIZE,
814*9c5db199SXin Li                                   duration=DURATION, pin_device=PIN_DEVICE,
815*9c5db199SXin Li                                   sample_format=SAMPLE_FORMAT,
816*9c5db199SXin Li                                   channels=CHANNELS, rate=RATE):
817*9c5db199SXin Li        """Start capturing in a subprocess.
818*9c5db199SXin Li
819*9c5db199SXin Li        @param capture_file: the name of file the audio to be stored in
820*9c5db199SXin Li        @param block_size: the number of frames per callback(dictates latency)
821*9c5db199SXin Li        @param duration: seconds to record. If it is None, duration is not set,
822*9c5db199SXin Li                         and will keep capturing audio until terminated
823*9c5db199SXin Li        @param sample_format: the sample format
824*9c5db199SXin Li        @param pin_device: the device id to record from
825*9c5db199SXin Li        @param channels: number of channels
826*9c5db199SXin Li        @param rate: the sampling rate
827*9c5db199SXin Li
828*9c5db199SXin Li        @returns: True if the process is started successfully
829*9c5db199SXin Li        """
830*9c5db199SXin Li        proc_cmd = capture_cmd(capture_file, block_size=block_size,
831*9c5db199SXin Li                               duration=duration, sample_format=sample_format,
832*9c5db199SXin Li                               pin_device=pin_device, channels=channels,
833*9c5db199SXin Li                               rate=rate)
834*9c5db199SXin Li        result = self.start_subprocess(self._capturing_proc, proc_cmd,
835*9c5db199SXin Li                                       capture_file, self._capturing_msg)
836*9c5db199SXin Li        if result:
837*9c5db199SXin Li            self._capturing_proc = self._proc
838*9c5db199SXin Li        return result
839*9c5db199SXin Li
840*9c5db199SXin Li
841*9c5db199SXin Li    def stop_capturing_subprocess(self):
842*9c5db199SXin Li        """Stop the capturing subprocess."""
843*9c5db199SXin Li        result = self.stop_subprocess(self._capturing_proc, self._capturing_msg)
844*9c5db199SXin Li        if result:
845*9c5db199SXin Li            self._capturing_proc = None
846*9c5db199SXin Li        return result
847*9c5db199SXin Li
848*9c5db199SXin Li
849*9c5db199SXin Li    def start_playing_subprocess(self, audio_file, block_size=BLOCK_SIZE,
850*9c5db199SXin Li                                 duration=DURATION, pin_device=PIN_DEVICE,
851*9c5db199SXin Li                                 channels=CHANNELS, rate=RATE):
852*9c5db199SXin Li        """Start playing the audio file in a subprocess.
853*9c5db199SXin Li
854*9c5db199SXin Li        @param audio_file: the name of audio file to play
855*9c5db199SXin Li        @param block_size: the number of frames per callback(dictates latency)
856*9c5db199SXin Li        @param duration: seconds to play. If it is None, duration is not set,
857*9c5db199SXin Li                         and will keep playing audio until terminated
858*9c5db199SXin Li        @param pin_device: the device id to play to
859*9c5db199SXin Li        @param channels: number of channels
860*9c5db199SXin Li        @param rate: the sampling rate
861*9c5db199SXin Li
862*9c5db199SXin Li        @returns: True if the process is started successfully
863*9c5db199SXin Li        """
864*9c5db199SXin Li        proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device,
865*9c5db199SXin Li                                channels, rate)
866*9c5db199SXin Li        result = self.start_subprocess(self._playing_proc, proc_cmd,
867*9c5db199SXin Li                                       audio_file, self._playing_msg)
868*9c5db199SXin Li        if result:
869*9c5db199SXin Li            self._playing_proc = self._proc
870*9c5db199SXin Li        return result
871*9c5db199SXin Li
872*9c5db199SXin Li
873*9c5db199SXin Li    def stop_playing_subprocess(self):
874*9c5db199SXin Li        """Stop the playing subprocess."""
875*9c5db199SXin Li        result = self.stop_subprocess(self._playing_proc, self._playing_msg)
876*9c5db199SXin Li        if result:
877*9c5db199SXin Li            self._playing_proc = None
878*9c5db199SXin Li        return result
879*9c5db199SXin Li
880*9c5db199SXin Li
881*9c5db199SXin Li    def play(self, audio_file, block_size=BLOCK_SIZE, duration=DURATION,
882*9c5db199SXin Li             pin_device=PIN_DEVICE, channels=CHANNELS, rate=RATE):
883*9c5db199SXin Li        """Play the audio file.
884*9c5db199SXin Li
885*9c5db199SXin Li        This method will get blocked until it has completed playing back.
886*9c5db199SXin Li        If you do not want to get blocked, use start_playing_subprocess()
887*9c5db199SXin Li        above instead.
888*9c5db199SXin Li
889*9c5db199SXin Li        @param audio_file: the name of audio file to play
890*9c5db199SXin Li        @param block_size: the number of frames per callback(dictates latency)
891*9c5db199SXin Li        @param duration: seconds to play. If it is None, duration is not set,
892*9c5db199SXin Li                         and will keep playing audio until terminated
893*9c5db199SXin Li        @param pin_device: the device id to play to
894*9c5db199SXin Li        @param channels: number of channels
895*9c5db199SXin Li        @param rate: the sampling rate
896*9c5db199SXin Li
897*9c5db199SXin Li        @returns: True if the process is started successfully
898*9c5db199SXin Li        """
899*9c5db199SXin Li        proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device,
900*9c5db199SXin Li                                channels, rate)
901*9c5db199SXin Li        try:
902*9c5db199SXin Li            self._proc = subprocess.call(proc_cmd)
903*9c5db199SXin Li            logging.debug('call "%s" on the DUT', proc_cmd)
904*9c5db199SXin Li        except Exception as e:
905*9c5db199SXin Li            logging.error('Failed to call: %s (%s)', proc_cmd, e)
906*9c5db199SXin Li            return False
907*9c5db199SXin Li        return True
908*9c5db199SXin Li
909*9c5db199SXin Li
910*9c5db199SXin Li    def enable_wbs(self, value):
911*9c5db199SXin Li        """Enable or disable wideband speech (wbs) per the value.
912*9c5db199SXin Li
913*9c5db199SXin Li        @param value: True to enable wbs.
914*9c5db199SXin Li
915*9c5db199SXin Li        @returns: True if the operation succeeds.
916*9c5db199SXin Li        """
917*9c5db199SXin Li        cmd = self._enable_wbs_cmd if value else self._disable_wbs_cmd
918*9c5db199SXin Li        logging.debug('call "%s" on the DUT', cmd)
919*9c5db199SXin Li        if subprocess.call(cmd):
920*9c5db199SXin Li            logging.error('Failed to call: %s (%s)', cmd)
921*9c5db199SXin Li            return False
922*9c5db199SXin Li        return True
923*9c5db199SXin Li
924*9c5db199SXin Li
925*9c5db199SXin Li    def select_input_device(self, device_name):
926*9c5db199SXin Li        """Select the audio input device.
927*9c5db199SXin Li
928*9c5db199SXin Li        @param device_name: the name of the Bluetooth peer device
929*9c5db199SXin Li
930*9c5db199SXin Li        @returns: True if the operation succeeds.
931*9c5db199SXin Li        """
932*9c5db199SXin Li        logging.debug('to select input device for device_name: %s', device_name)
933*9c5db199SXin Li        try:
934*9c5db199SXin Li            info = subprocess.check_output(self._info_cmd)
935*9c5db199SXin Li            logging.debug('info: %s', info)
936*9c5db199SXin Li        except Exception as e:
937*9c5db199SXin Li            logging.error('Failed to call: %s (%s)', self._info_cmd, e)
938*9c5db199SXin Li            return False
939*9c5db199SXin Li
940*9c5db199SXin Li        flag_input_nodes = False
941*9c5db199SXin Li        audio_input_node = None
942*9c5db199SXin Li        for line in info.decode().splitlines():
943*9c5db199SXin Li            if 'Input Nodes' in line:
944*9c5db199SXin Li                flag_input_nodes = True
945*9c5db199SXin Li            elif 'Attached clients' in line:
946*9c5db199SXin Li                flag_input_nodes = False
947*9c5db199SXin Li
948*9c5db199SXin Li            if flag_input_nodes:
949*9c5db199SXin Li                if device_name in line:
950*9c5db199SXin Li                    audio_input_node = line.split()[1]
951*9c5db199SXin Li                    logging.debug('%s', audio_input_node)
952*9c5db199SXin Li                    break
953*9c5db199SXin Li
954*9c5db199SXin Li        if audio_input_node is None:
955*9c5db199SXin Li            logging.error('Failed to find audio input node: %s', device_name)
956*9c5db199SXin Li            return False
957*9c5db199SXin Li
958*9c5db199SXin Li        select_input_cmd = (self._select_input_cmd + audio_input_node).split()
959*9c5db199SXin Li        if subprocess.call(select_input_cmd):
960*9c5db199SXin Li            logging.error('Failed to call: %s (%s)', select_input_cmd, e)
961*9c5db199SXin Li            return False
962*9c5db199SXin Li
963*9c5db199SXin Li        logging.debug('call "%s" on the DUT', select_input_cmd)
964*9c5db199SXin Li        return True
965*9c5db199SXin Li
966*9c5db199SXin Li
967*9c5db199SXin Li    def set_player_playback_status(self, status):
968*9c5db199SXin Li        """Set playback status for the registered media player.
969*9c5db199SXin Li
970*9c5db199SXin Li        @param status: playback status in string.
971*9c5db199SXin Li
972*9c5db199SXin Li        """
973*9c5db199SXin Li        try:
974*9c5db199SXin Li            get_cras_control_interface().SetPlayerPlaybackStatus(status)
975*9c5db199SXin Li        except Exception as e:
976*9c5db199SXin Li            logging.error('Failed to set player playback status: %s', e)
977*9c5db199SXin Li            return False
978*9c5db199SXin Li
979*9c5db199SXin Li        return True
980*9c5db199SXin Li
981*9c5db199SXin Li
982*9c5db199SXin Li    def set_player_position(self, position):
983*9c5db199SXin Li        """Set media position for the registered media player.
984*9c5db199SXin Li
985*9c5db199SXin Li        @param position: position in micro seconds.
986*9c5db199SXin Li
987*9c5db199SXin Li        """
988*9c5db199SXin Li        try:
989*9c5db199SXin Li            get_cras_control_interface().SetPlayerPosition(position)
990*9c5db199SXin Li        except Exception as e:
991*9c5db199SXin Li            logging.error('Failed to set player position: %s', e)
992*9c5db199SXin Li            return False
993*9c5db199SXin Li
994*9c5db199SXin Li        return True
995*9c5db199SXin Li
996*9c5db199SXin Li
997*9c5db199SXin Li    def set_player_metadata(self, metadata):
998*9c5db199SXin Li        """Set title, artist, and album for the registered media player.
999*9c5db199SXin Li
1000*9c5db199SXin Li        @param metadata: dictionary of media metadata.
1001*9c5db199SXin Li
1002*9c5db199SXin Li        """
1003*9c5db199SXin Li        try:
1004*9c5db199SXin Li            get_cras_control_interface().SetPlayerMetadata(metadata)
1005*9c5db199SXin Li        except Exception as e:
1006*9c5db199SXin Li            logging.error('Failed to set player metadata: %s', e)
1007*9c5db199SXin Li            return False
1008*9c5db199SXin Li
1009*9c5db199SXin Li        return True
1010*9c5db199SXin Li
1011*9c5db199SXin Li
1012*9c5db199SXin Li    def _encode_length_for_dbus(self, length):
1013*9c5db199SXin Li        """Encode length as Int64 for |SetPlayerMetadata|."""
1014*9c5db199SXin Li        try:
1015*9c5db199SXin Li            import dbus
1016*9c5db199SXin Li        except ImportError as e:
1017*9c5db199SXin Li            logging.exception(
1018*9c5db199SXin Li                    'Can not import dbus: %s. This method should only be '
1019*9c5db199SXin Li                    'called on Cros device.', e)
1020*9c5db199SXin Li            raise
1021*9c5db199SXin Li
1022*9c5db199SXin Li        length_variant = dbus.types.Int64(length, variant_level=1)
1023*9c5db199SXin Li        return dbus.Dictionary({'length': length_variant}, signature='sv')
1024*9c5db199SXin Li
1025*9c5db199SXin Li    def set_player_length(self, length):
1026*9c5db199SXin Li        """Set metadata length for the registered media player.
1027*9c5db199SXin Li
1028*9c5db199SXin Li        Media length is a part of metadata information. However, without
1029*9c5db199SXin Li        specify its type to int64. dbus-python will guess the variant type to
1030*9c5db199SXin Li        be int32 by default. Separate it from the metadata function to help
1031*9c5db199SXin Li        prepare the data differently.
1032*9c5db199SXin Li
1033*9c5db199SXin Li        @param length: Integer value that will be encoded for dbus.
1034*9c5db199SXin Li
1035*9c5db199SXin Li        """
1036*9c5db199SXin Li        try:
1037*9c5db199SXin Li            length_dbus = self._encode_length_for_dbus(length)
1038*9c5db199SXin Li            get_cras_control_interface().SetPlayerMetadata(length_dbus)
1039*9c5db199SXin Li        except Exception as e:
1040*9c5db199SXin Li            logging.error('Failed to set player length: %s', e)
1041*9c5db199SXin Li            return False
1042*9c5db199SXin Li
1043*9c5db199SXin Li        return True
1044