• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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