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