1# Copyrigh The Chromium OS 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
5"""Get speaker/microphone status from cras_client_test, /proc/asound and
6   atrus.log.
7"""
8
9from __future__ import print_function
10
11import logging
12import re
13
14
15NUM_AUDIO_STREAM_IN_MEETING = 3
16
17def get_soundcard_by_name(dut, name):
18    """
19    Returns the soundcard number of specified soundcard by name.
20    @param dut: The handle of the device under test.
21    @param name: The name of Speaker
22                 For example: 'Hangouts Meet speakermic'
23    @returns the soundcard, if no device found returns None.
24    """
25    soundcard = None
26    cmd = "cat /proc/asound/cards | grep \"{}\" | grep USB".format(name)
27    logging.info('---cmd: %s', cmd)
28    try:
29        soundcard = dut.run(cmd, ignore_status=True).stdout.strip().split()[0]
30    except Exception as e:
31        soundcard = None
32        logging.exception('Fail to execute %s.', cmd)
33    if soundcard:
34        soundcard = "card{}".format(soundcard)
35        logging.info('---audio card %s', soundcard)
36    else:
37        logging.exception('Fail to get sound card, cli=%s.', cmd)
38    return soundcard
39
40def check_soundcard_by_name(dut, name):
41    """
42    check soundcard by name exists
43    @param dut: The handle of the device under test.
44    @param name: The name of Speaker
45                 For example: 'Hangouts Meet speakermic'
46    @returns: True, None if test passes,
47              False, errMsg if test fails
48    """
49    if get_soundcard_by_name(dut, name):
50        return True, None
51    else:
52        return False, 'Soundcard is not found under /proc/asound/cards.'
53
54def check_audio_stream(dut, is_in_meeting):
55    """
56    Verify speaker is streaming or not streaming as expected.
57    @param dut: The handle of the device under test.
58    @is_in_meeting: True if CfM is in meeting, False, if not
59    @returns: True, None if test passes,
60              False, errMsg if test fails
61    """
62    number_stream = get_number_of_active_streams(dut)
63    if is_in_meeting:
64       if number_stream  >= NUM_AUDIO_STREAM_IN_MEETING:
65           return True, None
66       else:
67           return False, 'Number of Audio streams is not expected.'
68    else:
69       if number_stream  <=  NUM_AUDIO_STREAM_IN_MEETING:
70           return True, None
71       else:
72           return False, 'Number of Audio streams is not expected.'
73
74def get_audio_stream_state(dut, soundcard):
75    """
76    Returns the state of stream0 for specified soundcard.
77
78    @param dut: The handle of the device under test. Should be initialized in
79                 autotest.
80    @param soundcard: soundcard
81                 For example: 'card0'
82
83    @returns the list of state of steam0, "Running" or "Stop"
84
85    """
86    stream_state = []
87    try:
88        cmd = ("cat /proc/asound/%s/stream0 | grep Status | "
89               "awk -v N=2 '{print $N}'" % soundcard)
90        stream_state = dut.run(cmd, ignore_status=True).stdout.split()
91    except Exception as e:
92        logging.exception('Fail to run cli: %s.', cmd)
93    return stream_state
94
95
96def check_default_speaker_volume(dut, cfm_facade):
97    """Check volume of speaker is the same as expected.
98    @param dut: The handle of the device under test.
99    @param cfm_facade: the handle of cfm facade
100    @returns True, None if default speakers have same volume as one read
101             from hotrod,
102             False, errMsg, otherwise
103    """
104    try:
105        expected_volume = int(cfm_facade.get_speaker_volume())
106    except Exception as e:
107        errmsg = 'Fail to run telemetry api to get speaker volume.'
108        logging.exception(errmsg)
109        return False, errmsg
110    if expected_volume < 1:
111        return False, 'Fail to get speaker volume from Hotrod.'
112    nodes = get_nodes_for_default_speakers_cras(dut)
113    if not nodes:
114        logging.info('---Fail to get node for default speaker.')
115        return False, 'Fail to get node for default speaker.'
116    for node in nodes:
117        cras_volume =  get_speaker_volume_cras(dut, node)
118        logging.info('---Volume for default speaker are sync for '
119                     'node %s? cfm: %d, cras: %d.'
120                     'format(node, expected_volume, cras_volume)')
121        if not expected_volume == cras_volume:
122            logging.info('---Volume Check Fail for default speaker: '
123                         'expected_volume:%d, actual_volume:%d.'
124                         'format(expected_volume, cras_volume)')
125            return False, ('Volume Check fails for default speaker: '
126                           'expected_volume:%d, actual_volume:%d',
127                           '% expected_volume, cras_volume')
128    logging.info('---Expected volume: %d, actual: %d',
129                     expected_volume, cras_volume)
130    return True, None
131
132def get_number_of_active_streams(dut):
133    """
134    Returns the number of active stream.
135    @param dut: The handle of the device under test. Should be initialized in
136                 autotest.
137    @returns the number of active streams.
138    """
139    cmd = ("cras_test_client --dump_server_info "
140           "| grep 'Num active streams:' "
141           "| awk -v N=4 '{print $N}'")
142
143    try:
144        number_of_streams = int(dut.run(cmd, ignore_status=True).stdout.strip())
145    except Exception as e:
146        logging.exception('Fail to execute cli to get number of streams: %s.',
147                          cmd)
148        return None
149    logging.info('---number of audio streaming: %d', number_of_streams)
150    return number_of_streams
151
152
153def get_nodes_for_default_speakers_cras(dut):
154    """get node for default speakers from cras_test_client.
155    @param dut: The handle of the device under test. Should be initialized in
156                 autotest.
157    @returns the list of nodes for default speakers. If device not found,
158     returns [].
159    """
160    nodes = []
161    cmd = ("cras_test_client --dump_server_info | awk '/Output Nodes:/,"
162           "/Input Devices:/'")
163    try:
164        lines = dut.run(cmd, ignore_status=True).stdout.splitlines()
165    except Exception as e:
166        logging.exception('Fail to execute cli to get nodes for default'
167                          'speaker: %s', cmd)
168        return nodes
169    for _line in lines:
170        match = re.findall(r"(\d+):\d+.*USB\s+\*.*", _line)
171        if match:
172            nodes.append(match[0])
173    logging.info('---found nodes for default speaker %s', nodes)
174    return nodes
175
176
177def get_speaker_for_node_cras(dut, node):
178    """get node for default speakers from cras_test_client.
179    @param dut: The handle of the device under test. Should be initialized in
180                 autotest.
181
182    @returns the list of nodes for default speakers. If device not found,
183     returns [].
184    """
185    cmd = ("cras_test_client --dump_server_info | awk '/Output Devices:/,"
186           "/Output Nodes:/' | grep '%s'" % node)
187
188    try:
189        line = dut.run(cmd, ignore_status=True).stdout.stripe()
190        speaker = re.findall(r"^[0-9]+\s*(.*):\s+USB\s+Audio:", line)[0]
191    except Exception as e:
192        logging.exception('Fail to execute cli to get nodes for default'
193                          'speaker: %s.', cmd)
194
195    logging.info('---speaker for %s is %s', node, speaker)
196    return speaker
197
198
199def get_nodes_for_default_microphone_cras(dut):
200    """get node for default microphones from cras_test_client.
201    @param dut: The handle of the device under test. Should be initialized in
202                 autotest.
203
204    @returns the list of nodes for default microphone. If device not found,
205     returns [].
206    """
207    nodes = None
208    cmd = ("cras_test_client --dump_server_info | awk '/Input Nodes:/,"
209           "/Attached clients:/'")
210    try:
211        lines = dut.run(cmd, ignore_status=True).stdout.splitlines()
212        for _line in lines:
213            nodes.append(re.findall(r"(\d+):\d+.*USB\s+\*.*", _line)[0])
214    except Exception as e:
215        logging.exception('Fail to execute cli to get nodes for default'
216                          ' speaker: %s.', cmd)
217    return nodes
218
219
220def get_microphone_for_node_cras(dut, node):
221    """get node for default speakers from cras_test_client.
222    @param dut: The handle of the device under test. Should be initialized in
223                 autotest.
224
225    @returns the list of nodes for default speakers. If device not found,
226     returns [].
227    """
228    cmd = ("cras_test_client --dump_server_info | awk '/Input Devices:/,"
229           "/Input Nodes:/' | grep '%s' " % node)
230
231    try:
232        line = dut.run(cmd, ignore_status=True).stdout
233        microphone = re.findall(r"10\t(.*):\s+USB\s+Audio:", line)[0]
234    except Exception as e:
235        logging.exception('Fail to execute cli to get nodes for default'
236                          ' speaker: %s.', cmd)
237    logging.info('---mic for %s is %s', node, microphone)
238    return microphone
239
240
241def get_speaker_volume_cras(dut, node):
242    """get volume for speaker from cras_test_client based on node
243    @param dut: The handle of the device under test. Should be initialized in
244                 autotest.
245    @param node: The node of Speaker
246                 Example cli:
247                 cras_test_client --dump_server_info | awk
248                 '/Output Nodes:/,/Input Devices:/' |  grep 9:0 |
249                 awk -v N=3 '{print $N}'
250
251    @returns the volume of speaker. If device not found, returns None.
252    """
253    cmd = ("cras_test_client --dump_server_info | awk '/Output Nodes:/,"
254           "/Input Devices:/' | grep -E 'USB' | grep '%s':0 "
255           "|  awk -v N=3 '{print $N}'" % node)
256    try:
257        volume = int(dut.run(cmd, ignore_status=True).stdout.strip())
258    except Exception as e:
259        logging.exception('Fail to execute cli %s to get volume for node %d',
260                           cmd, node)
261        return None
262    return volume
263
264
265def check_cras_mic_mute(dut, cfm_facade):
266    """
267    check microphone is muted or unmuted as expected/.
268    @param dut: The handle of the device under test.
269    @param cfm_facade:  facade of CfM
270    @param is_mic_muted: True if muted, False otherwise
271    @returns True, none if test passes
272             False, errMsg if test fails
273    """
274    try:
275        is_mic_muted = cfm_facade.is_mic_muted()
276    except Exception as e:
277        errmsg = 'Fail to run telemetry api to check mute state for mic.'
278        logging.exception(errmsg)
279        return False, errmsg
280    actual_muted = get_mic_muted_cras(dut)
281    if is_mic_muted == actual_muted:
282        return True, None
283    else:
284        if is_mic_muted:
285            logging.info('Hotrod setting: microphone is muted')
286        else:
287            logging.info('Hotrod setting: microphone is not muted')
288        if actual_muted:
289            logging.info('cras setting: microphone is muted')
290        else:
291            logging.info('cras setting: microphone is not muted')
292        return False, 'Microphone is not muted/unmuted as shown in Hotrod.'
293
294def check_is_preferred_speaker(dut, name):
295    """
296    check preferred speaker is speaker to be tested.
297    @param dut: The handle of the device under test.
298    @param cfm_facade:  facade of CfM
299    @param name: name of speaker
300    @returns True, none if test passes
301             False, errMsg if test fails
302    """
303    node = None
304    cmd = ("cras_test_client --dump_server_info | awk "
305           "'/Output Devices:/,/Output Nodes:/' "
306           "| grep '%s' " % name)
307    try:
308        output = dut.run(cmd, ignore_status=True).stdout.strip()
309    except Exception as e:
310        logging.exception('Fail to run cli %s to find %s in cras_test_client.',
311                          cmd, node)
312        return False, 'Fail to run cli {}:, reason: {}'.format(cmd, str(e))
313    logging.info('---output = %s', output)
314    if output:
315        node = output.split()[0]
316        logging.info('---found node for %s is %s', name, node)
317    else:
318        return False, 'Fail in get node for speaker in cras.'
319    default_nodes = get_nodes_for_default_speakers_cras(dut)
320    logging.info('---default speaker node is %s', default_nodes)
321    if node in default_nodes:
322        return True, None
323    return False, '{} is not set to preferred speaker.'.format(name)
324
325
326def check_is_preferred_mic(dut, name):
327    """check preferred mic is set to speaker to be tested."""
328    cmd = ("cras_test_client --dump_server_info | "
329           "awk '/Input Devices/,/Input Nodes/'  | grep '%s' | "
330           "awk -v N=1 '{print $N}'" % name)
331    logging.info('---cmd = %s',cmd)
332    try:
333        mic_node = dut.run(cmd, ignore_status=True).stdout.strip()
334        logging.info('---mic_node : %s', mic_node)
335    except Exception as e:
336        logging.exception('Fail to execute: %s to check preferred mic.', cmd)
337        return False, 'Fail to run cli.'
338    try:
339         cmd = ("cras_test_client --dump_server_info | awk '/Input Nodes:/,"
340               "/Attached clients:/'  | grep default "
341               "| awk -v N=2 '{print $N}'")
342         mic_node_default = dut.run(cmd, ignore_status=True).stdout.strip()
343         if not mic_node_default:
344             cmd = ("cras_test_client --dump_server_info | awk '/Input Nodes:/,"
345                   "/Attached clients:/'  | grep '%s' "
346                   "| awk -v N=2 '{print $N}'" %name)
347             mic_node_default = dut.run(cmd,ignore_status=True).stdout.strip()
348         logging.info('---%s',cmd)
349         logging.info('---%s', mic_node_default)
350    except Exception as e:
351         msg = 'Fail to execute: {} to check preferred mic'.format(cmd)
352         logging.exception(msg)
353         return False, msg
354    logging.info('---mic node:%s, default node:%s',
355                 mic_node, mic_node_default)
356    if mic_node == mic_node_default.split(':')[0]:
357        return True,  None
358    return False, '{} is not preferred microphone.'.format(name)
359
360
361def get_mic_muted_cras(dut):
362    """
363    Get the status of mute or unmute for microphone
364    @param dut: the handle of CfM under test
365    @returns True if mic is muted
366             False if mic not not muted
367    """
368    cmd = 'cras_test_client --dump_server_info | grep "Capture Gain"'
369    try:
370        microphone_muted = dut.run(cmd, ignore_status=True).stdout.strip()
371    except Exception as e:
372        logging.exception('Fail to execute: %s.', cmd)
373        return False, 'Fail to execute: {}.'.format(cmd)
374    logging.info('---%s',  microphone_muted)
375    if "Muted" in microphone_muted:
376       return True
377    else:
378       return False
379
380
381def check_speaker_exist_cras(dut, name):
382    """
383    Check speaker exists in cras.
384    @param dut: The handle of the device under test.
385    @param name: name of speaker
386    @returns: True, None if test passes,
387              False, errMsg if test fails
388    """
389    cmd = ("cras_test_client --dump_server_info | awk "
390           "'/Output Devices:/, /Output Nodes:/' "
391           "| grep '%s'" % name)
392    try:
393        speaker = dut.run(cmd, ignore_status=True).stdout.splitlines()[0]
394    except Exception as e:
395        logging.exception('Fail to find %s in cras_test_client running %s.',
396                          name, cmd)
397        speaker = None
398    logging.info('---cmd: %s\n---output = %s', cmd, speaker)
399    if speaker:
400        return True, None
401    return False, 'Fail to execute cli {}: Reason: {}'.format(cmd, str(e))
402
403
404def check_microphone_exist_cras(dut, name):
405    """
406    Check microphone exists in cras.
407    @param dut: The handle of the device under test.
408    @param name: name of speaker
409    @returns: True, None if test passes,
410              False, errMsg if test fails
411    """
412    microphone = None
413    cmd = ("cras_test_client --dump_server_info | awk "
414           "'/Input Devices:/, /Input Nodes:/' "
415           "| grep '%s'" % name )
416    try:
417        microphone = dut.run(cmd, ignore_status=True).stdout.splitlines()[0]
418    except Exception as e:
419        logging.exception('Fail to execute cli %s.', cmd)
420    logging.info('---cmd: %s\n---output = %s', cmd, microphone)
421    if microphone:
422        return True, None
423    return False, 'Fail to find microphone {}.'.format(name)
424
425def check_audio_stream(dut, is_in_meet):
426    """
427    Verify speaker is streaming or not streaming as expected.
428    @param dut: The handle of the device under test.
429    @is_in_meeting: True if CfM is in meeting, False, if not
430    @returns: True, None if test passes,
431              False, errMsg if test fails
432    """
433    number_stream = get_number_of_active_streams(dut)
434    if is_in_meet:
435       if number_stream  >= NUM_AUDIO_STREAM_IN_MEETING:
436           return True, None
437       else:
438           return False, 'Number of Audio streams is not expected.'
439    else:
440       if number_stream  <=  NUM_AUDIO_STREAM_IN_MEETING:
441           return True, None
442       else:
443           return False, 'Number of Audio streams is not expected.'
444
445