desktopui_AudioFeedback.py revision f80337a4fc85e970400240fb8ff62aedafdbcc66
1# Copyright (c) 2012 The Chromium 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 5import logging, threading, tempfile 6 7from autotest_lib.client.bin import test 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.cros import cros_ui_test, httpd 10from autotest_lib.client.cros.audio import audio_helper 11 12# Names of mixer controls. 13_CONTROL_MASTER = "'Master Playback Volume'" 14_CONTROL_HEADPHONE = "'Headphone Playback Volume'" 15_CONTROL_SPEAKER = "'Speaker Playback Volume'" 16_CONTROL_SPEAKER_HP = "'HP/Speakers'" 17_CONTROL_MIC_BOOST = "'Mic Boost Volume'" 18_CONTROL_CAPTURE = "'Capture Volume'" 19_CONTROL_PCM = "'PCM Playback Volume'" 20_CONTROL_DIGITAL = "'Digital Capture Volume'" 21_CONTROL_CAPTURE_SWITCH = "'Capture Switch'" 22 23# Default test configuration. 24_DEFAULT_CARD = '0' 25_DEFAULT_MIXER_SETTINGS = [{'name': _CONTROL_MASTER, 'value': "100%"}, 26 {'name': _CONTROL_HEADPHONE, 'value': "100%"}, 27 {'name': _CONTROL_MIC_BOOST, 'value': "50%"}, 28 {'name': _CONTROL_PCM, 'value': "100%"}, 29 {'name': _CONTROL_DIGITAL, 'value': "100%"}, 30 {'name': _CONTROL_CAPTURE, 'value': "100%"}, 31 {'name': _CONTROL_CAPTURE_SWITCH, 'value': "on"}] 32 33_CONTROL_SPEAKER_DEVICE = ['x86-alex', 'x86-mario', 'x86-zgb'] 34_CONTROL_SPEAKER_DEVICE_HP = ['stumpy', 'lumpy'] 35 36_DEFAULT_NUM_CHANNELS = 2 37_DEFAULT_RECORD_DURATION = 15 38# Minimum RMS value to consider a "pass". 39_DEFAULT_SOX_RMS_THRESHOLD = 0.30 40 41 42class RecordSampleThread(threading.Thread): 43 """Wraps the execution of arecord in a thread.""" 44 def __init__(self, audio, duration, recordfile): 45 threading.Thread.__init__(self) 46 self._audio = audio 47 self._duration = duration 48 self._recordfile = recordfile 49 50 def run(self): 51 self._audio.record_sample(self._duration, self._recordfile) 52 53 54class desktopui_AudioFeedback(cros_ui_test.UITest): 55 version = 1 56 57 def initialize(self, 58 card=_DEFAULT_CARD, 59 mixer_settings=_DEFAULT_MIXER_SETTINGS, 60 num_channels=_DEFAULT_NUM_CHANNELS, 61 record_duration=_DEFAULT_RECORD_DURATION, 62 sox_min_rms=_DEFAULT_SOX_RMS_THRESHOLD): 63 """Setup the deps for the test. 64 65 Args: 66 card: The index of the sound card to use. 67 mixer_settings: Alsa control settings to apply to the mixer before 68 starting the test. 69 num_channels: The number of channels on the device to test. 70 record_duration: How long of a sample to record. 71 sox_min_rms: The minimum RMS value to consider a pass. 72 73 Raises: 74 error.TestError if the deps can't be run. 75 """ 76 self._card = card 77 self._mixer_settings = mixer_settings 78 self._num_channels = num_channels 79 self._record_duration = record_duration 80 self._sox_min_rms = sox_min_rms 81 82 self._ah = audio_helper.AudioHelper(self) 83 self._ah.setup_deps(['sox']) 84 85 super(desktopui_AudioFeedback, self).initialize() 86 self._test_url = 'http://localhost:8000/youtube.html' 87 self._testServer = httpd.HTTPListener(8000, docroot=self.bindir) 88 self._testServer.run() 89 90 def run_once(self): 91 # Speaker control settings may differ from device to device. 92 if self.pyauto.ChromeOSBoard() in _CONTROL_SPEAKER_DEVICE: 93 self._mixer_settings.append({'name': _CONTROL_SPEAKER, 94 'value': "0%"}) 95 elif self.pyauto.ChromeOSBoard() in _CONTROL_SPEAKER_DEVICE_HP: 96 self._mixer_settings.append({'name': _CONTROL_SPEAKER_HP, 97 'value': "0%"}) 98 self._ah.set_mixer_controls(self._mixer_settings, self._card) 99 100 # Record a sample of "silence" to use as a noise profile. 101 with tempfile.NamedTemporaryFile(mode='w+t') as noise_file: 102 logging.info('Noise file: %s' % noise_file.name) 103 self._ah.record_sample(1, noise_file.name) 104 105 # Test each channel separately. Assume two channels. 106 for channel in xrange(0, self._num_channels): 107 self.loopback_test_one_channel(channel, noise_file.name) 108 109 def play_video(self): 110 """Plays a Youtube video to record audio samples. 111 112 Skipping initial 60 seconds so we can ignore initial silence 113 in the video. 114 """ 115 logging.info('Playing back youtube media file %s.' % self._test_url) 116 self.pyauto.NavigateToURL(self._test_url) 117 if not self.pyauto.WaitUntil(lambda: self.pyauto.ExecuteJavascript(""" 118 player_status = document.getElementById('player_status'); 119 window.domAutomationController.send(player_status.innerHTML); 120 """), expect_retval='player ready'): 121 raise error.TestError('Failed to load the Youtube player') 122 self.pyauto.ExecuteJavascript(""" 123 ytplayer.pauseVideo(); 124 ytplayer.seekTo(60, true); 125 ytplayer.playVideo(); 126 window.domAutomationController.send(''); 127 """) 128 129 def loopback_test_one_channel(self, channel, noise_file): 130 """Test loopback for a given channel. 131 132 Args: 133 channel: The channel to test loopback on. 134 noise_file: Noise profile to use for filtering, None to skip noise 135 filtering. 136 """ 137 with tempfile.NamedTemporaryFile(mode='w+t') as reduced_file: 138 with tempfile.NamedTemporaryFile(mode='w+t') as tmpfile: 139 record_thread = RecordSampleThread(self._ah, 140 self._record_duration, tmpfile.name) 141 self.play_video() 142 record_thread.start() 143 record_thread.join() 144 145 self._ah.noise_reduce_file(tmpfile.name, noise_file, 146 reduced_file.name) 147 self.check_recorded_audio(reduced_file.name, channel) 148 149 def check_recorded_audio(self, infile, channel): 150 """Runs the sox command to check if we captured audio. 151 152 Note: if we captured any sufficient loud audio which can generate 153 the rms_value greater than the threshold value, test will pass. 154 TODO (rohitbm) : find a way to compare the recorded audio with 155 an actual sample file. 156 157 Args: 158 infile: The file is to test for (strong) audio content via the RMS 159 method. 160 channel: The audio channel to test. 161 162 Raises: 163 error.TestFail if the RMS amplitude of the recording isn't above 164 the threshold. 165 """ 166 rms_val = self._ah.get_audio_rms(infile, channel) 167 # In case sox didn't return an RMS value. 168 if rms_val is None: 169 raise error.TestError( 170 'Failed to generate an audio RMS value from playback.') 171 172 logging.info('Got audio RMS value of %f. Minimum pass is %f.' % 173 (rms_val, self._sox_min_rms)) 174 if rms_val < self._sox_min_rms: 175 raise error.TestError( 176 'Audio RMS value %f too low. Minimum pass is %f.' % 177 (rms_val, self._sox_min_rms)) 178