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