blob: 57fe66b9d7bf5c03cd412982134c675c39801236 [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'
Owen Linca365f82013-11-08 16:52:28 +080013
14def playback(*args, **kargs):
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080015 """A helper function to execute the playback_cmd.
16
17 @param args: args passed to playback_cmd.
18 @param kargs: kargs passed to playback_cmd.
19
20 """
Owen Lin9d19b272013-11-28 12:13:24 +080021 cmd_utils.execute(playback_cmd(*args, **kargs))
Owen Linca365f82013-11-08 16:52:28 +080022
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080023
Owen Linca365f82013-11-08 16:52:28 +080024def capture(*args, **kargs):
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080025 """A helper function to execute the capture_cmd.
26
27 @param args: args passed to capture_cmd.
28 @param kargs: kargs passed to capture_cmd.
29
30 """
Owen Lin9d19b272013-11-28 12:13:24 +080031 cmd_utils.execute(capture_cmd(*args, **kargs))
Owen Linca365f82013-11-08 16:52:28 +080032
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080033
Owen Lin2f7e7f42013-12-25 17:25:43 +080034def playback_cmd(playback_file, block_size=None, duration=None,
Owen Lin2013e462013-12-05 17:54:42 +080035 channels=2, rate=48000):
Owen Linca365f82013-11-08 16:52:28 +080036 """Gets a command to playback a file with given settings.
37
38 @param playback_file: the name of the file to play. '-' indicates to
39 playback raw audio from the stdin.
Owen Lin2f7e7f42013-12-25 17:25:43 +080040 @param block_size: the number of frames per callback(dictates latency).
Owen Linca365f82013-11-08 16:52:28 +080041 @param duration: seconds to playback
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080042 @param channels: number of channels.
Owen Linca365f82013-11-08 16:52:28 +080043 @param rate: the sampling rate
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080044
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080045 @returns: The command args put in a list of strings.
46
Owen Linca365f82013-11-08 16:52:28 +080047 """
48 args = [_CRAS_TEST_CLIENT]
49 args += ['--playback_file', playback_file]
Owen Lin2f7e7f42013-12-25 17:25:43 +080050 if block_size is not None:
51 args += ['--block_size', str(block_size)]
Owen Linca365f82013-11-08 16:52:28 +080052 if duration is not None:
53 args += ['--duration', str(duration)]
Owen Lin2013e462013-12-05 17:54:42 +080054 args += ['--num_channels', str(channels)]
Owen Linca365f82013-11-08 16:52:28 +080055 args += ['--rate', str(rate)]
56 return args
57
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080058
Owen Lina8129c62013-11-26 16:51:46 +080059def capture_cmd(
Owen Lin2f7e7f42013-12-25 17:25:43 +080060 capture_file, block_size=None, duration=10, channels=1, rate=48000):
Owen Linca365f82013-11-08 16:52:28 +080061 """Gets a command to capture the audio into the file with given settings.
62
63 @param capture_file: the name of file the audio to be stored in.
Owen Lin2f7e7f42013-12-25 17:25:43 +080064 @param block_size: the number of frames per callback(dictates latency).
Cheng-Yi Chiangeaf53292015-01-12 20:53:56 +080065 @param duration: seconds to record. If it is None, duration is not set,
66 and command will keep capturing audio until it is
67 terminated.
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080068 @param channels: number of channels.
Owen Linca365f82013-11-08 16:52:28 +080069 @param rate: the sampling rate.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +080070
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080071 @returns: The command args put in a list of strings.
72
Owen Linca365f82013-11-08 16:52:28 +080073 """
74 args = [_CRAS_TEST_CLIENT]
75 args += ['--capture_file', capture_file]
Owen Lin2f7e7f42013-12-25 17:25:43 +080076 if block_size is not None:
77 args += ['--block_size', str(block_size)]
Cheng-Yi Chiangeaf53292015-01-12 20:53:56 +080078 if duration is not None:
79 args += ['--duration', str(duration)]
Owen Lina8129c62013-11-26 16:51:46 +080080 args += ['--num_channels', str(channels)]
Owen Linca365f82013-11-08 16:52:28 +080081 args += ['--rate', str(rate)]
82 return args
83
Owen Lindae7a0d2013-12-05 13:34:06 +080084
85def loopback(*args, **kargs):
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +080086 """A helper function to execute loopback_cmd.
87
88 @param args: args passed to loopback_cmd.
89 @param kargs: kargs passed to loopback_cmd.
90
91 """
92
Owen Lindae7a0d2013-12-05 13:34:06 +080093 cmd_utils.execute(loopback_cmd(*args, **kargs))
94
95
Owen Lin2013e462013-12-05 17:54:42 +080096def loopback_cmd(output_file, duration=10, channels=2, rate=48000):
Owen Lindae7a0d2013-12-05 13:34:06 +080097 """Gets a command to record the loopback.
98
99 @param output_file: The name of the file the loopback to be stored in.
100 @param channels: The number of channels of the recorded audio.
101 @param duration: seconds to record.
102 @param rate: the sampling rate.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800103
Cheng-Yi Chiangaaa0a5e2015-05-27 12:09:17 +0800104 @returns: The command args put in a list of strings.
105
Owen Lindae7a0d2013-12-05 13:34:06 +0800106 """
107 args = [_CRAS_TEST_CLIENT]
108 args += ['--loopback_file', output_file]
109 args += ['--duration_seconds', str(duration)]
110 args += ['--num_channels', str(channels)]
111 args += ['--rate', str(rate)]
112 return args
113
114
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800115def get_cras_nodes_cmd():
116 """Gets a command to query the nodes from Cras.
117
118 @returns: The command to query nodes information from Cras using dbus-send.
119
120 """
121 return ('dbus-send --system --type=method_call --print-reply '
122 '--dest=org.chromium.cras /org/chromium/cras '
123 'org.chromium.cras.Control.GetNodes')
124
125
Owen Linca365f82013-11-08 16:52:28 +0800126def set_system_volume(volume):
127 """Set the system volume.
128
129 @param volume: the system output vlume to be set(0 - 100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800130
Owen Linca365f82013-11-08 16:52:28 +0800131 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800132 get_cras_control_interface().SetOutputVolume(volume)
Owen Linca365f82013-11-08 16:52:28 +0800133
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800134
Owen Linca365f82013-11-08 16:52:28 +0800135def set_node_volume(node_id, volume):
136 """Set the volume of the given output node.
137
138 @param node_id: the id of the output node to be set the volume.
139 @param volume: the volume to be set(0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800140
Owen Linca365f82013-11-08 16:52:28 +0800141 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800142 get_cras_control_interface().SetOutputNodeVolume(node_id, volume)
Owen Linca365f82013-11-08 16:52:28 +0800143
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800144
Owen Linca365f82013-11-08 16:52:28 +0800145def set_capture_gain(gain):
146 """Set the system capture gain.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800147
Owen Linca365f82013-11-08 16:52:28 +0800148 @param gain the capture gain in db*100 (100 = 1dB)
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800149
Owen Linca365f82013-11-08 16:52:28 +0800150 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800151 get_cras_control_interface().SetInputGain(gain)
Owen Linca365f82013-11-08 16:52:28 +0800152
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800153
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800154def get_cras_control_interface():
155 """Gets Cras DBus control interface.
156
157 @returns: A dBus.Interface object with Cras Control interface.
158
159 @raises: ImportError if this is not called on Cros device.
160
161 """
162 try:
163 import dbus
164 except ImportError, e:
165 logging.exception(
166 'Can not import dbus: %s. This method should only be '
167 'called on Cros device.', e)
168 raise
169 bus = dbus.SystemBus()
170 cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras')
171 return dbus.Interface(cras_object, 'org.chromium.cras.Control')
172
173
174def get_cras_nodes():
175 """Gets nodes information from Cras.
176
177 @returns: A dict containing information of each node.
178
179 """
180 return get_cras_control_interface().GetNodes()
Owen Linca365f82013-11-08 16:52:28 +0800181
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800182
Owen Linca365f82013-11-08 16:52:28 +0800183def get_selected_nodes():
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800184 """Gets selected output nodes and input nodes.
Owen Linca365f82013-11-08 16:52:28 +0800185
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800186 @returns: A tuple (output_nodes, input_nodes) where each
187 field is a list of selected node IDs returned from Cras DBus API.
188 Note that there may be multiple output/input nodes being selected
189 at the same time.
190
191 """
192 output_nodes = []
193 input_nodes = []
194 nodes = get_cras_nodes()
195 for node in nodes:
196 if node['Active']:
197 if node['IsInput']:
198 input_nodes.append(node['Id'])
199 else:
200 output_nodes.append(node['Id'])
201 return (output_nodes, input_nodes)
Owen Lin7ab45a22013-11-19 17:26:33 +0800202
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800203
204def set_selected_output_node_volume(volume):
205 """Sets the selected output node volume.
206
207 @param volume: the volume to be set (0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800208
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800209 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800210 selected_output_node_ids, _ = get_selected_nodes()
211 for node_id in selected_output_node_ids:
212 set_node_volume(node_id, volume)
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800213
214
Owen Lin7ab45a22013-11-19 17:26:33 +0800215def get_active_stream_count():
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800216 """Gets the number of active streams.
217
218 @returns: The number of active streams.
219
220 """
221 return int(get_cras_control_interface().GetNumberOfActiveStreams())
Owen Lin56050862013-12-09 11:42:51 +0800222
223
224def set_system_mute(is_mute):
225 """Sets the system mute switch.
226
227 @param is_mute: Set True to mute the system playback.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800228
Owen Lin56050862013-12-09 11:42:51 +0800229 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800230 get_cras_control_interface().SetOutputMute(is_mute)
Owen Lin56050862013-12-09 11:42:51 +0800231
232
233def set_capture_mute(is_mute):
234 """Sets the capture mute switch.
235
236 @param is_mute: Set True to mute the capture.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800237
Owen Lin56050862013-12-09 11:42:51 +0800238 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800239 get_cras_control_interface().SetInputMute(is_mute)
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800240
241
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800242def node_type_is_plugged(node_type, nodes_info):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800243 """Determine if there is any node of node_type plugged.
244
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800245 This method is used in has_loopback_dongle in cros_host, where
246 the call is executed on autotest server. Use get_cras_nodes instead if
247 the call can be executed on Cros device.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800248
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800249 Since Cras only reports the plugged node in GetNodes, we can
250 parse the return value to see if there is any node with the given type.
251 For example, if INTERNAL_MIC is of intereset, the pattern we are
252 looking for is:
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800253
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800254 dict entry(
255 string "Type"
256 variant string "INTERNAL_MIC"
257 )
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800258
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800259 @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
260 @param nodes_info: A str containing output of command get_nodes_cmd.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800261
262 @returns: True if there is any node of node_type plugged. False otherwise.
263
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800264 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800265 match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
266 nodes_info)
267 return True if match else False
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800268
269
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800270# Cras node types reported from Cras DBus control API.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800271CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
272 'BLUETOOTH']
273CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH']
274CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
275
276
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700277def get_filtered_node_types(callback):
278 """Returns the pair of filtered output node types and input node types.
279
280 @param callback: A callback function which takes a node as input parameter
281 and filter the node based on its return value.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800282
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800283 @returns: A tuple (output_node_types, input_node_types) where each
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700284 field is a list of node types defined in CRAS_NODE_TYPES,
285 and their 'attribute_name' is True.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800286
287 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800288 output_node_types = []
289 input_node_types = []
290 nodes = get_cras_nodes()
291 for node in nodes:
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700292 if callback(node):
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800293 node_type = str(node['Type'])
294 if node_type not in CRAS_NODE_TYPES:
295 raise RuntimeError(
296 'node type %s is not valid' % node_type)
297 if node['IsInput']:
298 input_node_types.append(node_type)
299 else:
300 output_node_types.append(node_type)
301 return (output_node_types, input_node_types)
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800302
303
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700304def get_selected_node_types():
305 """Returns the pair of active output node types and input node types.
306
307 @returns: A tuple (output_node_types, input_node_types) where each
308 field is a list of selected node types defined in CRAS_NODE_TYPES.
309
310 """
311 def is_selected(node):
312 """Checks if a node is selected.
313
314 A node is selected if its Active attribute is True.
315
316 @returns: True is a node is selected, False otherwise.
317
318 """
319 return node['Active']
320
321 return get_filtered_node_types(is_selected)
322
323
324def get_plugged_node_types():
325 """Returns the pair of plugged output node types and input node types.
326
327 @returns: A tuple (output_node_types, input_node_types) where each
328 field is a list of plugged node types defined in CRAS_NODE_TYPES.
329
330 """
331 def is_plugged(node):
332 """Checks if a node is plugged and is not unknown node.
333
334 Cras DBus API only reports plugged node, so every node reported by Cras
335 DBus API is plugged. However, we filter out UNKNOWN node here because
336 the existence of unknown node depends on the number of redundant
337 playback/record audio device created on audio card. Also, the user of
338 Cras will ignore unknown nodes.
339
340 @returns: True if a node is plugged and is not an UNKNOWN node.
341
342 """
343 return node['Type'] != 'UNKNOWN'
344
345 return get_filtered_node_types(is_plugged)
346
347
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800348def set_selected_node_types(output_node_types, input_node_types):
349 """Sets selected node types.
350
351 @param output_node_types: A list of output node types. None to skip setting.
352 @param input_node_types: A list of input node types. None to skip setting.
353
354 """
355 if output_node_types:
356 set_selected_output_nodes(output_node_types)
357 if input_node_types:
358 set_selected_input_nodes(input_node_types)
359
360
361def set_selected_output_nodes(types):
362 """Sets selected output node types.
363
364 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
365 to select one output node. Here we use add/remove active output node
366 to support multiple nodes.
367
368 @param types: A list of output node types.
369
370 """
371 nodes = get_cras_nodes()
372 for node in nodes:
373 if node['IsInput']:
374 continue
375 if node['Type'] in types:
376 add_active_output_node(node['Id'])
377 elif node['Active']:
378 remove_active_output_node(node['Id'])
379
380
381def set_selected_input_nodes(types):
382 """Sets selected input node types.
383
384 Note that Chrome UI uses SetActiveInputNode of Cras DBus API
385 to select one input node. Here we use add/remove active input node
386 to support multiple nodes.
387
388 @param types: A list of input node types.
389
390 """
391 nodes = get_cras_nodes()
392 for node in nodes:
393 if not node['IsInput']:
394 continue
395 if node['Type'] in types:
396 add_active_input_node(node['Id'])
397 elif node['Active']:
398 remove_active_input_node(node['Id'])
399
400
401def add_active_output_node(node_id):
402 """Adds an active output node.
403
404 @param node_id: node id.
405
406 """
407 get_cras_control_interface().AddActiveOutputNode(node_id)
408
409
410def add_active_input_node(node_id):
411 """Adds an active input node.
412
413 @param node_id: node id.
414
415 """
416 get_cras_control_interface().AddActiveInputNode(node_id)
417
418
419def remove_active_output_node(node_id):
420 """Removes an active output node.
421
422 @param node_id: node id.
423
424 """
425 get_cras_control_interface().RemoveActiveOutputNode(node_id)
426
427
428def remove_active_input_node(node_id):
429 """Removes an active input node.
430
431 @param node_id: node id.
432
433 """
434 get_cras_control_interface().RemoveActiveInputNode(node_id)