15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/usr/bin/env python
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""Audio basic playback test.  Verifies basic audio output.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The tests plays an audio file and records its output while playing.  It uses
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)SOX tool to eliminate silence from begining and end of audio recorded.  Then it
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)uses PESQ tool to verify the recorded audio against expected file.
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The output of PESQ is a pair of numbers between 0 and 5 describing how close the
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)recorded audio is to its reference file.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)The output of this test are the PESQ values that are graphed, example:
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)RESULT audio_pesq: ref= 4.498 score
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)RESULT audio_pesq: actual= 4.547 score
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import logging
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import os
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import sys
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import tempfile
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import audio_tools
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import pyauto_media
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import pyauto
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import pyauto_utils
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_TEST_HTML_PATH = pyauto.PyUITest.GetFileURLForDataPath('media', 'html',
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                        'audio_record.html')
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_MEDIA_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(),
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           'pyauto_private', 'media'))
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The media file to play.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_TEST_AUDIO = pyauto.PyUITest.GetFileURLForPath(_MEDIA_PATH, 'dance2_10sec.wav')
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The recording duration should be at least as the media duration.  We use 5
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# extra seconds of record in this test.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)_RECORD_DURATION = 15
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Due to different noise introduced by audio recording tools on windows and
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# linux, we require an expected audio file per platform.
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if 'win32' in sys.platform:
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _TEST_EXPECTED_AUDIO = os.path.join(_MEDIA_PATH, 'audio_record_win.wav')
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)else:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  _TEST_EXPECTED_AUDIO = os.path.join(_MEDIA_PATH, 'audio_record_lin.wav')
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)def GetTempFilename():
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """Returns an absolute path to an empty temp file."""
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  f, path = tempfile.mkstemp(suffix='_actual.wav')
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  os.close(f)
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return path
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class AudioRecordPerfTest(pyauto.PyUITest):
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """PyAuto test container.  See file doc string for more information."""
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def testBasicAudioPlaybackRecord(self):
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Plays an audio file and verifies its output against expected."""
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The 2 temp files that will be potentially used in the test.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    temp_file = None
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    file_no_silence = None
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Rebase test expectation file is REBASE=1 env variable is set.
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rebase = 'REBASE' in os.environ
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    try:
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      temp_file = GetTempFilename()
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      record_thread = audio_tools.AudioRecorderThread(_RECORD_DURATION,
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                      temp_file)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      record_thread.start()
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.NavigateToURL(_TEST_HTML_PATH)
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.assertTrue(self.ExecuteJavascript("startTest('%s');" % _TEST_AUDIO))
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      record_thread.join()
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if record_thread.error:
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.fail(record_thread.error)
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      file_no_silence = _TEST_EXPECTED_AUDIO if rebase else GetTempFilename()
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      audio_tools.RemoveSilence(temp_file, file_no_silence)
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Exit if we just want to rebase expected output.
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if rebase:
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pesq_values = audio_tools.RunPESQ(_TEST_EXPECTED_AUDIO, file_no_silence)
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not pesq_values:
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.fail('Test failed to get pesq results.')
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pyauto_utils.PrintPerfResult('audio_pesq', 'ref', pesq_values[0], 'score')
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      pyauto_utils.PrintPerfResult('audio_pesq', 'actual', pesq_values[1],
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                   'score')
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    except:
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      logging.error('Test failed: %s', self.GetDOMValue('error'))
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    finally:
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Delete the temporary files used by the test.
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if temp_file:
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.remove(temp_file)
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if not rebase and file_no_silence:
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        os.remove(file_no_silence)
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if __name__ == '__main__':
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  pyauto_media.Main()
104