Home | History | Annotate | Download | only in functional
      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()