151 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
	
| # Copyright 2020 The Chromium Authors. All rights reserved.
 | |
| # Use of this source code is governed by a BSD-style license that can be
 | |
| # found in the LICENSE file.
 | |
| 
 | |
| import collections
 | |
| import logging
 | |
| import os
 | |
| import time
 | |
| 
 | |
| from autotest_lib.client.bin import utils
 | |
| from autotest_lib.client.common_lib import error
 | |
| from autotest_lib.client.common_lib.cros import chrome
 | |
| from autotest_lib.client.cros import constants as cros_constants
 | |
| from autotest_lib.client.cros.audio import audio_helper
 | |
| from autotest_lib.client.cros.audio import audio_test_data
 | |
| from autotest_lib.client.cros.audio import check_quality
 | |
| from autotest_lib.client.cros.audio import cmd_utils
 | |
| from autotest_lib.client.cros.audio import cras_utils
 | |
| from autotest_lib.client.cros.audio import sox_utils
 | |
| from autotest_lib.client.cros.multimedia import audio_facade_native
 | |
| 
 | |
| 
 | |
| CheckQualityArgsClass = collections.namedtuple(
 | |
|         'args_type', ['filename', 'rate', 'channel', 'bit_width'])
 | |
| 
 | |
| 
 | |
| class audio_AudioInputGain(audio_helper.cras_rms_test):
 | |
|     """Verifies input capture gain of chrome.audio API."""
 | |
|     version = 1
 | |
| 
 | |
|     ALOOP_CRAS_NODE_TYPE = 'ALSA_LOOPBACK'
 | |
|     ALOOP_MODULE_NAME = 'snd-aloop'
 | |
|     CAPTURE_DURATION = 1
 | |
|     # 25: -20 dB
 | |
|     # 75: 10 dB
 | |
|     # Expected gain: 10 * sqrt(10)
 | |
|     LOW_GAIN = 25
 | |
|     HIGH_GAIN = 75
 | |
|     EXPECTED_GAIN = 31.62
 | |
|     FREQ_TOLERANCE = 1
 | |
|     SECOND_PEAK_RATIO_TOLERANCE = 0.05
 | |
|     GAIN_TOLERANCE = 10
 | |
| 
 | |
|     def run_once(self):
 | |
|         """Entry point of this test."""
 | |
|         def cras_playback_record(gain_level):
 | |
|             """Do capture record at CRAS level.
 | |
| 
 | |
|             @param gain_level: The input gain level.
 | |
| 
 | |
|             @returns: A string for the recorded file path.
 | |
| 
 | |
|             """
 | |
|             # Sine raw file lasts 5 seconds
 | |
|             raw_path = os.path.join(self.bindir, '5SEC.raw')
 | |
|             raw_file = audio_test_data.GenerateAudioTestData(
 | |
|                     path=raw_path,
 | |
|                     duration_secs=5,
 | |
|                     frequencies=[440, 440],
 | |
|                     volume_scale=0.05)
 | |
| 
 | |
|             recorded_file = os.path.join(self.resultsdir,
 | |
|                                          'cras_recorded_%d.raw' % gain_level)
 | |
| 
 | |
|             # Note: we've found that a couple of seconds after Chrome is up,
 | |
|             #       there may be a ~30-second-long output stream sourced from
 | |
|             #       "What's New In Your Chromebook", and it plays no sound.
 | |
|             #       Just ignore it and continue testing.
 | |
|             p = cmd_utils.popen(cras_utils.playback_cmd(raw_file.path))
 | |
|             try:
 | |
|                 cras_utils.capture(recorded_file,
 | |
|                                    duration=self.CAPTURE_DURATION)
 | |
|                 # Make sure the audio is still playing.
 | |
|                 if p.poll() != None:
 | |
|                     raise error.TestError('playback stopped')
 | |
|             finally:
 | |
|                 cmd_utils.kill_or_log_returncode(p)
 | |
|                 raw_file.delete()
 | |
|             return recorded_file
 | |
| 
 | |
|         # Check CRAS server is alive. If not, restart it and wait a second to
 | |
|         # get server ready.
 | |
