1 # Copyright 2016 The Chromium OS Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """This is a server side audio volume test using the Chameleon board.""" 6 7 import logging 8 import os 9 import time 10 11 from autotest_lib.client.common_lib import error 12 from autotest_lib.client.cros.chameleon import audio_test_utils 13 from autotest_lib.client.cros.chameleon import chameleon_audio_ids 14 from autotest_lib.client.cros.chameleon import chameleon_audio_helper 15 from autotest_lib.server.cros.audio import audio_test 16 from autotest_lib.server.cros.multimedia import remote_facade_factory 17 18 19 class 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 = 5 28 DELAY_AFTER_BINDING = 0.5 29 30 def run_once(self, host, source_id, sink_id, recorder_id, volume_spec, 31 golden_file, cfm_speaker=False, switch_hsp=False): 32 """Running audio volume test. 33 34 @param host: device under test CrosHost 35 @param source_id: An ID defined in chameleon_audio_ids for source. 36 @param sink_id: An ID defined in chameleon_audio_ids for sink if needed. 37 Currently this is only used on bluetooth. 38 @param recorder: An ID defined in chameleon_audio_ids for recording. 39 @param volume_spec: A tuple (low_volume, high_volume, highest_ratio). 40 Low volume and high volume specifies the two volumes 41 used in the test, and highest_ratio speficies the 42 highest acceptable value for 43 recorded_volume_low / recorded_volume_high. 44 For example, (50, 100, 0.2) asserts that 45 (recorded magnitude at volume 50) should be lower 46 than (recorded magnitude at volume 100) * 0.2. 47 @param golden_file: A test file defined in audio_test_data. 48 @param switch_hsp: Run a recording process on Cros device. This is 49 to trigger Cros switching from A2DP to HSP. 50 @param cfm_speaker: whether cfm_speaker's audio is tested which is an 51 external USB speaker on CFM (ChromeBox For Meetings) devices. 52 53 """ 54 if (source_id == chameleon_audio_ids.CrosIds.SPEAKER and 55 (not cfm_speaker and 56 not audio_test_utils.has_internal_speaker(host))): 57 return 58 59 chameleon_board = host.chameleon 60 factory = remote_facade_factory.RemoteFacadeFactory( 61 host, results_dir=self.resultsdir) 62 63 chameleon_board.setup_and_reset(self.outputdir) 64 65 widget_factory = chameleon_audio_helper.AudioWidgetFactory( 66 factory, host) 67 68 source = widget_factory.create_widget(source_id) 69 recorder = widget_factory.create_widget(recorder_id) 70 71 # Chameleon Mic does not need binding. 72 binding = (recorder_id != chameleon_audio_ids.ChameleonIds.MIC) 73 74 binder = None 75 76 if binding: 77 if sink_id: 78 sink = widget_factory.create_widget(sink_id) 79 binder = widget_factory.create_binder(source, sink, recorder) 80 else: 81 binder = widget_factory.create_binder(source, recorder) 82 83 low_volume, high_volume, highest_ratio = volume_spec 84 ignore_frequencies = [50, 60] 85 86 second_peak_ratio = audio_test_utils.get_second_peak_ratio( 87 source_id=source_id, 88 recorder_id=recorder_id, 89 is_hsp=switch_hsp) 90 91 with chameleon_audio_helper.bind_widgets(binder): 92 # Checks the node selected by cras is correct. 93 time.sleep(self.DELAY_AFTER_BINDING) 94 audio_facade = factory.create_audio_facade() 95 96 audio_test_utils.dump_cros_audio_logs( 97 host, audio_facade, self.resultsdir, 'after_binding') 98 99 if not cfm_speaker: 100 audio_test_utils.check_output_port(audio_facade, source.port_id) 101 else: 102 audio_test_utils.check_audio_nodes(audio_facade, 103 (['USB'], None)) 104 105 if switch_hsp: 106 audio_test_utils.switch_to_hsp(audio_facade) 107 108 logging.info('Setting playback data on Cros device') 109 source.set_playback_data(golden_file) 110 111 def playback_record(tag): 112 """Playback and record. 113 114 @param tag: file name tag. 115 116 """ 117 logging.info('Start recording from Chameleon.') 118 recorder.start_recording() 119 120 logging.info('Start playing %s on Cros device', 121 golden_file.path) 122 source.start_playback() 123 124 time.sleep(self.RECORD_SECONDS) 125 126 recorder.stop_recording() 127 logging.info('Stopped recording from Chameleon.') 128 129 audio_test_utils.dump_cros_audio_logs( 130 host, audio_facade, self.resultsdir, 131 'after_recording_' + 'tag') 132 133 source.stop_playback() 134 135 recorder.read_recorded_binary() 136 logging.info('Read recorded binary from Chameleon.') 137 138 recorded_file = os.path.join( 139 self.resultsdir, "recorded_%s.raw" % tag) 140 logging.info('Saving recorded data to %s', recorded_file) 141 recorder.save_file(recorded_file) 142 recorder.remove_head(0.5) 143 144 audio_facade.set_chrome_active_volume(low_volume) 145 playback_record('low') 146 low_dominant_spectrals = audio_test_utils.check_recorded_frequency( 147 golden_file, recorder, 148 second_peak_ratio=second_peak_ratio, 149 ignore_frequencies=ignore_frequencies) 150 151 audio_facade.set_chrome_active_volume(high_volume) 152 playback_record('high') 153 high_dominant_spectrals = audio_test_utils.check_recorded_frequency( 154 golden_file, recorder, 155 second_peak_ratio=second_peak_ratio, 156 ignore_frequencies=ignore_frequencies) 157 158 error_messages = [] 159 logging.info('low_dominant_spectrals: %s', low_dominant_spectrals) 160 logging.info('high_dominant_spectrals: %s', high_dominant_spectrals) 161 162 for channel in xrange(len(low_dominant_spectrals)): 163 _, low_coeff = low_dominant_spectrals[channel] 164 _, high_coeff = high_dominant_spectrals[channel] 165 ratio = low_coeff / high_coeff 166 logging.info('Channel %d volume(at %f) / volume(at %f) = %f', 167 channel, low_volume, high_volume, ratio) 168 if ratio > highest_ratio: 169 error_messages.append( 170 'Channel %d volume ratio: %f is incorrect.' % ( 171 channel, ratio)) 172 if error_messages: 173 raise error.TestFail( 174 'Failed volume check: %s' % ' '.join(error_messages)) 175