1# Copyright (c) 2013 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
5import logging
6import time
7
8from autotest_lib.client.bin import test, utils
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.cros.audio import alsa_utils
11from autotest_lib.client.cros.audio import audio_spec
12from autotest_lib.client.cros.audio import cras_utils
13
14APLAY_FILE = '/dev/zero' # raw data
15
16# Expected results of 'aplay -v' commands.
17APLAY_EXPECTED = set([
18      ('stream', 'PLAYBACK')])
19
20
21def _play_audio(device_name, duration=1):
22    """Play a tone and try to ensure it played properly.
23
24    Sample output from aplay -v:
25
26    Playing raw data '/dev/zero' : Signed 16 bit Little Endian, Rate 44100 Hz,
27    Stereo
28    Hardware PCM card 0 'HDA Intel PCH' device 0 subdevice 0
29    Its setup is:
30      stream       : PLAYBACK
31      access       : RW_INTERLEAVED  format       : S16_LE
32      subformat    : STD
33      channels     : 2
34      rate         : 44100
35      exact rate   : 44100 (44100/1)
36      msbits       : 16
37      buffer_size  : 16384
38      period_size  : 4096
39      period_time  : 92879
40      tstamp_mode  : NONE
41      period_step  : 1
42      avail_min    : 4096
43      period_event : 0
44      start_threshold  : 16384
45      stop_threshold   : 16384
46      silence_threshold: 0
47      silence_size : 0
48      boundary     : 4611686018427387904
49      appl_ptr     : 0
50      hw_ptr       : 0
51
52    @param device_name: The output device for aplay.
53    @param duration: Duration supplied to aplay.
54    @return String output from the command (may be empty).
55    @raises CmdError when cmd returns <> 0.
56    """
57    cmd = ['aplay',
58           '-v', # show verbose details
59           '-D %s' % device_name,
60           '-d %d' % duration,
61           '-f cd', # format
62           APLAY_FILE,
63           '2>&1'] # verbose details
64    return utils.system_output(' '.join(cmd)).strip()
65
66
67def _check_play(device_name, duration, expected):
68    """Runs aplay command and checks the output against an expected result.
69
70    The expected results are compared as sets of tuples.
71
72    @param device_name: The output device for aplay.
73    @param duration: Duration supplied to aplay.
74    @param expected: The set of expected tuples.
75    @raises error.TestError for invalid output or invalidly matching expected.
76    """
77    error_msg = 'invalid response from aplay'
78    results = _play_audio(device_name, duration)
79    if not results.startswith("Playing raw data '%s' :" % APLAY_FILE):
80        raise error.TestError('%s: %s' % (error_msg, results))
81    result_set = utils.set_from_keyval_output(results, '[\s]*:[\s]*')
82    if set(expected) <= result_set:
83        return
84    raise error.TestError('%s: expected=%s.' %
85                          (error_msg, sorted(set(expected) - result_set)))
86
87
88class audio_Aplay(test.test):
89    """Checks that simple aplay functions correctly."""
90    version = 1
91
92    def run_once(self, duration=1, test_headphone=False):
93        """Run aplay and verify its output is as expected.
94
95        @param duration: The duration to run aplay in seconds.
96        @param test_headphone: If the value is true, test a headphone. If false,
97                               test an internal speaker.
98        """
99
100        # Check CRAS server is alive. If not, restart it and wait a second to
101        # get server ready.
102        if utils.get_service_pid('cras') == 0:
103            logging.debug("CRAS server is down. Restart it.")
104            utils.start_service('cras', ignore_status=True)
105            time.sleep(1)
106
107        # Skip test if there is no internal speaker on the board.
108        if not test_headphone:
109            board_type = utils.get_board_type()
110            board_name = utils.get_board()
111            if not audio_spec.has_internal_speaker(board_type, board_name):
112                logging.debug("No internal speaker. Skipping the test.")
113                return
114
115        if test_headphone:
116            output_node = audio_spec.get_headphone_node(utils.get_board())
117        else:
118            output_node = "INTERNAL_SPEAKER"
119        logging.debug("Test output device %s", output_node)
120
121        cras_utils.set_single_selected_output_node(output_node)
122
123        cras_device_type = cras_utils.get_selected_output_device_type()
124        logging.debug("Selected output device type=%s", cras_device_type)
125        if cras_device_type != output_node:
126            raise error.TestFail("Fail to select output device.")
127
128        cras_device_name = cras_utils.get_selected_output_device_name()
129        logging.debug("Selected output device name=%s", cras_device_name)
130        if cras_device_name is None:
131            raise error.TestFail("Fail to get selected output device.")
132
133        alsa_device_name = alsa_utils.convert_device_name(cras_device_name)
134
135        # Stop CRAS to make sure the audio device won't be occupied.
136        utils.stop_service('cras', ignore_status=True)
137        try:
138            _check_play(alsa_device_name, duration, APLAY_EXPECTED)
139        finally:
140            #Restart CRAS
141            utils.start_service('cras', ignore_status=True)
142