1# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Copyright 2016 The Chromium OS 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 is a server side internal speaker test using the Chameleon board,
7audio board and the audio box enclosure for sound isolation."""
8
9import logging
10import os
11import time
12
13from autotest_lib.server.cros.audio import audio_test
14from autotest_lib.client.cros.audio import audio_test_data
15from autotest_lib.client.cros.chameleon import audio_test_utils
16from autotest_lib.client.cros.chameleon import chameleon_audio_helper
17from autotest_lib.client.cros.chameleon import chameleon_audio_ids
18from autotest_lib.client.common_lib import error
19from autotest_lib.server.cros.multimedia import remote_facade_factory
20
21
22class audio_LeftRightInternalSpeaker(audio_test.AudioTest):
23    """Server side left/right internal speaker audio test.
24
25    This test verifies:
26    1. When a file with audio on the left channel is played, that a sound is
27       emitted from at least one speaker.
28    2. When a file with audio on the right channel is played, that a sound
29       is emitted from at least one speaker.
30
31    This test cannot verify:
32    1. If the speaker making the sound is not the one corresponding to the
33       channel the audio was embedded in in the file.
34
35    """
36    version = 1
37    DELAY_BEFORE_RECORD_SECONDS = 0.5
38    DELAY_AFTER_BINDING = 0.5
39    RECORD_SECONDS = 8
40    RIGHT_WAV_FILE_URL = (
41        'http://commondatastorage.googleapis.com/chromiumos-test-assets-'
42        'public/audio_test/chameleon/Speaker/right_440_half.wav')
43    LEFT_WAV_FILE_URL = (
44        'http://commondatastorage.googleapis.com/chromiumos-test-assets-'
45        'public/audio_test/chameleon/Speaker/left_440_half.wav')
46
47    def run_once(self, host, player):
48        """
49
50        Entry point for test case.
51
52        @param host: A reference to the DUT.
53        @param player: A string representing what audio player to use. Could
54                       be 'native' or 'browser'.
55
56        """
57
58        if not audio_test_utils.has_internal_speaker(host):
59            return
60
61        host.chameleon.setup_and_reset(self.outputdir)
62
63        facade_factory = remote_facade_factory.RemoteFacadeFactory(
64            host,
65            results_dir=self.resultsdir)
66        self.audio_facade = facade_factory.create_audio_facade()
67        self.browser_facade = facade_factory.create_browser_facade()
68
69        widget_factory = chameleon_audio_helper.AudioWidgetFactory(
70            facade_factory,
71            host)
72        self.sound_source = widget_factory.create_widget(
73            chameleon_audio_ids.CrosIds.SPEAKER)
74        self.sound_recorder = widget_factory.create_widget(
75            chameleon_audio_ids.ChameleonIds.MIC)
76
77        self.play_and_record(
78            host,
79            player,
80            'left')
81        self.process_and_save_data(channel='left')
82        self.validate_recorded_data(channel='left')
83
84        self.play_and_record(
85            host,
86            player,
87            'right')
88        self.process_and_save_data(channel='right')
89        self.validate_recorded_data(channel='right')
90
91
92    def play_and_record(self, host, player, channel):
93        """Play file using given details and record playback.
94
95        The recording is accessible through the recorder object and doesn't
96        need to be returned explicitly.
97
98        @param host: The DUT.
99        @param player: String name of audio player we intend to use.
100        @param channel: Either 'left' or 'right'
101
102        """
103
104        #audio_facade = factory.create_audio_facade()
105        audio_test_utils.dump_cros_audio_logs(
106            host, self.audio_facade, self.resultsdir,
107            'before_recording_' + channel)
108
109        # Verify that output node is correct.
110        output_nodes, _ = self.audio_facade.get_selected_node_types()
111        if output_nodes != ['INTERNAL_SPEAKER']:
112            raise error.TestFail(
113                '%s rather than internal speaker is selected on Cros '
114                'device' % output_nodes)
115        self.audio_facade.set_selected_output_volume(80)
116
117        if player == 'native':
118            data_format=dict(file_type='raw',
119                             sample_format='S16_LE',
120                             channel=2,
121                             rate=48000)
122            if channel == 'left':
123                frequencies = [440, 0]
124            else:
125                frequencies = [0, 440]
126            sound_file = audio_test_data.GenerateAudioTestData(
127                    data_format=data_format,
128                    path=os.path.join(self.bindir, '440_half.raw'),
129                    duration_secs=10,
130                    frequencies=frequencies)
131
132            logging.info('Going to use cras_test_client on CrOS')
133            logging.info('Playing the file %s', sound_file)
134            self.sound_source.set_playback_data(sound_file)
135            self.sound_source.start_playback()
136            time.sleep(self.DELAY_BEFORE_RECORD_SECONDS)
137            self.sound_recorder.start_recording()
138            time.sleep(self.RECORD_SECONDS)
139            self.sound_recorder.stop_recording()
140            self.sound_source.stop_playback()
141            sound_file.delete()
142            logging.info('Recording finished. Was done in format %s',
143                         self.sound_recorder.data_format)
144
145        elif player == 'browser':
146            if channel == 'left':
147                sound_file = self.LEFT_WAV_FILE_URL
148            else:
149                sound_file = self.RIGHT_WAV_FILE_URL
150
151            tab_descriptor = self.browser_facade.new_tab(sound_file)
152
153            time.sleep(self.DELAY_BEFORE_RECORD_SECONDS)
154            logging.info('Start recording from Chameleon.')
155            self.sound_recorder.start_recording()
156
157            time.sleep(self.RECORD_SECONDS)
158
159            self.sound_recorder.stop_recording()
160            logging.info('Stopped recording from Chameleon.')
161            self.browser_facade.close_tab(tab_descriptor)
162
163        else:
164            raise error.TestFail(
165                '%s is not in list of accepted audio players',
166                player)
167
168        audio_test_utils.dump_cros_audio_logs(
169            host, self.audio_facade, self.resultsdir,
170            'after_recording_' + channel)
171
172
173    def process_and_save_data(self, channel):
174        """Save recorded data to files and process for analysis.
175
176        @param channel: 'left' or 'right'.
177
178        """
179
180        self.sound_recorder.read_recorded_binary()
181        file_name = 'recorded_' + channel + '.raw'
182        unprocessed_file = os.path.join(self.resultsdir, file_name)
183        logging.info('Saving raw unprocessed output to %s', unprocessed_file)
184        self.sound_recorder.save_file(unprocessed_file)
185
186        # Removes the beginning of recorded data. This is to avoid artifact
187        # caused by Chameleon codec initialization in the beginning of
188        # recording.
189        self.sound_recorder.remove_head(1.0)
190
191        # Reduce noise
192        self.sound_recorder.lowpass_filter(1000)
193        file_name = 'recorded_filtered_' + channel + '.raw'
194        processsed_file = os.path.join(self.resultsdir, file_name)
195        logging.info('Saving processed sound output to %s', processsed_file)
196        self.sound_recorder.save_file(processsed_file)
197
198
199    def validate_recorded_data(self, channel):
200        """Read processed data and validate by comparing to golden file.
201
202        @param channel: 'left' or 'right'.
203
204        """
205
206        # Compares data by frequency. Audio signal recorded by microphone has
207        # gone through analog processing and through the air.
208        # This suffers from codec artifacts and noise on the path.
209        # Comparing data by frequency is more robust than comparing by
210        # correlation, which is suitable for fully-digital audio path like USB
211        # and HDMI.
212        logging.info('Validating recorded output for channel %s', channel)
213        audio_test_utils.check_recorded_frequency(
214            audio_test_data.SIMPLE_FREQUENCY_SPEAKER_TEST_FILE,
215            self.sound_recorder,
216            second_peak_ratio=0.1,
217            ignore_frequencies=[50, 60])
218