1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 import re 6 import shlex 7 8 from autotest_lib.client.cros.audio import cmd_utils 9 10 11 ARECORD_PATH = '/usr/bin/arecord' 12 APLAY_PATH = '/usr/bin/aplay' 13 AMIXER_PATH = '/usr/bin/amixer' 14 CARD_NUM_RE = re.compile('(\d+) \[.*\]:.*') 15 DEV_NUM_RE = re.compile('.* \[.*\], device (\d+):.*') 16 CONTROL_NAME_RE = re.compile("name='(.*)'") 17 SCONTROL_NAME_RE = re.compile("Simple mixer control '(.*)'") 18 19 20 def _get_format_args(channels, bits, rate): 21 args = ['-c', str(channels)] 22 args += ['-f', 'S%d_LE' % bits] 23 args += ['-r', str(rate)] 24 return args 25 26 27 def get_num_soundcards(): 28 '''Returns the number of soundcards. 29 30 Number of soundcards is parsed from /proc/asound/cards. 31 Sample content: 32 33 0 [PCH ]: HDA-Intel - HDA Intel PCH 34 HDA Intel PCH at 0xef340000 irq 103 35 1 [NVidia ]: HDA-Intel - HDA NVidia 36 HDA NVidia at 0xef080000 irq 36 37 ''' 38 39 card_id = None 40 with open('/proc/asound/cards', 'r') as f: 41 for line in f: 42 match = CARD_NUM_RE.search(line) 43 if match: 44 card_id = int(match.group(1)) 45 if card_id is None: 46 return 0 47 else: 48 return card_id + 1 49 50 51 def _get_soundcard_controls(card_id): 52 '''Gets the controls for a soundcard. 53 54 @param card_id: Soundcard ID. 55 @raise RuntimeError: If failed to get soundcard controls. 56 57 Controls for a soundcard is retrieved by 'amixer controls' command. 58 amixer output format: 59 60 numid=32,iface=CARD,name='Front Headphone Jack' 61 numid=28,iface=CARD,name='Front Mic Jack' 62 numid=1,iface=CARD,name='HDMI/DP,pcm=3 Jack' 63 numid=8,iface=CARD,name='HDMI/DP,pcm=7 Jack' 64 65 Controls with iface=CARD are parsed from the output and returned in a set. 66 ''' 67 68 cmd = AMIXER_PATH + ' -c %d controls' % card_id 69 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) 70 output, _ = p.communicate() 71 if p.wait() != 0: 72 raise RuntimeError('amixer command failed') 73 74 controls = set() 75 for line in output.splitlines(): 76 if not 'iface=CARD' in line: 77 continue 78 match = CONTROL_NAME_RE.search(line) 79 if match: 80 controls.add(match.group(1)) 81 return controls 82 83 84 def _get_soundcard_scontrols(card_id): 85 '''Gets the simple mixer controls for a soundcard. 86 87 @param card_id: Soundcard ID. 88 @raise RuntimeError: If failed to get soundcard simple mixer controls. 89 90 Simple mixer controls for a soundcard is retrieved by 'amixer scontrols' 91 command. amixer output format: 92 93 Simple mixer control 'Master',0 94 Simple mixer control 'Headphone',0 95 Simple mixer control 'Speaker',0 96 Simple mixer control 'PCM',0 97 98 Simple controls are parsed from the output and returned in a set. 99 ''' 100 101 cmd = AMIXER_PATH + ' -c %d scontrols' % card_id 102 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) 103 output, _ = p.communicate() 104 if p.wait() != 0: 105 raise RuntimeError('amixer command failed') 106 107 scontrols = set() 108 for line in output.splitlines(): 109 match = SCONTROL_NAME_RE.findall(line) 110 if match: 111 scontrols.add(match[0]) 112 return scontrols 113 114 115 def get_first_soundcard_with_control(cname, scname): 116 '''Returns the soundcard ID with matching control name. 117 118 @param cname: Control name to look for. 119 @param scname: Simple control name to look for. 120 ''' 121 122 cpat = re.compile(r'\b%s\b' % cname, re.IGNORECASE) 123 scpat = re.compile(r'\b%s\b' % scname, re.IGNORECASE) 124 for card_id in xrange(get_num_soundcards()): 125 for pat, func in [(cpat, _get_soundcard_controls), 126 (scpat, _get_soundcard_scontrols)]: 127 if any(pat.search(c) for c in func(card_id)): 128 return card_id 129 return None 130 131 132 def get_default_playback_device(): 133 '''Gets the first playback device. 134 135 Returns the first playback device or None if it fails to find one. 136 ''' 137 138 card_id = get_first_soundcard_with_control(cname='Headphone Jack', 139 scname='Headphone') 140 if card_id is None: 141 return None 142 return 'plughw:%d' % card_id 143 144 145 def get_default_record_device(): 146 '''Gets the first record device. 147 148 Returns the first record device or None if it fails to find one. 149 ''' 150 151 card_id = get_first_soundcard_with_control(cname='Mic Jack', scname='Mic') 152 if card_id is None: 153 return None 154 155 # Get first device id of this card. 156 cmd = ARECORD_PATH + ' -l' 157 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) 158 output, _ = p.communicate() 159 if p.wait() != 0: 160 raise RuntimeError('arecord -l command failed') 161 162 dev_id = 0 163 for line in output.splitlines(): 164 if 'card %d:' % card_id in line: 165 match = DEV_NUM_RE.search(line) 166 if match: 167 dev_id = int(match.group(1)) 168 break 169 return 'plughw:%d,%d' % (card_id, dev_id) 170 171 172 def _get_sysdefault(cmd): 173 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) 174 output, _ = p.communicate() 175 if p.wait() != 0: 176 raise RuntimeError('%s failed' % cmd) 177 178 for line in output.splitlines(): 179 if 'sysdefault' in line: 180 return line 181 return None 182 183 184 def get_sysdefault_playback_device(): 185 '''Gets the sysdefault device from aplay -L output.''' 186 187 return _get_sysdefault(APLAY_PATH + ' -L') 188 189 190 def get_sysdefault_record_device(): 191 '''Gets the sysdefault device from arecord -L output.''' 192 193 return _get_sysdefault(ARECORD_PATH + ' -L') 194 195 196 def playback(*args, **kwargs): 197 '''A helper funciton to execute playback_cmd. 198 199 @param kwargs: kwargs passed to playback_cmd. 200 ''' 201 cmd_utils.execute(playback_cmd(*args, **kwargs)) 202 203 204 def playback_cmd( 205 input, duration=None, channels=2, bits=16, rate=48000, device=None): 206 '''Plays the given input audio by the ALSA utility: 'aplay'. 207 208 @param input: The input audio to be played. 209 @param duration: The length of the playback (in seconds). 210 @param channels: The number of channels of the input audio. 211 @param bits: The number of bits of each audio sample. 212 @param rate: The sampling rate. 213 @param device: The device to play the audio on. 214 @raise RuntimeError: If no playback device is available. 215 ''' 216 args = [APLAY_PATH] 217 if duration is not None: 218 args += ['-d', str(duration)] 219 args += _get_format_args(channels, bits, rate) 220 if device is None: 221 device = get_default_playback_device() 222 if device is None: 223 raise RuntimeError('no playback device') 224 args += ['-D', device] 225 args += [input] 226 return args 227 228 229 def record(*args, **kwargs): 230 '''A helper function to execute record_cmd. 231 232 @param kwargs: kwargs passed to record_cmd. 233 ''' 234 cmd_utils.execute(record_cmd(*args, **kwargs)) 235 236 237 def record_cmd( 238 output, duration=None, channels=1, bits=16, rate=48000, device=None): 239 '''Records the audio to the specified output by ALSA utility: 'arecord'. 240 241 @param output: The filename where the recorded audio will be stored to. 242 @param duration: The length of the recording (in seconds). 243 @param channels: The number of channels of the recorded audio. 244 @param bits: The number of bits of each audio sample. 245 @param rate: The sampling rate. 246 @param device: The device used to recorded the audio from. 247 @raise RuntimeError: If no record device is available. 248 ''' 249 args = [ARECORD_PATH] 250 if duration is not None: 251 args += ['-d', str(duration)] 252 args += _get_format_args(channels, bits, rate) 253 if device is None: 254 device = get_default_record_device() 255 if device is None: 256 raise RuntimeError('no record device') 257 args += ['-D', device] 258 args += [output] 259 return args 260 261 262 def mixer_cmd(card_id, cmd): 263 '''Executes amixer command. 264 265 @param card_id: Soundcard ID. 266 @param cmd: Amixer command to execute. 267 @raise RuntimeError: If failed to execute command. 268 269 Amixer command like "set PCM 2dB+" with card_id 1 will be executed as: 270 amixer -c 1 set PCM 2dB+ 271 272 Command output will be returned if any. 273 ''' 274 275 cmd = AMIXER_PATH + ' -c %d ' % card_id + cmd 276 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) 277 output, _ = p.communicate() 278 if p.wait() != 0: 279 raise RuntimeError('amixer command failed') 280 return output 281