blob: b16ffdac5f8a823f841a8f52268ca80b7817d537 [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
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +080094def get_cras_nodes_cmd():
95 """Gets a command to query the nodes from Cras.
96
97 @returns: The command to query nodes information from Cras using dbus-send.
98
99 """
100 return ('dbus-send --system --type=method_call --print-reply '
101 '--dest=org.chromium.cras /org/chromium/cras '
102 'org.chromium.cras.Control.GetNodes')
103
104
Owen Linca365f82013-11-08 16:52:28 +0800105def set_system_volume(volume):
106 """Set the system volume.
107
108 @param volume: the system output vlume to be set(0 - 100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800109
Owen Linca365f82013-11-08 16:52:28 +0800110 """
111 args = [_CRAS_TEST_CLIENT]
112 args += ['--volume', str(volume)]
Owen Lin9d19b272013-11-28 12:13:24 +0800113 cmd_utils.execute(args)
Owen Linca365f82013-11-08 16:52:28 +0800114
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800115
Owen Linca365f82013-11-08 16:52:28 +0800116def set_node_volume(node_id, volume):
117 """Set the volume of the given output node.
118
119 @param node_id: the id of the output node to be set the volume.
120 @param volume: the volume to be set(0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800121
Owen Linca365f82013-11-08 16:52:28 +0800122 """
123 args = [_CRAS_TEST_CLIENT]
124 args += ['--set_node_volume', '%s:%d' % (node_id, volume)]
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 set_capture_gain(gain):
129 """Set the system capture gain.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800130
Owen Linca365f82013-11-08 16:52:28 +0800131 @param gain the capture gain in db*100 (100 = 1dB)
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800132
Owen Linca365f82013-11-08 16:52:28 +0800133 """
134 args = [_CRAS_TEST_CLIENT]
135 args += ['--capture_gain', str(gain)]
Owen Lin9d19b272013-11-28 12:13:24 +0800136 cmd_utils.execute(args)
Owen Linca365f82013-11-08 16:52:28 +0800137
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800138
Owen Linca365f82013-11-08 16:52:28 +0800139def dump_server_info():
140 """Gets the CRAS's server information."""
Owen Lin0d65e8a2013-11-28 14:29:54 +0800141 args = [_CRAS_TEST_CLIENT, '--dump_server_info']
Owen Linad6610a2013-12-13 11:20:48 +0800142 return cmd_utils.execute(args, stdout=cmd_utils.PIPE)
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 get_selected_nodes():
146 """Returns the pair of active output node and input node."""
147 server_info = dump_server_info()
148 output_match = _RE_SELECTED_OUTPUT_NODE.search(server_info)
149 input_match = _RE_SELECTED_INPUT_NODE.search(server_info)
150 if not output_match or not input_match:
151 logging.error(server_info)
152 raise RuntimeError('No match for the pattern')
153
154 return (output_match.group(1).strip(), input_match.group(1).strip())
Owen Lin7ab45a22013-11-19 17:26:33 +0800155
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800156
157def set_selected_output_node_volume(volume):
158 """Sets the selected output node volume.
159
160 @param volume: the volume to be set (0-100).
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800161
Cheng-Yi Chiangc902f2e2015-03-09 10:45:44 +0800162 """
163 selected_output_node_id, _ = get_selected_nodes()
164 set_node_volume(selected_output_node_id, volume)
165
166
Owen Lin7ab45a22013-11-19 17:26:33 +0800167def get_active_stream_count():
168 """Gets the number of active streams."""
169 server_info = dump_server_info()
170 match = _RE_NUM_ACTIVE_STREAM.search(server_info)
171 if not match:
172 logging.error(server_info)
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800173 raise RuntimeError('Cannot find matched pattern')
Owen Lin7ab45a22013-11-19 17:26:33 +0800174 return int(match.group(1))
Owen Lin56050862013-12-09 11:42:51 +0800175
176
177def set_system_mute(is_mute):
178 """Sets the system mute switch.
179
180 @param is_mute: Set True to mute the system playback.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800181
Owen Lin56050862013-12-09 11:42:51 +0800182 """
183 args = [_CRAS_TEST_CLIENT, '--mute', '1' if is_mute else '0']
184 cmd_utils.execute(args)
185
186
187def set_capture_mute(is_mute):
188 """Sets the capture mute switch.
189
190 @param is_mute: Set True to mute the capture.
Cheng-Yi Chiangb0ec9042015-03-10 15:45:18 +0800191
Owen Lin56050862013-12-09 11:42:51 +0800192 """
193 args = [_CRAS_TEST_CLIENT, '--capture_mute', '1' if is_mute else '0']
194 cmd_utils.execute(args)
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800195
196
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800197def node_type_is_plugged(node_type, nodes_info):
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800198 """Determine if there is any node of node_type plugged.
199
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800200 This method is used in has_loopback_dongle in cros_host, where
201 the call is executed on autotest server. Use get_cras_nodes instead if
202 the call can be executed on Cros device.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800203
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800204 Since Cras only reports the plugged node in GetNodes, we can
205 parse the return value to see if there is any node with the given type.
206 For example, if INTERNAL_MIC is of intereset, the pattern we are
207 looking for is:
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800208
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800209 dict entry(
210 string "Type"
211 variant string "INTERNAL_MIC"
212 )
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800213
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800214 @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
215 @param nodes_info: A str containing output of command get_nodes_cmd.
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800216
217 @returns: True if there is any node of node_type plugged. False otherwise.
218
Cheng-Yi Chiangf4104ff2014-12-23 19:39:01 +0800219 """
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800220 match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
221 nodes_info)
222 return True if match else False
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800223
224
Cheng-Yi Chiang8de78112015-05-27 14:47:08 +0800225# Cras node types reported from Cras DBus control API.
Cheng-Yi Chiangea5a71f2015-03-19 21:01:24 +0800226CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
227 'BLUETOOTH']
228CRAS_INPUT_NODE_TYPES = ['MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH']
229CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
230
231
232def get_node_type(node):
233 """Gets node type by node id.
234
235 @param node: A string for node id, e.g. 4:1.
236
237 @returns: The node type reported by cras. The types are:
238
239
240 @raises: RuntimeError if node type is invalid or can not be determined.
241
242 """
243 # From server info, find a line starting with node id and get its
244 # node type. E.g.:
245 # 3:0 75 yes no 1419323058 HEADPHONE *Headphone
246 _MIN_LENGTH = 7
247 _INDEX_NODE_ID = 0
248 _INDEX_NODE_TYPE = 5
249 server_info = dump_server_info()
250 for line in server_info.splitlines():
251 # '*' is the mark that a node is selected, replace it with ' ' so it
252 # will not break field spliting.
253 line_split = line.replace('*', ' ').split()
254 if len(line_split) < _MIN_LENGTH:
255 continue
256 if line_split[_INDEX_NODE_ID] != node:
257 continue
258 node_type = line_split[_INDEX_NODE_TYPE]
259 if node_type not in CRAS_NODE_TYPES:
260 raise RuntimeError('Node type %s is invalid' % node_type)
261 return node_type
262 raise RuntimeError('Can not find node type for node %s' % node)