blob: 0acdfbedefc6fee17e0c23d706046009a1da51a3 [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+) \[.*\]:.*')
15CONTROL_NAME_RE = re.compile("name='(.*)'")
16SCONTROL_NAME_RE = re.compile("Simple mixer control '(.*)'")
17
Owen Lin21d49eb2013-12-03 10:47:58 +080018
19def _get_format_args(channels, bits, rate):
20 args = ['-c', str(channels)]
21 args += ['-f', 'S%d_LE' % bits]
22 args += ['-r', str(rate)]
23 return args
24
25
Chinyue Chend0b99322014-07-18 15:57:30 +080026def get_num_soundcards():
27 '''Returns the number of soundcards.
28
29 Number of soundcards is parsed from /proc/asound/cards.
30 Sample content:
31
32 0 [PCH ]: HDA-Intel - HDA Intel PCH
33 HDA Intel PCH at 0xef340000 irq 103
34 1 [NVidia ]: HDA-Intel - HDA NVidia
35 HDA NVidia at 0xef080000 irq 36
36 '''
37
38 card_id = None
39 with open('/proc/asound/cards', 'r') as f:
40 for line in f:
41 match = CARD_NUM_RE.search(line)
42 if match:
43 card_id = int(match.group(1))
44 if card_id is None:
45 return 0
46 else:
47 return card_id + 1
Owen Lin21d49eb2013-12-03 10:47:58 +080048
49
Chinyue Chend0b99322014-07-18 15:57:30 +080050def _get_soundcard_controls(card_id):
51 '''Gets the controls for a soundcard.
52
53 @param card_id: Soundcard ID.
54 @raise RuntimeError: If failed to get soundcard controls.
55
56 Controls for a soundcard is retrieved by 'amixer controls' command.
57 amixer output format:
58
59 numid=32,iface=CARD,name='Front Headphone Jack'
60 numid=28,iface=CARD,name='Front Mic Jack'
61 numid=1,iface=CARD,name='HDMI/DP,pcm=3 Jack'
62 numid=8,iface=CARD,name='HDMI/DP,pcm=7 Jack'
63
64 Controls with iface=CARD are parsed from the output and returned in a set.
65 '''
66
67 cmd = AMIXER_PATH + ' -c %d controls' % card_id
68 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE)
69 output, _ = p.communicate()
70 if p.wait() != 0:
71 raise RuntimeError('amixer command failed')
72
73 controls = set()
74 for line in output.splitlines():
75 if not 'iface=CARD' in line:
76 continue
77 match = CONTROL_NAME_RE.search(line)
78 if match:
79 controls.add(match.group(1))
80 return controls
81
82
83def _get_soundcard_scontrols(card_id):
84 '''Gets the simple mixer controls for a soundcard.
85
86 @param card_id: Soundcard ID.
87 @raise RuntimeError: If failed to get soundcard simple mixer controls.
88
89 Simple mixer controls for a soundcard is retrieved by 'amixer scontrols'
90 command. amixer output format:
91
92 Simple mixer control 'Master',0
93 Simple mixer control 'Headphone',0
94 Simple mixer control 'Speaker',0
95 Simple mixer control 'PCM',0
96
97 Simple controls are parsed from the output and returned in a set.
98 '''
99
100 cmd = AMIXER_PATH + ' -c %d scontrols' % card_id
101 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE)
102 output, _ = p.communicate()
103 if p.wait() != 0:
104 raise RuntimeError('amixer command failed')
105
106 scontrols = set()
107 for line in output.splitlines():
108 match = SCONTROL_NAME_RE.findall(line)
109 if match:
110 scontrols.add(match[0])
111 return scontrols
112
113
114def get_first_soundcard_with_control(cname, scname):
115 '''Returns the soundcard ID with matching control name.
116
117 @param cname: Control name to look for.
118 @param scname: Simple control name to look for.
119 '''
120
Chinyue Chen73407842014-07-24 17:34:44 +0800121 cpat = re.compile(r'\b%s\b' % cname, re.IGNORECASE)
122 scpat = re.compile(r'\b%s\b' % scname, re.IGNORECASE)
Chinyue Chend0b99322014-07-18 15:57:30 +0800123 for card_id in xrange(get_num_soundcards()):
Chinyue Chen73407842014-07-24 17:34:44 +0800124 for pat, func in [(cpat, _get_soundcard_controls),
125 (scpat, _get_soundcard_scontrols)]:
126 if any(pat.search(c) for c in func(card_id)):
Chinyue Chend0b99322014-07-18 15:57:30 +0800127 return card_id
128 return None
129
130
131def get_default_playback_device():
132 '''Gets the first playback device.
133
134 Returns the first playback device or None if it fails to find one.
135 '''
136
137 card_id = get_first_soundcard_with_control(cname='Headphone Jack',
138 scname='Headphone')
139 if card_id is None:
140 return None
141 return 'plughw:%d' % card_id
142
143
144def get_default_record_device():
145 '''Gets the first record device.
146
147 Returns the first record device or None if it fails to find one.
148 '''
149
150 card_id = get_first_soundcard_with_control(cname='Mic Jack', scname='Mic')
151 if card_id is None:
152 return None
153 return 'plughw:%d' % card_id
154
155
Chinyue Chenbea15172015-09-11 15:15:22 +0800156def _get_sysdefault(cmd):
157 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE)
158 output, _ = p.communicate()
159 if p.wait() != 0:
160 raise RuntimeError('%s failed' % cmd)
161
162 for line in output.splitlines():
163 if 'sysdefault' in line:
164 return line
165 return None
166
167
168def get_sysdefault_playback_device():
169 '''Gets the sysdefault device from aplay -L output.'''
170
171 return _get_sysdefault(APLAY_PATH + ' -L')
172
173
174def get_sysdefault_record_device():
175 '''Gets the sysdefault device from arecord -L output.'''
176
177 return _get_sysdefault(ARECORD_PATH + ' -L')
178
179
Chinyue Chend0b99322014-07-18 15:57:30 +0800180def playback(*args, **kwargs):
181 '''A helper funciton to execute playback_cmd.
182
183 @param kwargs: kwargs passed to playback_cmd.
184 '''
185 cmd_utils.execute(playback_cmd(*args, **kwargs))
Owen Lin21d49eb2013-12-03 10:47:58 +0800186
187
188def playback_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800189 input, duration=None, channels=2, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800190 '''Plays the given input audio by the ALSA utility: 'aplay'.
191
192 @param input: The input audio to be played.
193 @param duration: The length of the playback (in seconds).
194 @param channels: The number of channels of the input audio.
195 @param bits: The number of bits of each audio sample.
196 @param rate: The sampling rate.
197 @param device: The device to play the audio on.
Chinyue Chend0b99322014-07-18 15:57:30 +0800198 @raise RuntimeError: If no playback device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800199 '''
200 args = [APLAY_PATH]
201 if duration is not None:
202 args += ['-d', str(duration)]
203 args += _get_format_args(channels, bits, rate)
204 if device is None:
Chinyue Chend0b99322014-07-18 15:57:30 +0800205 device = get_default_playback_device()
206 if device is None:
207 raise RuntimeError('no playback device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800208 args += ['-D', device]
209 args += [input]
210 return args
211
212
Chinyue Chend0b99322014-07-18 15:57:30 +0800213def record(*args, **kwargs):
214 '''A helper function to execute record_cmd.
215
216 @param kwargs: kwargs passed to record_cmd.
217 '''
218 cmd_utils.execute(record_cmd(*args, **kwargs))
Owen Lin21d49eb2013-12-03 10:47:58 +0800219
220
221def record_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800222 output, duration=None, channels=1, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800223 '''Records the audio to the specified output by ALSA utility: 'arecord'.
224
225 @param output: The filename where the recorded audio will be stored to.
226 @param duration: The length of the recording (in seconds).
227 @param channels: The number of channels of the recorded audio.
228 @param bits: The number of bits of each audio sample.
229 @param rate: The sampling rate.
230 @param device: The device used to recorded the audio from.
Chinyue Chend0b99322014-07-18 15:57:30 +0800231 @raise RuntimeError: If no record device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800232 '''
233 args = [ARECORD_PATH]
234 if duration is not None:
235 args += ['-d', str(duration)]
236 args += _get_format_args(channels, bits, rate)
237 if device is None:
Chinyue Chend0b99322014-07-18 15:57:30 +0800238 device = get_default_record_device()
239 if device is None:
240 raise RuntimeError('no record device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800241 args += ['-D', device]
242 args += [output]
243 return args
Chinyue Chend5292c72015-07-17 16:23:53 +0800244
245
246def mixer_cmd(card_id, cmd):
247 '''Executes amixer command.
248
249 @param card_id: Soundcard ID.
250 @param cmd: Amixer command to execute.
251 @raise RuntimeError: If failed to execute command.
252
253 Amixer command like "set PCM 2dB+" with card_id 1 will be executed as:
254 amixer -c 1 set PCM 2dB+
255
256 Command output will be returned if any.
257 '''
258
259 cmd = AMIXER_PATH + ' -c %d ' % card_id + cmd
260 p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE)
261 output, _ = p.communicate()
262 if p.wait() != 0:
263 raise RuntimeError('amixer command failed')
264 return output