xref: /aosp_15_r20/external/autotest/client/cros/audio/alsa_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 Lifrom __future__ import absolute_import
7*9c5db199SXin Lifrom __future__ import division
8*9c5db199SXin Lifrom __future__ import print_function
9*9c5db199SXin Li
10*9c5db199SXin Liimport logging
11*9c5db199SXin Liimport re
12*9c5db199SXin Liimport subprocess
13*9c5db199SXin Li
14*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
15*9c5db199SXin Lifrom autotest_lib.client.common_lib import utils
16*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cmd_utils
17*9c5db199SXin Lifrom six.moves import range
18*9c5db199SXin Li
19*9c5db199SXin Li
20*9c5db199SXin LiACONNECT_PATH = '/usr/bin/aconnect'
21*9c5db199SXin LiARECORD_PATH = '/usr/bin/arecord'
22*9c5db199SXin LiAPLAY_PATH = '/usr/bin/aplay'
23*9c5db199SXin LiAMIXER_PATH = '/usr/bin/amixer'
24*9c5db199SXin LiCARD_NUM_RE = re.compile(r'(\d+) \[.*\]:')
25*9c5db199SXin LiCLIENT_NUM_RE = re.compile(r'client (\d+):')
26*9c5db199SXin LiDEV_NUM_RE = re.compile(r'.* \[.*\], device (\d+):')
27*9c5db199SXin LiCONTROL_NAME_RE = re.compile(r"name='(.*)'")
28*9c5db199SXin LiSCONTROL_NAME_RE = re.compile(r"Simple mixer control '(.*)'")
29*9c5db199SXin LiAUDIO_DEVICE_STATUS_CMD = 'cat /proc/asound/card%s/pcm%sp/sub0/status'
30*9c5db199SXin LiOUTPUT_DEVICE_CMD = 'cras_test_client --dump_audio_thread | grep "Output dev:"'
31*9c5db199SXin Li
32*9c5db199SXin LiCARD_PREF_RECORD_DEV_IDX = {
33*9c5db199SXin Li    'bxtda7219max': 3,
34*9c5db199SXin Li}
35*9c5db199SXin Li
36*9c5db199SXin Lidef _get_format_args(channels, bits, rate):
37*9c5db199SXin Li    args = ['-c', str(channels)]
38*9c5db199SXin Li    args += ['-f', 'S%d_LE' % bits]
39*9c5db199SXin Li    args += ['-r', str(rate)]
40*9c5db199SXin Li    return args
41*9c5db199SXin Li
42*9c5db199SXin Li
43*9c5db199SXin Lidef get_num_soundcards():
44*9c5db199SXin Li    '''Returns the number of soundcards.
45*9c5db199SXin Li
46*9c5db199SXin Li    Number of soundcards is parsed from /proc/asound/cards.
47*9c5db199SXin Li    Sample content:
48*9c5db199SXin Li
49*9c5db199SXin Li      0 [PCH            ]: HDA-Intel - HDA Intel PCH
50*9c5db199SXin Li                           HDA Intel PCH at 0xef340000 irq 103
51*9c5db199SXin Li      1 [NVidia         ]: HDA-Intel - HDA NVidia
52*9c5db199SXin Li                           HDA NVidia at 0xef080000 irq 36
53*9c5db199SXin Li    '''
54*9c5db199SXin Li
55*9c5db199SXin Li    card_id = None
56*9c5db199SXin Li    with open('/proc/asound/cards', 'r') as f:
57*9c5db199SXin Li        for line in f:
58*9c5db199SXin Li            match = CARD_NUM_RE.search(line)
59*9c5db199SXin Li            if match:
60*9c5db199SXin Li                card_id = int(match.group(1))
61*9c5db199SXin Li    if card_id is None:
62*9c5db199SXin Li        return 0
63*9c5db199SXin Li    else:
64*9c5db199SXin Li        return card_id + 1
65*9c5db199SXin Li
66*9c5db199SXin Li
67*9c5db199SXin Lidef _get_soundcard_controls(card_id):
68*9c5db199SXin Li    '''Gets the controls for a soundcard.
69*9c5db199SXin Li
70*9c5db199SXin Li    @param card_id: Soundcard ID.
71*9c5db199SXin Li    @raise RuntimeError: If failed to get soundcard controls.
72*9c5db199SXin Li
73*9c5db199SXin Li    Controls for a soundcard is retrieved by 'amixer controls' command.
74*9c5db199SXin Li    amixer output format:
75*9c5db199SXin Li
76*9c5db199SXin Li      numid=32,iface=CARD,name='Front Headphone Jack'
77*9c5db199SXin Li      numid=28,iface=CARD,name='Front Mic Jack'
78*9c5db199SXin Li      numid=1,iface=CARD,name='HDMI/DP,pcm=3 Jack'
79*9c5db199SXin Li      numid=8,iface=CARD,name='HDMI/DP,pcm=7 Jack'
80*9c5db199SXin Li
81*9c5db199SXin Li    Controls with iface=CARD are parsed from the output and returned in a set.
82*9c5db199SXin Li    '''
83*9c5db199SXin Li
84*9c5db199SXin Li    cmd = [AMIXER_PATH, '-c', str(card_id), 'controls']
85*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
86*9c5db199SXin Li    output, _ = p.communicate()
87*9c5db199SXin Li    if p.wait() != 0:
88*9c5db199SXin Li        raise RuntimeError('amixer command failed')
89*9c5db199SXin Li
90*9c5db199SXin Li    controls = set()
91*9c5db199SXin Li    for line in output.splitlines():
92*9c5db199SXin Li        if not 'iface=CARD' in line:
93*9c5db199SXin Li            continue
94*9c5db199SXin Li        match = CONTROL_NAME_RE.search(line)
95*9c5db199SXin Li        if match:
96*9c5db199SXin Li            controls.add(match.group(1))
97*9c5db199SXin Li    return controls
98*9c5db199SXin Li
99*9c5db199SXin Li
100*9c5db199SXin Lidef _get_soundcard_scontrols(card_id):
101*9c5db199SXin Li    '''Gets the simple mixer controls for a soundcard.
102*9c5db199SXin Li
103*9c5db199SXin Li    @param card_id: Soundcard ID.
104*9c5db199SXin Li    @raise RuntimeError: If failed to get soundcard simple mixer controls.
105*9c5db199SXin Li
106*9c5db199SXin Li    # TODO b:169251326 terms below are set outside of this codebase
107*9c5db199SXin Li    # and should be updated when possible. ("Master" -> "Main")
108*9c5db199SXin Li    Simple mixer controls for a soundcard is retrieved by 'amixer scontrols'
109*9c5db199SXin Li    command.  amixer output format:
110*9c5db199SXin Li
111*9c5db199SXin Li      Simple mixer control 'Master',0
112*9c5db199SXin Li      Simple mixer control 'Headphone',0
113*9c5db199SXin Li      Simple mixer control 'Speaker',0
114*9c5db199SXin Li      Simple mixer control 'PCM',0
115*9c5db199SXin Li
116*9c5db199SXin Li    Simple controls are parsed from the output and returned in a set.
117*9c5db199SXin Li    '''
118*9c5db199SXin Li
119*9c5db199SXin Li    cmd = [AMIXER_PATH, '-c', str(card_id), 'scontrols']
120*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
121*9c5db199SXin Li    output, _ = p.communicate()
122*9c5db199SXin Li    if p.wait() != 0:
123*9c5db199SXin Li        raise RuntimeError('amixer command failed')
124*9c5db199SXin Li
125*9c5db199SXin Li    scontrols = set()
126*9c5db199SXin Li    for line in output.splitlines():
127*9c5db199SXin Li        match = SCONTROL_NAME_RE.findall(line)
128*9c5db199SXin Li        if match:
129*9c5db199SXin Li            scontrols.add(match[0])
130*9c5db199SXin Li    return scontrols
131*9c5db199SXin Li
132*9c5db199SXin Li
133*9c5db199SXin Lidef get_first_soundcard_with_control(cname, scname):
134*9c5db199SXin Li    '''Returns the soundcard ID with matching control name.
135*9c5db199SXin Li
136*9c5db199SXin Li    @param cname: Control name to look for.
137*9c5db199SXin Li    @param scname: Simple control name to look for.
138*9c5db199SXin Li    '''
139*9c5db199SXin Li
140*9c5db199SXin Li    cpat = re.compile(r'\b%s\b' % cname, re.IGNORECASE)
141*9c5db199SXin Li    scpat = re.compile(r'\b%s\b' % scname, re.IGNORECASE)
142*9c5db199SXin Li    for card_id in range(get_num_soundcards()):
143*9c5db199SXin Li        for pat, func in [(cpat, _get_soundcard_controls),
144*9c5db199SXin Li                          (scpat, _get_soundcard_scontrols)]:
145*9c5db199SXin Li            if any(pat.search(c) for c in func(card_id)):
146*9c5db199SXin Li                return card_id
147*9c5db199SXin Li    return None
148*9c5db199SXin Li
149*9c5db199SXin Li
150*9c5db199SXin Lidef get_soundcard_names():
151*9c5db199SXin Li    '''Returns a dictionary of card names, keyed by card number.'''
152*9c5db199SXin Li
153*9c5db199SXin Li    cmd = "alsa_helpers -l"
154*9c5db199SXin Li    try:
155*9c5db199SXin Li        output = utils.system_output(command=cmd, retain_output=True)
156*9c5db199SXin Li    except error.CmdError:
157*9c5db199SXin Li        raise RuntimeError('alsa_helpers -l failed to return card names')
158*9c5db199SXin Li
159*9c5db199SXin Li    return dict((index, name) for index, name in (
160*9c5db199SXin Li        line.split(',') for line in output.splitlines()))
161*9c5db199SXin Li
162*9c5db199SXin Li
163*9c5db199SXin Lidef get_default_playback_device():
164*9c5db199SXin Li    '''Gets the first playback device.
165*9c5db199SXin Li
166*9c5db199SXin Li    Returns the first playback device or None if it fails to find one.
167*9c5db199SXin Li    '''
168*9c5db199SXin Li
169*9c5db199SXin Li    card_id = get_first_soundcard_with_control(cname='Headphone Jack',
170*9c5db199SXin Li                                               scname='Headphone')
171*9c5db199SXin Li    if card_id is None:
172*9c5db199SXin Li        return None
173*9c5db199SXin Li    return 'plughw:%d' % card_id
174*9c5db199SXin Li
175*9c5db199SXin Lidef get_record_card_name(card_idx):
176*9c5db199SXin Li    '''Gets the recording sound card name for given card idx.
177*9c5db199SXin Li
178*9c5db199SXin Li    Returns the card name inside the square brackets of arecord output lines.
179*9c5db199SXin Li    '''
180*9c5db199SXin Li    card_name_re = re.compile(r'card %d: .*?\[(.*?)\]' % card_idx)
181*9c5db199SXin Li    cmd = [ARECORD_PATH, '-l']
182*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
183*9c5db199SXin Li    output, _ = p.communicate()
184*9c5db199SXin Li    if p.wait() != 0:
185*9c5db199SXin Li        raise RuntimeError('arecord -l command failed')
186*9c5db199SXin Li
187*9c5db199SXin Li    for line in output.splitlines():
188*9c5db199SXin Li        match = card_name_re.search(line)
189*9c5db199SXin Li        if match:
190*9c5db199SXin Li            return match.group(1)
191*9c5db199SXin Li    return None
192*9c5db199SXin Li
193*9c5db199SXin Li
194*9c5db199SXin Lidef get_record_device_supported_channels(device):
195*9c5db199SXin Li    '''Gets the supported channels for the record device.
196*9c5db199SXin Li
197*9c5db199SXin Li    @param device: The device to record the audio. E.g. hw:0,1
198*9c5db199SXin Li
199*9c5db199SXin Li    Returns the supported values in integer in a list for the device.
200*9c5db199SXin Li    If the value doesn't exist or the command fails, return None.
201*9c5db199SXin Li    '''
202*9c5db199SXin Li    cmd = "alsa_helpers --device %s --get_capture_channels" % device
203*9c5db199SXin Li    try:
204*9c5db199SXin Li        output = utils.system_output(command=cmd, retain_output=True)
205*9c5db199SXin Li    except error.CmdError:
206*9c5db199SXin Li        logging.error("Fail to get supported channels for %s", device)
207*9c5db199SXin Li        return None
208*9c5db199SXin Li
209*9c5db199SXin Li    supported_channels = output.splitlines()
210*9c5db199SXin Li    if not supported_channels:
211*9c5db199SXin Li        logging.error("Supported channels are empty for %s", device)
212*9c5db199SXin Li        return None
213*9c5db199SXin Li    return [int(i) for i in supported_channels]
214*9c5db199SXin Li
215*9c5db199SXin Li
216*9c5db199SXin Lidef get_default_record_device():
217*9c5db199SXin Li    '''Gets the first record device.
218*9c5db199SXin Li
219*9c5db199SXin Li    Returns the first record device or None if it fails to find one.
220*9c5db199SXin Li    '''
221*9c5db199SXin Li
222*9c5db199SXin Li    card_id = get_first_soundcard_with_control(cname='Mic Jack', scname='Mic')
223*9c5db199SXin Li    if card_id is None:
224*9c5db199SXin Li        return None
225*9c5db199SXin Li
226*9c5db199SXin Li    card_name = get_record_card_name(card_id)
227*9c5db199SXin Li    if card_name in CARD_PREF_RECORD_DEV_IDX:
228*9c5db199SXin Li        return 'plughw:%d,%d' % (card_id, CARD_PREF_RECORD_DEV_IDX[card_name])
229*9c5db199SXin Li
230*9c5db199SXin Li    # Get first device id of this card.
231*9c5db199SXin Li    cmd = [ARECORD_PATH, '-l']
232*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
233*9c5db199SXin Li    output, _ = p.communicate()
234*9c5db199SXin Li    if p.wait() != 0:
235*9c5db199SXin Li        raise RuntimeError('arecord -l command failed')
236*9c5db199SXin Li
237*9c5db199SXin Li    dev_id = 0
238*9c5db199SXin Li    for line in output.splitlines():
239*9c5db199SXin Li        if 'card %d:' % card_id in line:
240*9c5db199SXin Li            match = DEV_NUM_RE.search(line)
241*9c5db199SXin Li            if match:
242*9c5db199SXin Li                dev_id = int(match.group(1))
243*9c5db199SXin Li                break
244*9c5db199SXin Li    return 'plughw:%d,%d' % (card_id, dev_id)
245*9c5db199SXin Li
246*9c5db199SXin Li
247*9c5db199SXin Lidef _get_sysdefault(cmd):
248*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
249*9c5db199SXin Li    output, _ = p.communicate()
250*9c5db199SXin Li    if p.wait() != 0:
251*9c5db199SXin Li        raise RuntimeError('%s failed' % cmd)
252*9c5db199SXin Li
253*9c5db199SXin Li    for line in output.splitlines():
254*9c5db199SXin Li        if 'sysdefault' in line:
255*9c5db199SXin Li            return line
256*9c5db199SXin Li    return None
257*9c5db199SXin Li
258*9c5db199SXin Li
259*9c5db199SXin Lidef get_sysdefault_playback_device():
260*9c5db199SXin Li    '''Gets the sysdefault device from aplay -L output.'''
261*9c5db199SXin Li
262*9c5db199SXin Li    return _get_sysdefault([APLAY_PATH, '-L'])
263*9c5db199SXin Li
264*9c5db199SXin Li
265*9c5db199SXin Lidef get_sysdefault_record_device():
266*9c5db199SXin Li    '''Gets the sysdefault device from arecord -L output.'''
267*9c5db199SXin Li
268*9c5db199SXin Li    return _get_sysdefault([ARECORD_PATH, '-L'])
269*9c5db199SXin Li
270*9c5db199SXin Li
271*9c5db199SXin Lidef playback(*args, **kwargs):
272*9c5db199SXin Li    '''A helper funciton to execute playback_cmd.
273*9c5db199SXin Li
274*9c5db199SXin Li    @param kwargs: kwargs passed to playback_cmd.
275*9c5db199SXin Li    '''
276*9c5db199SXin Li    cmd_utils.execute(playback_cmd(*args, **kwargs))
277*9c5db199SXin Li
278*9c5db199SXin Li
279*9c5db199SXin Lidef playback_cmd(
280*9c5db199SXin Li        input, duration=None, channels=2, bits=16, rate=48000, device=None):
281*9c5db199SXin Li    '''Plays the given input audio by the ALSA utility: 'aplay'.
282*9c5db199SXin Li
283*9c5db199SXin Li    @param input: The input audio to be played.
284*9c5db199SXin Li    @param duration: The length of the playback (in seconds).
285*9c5db199SXin Li    @param channels: The number of channels of the input audio.
286*9c5db199SXin Li    @param bits: The number of bits of each audio sample.
287*9c5db199SXin Li    @param rate: The sampling rate.
288*9c5db199SXin Li    @param device: The device to play the audio on. E.g. hw:0,1
289*9c5db199SXin Li    @raise RuntimeError: If no playback device is available.
290*9c5db199SXin Li    '''
291*9c5db199SXin Li    args = [APLAY_PATH]
292*9c5db199SXin Li    if duration is not None:
293*9c5db199SXin Li        args += ['-d', str(duration)]
294*9c5db199SXin Li    args += _get_format_args(channels, bits, rate)
295*9c5db199SXin Li    if device is None:
296*9c5db199SXin Li        device = get_default_playback_device()
297*9c5db199SXin Li        if device is None:
298*9c5db199SXin Li            raise RuntimeError('no playback device')
299*9c5db199SXin Li    else:
300*9c5db199SXin Li        device = "plug%s" % device
301*9c5db199SXin Li    args += ['-D', device]
302*9c5db199SXin Li    args += [input]
303*9c5db199SXin Li    return args
304*9c5db199SXin Li
305*9c5db199SXin Li
306*9c5db199SXin Lidef record(*args, **kwargs):
307*9c5db199SXin Li    '''A helper function to execute record_cmd.
308*9c5db199SXin Li
309*9c5db199SXin Li    @param kwargs: kwargs passed to record_cmd.
310*9c5db199SXin Li    '''
311*9c5db199SXin Li    cmd_utils.execute(record_cmd(*args, **kwargs))
312*9c5db199SXin Li
313*9c5db199SXin Li
314*9c5db199SXin Lidef record_cmd(
315*9c5db199SXin Li        output, duration=None, channels=1, bits=16, rate=48000, device=None):
316*9c5db199SXin Li    '''Records the audio to the specified output by ALSA utility: 'arecord'.
317*9c5db199SXin Li
318*9c5db199SXin Li    @param output: The filename where the recorded audio will be stored to.
319*9c5db199SXin Li    @param duration: The length of the recording (in seconds).
320*9c5db199SXin Li    @param channels: The number of channels of the recorded audio.
321*9c5db199SXin Li    @param bits: The number of bits of each audio sample.
322*9c5db199SXin Li    @param rate: The sampling rate.
323*9c5db199SXin Li    @param device: The device used to recorded the audio from. E.g. hw:0,1
324*9c5db199SXin Li    @raise RuntimeError: If no record device is available.
325*9c5db199SXin Li    '''
326*9c5db199SXin Li    args = [ARECORD_PATH]
327*9c5db199SXin Li    if duration is not None:
328*9c5db199SXin Li        args += ['-d', str(duration)]
329*9c5db199SXin Li    args += _get_format_args(channels, bits, rate)
330*9c5db199SXin Li    if device is None:
331*9c5db199SXin Li        device = get_default_record_device()
332*9c5db199SXin Li        if device is None:
333*9c5db199SXin Li            raise RuntimeError('no record device')
334*9c5db199SXin Li    else:
335*9c5db199SXin Li        device = "plug%s" % device
336*9c5db199SXin Li    args += ['-D', device]
337*9c5db199SXin Li    args += [output]
338*9c5db199SXin Li    return args
339*9c5db199SXin Li
340*9c5db199SXin Li
341*9c5db199SXin Lidef mixer_cmd(card_id, cmd):
342*9c5db199SXin Li    '''Executes amixer command.
343*9c5db199SXin Li
344*9c5db199SXin Li    @param card_id: Soundcard ID.
345*9c5db199SXin Li    @param cmd: Amixer command to execute.
346*9c5db199SXin Li    @raise RuntimeError: If failed to execute command.
347*9c5db199SXin Li
348*9c5db199SXin Li    Amixer command like ['set', 'PCM', '2dB+'] with card_id 1 will be executed
349*9c5db199SXin Li    as:
350*9c5db199SXin Li        amixer -c 1 set PCM 2dB+
351*9c5db199SXin Li
352*9c5db199SXin Li    Command output will be returned if any.
353*9c5db199SXin Li    '''
354*9c5db199SXin Li
355*9c5db199SXin Li    cmd = [AMIXER_PATH, '-c', str(card_id)] + cmd
356*9c5db199SXin Li    p = cmd_utils.popen(cmd, stdout=subprocess.PIPE)
357*9c5db199SXin Li    output, _ = p.communicate()
358*9c5db199SXin Li    if p.wait() != 0:
359*9c5db199SXin Li        raise RuntimeError('amixer command failed')
360*9c5db199SXin Li    return output
361*9c5db199SXin Li
362*9c5db199SXin Li
363*9c5db199SXin Lidef get_num_seq_clients():
364*9c5db199SXin Li    '''Returns the number of seq clients.
365*9c5db199SXin Li
366*9c5db199SXin Li    The number of clients is parsed from aconnect -io.
367*9c5db199SXin Li    This is run as the chronos user to catch permissions problems.
368*9c5db199SXin Li    Sample content:
369*9c5db199SXin Li
370*9c5db199SXin Li      client 0: 'System' [type=kernel]
371*9c5db199SXin Li          0 'Timer           '
372*9c5db199SXin Li          1 'Announce        '
373*9c5db199SXin Li      client 14: 'Midi Through' [type=kernel]
374*9c5db199SXin Li          0 'Midi Through Port-0'
375*9c5db199SXin Li
376*9c5db199SXin Li    @raise RuntimeError: If no seq device is available.
377*9c5db199SXin Li    '''
378*9c5db199SXin Li    cmd = [ACONNECT_PATH, '-io']
379*9c5db199SXin Li    output = cmd_utils.execute(cmd, stdout=subprocess.PIPE, run_as='chronos')
380*9c5db199SXin Li
381*9c5db199SXin Li    #py3 migration
382*9c5db199SXin Li    output = output.decode()
383*9c5db199SXin Li    num_clients = 0
384*9c5db199SXin Li    for line in output.splitlines():
385*9c5db199SXin Li        match = CLIENT_NUM_RE.match(line)
386*9c5db199SXin Li        if match:
387*9c5db199SXin Li            num_clients += 1
388*9c5db199SXin Li    return num_clients
389*9c5db199SXin Li
390*9c5db199SXin Lidef convert_device_name(cras_device_name):
391*9c5db199SXin Li    '''Converts cras device name to alsa device name.
392*9c5db199SXin Li
393*9c5db199SXin Li    @returns: alsa device name that can be passed to aplay -D or arecord -D.
394*9c5db199SXin Li              For example, if cras_device_name is "kbl_r5514_5663_max: :0,1",
395*9c5db199SXin Li              this function will return "hw:0,1".
396*9c5db199SXin Li    '''
397*9c5db199SXin Li    tokens = cras_device_name.split(":")
398*9c5db199SXin Li    return "hw:%s" % tokens[2]
399*9c5db199SXin Li
400*9c5db199SXin Lidef check_audio_stream_at_selected_device(device_name, device_type):
401*9c5db199SXin Li    """Checks the audio output at expected node
402*9c5db199SXin Li
403*9c5db199SXin Li    @param device_name: Audio output device name, Ex: kbl_r5514_5663_max: :0,1
404*9c5db199SXin Li    @param device_type: Audio output device type, Ex: INTERNAL_SPEAKER
405*9c5db199SXin Li    """
406*9c5db199SXin Li    if device_type == 'BLUETOOTH':
407*9c5db199SXin Li        output_device_output = utils.system_output(OUTPUT_DEVICE_CMD).strip()
408*9c5db199SXin Li        bt_device = output_device_output.split('Output dev:')[1].strip()
409*9c5db199SXin Li        if bt_device != device_name:
410*9c5db199SXin Li            raise error.TestFail("Audio is not routing through expected node")
411*9c5db199SXin Li        logging.info('Audio is routing through %s', bt_device)
412*9c5db199SXin Li    else:
413*9c5db199SXin Li        card_device_search = re.search(r':(\d),(\d)', device_name)
414*9c5db199SXin Li        if card_device_search:
415*9c5db199SXin Li            card_num = card_device_search.group(1)
416*9c5db199SXin Li            device_num = card_device_search.group(2)
417*9c5db199SXin Li        logging.debug("Sound card number is %s", card_num)
418*9c5db199SXin Li        logging.debug("Device number is %s", device_num)
419*9c5db199SXin Li        if card_num is None or device_num is None:
420*9c5db199SXin Li            raise error.TestError("Audio device name is not in expected format")
421*9c5db199SXin Li        device_status_output = utils.system_output(AUDIO_DEVICE_STATUS_CMD %
422*9c5db199SXin Li                                                   (card_num, device_num))
423*9c5db199SXin Li        logging.debug("Selected output device status is %s",
424*9c5db199SXin Li                      device_status_output)
425*9c5db199SXin Li
426*9c5db199SXin Li        if 'RUNNING' in device_status_output:
427*9c5db199SXin Li            logging.info("Audio is routing through expected node!")
428*9c5db199SXin Li        elif 'closed' in device_status_output:
429*9c5db199SXin Li            raise error.TestFail("Audio is not routing through expected audio "
430*9c5db199SXin Li                                 "node!")
431*9c5db199SXin Li        else:
432*9c5db199SXin Li            raise error.TestError("Audio routing error! Device may be "
433*9c5db199SXin Li                                  "preparing")