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, 7 audio board and the audio box enclosure for sound isolation.""" 8 9 import logging 10 import os 11 import time 12 13 from autotest_lib.server.cros.audio import audio_test 14 from autotest_lib.client.cros.audio import audio_test_data 15 from autotest_lib.client.cros.chameleon import audio_test_utils 16 from autotest_lib.client.cros.chameleon import chameleon_audio_helper 17 from autotest_lib.client.cros.chameleon import chameleon_audio_ids 18 from autotest_lib.client.common_lib import error 19 from autotest_lib.server.cros.multimedia import remote_facade_factory 20 21 22 class 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