1# Lint as: python2, python3 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"""This is a server side audio volume test using the Chameleon board.""" 6 7import logging 8import os 9import time 10 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.cros.audio import audio_test_data 13from autotest_lib.client.cros.chameleon import audio_test_utils 14from autotest_lib.client.cros.chameleon import chameleon_audio_ids 15from autotest_lib.client.cros.chameleon import chameleon_audio_helper 16from autotest_lib.server.cros.audio import audio_test 17 18 19class audio_AudioVolume(audio_test.AudioTest): 20 """Server side audio volume test. 21 22 This test talks to a Chameleon board and a Cros device to verify 23 audio volume function of the Cros device. 24 25 """ 26 version = 1 27 RECORD_SECONDS = 8 28 DELAY_AFTER_BINDING = 0.5 29 DELAY_BEFORE_PLAYBACK = 0.5 30 31 def run_once(self, source_id): 32 """Running audio volume test. 33 34 @param source_id: An ID defined in chameleon_audio_ids for source. 35 """ 36 37 def get_recorder_id(source_id): 38 """ Get corresponding recorder_id for the source_id.""" 39 if source_id == chameleon_audio_ids.CrosIds.SPEAKER: 40 return chameleon_audio_ids.ChameleonIds.MIC 41 elif source_id == chameleon_audio_ids.CrosIds.HEADPHONE: 42 return chameleon_audio_ids.ChameleonIds.LINEIN 43 elif source_id == chameleon_audio_ids.CrosIds.HDMI: 44 return chameleon_audio_ids.ChameleonIds.HDMI 45 elif source_id == chameleon_audio_ids.CrosIds.USBOUT: 46 return chameleon_audio_ids.ChameleonIds.USBIN 47 return None 48 49 def get_volume_spec(source_id): 50 """ Get corresponding volume spec for the source_id. 51 52 @return volume_spec: A tuple (low_volume, high_volume, ratio). 53 Low volume and high volume specifies the two volumes 54 used in the test, and ratio specifies the 55 highest acceptable value for 56 recorded_volume_low / recorded_volume_high. 57 For example, (50, 100, 0.2) asserts that 58 (recorded magnitude at volume 50) should be lower 59 than (recorded magnitude at volume 100) * 0.2. 60 """ 61 if source_id == chameleon_audio_ids.CrosIds.SPEAKER: 62 return (50, 100, 0.85) 63 elif source_id == chameleon_audio_ids.CrosIds.HEADPHONE: 64 return (40, 80, 0.5) 65 elif source_id == chameleon_audio_ids.CrosIds.HDMI: 66 return (40, 80, 0.2) 67 elif source_id == chameleon_audio_ids.CrosIds.USBOUT: 68 return (40, 80, 0.2) 69 return None 70 71 def get_golden_file(source_id): 72 """ Create the golden file for the source_id. """ 73 if source_id == chameleon_audio_ids.CrosIds.SPEAKER: 74 return audio_test_data.GenerateAudioTestData( 75 path=os.path.join(self.bindir, 'fix_440_16.raw'), 76 duration_secs=10, 77 frequencies=[440, 440]) 78 return audio_test_data.GenerateAudioTestData( 79 path=os.path.join(self.bindir, 'fix_2k_1k_16.raw'), 80 duration_secs=10, 81 frequencies=[2000, 1000]) 82 83 if (source_id == chameleon_audio_ids.CrosIds.SPEAKER 84 and not audio_test_utils.has_internal_speaker(self.host)): 85 return 86 87 golden_file = get_golden_file(source_id) 88 89 source = self.widget_factory.create_widget(source_id) 90 91 recorder_id = get_recorder_id(source_id) 92 recorder = self.widget_factory.create_widget(recorder_id) 93 94 binder = None 95 # Chameleon Mic does not need binding. 96 if recorder_id != chameleon_audio_ids.ChameleonIds.MIC: 97 binder = self.widget_factory.create_binder(source, recorder) 98 99 low_volume, high_volume, highest_ratio = get_volume_spec(source_id) 100 ignore_frequencies = [50, 60] 101 102 second_peak_ratio = audio_test_utils.get_second_peak_ratio( 103 source_id=source_id, recorder_id=recorder_id, is_hsp=False) 104 105 with chameleon_audio_helper.bind_widgets(binder): 106 # Checks the node selected by cras is correct. 107 time.sleep(self.DELAY_AFTER_BINDING) 108 109 audio_test_utils.dump_cros_audio_logs( 110 self.host, self.facade, self.resultsdir, 'after_binding') 111 112 node_type = audio_test_utils.cros_port_id_to_cras_node_type( 113 source.port_id) 114 if node_type == 'HEADPHONE': 115 node_type = audio_test_utils.get_headphone_node(self.host) 116 audio_test_utils.check_and_set_chrome_active_node_types( 117 self.facade, node_type, None) 118 audio_test_utils.dump_cros_audio_logs( 119 self.host, self.facade, self.resultsdir, 'after_select') 120 audio_test_utils.check_output_port(self.facade, source.port_id) 121 122 logging.info('Setting playback data on Cros device') 123 source.set_playback_data(golden_file) 124 125 def playback_record(tag): 126 """Playback and record. 127 128 @param tag: file name tag. 129 130 """ 131 # Starts recording, waits for some time, and starts playing. 132 # This is to avoid artifact caused by Chameleon codec 133 # initialization. The gap should be removed later. 134 logging.info('Start recording from Chameleon.') 135 recorder.start_recording() 136 time.sleep(self.DELAY_BEFORE_PLAYBACK) 137 138 logging.info('Start playing %s on Cros device', 139 golden_file.path) 140 source.start_playback() 141 142 time.sleep(self.RECORD_SECONDS) 143 self.facade.check_audio_stream_at_selected_device() 144 recorder.stop_recording() 145 logging.info('Stopped recording from Chameleon.') 146 147 audio_test_utils.dump_cros_audio_logs( 148 self.host, self.facade, self.resultsdir, 149 'after_recording_' + 'tag') 150 151 source.stop_playback() 152 153 recorder.read_recorded_binary() 154 logging.info('Read recorded binary from Chameleon.') 155 156 recorder.remove_head(self.DELAY_BEFORE_PLAYBACK) 157 if node_type == "INTERNAL_SPEAKER": 158 recorder.lowpass_filter(1000) 159 recorded_file = os.path.join(self.resultsdir, 160 "recorded_%s.raw" % tag) 161 logging.info('Saving recorded data to %s', recorded_file) 162 recorder.save_file(recorded_file) 163 164 self.facade.set_chrome_active_volume(low_volume) 165 playback_record('low') 166 low_dominant_spectrals = audio_test_utils.check_recorded_frequency( 167 golden_file, 168 recorder, 169 second_peak_ratio=second_peak_ratio, 170 ignore_frequencies=ignore_frequencies) 171 172 self.facade.set_chrome_active_volume(high_volume) 173 playback_record('high') 174 high_dominant_spectrals = audio_test_utils.check_recorded_frequency( 175 golden_file, 176 recorder, 177 second_peak_ratio=second_peak_ratio, 178 ignore_frequencies=ignore_frequencies) 179 180 error_messages = [] 181 logging.info('low_dominant_spectrals: %s', low_dominant_spectrals) 182 logging.info('high_dominant_spectrals: %s', 183 high_dominant_spectrals) 184 185 for channel in range(len(low_dominant_spectrals)): 186 _, low_coeff = low_dominant_spectrals[channel] 187 _, high_coeff = high_dominant_spectrals[channel] 188 ratio = low_coeff / high_coeff 189 logging.info('Channel %d volume(at %f) / volume(at %f) = %f', 190 channel, low_volume, high_volume, ratio) 191 if ratio > highest_ratio: 192 error_messages.append( 193 'Channel %d volume ratio: %f is incorrect.' % 194 (channel, ratio)) 195 if error_messages: 196 raise error.TestFail( 197 'Failed volume check: %s' % ' '.join(error_messages)) 198