blob: 79d4676d8fd58f686dd6c6e70cbce273c12ee432 [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
Owen Lin21d49eb2013-12-03 10:47:58 +080019
20def _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
Chinyue Chend0b99322014-07-18 15:57:30 +080027def 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
Owen Lin21d49eb2013-12-03 10:47:58 +080049
50
Chinyue Chend0b99322014-07-18 15:57:30 +080051def _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
84def _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
115def 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
Chinyue Chen73407842014-07-24 17:34:44 +0800122 cpat = re.compile(r'\b%s\b' % cname, re.IGNORECASE)
123 scpat = re.compile(r'\b%s\b' % scname, re.IGNORECASE)
Chinyue Chend0b99322014-07-18 15:57:30 +0800124 for card_id in xrange(get_num_soundcards()):
Chinyue Chen73407842014-07-24 17:34:44 +0800125 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)):
Chinyue Chend0b99322014-07-18 15:57:30 +0800128 return card_id
129 return None
130
131
132def 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
145def 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
Koro Chen2972a602015-08-20 16:43:53 +0800154
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)
Chinyue Chend0b99322014-07-18 15:57:30 +0800170
171
Chinyue Chenbea15172015-09-11 15:15:22 +0800172def _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
184def get_sysdefault_playback_device():
185 '''Gets the sysdefault device from aplay -L output.'''
186
187 return _get_sysdefault(APLAY_PATH + ' -L')
188
189
190def get_sysdefault_record_device():
191 '''Gets the sysdefault device from arecord -L output.'''
192
193 return _get_sysdefault(ARECORD_PATH + ' -L')
194
195
Chinyue Chend0b99322014-07-18 15:57:30 +0800196def 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))
Owen Lin21d49eb2013-12-03 10:47:58 +0800202
203
204def playback_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800205 input, duration=None, channels=2, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800206 '''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.
Chinyue Chend0b99322014-07-18 15:57:30 +0800214 @raise RuntimeError: If no playback device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800215 '''
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:
Chinyue Chend0b99322014-07-18 15:57:30 +0800221 device = get_default_playback_device()
222 if device is None:
223 raise RuntimeError('no playback device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800224 args += ['-D', device]
225 args += [input]
226 return args
227
228
Chinyue Chend0b99322014-07-18 15:57:30 +0800229def 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))
Owen Lin21d49eb2013-12-03 10:47:58 +0800235
236
237def record_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800238 output, duration=None, channels=1, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800239 '''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.
Chinyue Chend0b99322014-07-18 15:57:30 +0800247 @raise RuntimeError: If no record device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800248 '''
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:
Chinyue Chend0b99322014-07-18 15:57:30 +0800254 device = get_default_record_device()
255 if device is None:
256 raise RuntimeError('no record device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800257 args += ['-D', device]
258 args += [output]
259 return args
Chinyue Chend5292c72015-07-17 16:23:53 +0800260
261
262def 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