1 #!/usr/bin/env python 2 # Copyright (c) 2013 The Chromium 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 import os 7 import sys 8 import tempfile 9 import time 10 11 import media.audio_tools as audio_tools 12 13 # Note: pyauto_functional must come before pyauto. 14 import pyauto_functional 15 import pyauto 16 import pyauto_utils 17 import webrtc_test_base 18 19 _MEDIA_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(), 20 'pyauto_private', 'webrtc')) 21 if 'win32' in sys.platform: 22 _REFERENCE_FILE = os.path.join(_MEDIA_PATH, 'human-voice-win.wav') 23 else: 24 _REFERENCE_FILE = os.path.join(_MEDIA_PATH, 'human-voice-linux.wav') 25 _JAVASCRIPT_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(), 26 'webrtc')) 27 28 29 class WebrtcAudioQualityTest(webrtc_test_base.WebrtcTestBase): 30 """Test we can set up a WebRTC call and play audio through it. 31 32 This test will only work on machines that have been configured to record their 33 own input*. 34 35 * On Linux: 36 1. # sudo apt-get install pavucontrol 37 2. For the user who will run the test: # pavucontrol 38 3. In a separate terminal, # arecord dummy 39 4. In pavucontrol, go to the recording tab. 40 5. For the ALSA plug-in [aplay]: ALSA Capture from, change from <x> to 41 <Monitor of x>, where x is whatever your primary sound device is called. 42 6. Try launching chrome as the target user on the target machine, try 43 playing, say, a YouTube video, and record with # arecord -f dat mine.dat. 44 Verify the recording with aplay (should have recorded what you played 45 from chrome). 46 47 * On Windows 7: 48 1. Control panel > Sound > Manage audio devices. 49 2. In the recording tab, right-click in an empty space in the pane with the 50 devices. Tick 'show disabled devices'. 51 3. You should see a 'stero mix' device - this is what your speakers output. 52 Right click > Properties. 53 4. In the Listen tab for the mix device, check the 'listen to this device' 54 checkbox. Ensure the mix device is the default recording device. 55 5. Launch chrome and try playing a video with sound. You should see movement 56 in the volume meter for the mix device. Configure the mix device to have 57 50 / 100 in level. Also go into the playback tab, right-click Speakers, 58 and set that level to 50 / 100. Otherwise you will get distortion in 59 the recording. 60 """ 61 def setUp(self): 62 pyauto.PyUITest.setUp(self) 63 self.StartPeerConnectionServer() 64 65 def tearDown(self): 66 self.StopPeerConnectionServer() 67 68 pyauto.PyUITest.tearDown(self) 69 self.assertEquals('', self.CheckErrorsAndCrashes()) 70 71 def testWebrtcAudioCallAndMeasureQuality(self): 72 """Measures how much WebRTC distorts speech. 73 74 The input file is about 9.3 seconds long and has had silence trimmed on both 75 sides. We will set up a WebRTC call, load the file with WebAudio in the 76 javascript, connect the WebAudio buffer node to the peer connection and play 77 it out on the other side (in a video tag). 78 79 We originally got the input file by playing a file through this test and 80 using the resulting file. The purpose is to lessen the impact on the score 81 from known distortions such as comfort noise. You can do such a rebase on 82 the _REFERENCE_FILE by setting REBASE=1 before running the test. The file 83 will end up in the system temp folder and will end with _webrtc.wav. 84 85 We then record what Chrome plays out. We give it plenty of time to play 86 the whole file over the connection, and then we trim silence on both ends. 87 That is finally fed into PESQ for comparison. 88 """ 89 # We'll use a relative path since the javascript will be loading the file 90 # relative to where the javascript itself is. 91 self.assertTrue(os.path.exists(_MEDIA_PATH), 92 msg='Missing pyauto_private in chrome/test/data: you need ' 93 'to check out src_internal in your .gclient to run ' 94 'this test.') 95 96 input_relative_path = os.path.relpath(_REFERENCE_FILE, _JAVASCRIPT_PATH) 97 98 def CallWithWebAudio(): 99 self._AudioCallWithWebAudio(duration_seconds=15, 100 input_relative_path=input_relative_path) 101 102 def MeasureQuality(output_no_silence): 103 results = audio_tools.RunPESQ(_REFERENCE_FILE, output_no_silence, 104 sample_rate=16000) 105 self.assertTrue(results, msg=('Failed to compute PESQ (most likely, we ' 106 'recorded only silence)')) 107 pyauto_utils.PrintPerfResult('audio_pesq', 'raw_mos', results[0], 'score') 108 pyauto_utils.PrintPerfResult('audio_pesq', 'mos_lqo', results[1], 'score') 109 110 self._RecordAndVerify(record_duration_seconds=20, 111 sound_producing_function=CallWithWebAudio, 112 verification_function=MeasureQuality) 113 114 def _AudioCallWithWebAudio(self, duration_seconds, input_relative_path): 115 self.LoadTestPageInTwoTabs(test_page='webrtc_audio_quality_test.html'); 116 117 self.Connect('user_1', tab_index=0) 118 self.Connect('user_2', tab_index=1) 119 120 self.CreatePeerConnection(tab_index=0) 121 self.AddWebAudioFile(tab_index=0, input_relative_path=input_relative_path) 122 123 self.EstablishCall(from_tab_with_index=0, to_tab_with_index=1) 124 125 # Note: the media flow isn't necessarily established on the connection just 126 # because the ready state is ok on both sides. We sleep a bit between call 127 # establishment and playing to avoid cutting of the beginning of the audio 128 # file. 129 time.sleep(2) 130 self.PlayWebAudioFile(tab_index=0) 131 132 # Keep the call up while we detect audio. 133 time.sleep(duration_seconds) 134 135 # The hang-up will automatically propagate to the second tab. 136 self.HangUp(from_tab_with_index=0) 137 self.WaitUntilHangUpVerified(tab_index=1) 138 139 self.Disconnect(tab_index=0) 140 self.Disconnect(tab_index=1) 141 142 # Ensure we didn't miss any errors. 143 self.AssertNoFailures(tab_index=0) 144 self.AssertNoFailures(tab_index=1) 145 146 def _RecordAndVerify(self, record_duration_seconds, sound_producing_function, 147 verification_function): 148 audio_tools.ForceMicrophoneVolumeTo100Percent() 149 rebase = 'REBASE' in os.environ 150 151 # The two temp files that will be potentially used in the test. 152 temp_file = None 153 file_no_silence = None 154 try: 155 temp_file = self._CreateTempFile() 156 record_thread = audio_tools.AudioRecorderThread(record_duration_seconds, 157 temp_file, 158 record_mono=True) 159 record_thread.start() 160 sound_producing_function() 161 record_thread.join() 162 163 if record_thread.error: 164 self.fail(record_thread.error) 165 file_no_silence = self._CreateTempFile() 166 audio_tools.RemoveSilence(temp_file, file_no_silence) 167 168 verification_function(file_no_silence) 169 finally: 170 # Delete the temporary files used by the test. 171 if temp_file: 172 os.remove(temp_file) 173 if file_no_silence and not rebase: 174 os.remove(file_no_silence) 175 176 def _CreateTempFile(self): 177 """Returns an absolute path to an empty temp file.""" 178 file_handle, path = tempfile.mkstemp(suffix='_webrtc.wav') 179 os.close(file_handle) 180 return path 181 182 183 if __name__ == '__main__': 184 pyauto_functional.Main()