blob: 35fcbcac373319f5bc534b0b401b4b687e610ef9 [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 Chiang3dd376d2015-10-20 16:28:21 +0800154def get_cras_control_interface(private=False):
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800155 """Gets Cras DBus control interface.
156
Cheng-Yi Chiang3dd376d2015-10-20 16:28:21 +0800157 @param private: Set to True to use a new instance for dbus.SystemBus
158 instead of the shared instance.
159
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800160 @returns: A dBus.Interface object with Cras Control interface.
161
162 @raises: ImportError if this is not called on Cros device.
163
164 """
165 try:
166 import dbus
167 except ImportError, e:
168 logging.exception(
169 'Can not import dbus: %s. This method should only be '
170 'called on Cros device.', e)
171 raise
Cheng-Yi Chiang3dd376d2015-10-20 16:28:21 +0800172 bus = dbus.SystemBus(private=private)
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800173 cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras')
174 return dbus.Interface(cras_object, 'org.chromium.cras.Control')
175
176
177def get_cras_nodes():
178 """Gets nodes information from Cras.
179
180 @returns: A dict containing information of each node.
181
182 """
183 return get_cras_control_interface().GetNodes()
Owen Linca365f82013-11-08 16:52:28 +0800184
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800185
Owen Linca365f82013-11-08 16:52:28 +0800186def get_selected_nodes():
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800187 """Gets selected output nodes and input nodes.
Owen Linca365f82013-11-08 16:52:28 +0800188
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800189 @returns: A tuple (output_nodes, input_nodes) where each
190 field is a list of selected node IDs returned from Cras DBus API.
191 Note that there may be multiple output/input nodes being selected
192 at the same time.
193
194 """
195 output_nodes = []
196 input_nodes = []
197 nodes = get_cras_nodes()
198 for node in nodes:
199 if node['Active']:
200 if node['IsInput']:
201 input_nodes.append(node['Id'])
202 else:
203 output_nodes.append(node['Id'])
204 return (output_nodes, input_nodes)
Owen Lin7ab45a22013-11-19 17:26:33 +0800205
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800206
207def set_selected_output_node_volume(volume):
208 """Sets the selected output node volume.
209
210 @param volume: the volume to be set (0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800211
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800212 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800213 selected_output_node_ids, _ = get_selected_nodes()
214 for node_id in selected_output_node_ids:
215 set_node_volume(node_id, volume)
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800216
217
Owen Lin7ab45a22013-11-19 17:26:33 +0800218def get_active_stream_count():
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800219 """Gets the number of active streams.
220
221 @returns: The number of active streams.
222
223 """
224 return int(get_cras_control_interface().GetNumberOfActiveStreams())
Owen Lin56050862013-12-09 11:42:51 +0800225
226
227def set_system_mute(is_mute):
228 """Sets the system mute switch.
229
230 @param is_mute: Set True to mute the system playback.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800231
Owen Lin56050862013-12-09 11:42:51 +0800232 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800233 get_cras_control_interface().SetOutputMute(is_mute)
Owen Lin56050862013-12-09 11:42:51 +0800234
235
236def set_capture_mute(is_mute):
237 """Sets the capture mute switch.
238
239 @param is_mute: Set True to mute the capture.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800240
Owen Lin56050862013-12-09 11:42:51 +0800241 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800242 get_cras_control_interface().SetInputMute(is_mute)
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800243
244
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800245def node_type_is_plugged(node_type, nodes_info):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800246 """Determine if there is any node of node_type plugged.
247
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800248 This method is used in has_loopback_dongle in cros_host, where
249 the call is executed on autotest server. Use get_cras_nodes instead if
250 the call can be executed on Cros device.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800251
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800252 Since Cras only reports the plugged node in GetNodes, we can
253 parse the return value to see if there is any node with the given type.
254 For example, if INTERNAL_MIC is of intereset, the pattern we are
255 looking for is:
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800256
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800257 dict entry(
258 string "Type"
259 variant string "INTERNAL_MIC"
260 )
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800261
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800262 @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
263 @param nodes_info: A str containing output of command get_nodes_cmd.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800264
265 @returns: True if there is any node of node_type plugged. False otherwise.
266
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800267 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800268 match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
269 nodes_info)
270 return True if match else False
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800271
272
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800273# Cras node types reported from Cras DBus control API.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800274CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
Cheng-Yi Chiang4d022122015-09-20 22:53:24 -0700275 'BLUETOOTH', 'UNKNOWN']
276CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH',
Cheng-Yi Chiangd2fe0b42015-09-23 11:47:58 -0700277 'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN',
278 'KEYBOARD_MIC', 'AOKR']
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800279CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
280
281
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700282def get_filtered_node_types(callback):
283 """Returns the pair of filtered output node types and input node types.
284
285 @param callback: A callback function which takes a node as input parameter
286 and filter the node based on its return value.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800287
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800288 @returns: A tuple (output_node_types, input_node_types) where each
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700289 field is a list of node types defined in CRAS_NODE_TYPES,
290 and their 'attribute_name' is True.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800291
292 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800293 output_node_types = []
294 input_node_types = []
295 nodes = get_cras_nodes()
296 for node in nodes:
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700297 if callback(node):
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800298 node_type = str(node['Type'])
299 if node_type not in CRAS_NODE_TYPES:
300 raise RuntimeError(
301 'node type %s is not valid' % node_type)
302 if node['IsInput']:
303 input_node_types.append(node_type)
304 else:
305 output_node_types.append(node_type)
306 return (output_node_types, input_node_types)
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800307
308
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700309def get_selected_node_types():
310 """Returns the pair of active output node types and input node types.
311
312 @returns: A tuple (output_node_types, input_node_types) where each
313 field is a list of selected node types defined in CRAS_NODE_TYPES.
314
315 """
316 def is_selected(node):
317 """Checks if a node is selected.
318
319 A node is selected if its Active attribute is True.
320
321 @returns: True is a node is selected, False otherwise.
322
323 """
324 return node['Active']
325
326 return get_filtered_node_types(is_selected)
327
328
329def get_plugged_node_types():
330 """Returns the pair of plugged output node types and input node types.
331
332 @returns: A tuple (output_node_types, input_node_types) where each
333 field is a list of plugged node types defined in CRAS_NODE_TYPES.
334
335 """
336 def is_plugged(node):
337 """Checks if a node is plugged and is not unknown node.
338
339 Cras DBus API only reports plugged node, so every node reported by Cras
340 DBus API is plugged. However, we filter out UNKNOWN node here because
341 the existence of unknown node depends on the number of redundant
342 playback/record audio device created on audio card. Also, the user of
343 Cras will ignore unknown nodes.
344
345 @returns: True if a node is plugged and is not an UNKNOWN node.
346
347 """
348 return node['Type'] != 'UNKNOWN'
349
350 return get_filtered_node_types(is_plugged)
351
352
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800353def set_selected_node_types(output_node_types, input_node_types):
354 """Sets selected node types.
355
356 @param output_node_types: A list of output node types. None to skip setting.
357 @param input_node_types: A list of input node types. None to skip setting.
358
359 """
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700360 if len(output_node_types) == 1:
361 set_single_selected_output_node(output_node_types[0])
362 elif output_node_types:
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800363 set_selected_output_nodes(output_node_types)
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700364 if len(input_node_types) == 1:
365 set_single_selected_input_node(input_node_types[0])
366 elif input_node_types:
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800367 set_selected_input_nodes(input_node_types)
368
369
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700370def set_single_selected_output_node(node_type):
371 """Sets one selected output node.
372
373 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
374 to select one output node.
375
376 @param node_type: A node type.
377
378 """
379 nodes = get_cras_nodes()
380 for node in nodes:
381 if node['IsInput']:
382 continue
383 if node['Type'] == node_type:
384 set_active_output_node(node['Id'])
385
386
387def set_single_selected_input_node(node_type):
388 """Sets one selected input node.
389
390 Note that Chrome UI uses SetActiveInputNode of Cras DBus API
391 to select one input node.
392
393 @param node_type: A node type.
394
395 """
396 nodes = get_cras_nodes()
397 for node in nodes:
398 if not node['IsInput']:
399 continue
400 if node['Type'] == node_type:
401 set_active_input_node(node['Id'])
402
403
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800404def set_selected_output_nodes(types):
405 """Sets selected output node types.
406
407 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
408 to select one output node. Here we use add/remove active output node
409 to support multiple nodes.
410
411 @param types: A list of output node types.
412
413 """
414 nodes = get_cras_nodes()
415 for node in nodes:
416 if node['IsInput']:
417 continue
418 if node['Type'] in types:
419 add_active_output_node(node['Id'])
420 elif node['Active']:
421 remove_active_output_node(node['Id'])
422
423
424def set_selected_input_nodes(types):
425 """Sets selected input node types.
426
427 Note that Chrome UI uses SetActiveInputNode of Cras DBus API
428 to select one input node. Here we use add/remove active input node
429 to support multiple nodes.
430
431 @param types: A list of input node types.
432
433 """
434 nodes = get_cras_nodes()
435 for node in nodes:
436 if not node['IsInput']:
437 continue
438 if node['Type'] in types:
439 add_active_input_node(node['Id'])
440 elif node['Active']:
441 remove_active_input_node(node['Id'])
442
443
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700444def set_active_input_node(node_id):
445 """Sets one active input node.
446
447 @param node_id: node id.
448
449 """
450 get_cras_control_interface().SetActiveInputNode(node_id)
451
452
453def set_active_output_node(node_id):
454 """Sets one active output node.
455
456 @param node_id: node id.
457
458 """
459 get_cras_control_interface().SetActiveOutputNode(node_id)
460
461
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800462def add_active_output_node(node_id):
463 """Adds an active output node.
464
465 @param node_id: node id.
466
467 """
468 get_cras_control_interface().AddActiveOutputNode(node_id)
469
470
471def add_active_input_node(node_id):
472 """Adds an active input node.
473
474 @param node_id: node id.
475
476 """
477 get_cras_control_interface().AddActiveInputNode(node_id)
478
479
480def remove_active_output_node(node_id):
481 """Removes an active output node.
482
483 @param node_id: node id.
484
485 """
486 get_cras_control_interface().RemoveActiveOutputNode(node_id)
487
488
489def remove_active_input_node(node_id):
490 """Removes an active input node.
491
492 @param node_id: node id.
493
494 """
495 get_cras_control_interface().RemoveActiveInputNode(node_id)