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