blob: f5e79912c05186a9908bdff1b472a42e3bb2ceb9 [file] [log] [blame]
# 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.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
LOW_GAIN = 25
HIGH_GAIN = 75
EXPECTED_GAIN = 100
FREQ_TOLERANCE = 1
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 feq is beyond the expectation: '
'got %.2f, expected 440.00, tolerance %f' %
(primary_freq, self.FREQ_TOLERANCE))
rms_value.append(float(spectra[0][0][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)