blob: e9e6e5a60bab7fade938e629944b252478eb8a8a [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',
Cheng-Yi Chiang4d022122015-09-20 22:53:24 -0700272 'BLUETOOTH', 'UNKNOWN']
273CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH',
Cheng-Yi Chiangd2fe0b42015-09-23 11:47:58 -0700274 'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN',
275 'KEYBOARD_MIC', 'AOKR']
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800276CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
277
278
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700279def get_filtered_node_types(callback):
280 """Returns the pair of filtered output node types and input node types.
281
282 @param callback: A callback function which takes a node as input parameter
283 and filter the node based on its return value.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800284
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800285 @returns: A tuple (output_node_types, input_node_types) where each
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700286 field is a list of node types defined in CRAS_NODE_TYPES,
287 and their 'attribute_name' is True.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800288
289 """
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800290 output_node_types = []
291 input_node_types = []
292 nodes = get_cras_nodes()
293 for node in nodes:
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700294 if callback(node):
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800295 node_type = str(node['Type'])
296 if node_type not in CRAS_NODE_TYPES:
297 raise RuntimeError(
298 'node type %s is not valid' % node_type)
299 if node['IsInput']:
300 input_node_types.append(node_type)
301 else:
302 output_node_types.append(node_type)
303 return (output_node_types, input_node_types)
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800304
305
Cheng-Yi Chiang94edf0d2015-09-20 22:51:34 -0700306def get_selected_node_types():
307 """Returns the pair of active output node types and input node types.
308
309 @returns: A tuple (output_node_types, input_node_types) where each
310 field is a list of selected node types defined in CRAS_NODE_TYPES.
311
312 """
313 def is_selected(node):
314 """Checks if a node is selected.
315
316 A node is selected if its Active attribute is True.
317
318 @returns: True is a node is selected, False otherwise.
319
320 """
321 return node['Active']
322
323 return get_filtered_node_types(is_selected)
324
325
326def get_plugged_node_types():
327 """Returns the pair of plugged output node types and input node types.
328
329 @returns: A tuple (output_node_types, input_node_types) where each
330 field is a list of plugged node types defined in CRAS_NODE_TYPES.
331
332 """
333 def is_plugged(node):
334 """Checks if a node is plugged and is not unknown node.
335
336 Cras DBus API only reports plugged node, so every node reported by Cras
337 DBus API is plugged. However, we filter out UNKNOWN node here because
338 the existence of unknown node depends on the number of redundant
339 playback/record audio device created on audio card. Also, the user of
340 Cras will ignore unknown nodes.
341
342 @returns: True if a node is plugged and is not an UNKNOWN node.
343
344 """
345 return node['Type'] != 'UNKNOWN'
346
347 return get_filtered_node_types(is_plugged)
348
349
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800350def set_selected_node_types(output_node_types, input_node_types):
351 """Sets selected node types.
352
353 @param output_node_types: A list of output node types. None to skip setting.
354 @param input_node_types: A list of input node types. None to skip setting.
355
356 """
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700357 if len(output_node_types) == 1:
358 set_single_selected_output_node(output_node_types[0])
359 elif output_node_types:
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800360 set_selected_output_nodes(output_node_types)
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700361 if len(input_node_types) == 1:
362 set_single_selected_input_node(input_node_types[0])
363 elif input_node_types:
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800364 set_selected_input_nodes(input_node_types)
365
366
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700367def set_single_selected_output_node(node_type):
368 """Sets one selected output node.
369
370 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
371 to select one output node.
372
373 @param node_type: A node type.
374
375 """
376 nodes = get_cras_nodes()
377 for node in nodes:
378 if node['IsInput']:
379 continue
380 if node['Type'] == node_type:
381 set_active_output_node(node['Id'])
382
383
384def set_single_selected_input_node(node_type):
385 """Sets one selected input node.
386
387 Note that Chrome UI uses SetActiveInputNode of Cras DBus API
388 to select one input node.
389
390 @param node_type: A node type.
391
392 """
393 nodes = get_cras_nodes()
394 for node in nodes:
395 if not node['IsInput']:
396 continue
397 if node['Type'] == node_type:
398 set_active_input_node(node['Id'])
399
400
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800401def set_selected_output_nodes(types):
402 """Sets selected output node types.
403
404 Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
405 to select one output node. Here we use add/remove active output node
406 to support multiple nodes.
407
408 @param types: A list of output node types.
409
410 """
411 nodes = get_cras_nodes()
412 for node in nodes:
413 if node['IsInput']:
414 continue
415 if node['Type'] in types:
416 add_active_output_node(node['Id'])
417 elif node['Active']:
418 remove_active_output_node(node['Id'])
419
420
421def set_selected_input_nodes(types):
422 """Sets selected input node types.
423
424 Note that Chrome UI uses SetActiveInputNode of Cras DBus API
425 to select one input node. Here we use add/remove active input node
426 to support multiple nodes.
427
428 @param types: A list of input node types.
429
430 """
431 nodes = get_cras_nodes()
432 for node in nodes:
433 if not node['IsInput']:
434 continue
435 if node['Type'] in types:
436 add_active_input_node(node['Id'])
437 elif node['Active']:
438 remove_active_input_node(node['Id'])
439
440
Cheng-Yi Chiang271b5cb2015-09-22 22:54:41 -0700441def set_active_input_node(node_id):
442 """Sets one active input node.
443
444 @param node_id: node id.
445
446 """
447 get_cras_control_interface().SetActiveInputNode(node_id)
448
449
450def set_active_output_node(node_id):
451 """Sets one active output node.
452
453 @param node_id: node id.
454
455 """
456 get_cras_control_interface().SetActiveOutputNode(node_id)
457
458
Cheng-Yi Chianga22adaf2015-07-07 12:02:12 +0800459def add_active_output_node(node_id):
460 """Adds an active output node.
461
462 @param node_id: node id.
463
464 """
465 get_cras_control_interface().AddActiveOutputNode(node_id)
466
467
468def add_active_input_node(node_id):
469 """Adds an active input node.
470
471 @param node_id: node id.
472
473 """
474 get_cras_control_interface().AddActiveInputNode(node_id)
475
476
477def remove_active_output_node(node_id):
478 """Removes an active output node.
479
480 @param node_id: node id.
481
482 """
483 get_cras_control_interface().RemoveActiveOutputNode(node_id)
484
485
486def remove_active_input_node(node_id):
487 """Removes an active input node.
488
489 @param node_id: node id.
490
491 """
492 get_cras_control_interface().RemoveActiveInputNode(node_id)
Cheng-Yi Chiangda8e4e62015-10-08 13:42:21 +0800493
494
495def _set_default_main_loop():
496 """Sets the gobject main loop to be the event loop for DBus.
497
498 @raises: ImportError if dbus.mainloop.glib can not be imported.
499
500 """
501 try:
502 import dbus.mainloop.glib
503 except ImportError, e:
504 logging.exception(
505 'Can not import dbus.mainloop.glib: %s. '
506 'This method should only be called on Cros device.', e)
507 raise
508 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
509
510
511def _get_gobject():
512 """Tries to import gobject.
513
514 @returns: The imported gobject module.
515
516 @raises: ImportError if gobject can not be imported.
517
518 """
519 try:
520 import gobject
521 except ImportError, e:
522 logging.exception(
523 'Can not import gobject: %s. This method should only be '
524 'called on Cros device.', e)
525 raise
526 return gobject
527
528
529class CrasDBusMonitorError(Exception):
530 """Error in CrasDBusMonitor."""
531 pass
532
533
534class CrasDBusMonitor(object):
535 """Monitor for DBus signal from Cras."""
536 def __init__(self):
537 _set_default_main_loop()
538 self._iface = get_cras_control_interface()
539 self._loop = _get_gobject().MainLoop()
540 self._count = 0
541 self._target_signal_count = 0
542
543
544 def wait_for_nodes_changed(self, target_signal_count, timeout_secs):
545 """Waits for NodesChanged signal.
546
547 @param target_signal_count: The expected number of signal.
548 @param timeout_secs: The timeout in seconds.
549
550 @raises: CrasDBusMonitorError if there is no enough signals before
551 timeout.
552
553 """
554 self._target_signal_count = target_signal_count
555 signal_match = self._iface.connect_to_signal(
556 'NodesChanged', self._nodes_changed_handler)
557 _get_gobject().timeout_add(
558 timeout_secs * 1000, self._timeout_quit_main_loop)
559
560 # Blocks here until _nodes_changed_handler or _timeout_quit_main_loop
561 # quits the loop.
562 self._loop.run()
563
564 signal_match.remove()
565 if self._count < self._target_signal_count:
566 raise CrasDBusMonitorError('Timeout')
567
568
569 def _nodes_changed_handler(self):
570 """Handler for NodesChanged signal."""
571 if self._loop.is_running():
572 logging.debug('Got NodesChanged signal when loop is running.')
573 self._count = self._count + 1
574 logging.debug('count = %d', self._count)
575 if self._count >= self._target_signal_count:
576 logging.debug('Quit main loop')
577 self._loop.quit()
578 else:
579 logging.debug('Got NodesChanged signal when loop is not running.'
580 ' Ignore it')
581
582
583 def _timeout_quit_main_loop(self):
584 """Handler for timeout in main loop.
585
586 @returns: False so this callback will not be called again.
587
588 """
589 if self._loop.is_running():
590 logging.error('Quit main loop because of timeout')
591 self._loop.quit()
592 else:
593 logging.debug(
594 'Got _quit_main_loop after main loop quits. Ignore it')
595
596 return False