blob: f51b2deb306a293f9684f185ff52aa2a2750a6e8 [file] [log] [blame]
Owen Linca365f82013-11-08 16:52:28 +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
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +08005"""This module provides cras audio utilities."""
6
Owen Linca365f82013-11-08 16:52:28 +08007import logging
8import re
9
Owen Lin9d19b272013-11-28 12:13:24 +080010from autotest_lib.client.cros.audio import cmd_utils
Owen Linca365f82013-11-08 16:52:28 +080011
12_CRAS_TEST_CLIENT = '/usr/bin/cras_test_client'
13_RE_SELECTED_OUTPUT_NODE = re.compile('Selected Output Node: (.*)')
14_RE_SELECTED_INPUT_NODE = re.compile('Selected Input Node: (.*)')
Owen Lin7ab45a22013-11-19 17:26:33 +080015_RE_NUM_ACTIVE_STREAM = re.compile('Num active streams: (.*)')
Owen Linca365f82013-11-08 16:52:28 +080016
17def playback(*args, **kargs):
18 """A helper function to execute the playback_cmd."""
Owen Lin9d19b272013-11-28 12:13:24 +080019 cmd_utils.execute(playback_cmd(*args, **kargs))
Owen Linca365f82013-11-08 16:52:28 +080020
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080021
Owen Linca365f82013-11-08 16:52:28 +080022def capture(*args, **kargs):
23 """A helper function to execute the capture_cmd."""
Owen Lin9d19b272013-11-28 12:13:24 +080024 cmd_utils.execute(capture_cmd(*args, **kargs))
Owen Linca365f82013-11-08 16:52:28 +080025
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080026
Owen Lin2f7e7f42013-12-25 17:25:43 +080027def playback_cmd(playback_file, block_size=None, duration=None,
Owen Lin2013e462013-12-05 17:54:42 +080028 channels=2, rate=48000):
Owen Linca365f82013-11-08 16:52:28 +080029 """Gets a command to playback a file with given settings.
30
31 @param playback_file: the name of the file to play. '-' indicates to
32 playback raw audio from the stdin.
Owen Lin2f7e7f42013-12-25 17:25:43 +080033 @param block_size: the number of frames per callback(dictates latency).
Owen Linca365f82013-11-08 16:52:28 +080034 @param duration: seconds to playback
35 @param rate: the sampling rate
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080036
Owen Linca365f82013-11-08 16:52:28 +080037 """
38 args = [_CRAS_TEST_CLIENT]
39 args += ['--playback_file', playback_file]
Owen Lin2f7e7f42013-12-25 17:25:43 +080040 if block_size is not None:
41 args += ['--block_size', str(block_size)]
Owen Linca365f82013-11-08 16:52:28 +080042 if duration is not None:
43 args += ['--duration', str(duration)]
Owen Lin2013e462013-12-05 17:54:42 +080044 args += ['--num_channels', str(channels)]
Owen Linca365f82013-11-08 16:52:28 +080045 args += ['--rate', str(rate)]
46 return args
47
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080048
Owen Lina8129c62013-11-26 16:51:46 +080049def capture_cmd(
Owen Lin2f7e7f42013-12-25 17:25:43 +080050 capture_file, block_size=None, duration=10, channels=1, rate=48000):
Owen Linca365f82013-11-08 16:52:28 +080051 """Gets a command to capture the audio into the file with given settings.
52
53 @param capture_file: the name of file the audio to be stored in.
Owen Lin2f7e7f42013-12-25 17:25:43 +080054 @param block_size: the number of frames per callback(dictates latency).
Cheng-Yi Chiangeaf53292015-01-12 20:53:56 +080055 @param duration: seconds to record. If it is None, duration is not set,
56 and command will keep capturing audio until it is
57 terminated.
Owen Linca365f82013-11-08 16:52:28 +080058 @param rate: the sampling rate.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080059
Owen Linca365f82013-11-08 16:52:28 +080060 """
61 args = [_CRAS_TEST_CLIENT]
62 args += ['--capture_file', capture_file]
Owen Lin2f7e7f42013-12-25 17:25:43 +080063 if block_size is not None:
64 args += ['--block_size', str(block_size)]
Cheng-Yi Chiangeaf53292015-01-12 20:53:56 +080065 if duration is not None:
66 args += ['--duration', str(duration)]
Owen Lina8129c62013-11-26 16:51:46 +080067 args += ['--num_channels', str(channels)]
Owen Linca365f82013-11-08 16:52:28 +080068 args += ['--rate', str(rate)]
69 return args
70
Owen Lindae7a0d2013-12-05 13:34:06 +080071
72def loopback(*args, **kargs):
73 """A helper function to execute loopback_cmd."""
74 cmd_utils.execute(loopback_cmd(*args, **kargs))
75
76
Owen Lin2013e462013-12-05 17:54:42 +080077def loopback_cmd(output_file, duration=10, channels=2, rate=48000):
Owen Lindae7a0d2013-12-05 13:34:06 +080078 """Gets a command to record the loopback.
79
80 @param output_file: The name of the file the loopback to be stored in.
81 @param channels: The number of channels of the recorded audio.
82 @param duration: seconds to record.
83 @param rate: the sampling rate.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080084
Owen Lindae7a0d2013-12-05 13:34:06 +080085 """
86 args = [_CRAS_TEST_CLIENT]
87 args += ['--loopback_file', output_file]
88 args += ['--duration_seconds', str(duration)]
89 args += ['--num_channels', str(channels)]
90 args += ['--rate', str(rate)]
91 return args
92
93
Owen Linca365f82013-11-08 16:52:28 +080094def set_system_volume(volume):
95 """Set the system volume.
96
97 @param volume: the system output vlume to be set(0 - 100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080098
Owen Linca365f82013-11-08 16:52:28 +080099 """
100 args = [_CRAS_TEST_CLIENT]
101 args += ['--volume', str(volume)]
Owen Lin9d19b272013-11-28 12:13:24 +0800102 cmd_utils.execute(args)
Owen Linca365f82013-11-08 16:52:28 +0800103
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800104
Owen Linca365f82013-11-08 16:52:28 +0800105def set_node_volume(node_id, volume):
106 """Set the volume of the given output node.
107
108 @param node_id: the id of the output node to be set the volume.
109 @param volume: the volume to be set(0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800110
Owen Linca365f82013-11-08 16:52:28 +0800111 """
112 args = [_CRAS_TEST_CLIENT]
113 args += ['--set_node_volume', '%s:%d' % (node_id, volume)]
Owen Lin9d19b272013-11-28 12:13:24 +0800114 cmd_utils.execute(args)
Owen Linca365f82013-11-08 16:52:28 +0800115
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800116
Owen Linca365f82013-11-08 16:52:28 +0800117def set_capture_gain(gain):
118 """Set the system capture gain.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800119
Owen Linca365f82013-11-08 16:52:28 +0800120 @param gain the capture gain in db*100 (100 = 1dB)
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800121
Owen Linca365f82013-11-08 16:52:28 +0800122 """
123 args = [_CRAS_TEST_CLIENT]
124 args += ['--capture_gain', str(gain)]
Owen Lin9d19b272013-11-28 12:13:24 +0800125 cmd_utils.execute(args)
Owen Linca365f82013-11-08 16:52:28 +0800126
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800127
Owen Linca365f82013-11-08 16:52:28 +0800128def dump_server_info():
129 """Gets the CRAS's server information."""
Owen Lin0d65e8a2013-11-28 14:29:54 +0800130 args = [_CRAS_TEST_CLIENT, '--dump_server_info']
Owen Linad6610a2013-12-13 11:20:48 +0800131 return cmd_utils.execute(args, stdout=cmd_utils.PIPE)
Owen Linca365f82013-11-08 16:52:28 +0800132
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800133
Owen Linca365f82013-11-08 16:52:28 +0800134def get_selected_nodes():
135 """Returns the pair of active output node and input node."""
136 server_info = dump_server_info()
137 output_match = _RE_SELECTED_OUTPUT_NODE.search(server_info)
138 input_match = _RE_SELECTED_INPUT_NODE.search(server_info)
139 if not output_match or not input_match:
140 logging.error(server_info)
141 raise RuntimeError('No match for the pattern')
142
143 return (output_match.group(1).strip(), input_match.group(1).strip())
Owen Lin7ab45a22013-11-19 17:26:33 +0800144
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800145
146def set_selected_output_node_volume(volume):
147 """Sets the selected output node volume.
148
149 @param volume: the volume to be set (0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800150
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800151 """
152 selected_output_node_id, _ = get_selected_nodes()
153 set_node_volume(selected_output_node_id, volume)
154
155
Owen Lin7ab45a22013-11-19 17:26:33 +0800156def get_active_stream_count():
157 """Gets the number of active streams."""
158 server_info = dump_server_info()
159 match = _RE_NUM_ACTIVE_STREAM.search(server_info)
160 if not match:
161 logging.error(server_info)
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800162 raise RuntimeError('Cannot find matched pattern')
Owen Lin7ab45a22013-11-19 17:26:33 +0800163 return int(match.group(1))
Owen Lin56050862013-12-09 11:42:51 +0800164
165
166def set_system_mute(is_mute):
167 """Sets the system mute switch.
168
169 @param is_mute: Set True to mute the system playback.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800170
Owen Lin56050862013-12-09 11:42:51 +0800171 """
172 args = [_CRAS_TEST_CLIENT, '--mute', '1' if is_mute else '0']
173 cmd_utils.execute(args)
174
175
176def set_capture_mute(is_mute):
177 """Sets the capture mute switch.
178
179 @param is_mute: Set True to mute the capture.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800180
Owen Lin56050862013-12-09 11:42:51 +0800181 """
182 args = [_CRAS_TEST_CLIENT, '--capture_mute', '1' if is_mute else '0']
183 cmd_utils.execute(args)
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800184
185
186def node_type_is_plugged(node_type, server_info=None):
187 """Determine if there is any node of node_type plugged.
188
189 Parses server info to get the plugged state of certain node type.
190 The server info of interest is in this format:
191
192 ...(other info)...
193
194 ID Vol Plugged L/R swapped Time Type Name
195 3:0 75 yes no 1419323058 HEADPHONE *Headphone
196 4:0 0 yes no 1419323059 MIC *Mic Jack
197
198 ...(other info)...
199
200
201 @param node_type: A str representing node type. e.g. 'HEADPHONE' or
202 'MIC'.
203 @param server_info: A str containing server info. None to call
204 dump_server_info in this function.
205
206 @returns: True if there is any node of node_type plugged. False otherwise.
207
208 @raises: ValueError: if cras server info format is not as expected.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800209
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800210 """
211 # The label line
212 # ID Vol Plugged L/R swapped Time Type Name
213 _MIN_LEN_LABELS = 8
214 _INDEX_LABEL_PLUGGED = 2
215 _INDEX_LABEL_TYPE = 6
216
217 # The value line
218 # 3:0 75 yes no 1419323058 HEADPHONE *Headphone
219 _MIN_LEN_VALUES = 7
220 _INDEX_VALUE_PLUGGED = 2
221 _INDEX_VALUE_TYPE = 5
222
223 if not server_info:
224 server_info = dump_server_info()
225 state = False
226 for line in server_info.splitlines():
227 line_split = line.split()
228 # Checks if a label line follows format.
229 if 'Plugged' in line_split and 'Type' in line_split:
230 if (len(line_split) < _MIN_LEN_LABELS or
231 line_split[_INDEX_LABEL_PLUGGED] != 'Plugged' or
232 line_split[_INDEX_LABEL_TYPE] != 'Type'):
233 raise ValueError('cras server info format is not as '
234 'expected')
235 if len(line_split) < _MIN_LEN_VALUES:
236 continue
237 # Checks a value line of interest.
238 # There might be other nodes of node_type, so keep searching if
239 # this node is not plugged.
240 if (line_split[_INDEX_VALUE_TYPE] == node_type and
241 line_split[_INDEX_VALUE_PLUGGED] == 'yes'):
242 state = True
243 return state
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800244
245
246# Cras node types reported from cras_test_client --dump_server_info.
247CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
248 'BLUETOOTH']
249CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH']
250CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
251
252
253def get_node_type(node):
254 """Gets node type by node id.
255
256 @param node: A string for node id, e.g. 4:1.
257
258 @returns: The node type reported by cras. The types are:
259
260
261 @raises: RuntimeError if node type is invalid or can not be determined.
262
263 """
264 # From server info, find a line starting with node id and get its
265 # node type. E.g.:
266 # 3:0 75 yes no 1419323058 HEADPHONE *Headphone
267 _MIN_LENGTH = 7
268 _INDEX_NODE_ID = 0
269 _INDEX_NODE_TYPE = 5
270 server_info = dump_server_info()
271 for line in server_info.splitlines():
272 # '*' is the mark that a node is selected, replace it with ' ' so it
273 # will not break field spliting.
274 line_split = line.replace('*', ' ').split()
275 if len(line_split) < _MIN_LENGTH:
276 continue
277 if line_split[_INDEX_NODE_ID] != node:
278 continue
279 node_type = line_split[_INDEX_NODE_TYPE]
280 if node_type not in CRAS_NODE_TYPES:
281 raise RuntimeError('Node type %s is invalid' % node_type)
282 return node_type
283 raise RuntimeError('Can not find node type for node %s' % node)