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")