blob: 51f91724585e986efca63a69b494fa61ed3051a0 [file] [log] [blame]
Owen Lin21d49eb2013-12-03 10:47:58 +08001# 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
Chinyue Chend0b99322014-07-18 15:57:30 +08005import re
Owen Lin21d49eb2013-12-03 10:47:58 +08006import shlex
Owen Lin21d49eb2013-12-03 10:47:58 +08007
8from autotest_lib.client.cros.audio import cmd_utils
9
Chinyue Chend0b99322014-07-18 15:57:30 +080010
11ARECORD_PATH = '/usr/bin/arecord'
12APLAY_PATH = '/usr/bin/aplay'
13AMIXER_PATH = '/usr/bin/amixer'
14CARD_NUM_RE = re.compile('(\d+) \[.*\]:.*')
Koro Chen2972a602015-08-20 16:43:53 +080015DEV_NUM_RE = re.compile('.* \[.*\], device (\d+):.*')
Chinyue Chend0b99322014-07-18 15:57:30 +080016CONTROL_NAME_RE = re.compile("name='(.*)'")
17SCONTROL_NAME_RE = re.compile("Simple mixer control '(.*)'")
18
Hsin-Yu Chaobfc70952017-02-20 17:15:42 +080019CARD_PREF_RECORD_DEV_IDX = {
20 'bxtda7219max': 3,
21}
22
23CARD_PREF_RECORD_CHANNELS = {
24 'bxtda7219max': [ 4 ],
25}
Owen Lin21d49eb2013-12-03 10:47:58 +080026
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
Chinyue Chend0b99322014-07-18 15:57:30 +080034def 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
Owen Lin21d49eb2013-12-03 10:47:58 +080056
57
Chinyue Chend0b99322014-07-18 15:57:30 +080058def _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 %d controls' % card_id
76 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.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 %d scontrols' % card_id
109 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.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
Chinyue Chen73407842014-07-24 17:34:44 +0800129 cpat = re.compile(r'\b%s\b' % cname, re.IGNORECASE)
130 scpat = re.compile(r'\b%s\b' % scname, re.IGNORECASE)
Chinyue Chend0b99322014-07-18 15:57:30 +0800131 for card_id in xrange(get_num_soundcards()):
Chinyue Chen73407842014-07-24 17:34:44 +0800132 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)):
Chinyue Chend0b99322014-07-18 15:57:30 +0800135 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
Hsin-Yu Chaobfc70952017-02-20 17:15:42 +0800151def 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('card %d: .*?\[(.*?)\]' % card_idx)
157 cmd = ARECORD_PATH + ' -l'
158 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.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
Chinyue Chend0b99322014-07-18 15:57:30 +0800182
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
Koro Chen2972a602015-08-20 16:43:53 +0800192
Hsin-Yu Chaobfc70952017-02-20 17:15:42 +0800193 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
Koro Chen2972a602015-08-20 16:43:53 +0800197 # Get first device id of this card.
198 cmd = ARECORD_PATH + ' -l'
199 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.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)
Chinyue Chend0b99322014-07-18 15:57:30 +0800212
213
Chinyue Chenbea15172015-09-11 15:15:22 +0800214def _get_sysdefault(cmd):
215 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.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
Chinyue Chend0b99322014-07-18 15:57:30 +0800238def 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))
Owen Lin21d49eb2013-12-03 10:47:58 +0800244
245
246def playback_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800247 input, duration=None, channels=2, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800248 '''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.
Chinyue Chend0b99322014-07-18 15:57:30 +0800256 @raise RuntimeError: If no playback device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800257 '''
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:
Chinyue Chend0b99322014-07-18 15:57:30 +0800263 device = get_default_playback_device()
264 if device is None:
265 raise RuntimeError('no playback device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800266 args += ['-D', device]
267 args += [input]
268 return args
269
270
Chinyue Chend0b99322014-07-18 15:57:30 +0800271def 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))
Owen Lin21d49eb2013-12-03 10:47:58 +0800277
278
279def record_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800280 output, duration=None, channels=1, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800281 '''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.
Chinyue Chend0b99322014-07-18 15:57:30 +0800289 @raise RuntimeError: If no record device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800290 '''
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:
Chinyue Chend0b99322014-07-18 15:57:30 +0800296 device = get_default_record_device()
297 if device is None:
298 raise RuntimeError('no record device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800299 args += ['-D', device]
300 args += [output]
301 return args
Chinyue Chend5292c72015-07-17 16:23:53 +0800302
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 as:
312 amixer -c 1 set PCM 2dB+
313
314 Command output will be returned if any.
315 '''
316
317 cmd = AMIXER_PATH + ' -c %d ' % card_id + cmd
318 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE)
319 output, _ = p.communicate()
320 if p.wait() != 0:
321 raise RuntimeError('amixer command failed')
322 return output