blob: 584c1d25c4de8d3399c1f731ca7b1ead6c670a1c [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
121 for card_id in xrange(get_num_soundcards()):
122 for name, func in [(cname, _get_soundcard_controls),
123 (scname, _get_soundcard_scontrols)]:
124 if any(name in c for c in func(card_id)):
125 return card_id
126 return None
127
128
129def get_default_playback_device():
130 '''Gets the first playback device.
131
132 Returns the first playback device or None if it fails to find one.
133 '''
134
135 card_id = get_first_soundcard_with_control(cname='Headphone Jack',
136 scname='Headphone')
137 if card_id is None:
138 return None
139 return 'plughw:%d' % card_id
140
141
142def get_default_record_device():
143 '''Gets the first record device.
144
145 Returns the first record device or None if it fails to find one.
146 '''
147
148 card_id = get_first_soundcard_with_control(cname='Mic Jack', scname='Mic')
149 if card_id is None:
150 return None
151 return 'plughw:%d' % card_id
152
153
154def playback(*args, **kwargs):
155 '''A helper funciton to execute playback_cmd.
156
157 @param kwargs: kwargs passed to playback_cmd.
158 '''
159 cmd_utils.execute(playback_cmd(*args, **kwargs))
Owen Lin21d49eb2013-12-03 10:47:58 +0800160
161
162def playback_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800163 input, duration=None, channels=2, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800164 '''Plays the given input audio by the ALSA utility: 'aplay'.
165
166 @param input: The input audio to be played.
167 @param duration: The length of the playback (in seconds).
168 @param channels: The number of channels of the input audio.
169 @param bits: The number of bits of each audio sample.
170 @param rate: The sampling rate.
171 @param device: The device to play the audio on.
Chinyue Chend0b99322014-07-18 15:57:30 +0800172 @raise RuntimeError: If no playback device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800173 '''
174 args = [APLAY_PATH]
175 if duration is not None:
176 args += ['-d', str(duration)]
177 args += _get_format_args(channels, bits, rate)
178 if device is None:
Chinyue Chend0b99322014-07-18 15:57:30 +0800179 device = get_default_playback_device()
180 if device is None:
181 raise RuntimeError('no playback device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800182 args += ['-D', device]
183 args += [input]
184 return args
185
186
Chinyue Chend0b99322014-07-18 15:57:30 +0800187def record(*args, **kwargs):
188 '''A helper function to execute record_cmd.
189
190 @param kwargs: kwargs passed to record_cmd.
191 '''
192 cmd_utils.execute(record_cmd(*args, **kwargs))
Owen Lin21d49eb2013-12-03 10:47:58 +0800193
194
195def record_cmd(
Owen Lin2013e462013-12-05 17:54:42 +0800196 output, duration=None, channels=1, bits=16, rate=48000, device=None):
Owen Lin21d49eb2013-12-03 10:47:58 +0800197 '''Records the audio to the specified output by ALSA utility: 'arecord'.
198
199 @param output: The filename where the recorded audio will be stored to.
200 @param duration: The length of the recording (in seconds).
201 @param channels: The number of channels of the recorded audio.
202 @param bits: The number of bits of each audio sample.
203 @param rate: The sampling rate.
204 @param device: The device used to recorded the audio from.
Chinyue Chend0b99322014-07-18 15:57:30 +0800205 @raise RuntimeError: If no record device is available.
Owen Lin21d49eb2013-12-03 10:47:58 +0800206 '''
207 args = [ARECORD_PATH]
208 if duration is not None:
209 args += ['-d', str(duration)]
210 args += _get_format_args(channels, bits, rate)
211 if device is None:
Chinyue Chend0b99322014-07-18 15:57:30 +0800212 device = get_default_record_device()
213 if device is None:
214 raise RuntimeError('no record device')
Owen Lin21d49eb2013-12-03 10:47:58 +0800215 args += ['-D', device]
216 args += [output]
217 return args