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