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)
|