• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This module provides cras audio utilities."""
7
8import logging
9import re
10import subprocess
11
12from autotest_lib.client.bin import utils
13from autotest_lib.client.cros.audio import cmd_utils
14
15_CRAS_TEST_CLIENT = '/usr/bin/cras_test_client'
16
17
18class CrasUtilsError(Exception):
19    """Error in CrasUtils."""
20    pass
21
22
23def dump_audio_thread():
24    """Dumps audio thread info.
25
26    @returns: A list of cras audio information.
27    """
28    proc = subprocess.Popen([_CRAS_TEST_CLIENT, '--dump_a'],
29                            stdout=subprocess.PIPE)
30
31    output, err = proc.communicate()
32    if err:
33        raise CrasUtilsError(err)
34    return output.decode().splitlines()
35
36
37def get_audio_thread_summary():
38    """Gets stream summary info.
39
40    @returns: A list of stream summary information.
41    """
42
43    lines = dump_audio_thread()
44    return [l for l in lines if l.startswith('Summary:')]
45
46
47def playback(blocking=True, stdin=None, *args, **kargs):
48    """A helper function to execute the playback_cmd.
49
50    @param blocking: Blocks this call until playback finishes.
51    @param stdin: the standard input of playback process
52    @param args: args passed to playback_cmd.
53    @param kargs: kargs passed to playback_cmd.
54
55    @returns: The process running the playback command. Note that if the
56              blocking parameter is true, this will return a finished process.
57    """
58    process = cmd_utils.popen(playback_cmd(*args, **kargs), stdin=stdin)
59    if blocking:
60        cmd_utils.wait_and_check_returncode(process)
61    return process
62
63
64def capture(*args, **kargs):
65    """A helper function to execute the capture_cmd.
66
67    @param args: args passed to capture_cmd.
68    @param kargs: kargs passed to capture_cmd.
69
70    """
71    cmd_utils.execute(capture_cmd(*args, **kargs))
72
73
74def playback_cmd(playback_file, block_size=None, duration=None,
75                 pin_device=None, channels=2, rate=48000):
76    """Gets a command to playback a file with given settings.
77
78    @param playback_file: the name of the file to play. '-' indicates to
79                          playback raw audio from the stdin.
80    @param pin_device: the device id to playback on.
81    @param block_size: the number of frames per callback(dictates latency).
82    @param duration: seconds to playback.
83    @param channels: number of channels.
84    @param rate: the sampling rate.
85
86    @returns: The command args put in a list of strings.
87
88    """
89    args = [_CRAS_TEST_CLIENT]
90    args += ['--playback_file', playback_file]
91    if pin_device is not None:
92        args += ['--pin_device', str(pin_device)]
93    if block_size is not None:
94        args += ['--block_size', str(block_size)]
95    if duration is not None:
96        args += ['--duration', str(duration)]
97    args += ['--num_channels', str(channels)]
98    args += ['--rate', str(rate)]
99    return args
100
101
102def capture_cmd(capture_file, block_size=None, duration=10,
103                sample_format='S16_LE',
104                pin_device=None, channels=1, rate=48000):
105    """Gets a command to capture the audio into the file with given settings.
106
107    @param capture_file: the name of file the audio to be stored in.
108    @param block_size: the number of frames per callback(dictates latency).
109    @param duration: seconds to record. If it is None, duration is not set,
110                     and command will keep capturing audio until it is
111                     terminated.
112    @param sample_format: the sample format;
113                          possible choices: 'S16_LE', 'S24_LE', and 'S32_LE'
114                          default to S16_LE: signed 16 bits/sample,
115                                             little endian
116    @param pin_device: the device id to record from.
117    @param channels: number of channels.
118    @param rate: the sampling rate.
119
120    @returns: The command args put in a list of strings.
121
122    """
123    args = [_CRAS_TEST_CLIENT]
124    args += ['--capture_file', capture_file]
125    if pin_device is not None:
126        args += ['--pin_device', str(pin_device)]
127    if block_size is not None:
128        args += ['--block_size', str(block_size)]
129    if duration is not None:
130        args += ['--duration', str(duration)]
131    args += ['--num_channels', str(channels)]
132    args += ['--rate', str(rate)]
133    args += ['--format', str(sample_format)]
134    return args
135
136
137def listen_cmd(
138        capture_file, block_size=None, duration=10, channels=1, rate=48000):
139    """Gets a command to listen on hotword and record audio into the file with
140       given settings.
141
142    @param capture_file: the name of file the audio to be stored in.
143    @param block_size: the number of frames per callback(dictates latency).
144    @param duration: seconds to record. If it is None, duration is not set,
145                     and command will keep capturing audio until it is
146                     terminated.
147    @param channels: number of channels.
148    @param rate: the sampling rate.
149
150    @returns: The command args put in a list of strings.
151
152    """
153    args = [_CRAS_TEST_CLIENT]
154    args += ['--listen_for_hotword', capture_file]
155    if block_size is not None:
156        args += ['--block_size', str(block_size)]
157    if duration is not None:
158        args += ['--duration', str(duration)]
159    args += ['--num_channels', str(channels)]
160    args += ['--rate', str(rate)]
161    return args
162
163
164def loopback(*args, **kargs):
165    """A helper function to execute loopback_cmd.
166
167    @param args: args passed to loopback_cmd.
168    @param kargs: kargs passed to loopback_cmd.
169
170    """
171
172    cmd_utils.execute(loopback_cmd(*args, **kargs))
173
174
175def loopback_cmd(output_file, duration=10, channels=2, rate=48000):
176    """Gets a command to record the loopback.
177
178    @param output_file: The name of the file the loopback to be stored in.
179    @param channels: The number of channels of the recorded audio.
180    @param duration: seconds to record.
181    @param rate: the sampling rate.
182
183    @returns: The command args put in a list of strings.
184
185    """
186    args = [_CRAS_TEST_CLIENT]
187    args += ['--loopback_file', output_file]
188    args += ['--duration_seconds', str(duration)]
189    args += ['--num_channels', str(channels)]
190    args += ['--rate', str(rate)]
191    return args
192
193
194def get_cras_nodes_cmd():
195    """Gets a command to query the nodes from Cras.
196
197    @returns: The command to query nodes information from Cras using dbus-send.
198
199    """
200    return ('dbus-send --system --type=method_call --print-reply '
201            '--dest=org.chromium.cras /org/chromium/cras '
202            'org.chromium.cras.Control.GetNodes')
203
204
205def set_system_volume(volume):
206    """Set the system volume.
207
208    @param volume: the system output vlume to be set(0 - 100).
209
210    """
211    get_cras_control_interface().SetOutputVolume(volume)
212
213
214def set_node_volume(node_id, volume):
215    """Set the volume of the given output node.
216
217    @param node_id: the id of the output node to be set the volume.
218    @param volume: the volume to be set(0-100).
219
220    """
221    get_cras_control_interface().SetOutputNodeVolume(node_id, volume)
222
223
224def get_cras_control_interface(private=False):
225    """Gets Cras DBus control interface.
226
227    @param private: Set to True to use a new instance for dbus.SystemBus
228                    instead of the shared instance.
229
230    @returns: A dBus.Interface object with Cras Control interface.
231
232    @raises: ImportError if this is not called on Cros device.
233
234    """
235    try:
236        import dbus
237    except ImportError as e:
238        logging.exception(
239                'Can not import dbus: %s. This method should only be '
240                'called on Cros device.', e)
241        raise
242    bus = dbus.SystemBus(private=private)
243    cras_object = bus.get_object('org.chromium.cras', '/org/chromium/cras')
244    return dbus.Interface(cras_object, 'org.chromium.cras.Control')
245
246
247def get_cras_nodes():
248    """Gets nodes information from Cras.
249
250    @returns: A dict containing information of each node.
251
252    """
253    return get_cras_control_interface().GetNodes()
254
255
256def get_selected_nodes():
257    """Gets selected output nodes and input nodes.
258
259    @returns: A tuple (output_nodes, input_nodes) where each
260              field is a list of selected node IDs returned from Cras DBus API.
261              Note that there may be multiple output/input nodes being selected
262              at the same time.
263
264    """
265    output_nodes = []
266    input_nodes = []
267    nodes = get_cras_nodes()
268    for node in nodes:
269        if node['Active']:
270            if node['IsInput']:
271                input_nodes.append(node['Id'])
272            else:
273                output_nodes.append(node['Id'])
274    return (output_nodes, input_nodes)
275
276
277def set_selected_output_node_volume(volume):
278    """Sets the selected output node volume.
279
280    @param volume: the volume to be set (0-100).
281
282    """
283    selected_output_node_ids, _ = get_selected_nodes()
284    for node_id in selected_output_node_ids:
285        set_node_volume(node_id, volume)
286
287
288def get_active_stream_count():
289    """Gets the number of active streams.
290
291    @returns: The number of active streams.
292
293    """
294    return int(get_cras_control_interface().GetNumberOfActiveStreams())
295
296
297def set_system_mute(is_mute):
298    """Sets the system mute switch.
299
300    @param is_mute: Set True to mute the system playback.
301
302    """
303    get_cras_control_interface().SetOutputMute(is_mute)
304
305
306def set_capture_mute(is_mute):
307    """Sets the capture mute switch.
308
309    @param is_mute: Set True to mute the capture.
310
311    """
312    get_cras_control_interface().SetInputMute(is_mute)
313
314
315def node_type_is_plugged(node_type, nodes_info):
316    """Determine if there is any node of node_type plugged.
317
318    This method is used in the AudioLoopbackDongleLabel class, where the
319    call is executed on autotest server. Use get_cras_nodes instead if
320    the call can be executed on Cros device.
321
322    Since Cras only reports the plugged node in GetNodes, we can
323    parse the return value to see if there is any node with the given type.
324    For example, if INTERNAL_MIC is of intereset, the pattern we are
325    looking for is:
326
327    dict entry(
328       string "Type"
329       variant             string "INTERNAL_MIC"
330    )
331
332    @param node_type: A str representing node type defined in CRAS_NODE_TYPES.
333    @param nodes_info: A str containing output of command get_nodes_cmd.
334
335    @returns: True if there is any node of node_type plugged. False otherwise.
336
337    """
338    match = re.search(r'string "Type"\s+variant\s+string "%s"' % node_type,
339                      nodes_info)
340    return True if match else False
341
342
343# Cras node types reported from Cras DBus control API.
344CRAS_OUTPUT_NODE_TYPES = ['HEADPHONE', 'INTERNAL_SPEAKER', 'HDMI', 'USB',
345                          'BLUETOOTH', 'LINEOUT', 'UNKNOWN', 'ALSA_LOOPBACK']
346CRAS_INPUT_NODE_TYPES = [
347        'MIC', 'INTERNAL_MIC', 'USB', 'BLUETOOTH', 'POST_DSP_DELAYED_LOOPBACK',
348        'POST_DSP_LOOPBACK', 'POST_MIX_LOOPBACK', 'UNKNOWN', 'KEYBOARD_MIC',
349        'HOTWORD', 'FRONT_MIC', 'REAR_MIC', 'ECHO_REFERENCE'
350]
351CRAS_NODE_TYPES = CRAS_OUTPUT_NODE_TYPES + CRAS_INPUT_NODE_TYPES
352
353
354def get_filtered_node_types(callback):
355    """Returns the pair of filtered output node types and input node types.
356
357    @param callback: A callback function which takes a node as input parameter
358                     and filter the node based on its return value.
359
360    @returns: A tuple (output_node_types, input_node_types) where each
361              field is a list of node types defined in CRAS_NODE_TYPES,
362              and their 'attribute_name' is True.
363
364    """
365    output_node_types = []
366    input_node_types = []
367    nodes = get_cras_nodes()
368    for node in nodes:
369        if callback(node):
370            node_type = str(node['Type'])
371            if node_type not in CRAS_NODE_TYPES:
372                logging.warning('node type %s is not in known CRAS_NODE_TYPES',
373                                node_type)
374            if node['IsInput']:
375                input_node_types.append(node_type)
376            else:
377                output_node_types.append(node_type)
378    return (output_node_types, input_node_types)
379
380
381def get_selected_node_types():
382    """Returns the pair of active output node types and input node types.
383
384    @returns: A tuple (output_node_types, input_node_types) where each
385              field is a list of selected node types defined in CRAS_NODE_TYPES.
386
387    """
388    def is_selected(node):
389        """Checks if a node is selected.
390
391        A node is selected if its Active attribute is True.
392
393        @returns: True is a node is selected, False otherwise.
394
395        """
396        return node['Active']
397
398    return get_filtered_node_types(is_selected)
399
400
401def get_selected_input_device_name():
402    """Returns the device name of the active input node.
403
404    @returns: device name string. E.g. kbl_r5514_5663_max: :0,1
405    """
406    nodes = get_cras_nodes()
407    for node in nodes:
408        if node['Active'] and node['IsInput']:
409            return node['DeviceName']
410    return None
411
412
413def get_selected_input_device_type():
414    """Returns the device type of the active input node.
415
416    @returns: device type string. E.g. INTERNAL_MICROPHONE
417    """
418    nodes = get_cras_nodes()
419    for node in nodes:
420        if node['Active'] and node['IsInput']:
421            return node['Type']
422    return None
423
424
425def get_selected_output_device_name():
426    """Returns the device name of the active output node.
427
428    @returns: device name string. E.g. mtk-rt5650: :0,0
429    """
430    nodes = get_cras_nodes()
431    for node in nodes:
432        if node['Active'] and not node['IsInput']:
433            return node['DeviceName']
434    return None
435
436
437def get_selected_output_device_type():
438    """Returns the device type of the active output node.
439
440    @returns: device type string. E.g. INTERNAL_SPEAKER
441    """
442    nodes = get_cras_nodes()
443    for node in nodes:
444        if node['Active'] and not node['IsInput']:
445            return node['Type']
446    return None
447
448
449def get_plugged_node_types():
450    """Returns the pair of plugged output node types and input node types.
451
452    @returns: A tuple (output_node_types, input_node_types) where each
453              field is a list of plugged node types defined in CRAS_NODE_TYPES.
454
455    """
456    def is_plugged(node):
457        """Checks if a node is plugged and is not unknown node.
458
459        Cras DBus API only reports plugged node, so every node reported by Cras
460        DBus API is plugged. However, we filter out UNKNOWN node here because
461        the existence of unknown node depends on the number of redundant
462        playback/record audio device created on audio card. Also, the user of
463        Cras will ignore unknown nodes.
464
465        @returns: True if a node is plugged and is not an UNKNOWN node.
466
467        """
468        return node['Type'] != 'UNKNOWN'
469
470    return get_filtered_node_types(is_plugged)
471
472
473def set_selected_node_types(output_node_types, input_node_types):
474    """Sets selected node types.
475
476    @param output_node_types: A list of output node types. None to skip setting.
477    @param input_node_types: A list of input node types. None to skip setting.
478
479    """
480    if output_node_types is not None and len(output_node_types) == 1:
481        set_single_selected_output_node(output_node_types[0])
482    elif output_node_types:
483        set_selected_output_nodes(output_node_types)
484    if input_node_types is not None and len(input_node_types) == 1:
485        set_single_selected_input_node(input_node_types[0])
486    elif input_node_types:
487        set_selected_input_nodes(input_node_types)
488
489
490def set_single_selected_output_node(node_type):
491    """Sets one selected output node.
492
493    Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
494    to select one output node.
495
496    @param node_type: A node type.
497
498    @returns: True if the output node type is found and set active.
499    """
500    nodes = get_cras_nodes()
501    for node in nodes:
502        if node['IsInput']:
503            continue
504        if node['Type'] == node_type:
505            set_active_output_node(node['Id'])
506            return True
507    return False
508
509
510def set_single_selected_input_node(node_type):
511    """Sets one selected input node.
512
513    Note that Chrome UI uses SetActiveInputNode of Cras DBus API
514    to select one input node.
515
516    @param node_type: A node type.
517
518    @returns: True if the input node type is found and set active.
519    """
520    nodes = get_cras_nodes()
521    for node in nodes:
522        if not node['IsInput']:
523            continue
524        if node['Type'] == node_type:
525            set_active_input_node(node['Id'])
526            return True
527    return False
528
529
530def set_selected_output_nodes(types):
531    """Sets selected output node types.
532
533    Note that Chrome UI uses SetActiveOutputNode of Cras DBus API
534    to select one output node. Here we use add/remove active output node
535    to support multiple nodes.
536
537    @param types: A list of output node types.
538
539    """
540    nodes = get_cras_nodes()
541    for node in nodes:
542        if node['IsInput']:
543            continue
544        if node['Type'] in types:
545            add_active_output_node(node['Id'])
546        elif node['Active']:
547            remove_active_output_node(node['Id'])
548
549
550def set_selected_input_nodes(types):
551    """Sets selected input node types.
552
553    Note that Chrome UI uses SetActiveInputNode of Cras DBus API
554    to select one input node. Here we use add/remove active input node
555    to support multiple nodes.
556
557    @param types: A list of input node types.
558
559    """
560    nodes = get_cras_nodes()
561    for node in nodes:
562        if not node['IsInput']:
563            continue
564        if node['Type'] in types:
565            add_active_input_node(node['Id'])
566        elif node['Active']:
567            remove_active_input_node(node['Id'])
568
569
570def set_active_input_node(node_id):
571    """Sets one active input node.
572
573    @param node_id: node id.
574
575    """
576    get_cras_control_interface().SetActiveInputNode(node_id)
577
578
579def set_active_output_node(node_id):
580    """Sets one active output node.
581
582    @param node_id: node id.
583
584    """
585    get_cras_control_interface().SetActiveOutputNode(node_id)
586
587
588def add_active_output_node(node_id):
589    """Adds an active output node.
590
591    @param node_id: node id.
592
593    """
594    get_cras_control_interface().AddActiveOutputNode(node_id)
595
596
597def add_active_input_node(node_id):
598    """Adds an active input node.
599
600    @param node_id: node id.
601
602    """
603    get_cras_control_interface().AddActiveInputNode(node_id)
604
605
606def remove_active_output_node(node_id):
607    """Removes an active output node.
608
609    @param node_id: node id.
610
611    """
612    get_cras_control_interface().RemoveActiveOutputNode(node_id)
613
614
615def remove_active_input_node(node_id):
616    """Removes an active input node.
617
618    @param node_id: node id.
619
620    """
621    get_cras_control_interface().RemoveActiveInputNode(node_id)
622
623
624def get_node_id_from_node_type(node_type, is_input):
625    """Gets node id from node type.
626
627    @param types: A node type defined in CRAS_NODE_TYPES.
628    @param is_input: True if the node is input. False otherwise.
629
630    @returns: A string for node id.
631
632    @raises: CrasUtilsError: if unique node id can not be found.
633
634    """
635    nodes = get_cras_nodes()
636    find_ids = []
637    for node in nodes:
638        if node['Type'] == node_type and node['IsInput'] == is_input:
639            find_ids.append(node['Id'])
640    if len(find_ids) != 1:
641        raise CrasUtilsError(
642                'Can not find unique node id from node type %s' % node_type)
643    return find_ids[0]
644
645
646def get_device_id_of(node_id):
647    """Gets the device id of the node id.
648
649    The conversion logic is replicated from the CRAS's type definition at
650    third_party/adhd/cras/src/common/cras_types.h.
651
652    @param node_id: A string for node id.
653
654    @returns: A string for device id.
655
656    @raise: CrasUtilsError: if device id is invalid.
657    """
658    device_id = str(int(node_id) >> 32)
659    if device_id == "0":
660        raise CrasUtilsError('Got invalid device_id: 0')
661    return device_id
662
663
664def get_device_id_from_node_type(node_type, is_input):
665    """Gets device id from node type.
666
667    @param types: A node type defined in CRAS_NODE_TYPES.
668    @param is_input: True if the node is input. False otherwise.
669
670    @returns: A string for device id.
671
672    """
673    node_id = get_node_id_from_node_type(node_type, is_input)
674    return get_device_id_of(node_id)
675
676
677def get_active_node_volume():
678    """Returns volume from active node.
679
680    @returns: int for volume
681
682    @raises: CrasUtilsError: if node volume cannot be found.
683    """
684    nodes = get_cras_nodes()
685    for node in nodes:
686        if node['Active'] == 1 and node['IsInput'] == 0:
687            return int(node['NodeVolume'])
688    raise CrasUtilsError('Cannot find active node volume from nodes.')
689
690
691def get_active_output_node_max_supported_channels():
692    """Returns max supported channels from active output node.
693
694    @returns: int for max supported channels.
695
696    @raises: CrasUtilsError: if node cannot be found.
697    """
698    nodes = get_cras_nodes()
699    for node in nodes:
700        if node['Active'] == 1 and node['IsInput'] == 0:
701            return int(node['MaxSupportedChannels'])
702    raise CrasUtilsError('Cannot find active output node.')
703
704
705def get_noise_cancellation_supported():
706    """Gets whether the device supports Noise Cancellation.
707
708    @returns: True is supported; False otherwise.
709    """
710    return bool(get_cras_control_interface().IsNoiseCancellationSupported())
711
712
713def set_bypass_block_noise_cancellation(bypass):
714    """Sets CRAS to bypass the blocking logic of Noise Cancellation.
715
716    @param bypass: True for bypass; False for un-bypass.
717    """
718    get_cras_control_interface().SetBypassBlockNoiseCancellation(bypass)
719
720
721def set_noise_cancellation_enabled(enabled):
722    """Sets the state to enable or disable Noise Cancellation.
723
724    @param enabled: True to enable; False to disable.
725    """
726    get_cras_control_interface().SetNoiseCancellationEnabled(enabled)
727
728
729def set_floss_enabled(enabled):
730    """Sets whether CRAS stack expects to use Floss.
731
732    @param enabled: True for Floss, False for Bluez.
733    """
734    get_cras_control_interface().SetFlossEnabled(enabled)
735
736
737class CrasTestClient(object):
738    """An object to perform cras_test_client functions."""
739
740    BLOCK_SIZE = None
741    PIN_DEVICE = None
742    SAMPLE_FORMAT = 'S16_LE'
743    DURATION = 10
744    CHANNELS = 2
745    RATE = 48000
746
747
748    def __init__(self):
749        self._proc = None
750        self._capturing_proc = None
751        self._playing_proc = None
752        self._capturing_msg = 'capturing audio file'
753        self._playing_msg = 'playing audio file'
754        self._wbs_cmd = '%s --set_wbs_enabled ' % _CRAS_TEST_CLIENT
755        self._enable_wbs_cmd = ('%s 1' % self._wbs_cmd).split()
756        self._disable_wbs_cmd = ('%s 0' % self._wbs_cmd).split()
757        self._info_cmd = [_CRAS_TEST_CLIENT,]
758        self._select_input_cmd = '%s --select_input ' % _CRAS_TEST_CLIENT
759
760
761    def start_subprocess(self, proc, proc_cmd, filename, proc_msg):
762        """Start a capture or play subprocess
763
764        @param proc: the process
765        @param proc_cmd: the process command and its arguments
766        @param filename: the file name to capture or play
767        @param proc_msg: the message to display in logging
768
769        @returns: True if the process is started successfully
770        """
771        if proc is None:
772            try:
773                self._proc = subprocess.Popen(proc_cmd)
774                logging.debug('Start %s %s on the DUT', proc_msg, filename)
775            except Exception as e:
776                logging.error('Failed to popen: %s (%s)', proc_msg, e)
777                return False
778        else:
779            logging.error('cannot run the command twice: %s', proc_msg)
780            return False
781        return True
782
783
784    def stop_subprocess(self, proc, proc_msg):
785        """Stop a subprocess
786
787        @param proc: the process to stop
788        @param proc_msg: the message to display in logging
789
790        @returns: True if the process is stopped successfully
791        """
792        if proc is None:
793            logging.error('cannot run stop %s before starting it.', proc_msg)
794            return False
795
796        proc.terminate()
797        try:
798            utils.poll_for_condition(
799                    condition=lambda: proc.poll() is not None,
800                    exception=CrasUtilsError,
801                    timeout=10,
802                    sleep_interval=0.5,
803                    desc='Waiting for subprocess to terminate')
804        except Exception:
805            logging.warning('Killing subprocess due to timeout')
806            proc.kill()
807            proc.wait()
808
809        logging.debug('stop %s on the DUT', proc_msg)
810        return True
811
812
813    def start_capturing_subprocess(self, capture_file, block_size=BLOCK_SIZE,
814                                   duration=DURATION, pin_device=PIN_DEVICE,
815                                   sample_format=SAMPLE_FORMAT,
816                                   channels=CHANNELS, rate=RATE):
817        """Start capturing in a subprocess.
818
819        @param capture_file: the name of file the audio to be stored in
820        @param block_size: the number of frames per callback(dictates latency)
821        @param duration: seconds to record. If it is None, duration is not set,
822                         and will keep capturing audio until terminated
823        @param sample_format: the sample format
824        @param pin_device: the device id to record from
825        @param channels: number of channels
826        @param rate: the sampling rate
827
828        @returns: True if the process is started successfully
829        """
830        proc_cmd = capture_cmd(capture_file, block_size=block_size,
831                               duration=duration, sample_format=sample_format,
832                               pin_device=pin_device, channels=channels,
833                               rate=rate)
834        result = self.start_subprocess(self._capturing_proc, proc_cmd,
835                                       capture_file, self._capturing_msg)
836        if result:
837            self._capturing_proc = self._proc
838        return result
839
840
841    def stop_capturing_subprocess(self):
842        """Stop the capturing subprocess."""
843        result = self.stop_subprocess(self._capturing_proc, self._capturing_msg)
844        if result:
845            self._capturing_proc = None
846        return result
847
848
849    def start_playing_subprocess(self, audio_file, block_size=BLOCK_SIZE,
850                                 duration=DURATION, pin_device=PIN_DEVICE,
851                                 channels=CHANNELS, rate=RATE):
852        """Start playing the audio file in a subprocess.
853
854        @param audio_file: the name of audio file to play
855        @param block_size: the number of frames per callback(dictates latency)
856        @param duration: seconds to play. If it is None, duration is not set,
857                         and will keep playing audio until terminated
858        @param pin_device: the device id to play to
859        @param channels: number of channels
860        @param rate: the sampling rate
861
862        @returns: True if the process is started successfully
863        """
864        proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device,
865                                channels, rate)
866        result = self.start_subprocess(self._playing_proc, proc_cmd,
867                                       audio_file, self._playing_msg)
868        if result:
869            self._playing_proc = self._proc
870        return result
871
872
873    def stop_playing_subprocess(self):
874        """Stop the playing subprocess."""
875        result = self.stop_subprocess(self._playing_proc, self._playing_msg)
876        if result:
877            self._playing_proc = None
878        return result
879
880
881    def play(self, audio_file, block_size=BLOCK_SIZE, duration=DURATION,
882             pin_device=PIN_DEVICE, channels=CHANNELS, rate=RATE):
883        """Play the audio file.
884
885        This method will get blocked until it has completed playing back.
886        If you do not want to get blocked, use start_playing_subprocess()
887        above instead.
888
889        @param audio_file: the name of audio file to play
890        @param block_size: the number of frames per callback(dictates latency)
891        @param duration: seconds to play. If it is None, duration is not set,
892                         and will keep playing audio until terminated
893        @param pin_device: the device id to play to
894        @param channels: number of channels
895        @param rate: the sampling rate
896
897        @returns: True if the process is started successfully
898        """
899        proc_cmd = playback_cmd(audio_file, block_size, duration, pin_device,
900                                channels, rate)
901        try:
902            self._proc = subprocess.call(proc_cmd)
903            logging.debug('call "%s" on the DUT', proc_cmd)
904        except Exception as e:
905            logging.error('Failed to call: %s (%s)', proc_cmd, e)
906            return False
907        return True
908
909
910    def enable_wbs(self, value):
911        """Enable or disable wideband speech (wbs) per the value.
912
913        @param value: True to enable wbs.
914
915        @returns: True if the operation succeeds.
916        """
917        cmd = self._enable_wbs_cmd if value else self._disable_wbs_cmd
918        logging.debug('call "%s" on the DUT', cmd)
919        if subprocess.call(cmd):
920            logging.error('Failed to call: %s (%s)', cmd)
921            return False
922        return True
923
924
925    def select_input_device(self, device_name):
926        """Select the audio input device.
927
928        @param device_name: the name of the Bluetooth peer device
929
930        @returns: True if the operation succeeds.
931        """
932        logging.debug('to select input device for device_name: %s', device_name)
933        try:
934            info = subprocess.check_output(self._info_cmd)
935            logging.debug('info: %s', info)
936        except Exception as e:
937            logging.error('Failed to call: %s (%s)', self._info_cmd, e)
938            return False
939
940        flag_input_nodes = False
941        audio_input_node = None
942        for line in info.decode().splitlines():
943            if 'Input Nodes' in line:
944                flag_input_nodes = True
945            elif 'Attached clients' in line:
946                flag_input_nodes = False
947
948            if flag_input_nodes:
949                if device_name in line:
950                    audio_input_node = line.split()[1]
951                    logging.debug('%s', audio_input_node)
952                    break
953
954        if audio_input_node is None:
955            logging.error('Failed to find audio input node: %s', device_name)
956            return False
957
958        select_input_cmd = (self._select_input_cmd + audio_input_node).split()
959        if subprocess.call(select_input_cmd):
960            logging.error('Failed to call: %s (%s)', select_input_cmd, e)
961            return False
962
963        logging.debug('call "%s" on the DUT', select_input_cmd)
964        return True
965
966
967    def set_player_playback_status(self, status):
968        """Set playback status for the registered media player.
969
970        @param status: playback status in string.
971
972        """
973        try:
974            get_cras_control_interface().SetPlayerPlaybackStatus(status)
975        except Exception as e:
976            logging.error('Failed to set player playback status: %s', e)
977            return False
978
979        return True
980
981
982    def set_player_position(self, position):
983        """Set media position for the registered media player.
984
985        @param position: position in micro seconds.
986
987        """
988        try:
989            get_cras_control_interface().SetPlayerPosition(position)
990        except Exception as e:
991            logging.error('Failed to set player position: %s', e)
992            return False
993
994        return True
995
996
997    def set_player_metadata(self, metadata):
998        """Set title, artist, and album for the registered media player.
999
1000        @param metadata: dictionary of media metadata.
1001
1002        """
1003        try:
1004            get_cras_control_interface().SetPlayerMetadata(metadata)
1005        except Exception as e:
1006            logging.error('Failed to set player metadata: %s', e)
1007            return False
1008
1009        return True
1010
1011
1012    def _encode_length_for_dbus(self, length):
1013        """Encode length as Int64 for |SetPlayerMetadata|."""
1014        try:
1015            import dbus
1016        except ImportError as e:
1017            logging.exception(
1018                    'Can not import dbus: %s. This method should only be '
1019                    'called on Cros device.', e)
1020            raise
1021
1022        length_variant = dbus.types.Int64(length, variant_level=1)
1023        return dbus.Dictionary({'length': length_variant}, signature='sv')
1024
1025    def set_player_length(self, length):
1026        """Set metadata length for the registered media player.
1027
1028        Media length is a part of metadata information. However, without
1029        specify its type to int64. dbus-python will guess the variant type to
1030        be int32 by default. Separate it from the metadata function to help
1031        prepare the data differently.
1032
1033        @param length: Integer value that will be encoded for dbus.
1034
1035        """
1036        try:
1037            length_dbus = self._encode_length_for_dbus(length)
1038            get_cras_control_interface().SetPlayerMetadata(length_dbus)
1039        except Exception as e:
1040            logging.error('Failed to set player length: %s', e)
1041            return False
1042
1043        return True
1044