1# Copyright 2016 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 os
7import subprocess
8import tempfile
9import time
10
11import common
12from autotest_lib.client.common_lib import error
13from autotest_lib.client.common_lib.feedback import client
14from autotest_lib.server import test
15
16
17_BITS_PER_BYTE = 8
18# The amount of time to wait when producing silence (i.e. no playback).
19_SILENCE_DURATION_SECS = 5
20
21# Number of channels to generate.
22_DEFAULT_NUM_CHANNELS = 1
23# Sine wave sample rate (48kHz).
24_DEFAULT_SAMPLE_RATE = 48000
25# Sine wave default sample format is signed 16-bit PCM (two bytes).
26_DEFAULT_SAMPLE_WIDTH = 2
27# Default sine wave frequency.
28_DEFAULT_SINE_FREQUENCY = 440
29# Default duration of the sine wave in seconds.
30_DEFAULT_DURATION_SECS = 10
31
32class brillo_PlaybackAudioTest(test.test):
33    """Verify that basic audio playback works."""
34    version = 1
35
36    def __init__(self, *args, **kwargs):
37        super(brillo_PlaybackAudioTest, self).__init__(*args, **kwargs)
38        self.host = None
39
40
41    def _get_playback_cmd(self, method, dut_play_file):
42        """Get the playback command to execute based on the playback method.
43
44        @param method: A string specifiying which method to use.
45        @param dut_play_file: A string containing the path to the file to play
46                              on the DUT.
47        @return: A string containing the command to play audio using the
48                 specified method.
49
50        @raises TestError: Invalid playback method.
51        """
52        if dut_play_file:
53            return 'su root slesTest_playFdPath %s 0' % dut_play_file
54        if method == 'libmedia':
55            return 'brillo_audio_test --playback --libmedia --sine'
56        elif method == 'stagefright':
57            return 'brillo_audio_test --playback --stagefright --sine'
58        elif method == 'opensles':
59            return 'slesTest_sawtoothBufferQueue'
60        else:
61            raise error.TestError('Test called with invalid playback method.')
62
63
64    def test_playback(self, fb_query, playback_cmd, sample_width, sample_rate,
65                      duration_secs, num_channels, play_file_path=None):
66        """Performs a playback test.
67
68        @param fb_query: A feedback query.
69        @param playback_cmd: The playback generating command, or None for no-op.
70        @param play_file_path: A string of the path to the file being played.
71        @param sample_width: Sample width to test playback at.
72        @param sample_rate: Sample rate to test playback at.
73        @param num_channels: Number of channels to test playback with.
74        """
75        fb_query.prepare(sample_width=sample_width,
76                         sample_rate=sample_rate,
77                         duration_secs=duration_secs,
78                         num_channels=num_channels)
79        if playback_cmd:
80            self.host.run(playback_cmd)
81        else:
82            time.sleep(_SILENCE_DURATION_SECS)
83        if play_file_path:
84            fb_query.validate(audio_file=play_file_path)
85        else:
86            fb_query.validate()
87
88
89    def run_once(self, host, fb_client, playback_method, use_file=False,
90                 sample_width=_DEFAULT_SAMPLE_WIDTH,
91                 sample_rate=_DEFAULT_SAMPLE_RATE,
92                 num_channels=_DEFAULT_NUM_CHANNELS,
93                 duration_secs=_DEFAULT_DURATION_SECS):
94        """Runs the test.
95
96        @param host: A host object representing the DUT.
97        @param fb_client: A feedback client implementation.
98        @param playback_method: A string representing a playback method to use.
99                                Either 'opensles', 'libmedia', or 'stagefright'.
100        @param use_file: Use a file to test audio. Must be used with
101                         playback_method 'opensles'.
102        @param sample_width: Sample width to test playback at.
103        @param sample_rate: Sample rate to test playback at.
104        @param num_channels: Number of channels to test playback with.
105        @param duration_secs: Duration to play file for.
106        """
107        self.host = host
108        with fb_client.initialize(self, host):
109            logging.info('Testing silent playback')
110            fb_query = fb_client.new_query(client.QUERY_AUDIO_PLAYBACK_SILENT)
111            self.test_playback(fb_query=fb_query,
112                               playback_cmd=None,
113                               sample_rate=sample_rate,
114                               sample_width=sample_width,
115                               num_channels=num_channels,
116                               duration_secs=duration_secs)
117
118            dut_play_file = None
119            host_filename = None
120            if use_file:
121                _, host_filename = tempfile.mkstemp(
122                        prefix='sine-', suffix='.wav',
123                        dir=tempfile.mkdtemp(dir=fb_client.tmp_dir))
124                if sample_width == 1:
125                    sine_format = '-e unsigned'
126                else:
127                    sine_format = '-e signed'
128                gen_file_cmd = ('sox -n -t wav -c %d %s -b %d -r %d %s synth %d '
129                       'sine %d vol 0.9' % (num_channels, sine_format,
130                                            sample_width * _BITS_PER_BYTE,
131                                            sample_rate, host_filename,
132                                            duration_secs,
133                                            _DEFAULT_SINE_FREQUENCY))
134                logging.info('Command to generate sine wave: %s', gen_file_cmd)
135                subprocess.call(gen_file_cmd, shell=True)
136                logging.info('Send file to DUT.')
137                dut_tmp_dir = '/data'
138                dut_play_file = os.path.join(dut_tmp_dir, 'sine.wav')
139                logging.info('dut_play_file %s', dut_play_file)
140                host.send_file(host_filename, dut_play_file)
141
142            logging.info('Testing audible playback')
143            fb_query = fb_client.new_query(client.QUERY_AUDIO_PLAYBACK_AUDIBLE)
144            playback_cmd = self._get_playback_cmd(playback_method, dut_play_file)
145
146            self.test_playback(fb_query=fb_query,
147                               playback_cmd=playback_cmd,
148                               sample_rate=sample_rate,
149                               sample_width=sample_width,
150                               num_channels=num_channels,
151                               duration_secs=duration_secs,
152                               play_file_path=host_filename)
153