|         if utils.get_service_pid('cras') == 0:
 | |
|             logging.debug('CRAS server is down. Restart it.')
 | |
|             utils.start_service('cras', ignore_status=True)
 | |
|             time.sleep(1)
 | |
| 
 | |
|         utils.load_module(self.ALOOP_MODULE_NAME)
 | |
| 
 | |
|         try:
 | |
|             with chrome.Chrome(
 | |
|                     extension_paths=[cros_constants.AUDIO_TEST_EXTENSION],
 | |
|                     autotest_ext=True) as cr:
 | |
|                 audio_facade = audio_facade_native.AudioFacadeNative(cr)
 | |
|                 audio_facade.set_chrome_active_node_type(
 | |
|                         self.ALOOP_CRAS_NODE_TYPE, self.ALOOP_CRAS_NODE_TYPE)
 | |
| 
 | |
|                 rms_value = []
 | |
|                 for gain in [self.LOW_GAIN, self.HIGH_GAIN]:
 | |
|                     logging.debug('Start testing loopback with gain %d.', gain)
 | |
|                     audio_facade.set_chrome_active_input_gain(gain)
 | |
|                     recorded_file = cras_playback_record(gain)
 | |
|                     args = CheckQualityArgsClass(filename=recorded_file,
 | |
|                                                  rate=48000,
 | |
|                                                  channel=1,
 | |
|                                                  bit_width=16)
 | |
|                     raw_data, rate = check_quality.read_audio_file(args)
 | |
|                     checker = check_quality.QualityChecker(raw_data, rate)
 | |
|                     # The highest frequency recorded would be near 24 Khz
 | |
|                     # as the max sample rate is 48000 in our tests.
 | |
|                     # So let's set ignore_high_freq to be 48000.
 | |
|                     checker.do_spectral_analysis(ignore_high_freq=48000,
 | |
|                                                  check_quality=False,
 | |
|                                                  quality_params=None)
 | |
|                     spectra = checker._spectrals
 | |
|                     primary_freq = float(spectra[0][0][0])
 | |
|                     if abs(primary_freq - 440.0) > self.FREQ_TOLERANCE:
 | |
|                         raise error.TestFail(
 | |
|                                 'Primary freq is beyond the expectation: '
 | |
|                                 'got %.2f, expected 440.00, tolerance %f' %
 | |
|                                         (primary_freq, self.FREQ_TOLERANCE))
 | |
| 
 | |
|                     if len(spectra[0]) > 1:
 | |
|                         peak_ratio = (float(spectra[0][1][1]) /
 | |
|                                 float(spectra[0][0][1]))
 | |
|                         if peak_ratio > self.SECOND_PEAK_RATIO_TOLERANCE:
 | |
|                             raise error.TestFail(
 | |
|                                     'The second peak is not negligible: '
 | |
|                                     'f %.2f, peak_ratio %f (tolerance %f)' %
 | |
|                                             (float(spectra[0][1][0]),
 | |
|                                              peak_ratio,
 | |
|                                              self.SECOND_PEAK_RATIO_TOLERANCE))
 | |
| 
 | |
|                     sox_stat = sox_utils.get_stat(input=recorded_file,
 | |
|                                                   channels=1,
 | |
|                                                   bits=16,
 | |
|                                                   rate=48000)
 | |
|                     rms_value.append(float(sox_stat.rms))
 | |
|                     logging.debug('signal RMS from sox = %f', rms_value[-1])
 | |
| 
 | |
|                 gain = rms_value[1] / rms_value[0]
 | |
|                 if abs(gain - self.EXPECTED_GAIN) > self.GAIN_TOLERANCE:
 | |
|                     raise error.TestFail(
 | |
|                         'Gain is beyond the expectation: '
 | |
|                         'got %.2f, expected %.2f, tolerance %f' %
 | |
|                                 (gain, self.EXPECTED_GAIN, self.GAIN_TOLERANCE))
 | |
|         finally:
 | |
|             utils.stop_service('cras', ignore_status=True)
 | |
|             utils.unload_module(self.ALOOP_MODULE_NAME)
 | |
|             utils.start_service('cras', ignore_status=True)
 |