1*9c5db199SXin Li#!/usr/bin/python3 2*9c5db199SXin Li# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Li 7*9c5db199SXin Lifrom __future__ import absolute_import 8*9c5db199SXin Lifrom __future__ import division 9*9c5db199SXin Lifrom __future__ import print_function 10*9c5db199SXin Li 11*9c5db199SXin Liimport logging 12*9c5db199SXin Liimport numpy 13*9c5db199SXin Liimport os 14*9c5db199SXin Liimport re 15*9c5db199SXin Liimport subprocess 16*9c5db199SXin Liimport tempfile 17*9c5db199SXin Liimport threading 18*9c5db199SXin Liimport time 19*9c5db199SXin Li 20*9c5db199SXin Lifrom glob import glob 21*9c5db199SXin Lifrom autotest_lib.client.bin import test, utils 22*9c5db199SXin Lifrom autotest_lib.client.bin.input.input_device import * 23*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 24*9c5db199SXin Lifrom autotest_lib.client.cros.audio import audio_data 25*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cmd_utils 26*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cras_utils 27*9c5db199SXin Lifrom autotest_lib.client.cros.audio import sox_utils 28*9c5db199SXin Lifrom six.moves import range 29*9c5db199SXin Li 30*9c5db199SXin LiLD_LIBRARY_PATH = 'LD_LIBRARY_PATH' 31*9c5db199SXin Li 32*9c5db199SXin Li_AUDIO_DIAGNOSTICS_PATH = '/usr/bin/audio_diagnostics' 33*9c5db199SXin Li 34*9c5db199SXin Li_DEFAULT_NUM_CHANNELS = 2 35*9c5db199SXin Li_DEFAULT_REC_COMMAND = 'arecord -D hw:0,0 -d 10 -f dat' 36*9c5db199SXin Li_DEFAULT_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L' 37*9c5db199SXin Li_DEFAULT_PLAYBACK_VOLUME = 100 38*9c5db199SXin Li_DEFAULT_CAPTURE_GAIN = 2500 39*9c5db199SXin Li_DEFAULT_ALSA_MAX_VOLUME = '100%' 40*9c5db199SXin Li_DEFAULT_ALSA_CAPTURE_GAIN = '25dB' 41*9c5db199SXin Li_DEFAULT_VOLUME_LEVEL = 75 42*9c5db199SXin Li_DEFAULT_MIC_GAIN = 75 43*9c5db199SXin Li 44*9c5db199SXin Li# Minimum RMS value to pass when checking recorded file. 45*9c5db199SXin Li_DEFAULT_SOX_RMS_THRESHOLD = 0.08 46*9c5db199SXin Li 47*9c5db199SXin Li_JACK_VALUE_ON_RE = re.compile(r'.*values=on') 48*9c5db199SXin Li_HP_JACK_CONTROL_RE = re.compile(r'numid=(\d+).*Headphone\sJack') 49*9c5db199SXin Li_MIC_JACK_CONTROL_RE = re.compile(r'numid=(\d+).*Mic\sJack') 50*9c5db199SXin Li 51*9c5db199SXin Li_SOX_RMS_AMPLITUDE_RE = re.compile(r'RMS\s+amplitude:\s+(.+)') 52*9c5db199SXin Li_SOX_ROUGH_FREQ_RE = re.compile(r'Rough\s+frequency:\s+(.+)') 53*9c5db199SXin Li 54*9c5db199SXin Li_AUDIO_NOT_FOUND_RE = r'Audio\snot\sdetected' 55*9c5db199SXin Li_MEASURED_LATENCY_RE = r'Measured\sLatency:\s(\d+)\suS' 56*9c5db199SXin Li_REPORTED_LATENCY_RE = r'Reported\sLatency:\s(\d+)\suS' 57*9c5db199SXin Li 58*9c5db199SXin Li# Tools from platform/audiotest 59*9c5db199SXin LiAUDIOFUNTEST_PATH = 'audiofuntest' 60*9c5db199SXin LiAUDIOLOOP_PATH = 'looptest' 61*9c5db199SXin LiLOOPBACK_LATENCY_PATH = 'loopback_latency' 62*9c5db199SXin LiSOX_PATH = 'sox' 63*9c5db199SXin LiTEST_TONES_PATH = 'test_tones' 64*9c5db199SXin Li 65*9c5db199SXin Li_MINIMUM_NORM = 0.001 66*9c5db199SXin Li_CORRELATION_INDEX_THRESHOLD = 0.999 67*9c5db199SXin Li# The minimum difference of estimated frequencies between two sine waves. 68*9c5db199SXin Li_FREQUENCY_DIFF_THRESHOLD = 20 69*9c5db199SXin Li# The minimum RMS value of meaningful audio data. 70*9c5db199SXin Li_MEANINGFUL_RMS_THRESHOLD = 0.001 71*9c5db199SXin Li 72*9c5db199SXin Lidef set_mixer_controls(mixer_settings={}, card='0'): 73*9c5db199SXin Li """Sets all mixer controls listed in the mixer settings on card. 74*9c5db199SXin Li 75*9c5db199SXin Li @param mixer_settings: Mixer settings to set. 76*9c5db199SXin Li @param card: Index of audio card to set mixer settings for. 77*9c5db199SXin Li """ 78*9c5db199SXin Li logging.info('Setting mixer control values on %s', card) 79*9c5db199SXin Li for item in mixer_settings: 80*9c5db199SXin Li logging.info('Setting %s to %s on card %s', 81*9c5db199SXin Li item['name'], item['value'], card) 82*9c5db199SXin Li cmd = 'amixer -c %s cset name=%s %s' 83*9c5db199SXin Li cmd = cmd % (card, item['name'], item['value']) 84*9c5db199SXin Li try: 85*9c5db199SXin Li utils.system(cmd) 86*9c5db199SXin Li except error.CmdError: 87*9c5db199SXin Li # A card is allowed not to support all the controls, so don't 88*9c5db199SXin Li # fail the test here if we get an error. 89*9c5db199SXin Li logging.info('amixer command failed: %s', cmd) 90*9c5db199SXin Li 91*9c5db199SXin Lidef set_default_volume_levels(): 92*9c5db199SXin Li """Sets the default volume and default capture gain through cras_test_client. 93*9c5db199SXin Li 94*9c5db199SXin Li """ 95*9c5db199SXin Li logging.info('Setting audio levels to their defaults') 96*9c5db199SXin Li set_volume_levels(_DEFAULT_VOLUME_LEVEL, _DEFAULT_MIC_GAIN) 97*9c5db199SXin Li 98*9c5db199SXin Lidef set_volume_levels(volume, capture): 99*9c5db199SXin Li """Sets the volume and capture gain through cras_test_client. 100*9c5db199SXin Li 101*9c5db199SXin Li @param volume: The playback volume to set. 102*9c5db199SXin Li @param capture: The capture gain to set. 103*9c5db199SXin Li """ 104*9c5db199SXin Li logging.info('Setting volume: %d capture: %d', volume, capture) 105*9c5db199SXin Li try: 106*9c5db199SXin Li utils.system('/usr/bin/cras_test_client --volume %d' % volume) 107*9c5db199SXin Li utils.system('/usr/bin/cras_test_client --capture_gain %d' % capture) 108*9c5db199SXin Li utils.system('/usr/bin/cras_test_client --dump_server_info') 109*9c5db199SXin Li utils.system('/usr/bin/cras_test_client --mute 0') 110*9c5db199SXin Li except error.CmdError as e: 111*9c5db199SXin Li raise error.TestError( 112*9c5db199SXin Li '*** Can not tune volume through CRAS. *** (' + str(e) + ')') 113*9c5db199SXin Li 114*9c5db199SXin Li try: 115*9c5db199SXin Li utils.system('amixer -c 0 contents') 116*9c5db199SXin Li except error.CmdError as e: 117*9c5db199SXin Li logging.info('amixer command failed: %s', str(e)) 118*9c5db199SXin Li 119*9c5db199SXin Lidef loopback_latency_check(**args): 120*9c5db199SXin Li """Checks loopback latency. 121*9c5db199SXin Li 122*9c5db199SXin Li @param args: additional arguments for loopback_latency. 123*9c5db199SXin Li 124*9c5db199SXin Li @return A tuple containing measured and reported latency in uS. 125*9c5db199SXin Li Return None if no audio detected. 126*9c5db199SXin Li """ 127*9c5db199SXin Li noise_threshold = str(args['n']) if 'n' in args else '400' 128*9c5db199SXin Li 129*9c5db199SXin Li cmd = '%s -n %s -c' % (LOOPBACK_LATENCY_PATH, noise_threshold) 130*9c5db199SXin Li 131*9c5db199SXin Li output = utils.system_output(cmd, retain_output=True) 132*9c5db199SXin Li 133*9c5db199SXin Li # Sleep for a short while to make sure device is not busy anymore 134*9c5db199SXin Li # after called loopback_latency. 135*9c5db199SXin Li time.sleep(.1) 136*9c5db199SXin Li 137*9c5db199SXin Li measured_latency = None 138*9c5db199SXin Li reported_latency = None 139*9c5db199SXin Li for line in output.split('\n'): 140*9c5db199SXin Li match = re.search(_MEASURED_LATENCY_RE, line, re.I) 141*9c5db199SXin Li if match: 142*9c5db199SXin Li measured_latency = int(match.group(1)) 143*9c5db199SXin Li continue 144*9c5db199SXin Li match = re.search(_REPORTED_LATENCY_RE, line, re.I) 145*9c5db199SXin Li if match: 146*9c5db199SXin Li reported_latency = int(match.group(1)) 147*9c5db199SXin Li continue 148*9c5db199SXin Li if re.search(_AUDIO_NOT_FOUND_RE, line, re.I): 149*9c5db199SXin Li return None 150*9c5db199SXin Li if measured_latency and reported_latency: 151*9c5db199SXin Li return (measured_latency, reported_latency) 152*9c5db199SXin Li else: 153*9c5db199SXin Li # Should not reach here, just in case. 154*9c5db199SXin Li return None 155*9c5db199SXin Li 156*9c5db199SXin Lidef get_mixer_jack_status(jack_reg_exp): 157*9c5db199SXin Li """Gets the mixer jack status. 158*9c5db199SXin Li 159*9c5db199SXin Li @param jack_reg_exp: The regular expression to match jack control name. 160*9c5db199SXin Li 161*9c5db199SXin Li @return None if the control does not exist, return True if jack control 162*9c5db199SXin Li is detected plugged, return False otherwise. 163*9c5db199SXin Li """ 164*9c5db199SXin Li output = utils.system_output('amixer -c0 controls', retain_output=True) 165*9c5db199SXin Li numid = None 166*9c5db199SXin Li for line in output.split('\n'): 167*9c5db199SXin Li m = jack_reg_exp.match(line) 168*9c5db199SXin Li if m: 169*9c5db199SXin Li numid = m.group(1) 170*9c5db199SXin Li break 171*9c5db199SXin Li 172*9c5db199SXin Li # Proceed only when matched numid is not empty. 173*9c5db199SXin Li if numid: 174*9c5db199SXin Li output = utils.system_output('amixer -c0 cget numid=%s' % numid) 175*9c5db199SXin Li for line in output.split('\n'): 176*9c5db199SXin Li if _JACK_VALUE_ON_RE.match(line): 177*9c5db199SXin Li return True 178*9c5db199SXin Li return False 179*9c5db199SXin Li else: 180*9c5db199SXin Li return None 181*9c5db199SXin Li 182*9c5db199SXin Lidef get_hp_jack_status(): 183*9c5db199SXin Li """Gets the status of headphone jack.""" 184*9c5db199SXin Li status = get_mixer_jack_status(_HP_JACK_CONTROL_RE) 185*9c5db199SXin Li if status is not None: 186*9c5db199SXin Li return status 187*9c5db199SXin Li 188*9c5db199SXin Li # When headphone jack is not found in amixer, lookup input devices 189*9c5db199SXin Li # instead. 190*9c5db199SXin Li # 191*9c5db199SXin Li # TODO(hychao): Check hp/mic jack status dynamically from evdev. And 192*9c5db199SXin Li # possibly replace the existing check using amixer. 193*9c5db199SXin Li for evdev in glob('/dev/input/event*'): 194*9c5db199SXin Li device = InputDevice(evdev) 195*9c5db199SXin Li if device.is_hp_jack(): 196*9c5db199SXin Li return device.get_headphone_insert() 197*9c5db199SXin Li else: 198*9c5db199SXin Li return None 199*9c5db199SXin Li 200*9c5db199SXin Lidef get_mic_jack_status(): 201*9c5db199SXin Li """Gets the status of mic jack.""" 202*9c5db199SXin Li status = get_mixer_jack_status(_MIC_JACK_CONTROL_RE) 203*9c5db199SXin Li if status is not None: 204*9c5db199SXin Li return status 205*9c5db199SXin Li 206*9c5db199SXin Li # When mic jack is not found in amixer, lookup input devices instead. 207*9c5db199SXin Li for evdev in glob('/dev/input/event*'): 208*9c5db199SXin Li device = InputDevice(evdev) 209*9c5db199SXin Li if device.is_mic_jack(): 210*9c5db199SXin Li return device.get_microphone_insert() 211*9c5db199SXin Li else: 212*9c5db199SXin Li return None 213*9c5db199SXin Li 214*9c5db199SXin Li# Functions to test audio palyback. 215*9c5db199SXin Lidef play_sound(duration_seconds=None, audio_file_path=None): 216*9c5db199SXin Li """Plays a sound file found at |audio_file_path| for |duration_seconds|. 217*9c5db199SXin Li 218*9c5db199SXin Li If |audio_file_path|=None, plays a default audio file. 219*9c5db199SXin Li If |duration_seconds|=None, plays audio file in its entirety. 220*9c5db199SXin Li 221*9c5db199SXin Li @param duration_seconds: Duration to play sound. 222*9c5db199SXin Li @param audio_file_path: Path to the audio file. 223*9c5db199SXin Li """ 224*9c5db199SXin Li if not audio_file_path: 225*9c5db199SXin Li audio_file_path = '/usr/local/autotest/cros/audio/sine440.wav' 226*9c5db199SXin Li duration_arg = ('-d %d' % duration_seconds) if duration_seconds else '' 227*9c5db199SXin Li utils.system('aplay %s %s' % (duration_arg, audio_file_path)) 228*9c5db199SXin Li 229*9c5db199SXin Lidef get_play_sine_args(channel, odev='default', freq=1000, duration=10, 230*9c5db199SXin Li sample_size=16): 231*9c5db199SXin Li """Gets the command args to generate a sine wav to play to odev. 232*9c5db199SXin Li 233*9c5db199SXin Li @param channel: 0 for left, 1 for right; otherwize, mono. 234*9c5db199SXin Li @param odev: alsa output device. 235*9c5db199SXin Li @param freq: frequency of the generated sine tone. 236*9c5db199SXin Li @param duration: duration of the generated sine tone. 237*9c5db199SXin Li @param sample_size: output audio sample size. Default to 16. 238*9c5db199SXin Li """ 239*9c5db199SXin Li cmdargs = [SOX_PATH, '-b', str(sample_size), '-n', '-t', 'alsa', 240*9c5db199SXin Li odev, 'synth', str(duration)] 241*9c5db199SXin Li if channel == 0: 242*9c5db199SXin Li cmdargs += ['sine', str(freq), 'sine', '0'] 243*9c5db199SXin Li elif channel == 1: 244*9c5db199SXin Li cmdargs += ['sine', '0', 'sine', str(freq)] 245*9c5db199SXin Li else: 246*9c5db199SXin Li cmdargs += ['sine', str(freq)] 247*9c5db199SXin Li 248*9c5db199SXin Li return cmdargs 249*9c5db199SXin Li 250*9c5db199SXin Lidef play_sine(channel, odev='default', freq=1000, duration=10, 251*9c5db199SXin Li sample_size=16): 252*9c5db199SXin Li """Generates a sine wave and plays to odev. 253*9c5db199SXin Li 254*9c5db199SXin Li @param channel: 0 for left, 1 for right; otherwize, mono. 255*9c5db199SXin Li @param odev: alsa output device. 256*9c5db199SXin Li @param freq: frequency of the generated sine tone. 257*9c5db199SXin Li @param duration: duration of the generated sine tone. 258*9c5db199SXin Li @param sample_size: output audio sample size. Default to 16. 259*9c5db199SXin Li """ 260*9c5db199SXin Li cmdargs = get_play_sine_args(channel, odev, freq, duration, sample_size) 261*9c5db199SXin Li utils.system(' '.join(cmdargs)) 262*9c5db199SXin Li 263*9c5db199SXin Lidef get_audio_rms(sox_output): 264*9c5db199SXin Li """Gets the audio RMS value from sox stat output 265*9c5db199SXin Li 266*9c5db199SXin Li @param sox_output: Output of sox stat command. 267*9c5db199SXin Li 268*9c5db199SXin Li @return The RMS value parsed from sox stat output. 269*9c5db199SXin Li """ 270*9c5db199SXin Li for rms_line in sox_output.split('\n'): 271*9c5db199SXin Li m = _SOX_RMS_AMPLITUDE_RE.match(rms_line) 272*9c5db199SXin Li if m is not None: 273*9c5db199SXin Li return float(m.group(1)) 274*9c5db199SXin Li 275*9c5db199SXin Lidef get_rough_freq(sox_output): 276*9c5db199SXin Li """Gets the rough audio frequency from sox stat output 277*9c5db199SXin Li 278*9c5db199SXin Li @param sox_output: Output of sox stat command. 279*9c5db199SXin Li 280*9c5db199SXin Li @return The rough frequency value parsed from sox stat output. 281*9c5db199SXin Li """ 282*9c5db199SXin Li for rms_line in sox_output.split('\n'): 283*9c5db199SXin Li m = _SOX_ROUGH_FREQ_RE.match(rms_line) 284*9c5db199SXin Li if m is not None: 285*9c5db199SXin Li return int(m.group(1)) 286*9c5db199SXin Li 287*9c5db199SXin Lidef check_audio_rms(sox_output, sox_threshold=_DEFAULT_SOX_RMS_THRESHOLD): 288*9c5db199SXin Li """Checks if the calculated RMS value is expected. 289*9c5db199SXin Li 290*9c5db199SXin Li @param sox_output: The output from sox stat command. 291*9c5db199SXin Li @param sox_threshold: The threshold to test RMS value against. 292*9c5db199SXin Li 293*9c5db199SXin Li @raises error.TestError if RMS amplitude can't be parsed. 294*9c5db199SXin Li @raises error.TestFail if the RMS amplitude of the recording isn't above 295*9c5db199SXin Li the threshold. 296*9c5db199SXin Li """ 297*9c5db199SXin Li rms_val = get_audio_rms(sox_output) 298*9c5db199SXin Li 299*9c5db199SXin Li # In case we don't get a valid RMS value. 300*9c5db199SXin Li if rms_val is None: 301*9c5db199SXin Li raise error.TestError( 302*9c5db199SXin Li 'Failed to generate an audio RMS value from playback.') 303*9c5db199SXin Li 304*9c5db199SXin Li logging.info('Got audio RMS value of %f. Minimum pass is %f.', 305*9c5db199SXin Li rms_val, sox_threshold) 306*9c5db199SXin Li if rms_val < sox_threshold: 307*9c5db199SXin Li raise error.TestFail( 308*9c5db199SXin Li 'Audio RMS value %f too low. Minimum pass is %f.' % 309*9c5db199SXin Li (rms_val, sox_threshold)) 310*9c5db199SXin Li 311*9c5db199SXin Lidef noise_reduce_file(in_file, noise_file, out_file, 312*9c5db199SXin Li sox_format=_DEFAULT_SOX_FORMAT): 313*9c5db199SXin Li """Runs the sox command to reduce noise. 314*9c5db199SXin Li 315*9c5db199SXin Li Runs the sox command to noise-reduce in_file using the noise 316*9c5db199SXin Li profile from noise_file. 317*9c5db199SXin Li 318*9c5db199SXin Li @param in_file: The file to noise reduce. 319*9c5db199SXin Li @param noise_file: The file containing the noise profile. 320*9c5db199SXin Li This can be created by recording silence. 321*9c5db199SXin Li @param out_file: The file contains the noise reduced sound. 322*9c5db199SXin Li @param sox_format: The sox format to generate sox command. 323*9c5db199SXin Li """ 324*9c5db199SXin Li prof_cmd = '%s -c 2 %s %s -n noiseprof' % (SOX_PATH, 325*9c5db199SXin Li sox_format, noise_file) 326*9c5db199SXin Li reduce_cmd = ('%s -c 2 %s %s -c 2 %s %s noisered' % 327*9c5db199SXin Li (SOX_PATH, sox_format, in_file, sox_format, out_file)) 328*9c5db199SXin Li utils.system('%s | %s' % (prof_cmd, reduce_cmd)) 329*9c5db199SXin Li 330*9c5db199SXin Lidef record_sample(tmpfile, record_command=_DEFAULT_REC_COMMAND): 331*9c5db199SXin Li """Records a sample from the default input device. 332*9c5db199SXin Li 333*9c5db199SXin Li @param tmpfile: The file to record to. 334*9c5db199SXin Li @param record_command: The command to record audio. 335*9c5db199SXin Li """ 336*9c5db199SXin Li utils.system('%s %s' % (record_command, tmpfile)) 337*9c5db199SXin Li 338*9c5db199SXin Lidef create_wav_file(wav_dir, prefix=""): 339*9c5db199SXin Li """Creates a unique name for wav file. 340*9c5db199SXin Li 341*9c5db199SXin Li The created file name will be preserved in autotest result directory 342*9c5db199SXin Li for future analysis. 343*9c5db199SXin Li 344*9c5db199SXin Li @param wav_dir: The directory of created wav file. 345*9c5db199SXin Li @param prefix: specified file name prefix. 346*9c5db199SXin Li """ 347*9c5db199SXin Li filename = "%s-%s.wav" % (prefix, time.time()) 348*9c5db199SXin Li return os.path.join(wav_dir, filename) 349*9c5db199SXin Li 350*9c5db199SXin Lidef run_in_parallel(*funs): 351*9c5db199SXin Li """Runs methods in parallel. 352*9c5db199SXin Li 353*9c5db199SXin Li @param funs: methods to run. 354*9c5db199SXin Li """ 355*9c5db199SXin Li threads = [] 356*9c5db199SXin Li for f in funs: 357*9c5db199SXin Li t = threading.Thread(target=f) 358*9c5db199SXin Li t.start() 359*9c5db199SXin Li threads.append(t) 360*9c5db199SXin Li 361*9c5db199SXin Li for t in threads: 362*9c5db199SXin Li t.join() 363*9c5db199SXin Li 364*9c5db199SXin Lidef get_channel_sox_stat( 365*9c5db199SXin Li input_audio, channel_index, channels=2, bits=16, rate=48000): 366*9c5db199SXin Li """Gets the sox stat info of the selected channel in the input audio file. 367*9c5db199SXin Li 368*9c5db199SXin Li @param input_audio: The input audio file to be analyzed. 369*9c5db199SXin Li @param channel_index: The index of the channel to be analyzed. 370*9c5db199SXin Li (1 for the first channel). 371*9c5db199SXin Li @param channels: The number of channels in the input audio. 372*9c5db199SXin Li @param bits: The number of bits of each audio sample. 373*9c5db199SXin Li @param rate: The sampling rate. 374*9c5db199SXin Li """ 375*9c5db199SXin Li if channel_index <= 0 or channel_index > channels: 376*9c5db199SXin Li raise ValueError('incorrect channel_indexi: %d' % channel_index) 377*9c5db199SXin Li 378*9c5db199SXin Li if channels == 1: 379*9c5db199SXin Li return sox_utils.get_stat( 380*9c5db199SXin Li input_audio, channels=channels, bits=bits, rate=rate) 381*9c5db199SXin Li 382*9c5db199SXin Li p1 = cmd_utils.popen( 383*9c5db199SXin Li sox_utils.extract_channel_cmd( 384*9c5db199SXin Li input_audio, '-', channel_index, 385*9c5db199SXin Li channels=channels, bits=bits, rate=rate), 386*9c5db199SXin Li stdout=subprocess.PIPE) 387*9c5db199SXin Li p2 = cmd_utils.popen( 388*9c5db199SXin Li sox_utils.stat_cmd('-', channels=1, bits=bits, rate=rate), 389*9c5db199SXin Li stdin=p1.stdout, stderr=subprocess.PIPE) 390*9c5db199SXin Li stat_output = p2.stderr.read() 391*9c5db199SXin Li cmd_utils.wait_and_check_returncode(p1, p2) 392*9c5db199SXin Li return sox_utils.parse_stat_output(stat_output) 393*9c5db199SXin Li 394*9c5db199SXin Li 395*9c5db199SXin Lidef get_rms(input_audio, channels=1, bits=16, rate=48000): 396*9c5db199SXin Li """Gets the RMS values of all channels of the input audio. 397*9c5db199SXin Li 398*9c5db199SXin Li @param input_audio: The input audio file to be checked. 399*9c5db199SXin Li @param channels: The number of channels in the input audio. 400*9c5db199SXin Li @param bits: The number of bits of each audio sample. 401*9c5db199SXin Li @param rate: The sampling rate. 402*9c5db199SXin Li """ 403*9c5db199SXin Li stats = [get_channel_sox_stat( 404*9c5db199SXin Li input_audio, i + 1, channels=channels, bits=bits, 405*9c5db199SXin Li rate=rate) for i in range(channels)] 406*9c5db199SXin Li 407*9c5db199SXin Li logging.info('sox stat: %s', [str(s) for s in stats]) 408*9c5db199SXin Li return [s.rms for s in stats] 409*9c5db199SXin Li 410*9c5db199SXin Li 411*9c5db199SXin Lidef reduce_noise_and_get_rms( 412*9c5db199SXin Li input_audio, noise_file, channels=1, bits=16, rate=48000): 413*9c5db199SXin Li """Reduces noise in the input audio by the given noise file and then gets 414*9c5db199SXin Li the RMS values of all channels of the input audio. 415*9c5db199SXin Li 416*9c5db199SXin Li @param input_audio: The input audio file to be analyzed. 417*9c5db199SXin Li @param noise_file: The noise file used to reduce noise in the input audio. 418*9c5db199SXin Li @param channels: The number of channels in the input audio. 419*9c5db199SXin Li @param bits: The number of bits of each audio sample. 420*9c5db199SXin Li @param rate: The sampling rate. 421*9c5db199SXin Li """ 422*9c5db199SXin Li with tempfile.NamedTemporaryFile() as reduced_file: 423*9c5db199SXin Li p1 = cmd_utils.popen( 424*9c5db199SXin Li sox_utils.noise_profile_cmd( 425*9c5db199SXin Li noise_file, '-', channels=channels, bits=bits, 426*9c5db199SXin Li rate=rate), 427*9c5db199SXin Li stdout=subprocess.PIPE) 428*9c5db199SXin Li p2 = cmd_utils.popen( 429*9c5db199SXin Li sox_utils.noise_reduce_cmd( 430*9c5db199SXin Li input_audio, reduced_file.name, '-', 431*9c5db199SXin Li channels=channels, bits=bits, rate=rate), 432*9c5db199SXin Li stdin=p1.stdout) 433*9c5db199SXin Li cmd_utils.wait_and_check_returncode(p1, p2) 434*9c5db199SXin Li return get_rms(reduced_file.name, channels, bits, rate) 435*9c5db199SXin Li 436*9c5db199SXin Li 437*9c5db199SXin Lidef cras_rms_test_setup(): 438*9c5db199SXin Li """Setups for the cras_rms_tests. 439*9c5db199SXin Li 440*9c5db199SXin Li To make sure the line_out-to-mic_in path is all green. 441*9c5db199SXin Li """ 442*9c5db199SXin Li # TODO(owenlin): Now, the nodes are choosed by chrome. 443*9c5db199SXin Li # We should do it here. 444*9c5db199SXin Li cras_utils.set_system_volume(_DEFAULT_PLAYBACK_VOLUME) 445*9c5db199SXin Li cras_utils.set_selected_output_node_volume(_DEFAULT_PLAYBACK_VOLUME) 446*9c5db199SXin Li 447*9c5db199SXin Li cras_utils.set_system_mute(False) 448*9c5db199SXin Li cras_utils.set_capture_mute(False) 449*9c5db199SXin Li 450*9c5db199SXin Li 451*9c5db199SXin Lidef dump_rms_retrospective(result_dir): 452*9c5db199SXin Li """Dumps retrospective for rms tests.""" 453*9c5db199SXin Li try: 454*9c5db199SXin Li dump_audio_diagnostics( 455*9c5db199SXin Li os.path.join(result_dir, "audio_diagnostics.txt")) 456*9c5db199SXin Li except Exception: 457*9c5db199SXin Li logging.exception('Error while generating retrospective report') 458*9c5db199SXin Li 459*9c5db199SXin Li 460*9c5db199SXin Lidef dump_audio_diagnostics(file_path=None): 461*9c5db199SXin Li """Dumps audio diagnostics results to a file 462*9c5db199SXin Li 463*9c5db199SXin Li Dumps the result of audio_diagnostics to a file. Returns a string 464*9c5db199SXin Li containing the result if the file_path is not specified. 465*9c5db199SXin Li 466*9c5db199SXin Li @returns: None if 'file_path' is specified, otherwise, a string containing 467*9c5db199SXin Li the audio diagnostic results. 468*9c5db199SXin Li """ 469*9c5db199SXin Li if file_path: 470*9c5db199SXin Li with open(file_path, 'w') as f: 471*9c5db199SXin Li return cmd_utils.execute([_AUDIO_DIAGNOSTICS_PATH], stdout=f) 472*9c5db199SXin Li 473*9c5db199SXin Li return cmd_utils.execute([_AUDIO_DIAGNOSTICS_PATH], stdout=subprocess.PIPE) 474*9c5db199SXin Li 475*9c5db199SXin Li 476*9c5db199SXin Lidef get_max_cross_correlation(signal_a, signal_b): 477*9c5db199SXin Li """Gets max cross-correlation and best time delay of two signals. 478*9c5db199SXin Li 479*9c5db199SXin Li Computes cross-correlation function between two 480*9c5db199SXin Li signals and gets the maximum value and time delay. 481*9c5db199SXin Li The steps includes: 482*9c5db199SXin Li 1. Compute cross-correlation function of X and Y and get Cxy. 483*9c5db199SXin Li The correlation function Cxy is an array where Cxy[k] is the 484*9c5db199SXin Li cross product of X and Y when Y is delayed by k. 485*9c5db199SXin Li Refer to manual of numpy.correlate for detail of correlation. 486*9c5db199SXin Li 2. Find the maximum value C_max and index C_index in Cxy. 487*9c5db199SXin Li 3. Compute L2 norm of X and Y to get norm(X) and norm(Y). 488*9c5db199SXin Li 4. Divide C_max by norm(X)*norm(Y) to get max cross-correlation. 489*9c5db199SXin Li 490*9c5db199SXin Li Max cross-correlation indicates the similarity of X and Y. The value 491*9c5db199SXin Li is 1 if X equals Y multiplied by a positive scalar. 492*9c5db199SXin Li The value is -1 if X equals Y multiplied by a negative scaler. 493*9c5db199SXin Li Any constant level shift will be regarded as distortion and will make 494*9c5db199SXin Li max cross-correlation value deviated from 1. 495*9c5db199SXin Li C_index is the best time delay of Y that make Y looks similar to X. 496*9c5db199SXin Li Refer to http://en.wikipedia.org/wiki/Cross-correlation. 497*9c5db199SXin Li 498*9c5db199SXin Li @param signal_a: A list of numbers which contains the first signal. 499*9c5db199SXin Li @param signal_b: A list of numbers which contains the second signal. 500*9c5db199SXin Li 501*9c5db199SXin Li @raises: ValueError if any number in signal_a or signal_b is not a float. 502*9c5db199SXin Li ValueError if norm of any array is less than _MINIMUM_NORM. 503*9c5db199SXin Li 504*9c5db199SXin Li @returns: A tuple (correlation index, best delay). If there are more than 505*9c5db199SXin Li one best delay, just return the first one. 506*9c5db199SXin Li """ 507*9c5db199SXin Li def check_list_contains_float(numbers): 508*9c5db199SXin Li """Checks the elements in a list are all float. 509*9c5db199SXin Li 510*9c5db199SXin Li @param numbers: A list of numbers. 511*9c5db199SXin Li 512*9c5db199SXin Li @raises: ValueError if there is any element which is not a float 513*9c5db199SXin Li in the list. 514*9c5db199SXin Li """ 515*9c5db199SXin Li if any(not isinstance(x, float) for x in numbers): 516*9c5db199SXin Li raise ValueError('List contains number which is not a float') 517*9c5db199SXin Li 518*9c5db199SXin Li check_list_contains_float(signal_a) 519*9c5db199SXin Li check_list_contains_float(signal_b) 520*9c5db199SXin Li 521*9c5db199SXin Li norm_a = numpy.linalg.norm(signal_a) 522*9c5db199SXin Li norm_b = numpy.linalg.norm(signal_b) 523*9c5db199SXin Li logging.debug('norm_a: %f', norm_a) 524*9c5db199SXin Li logging.debug('norm_b: %f', norm_b) 525*9c5db199SXin Li if norm_a <= _MINIMUM_NORM or norm_b <= _MINIMUM_NORM: 526*9c5db199SXin Li raise ValueError('No meaningful data as norm is too small.') 527*9c5db199SXin Li 528*9c5db199SXin Li correlation = numpy.correlate(signal_a, signal_b, 'full') 529*9c5db199SXin Li max_correlation = max(correlation) 530*9c5db199SXin Li best_delays = [i for i, j in enumerate(correlation) if j == max_correlation] 531*9c5db199SXin Li if len(best_delays) > 1: 532*9c5db199SXin Li logging.warning('There are more than one best delay: %r', best_delays) 533*9c5db199SXin Li return max_correlation / (norm_a * norm_b), best_delays[0] 534*9c5db199SXin Li 535*9c5db199SXin Li 536*9c5db199SXin Lidef trim_data(data, threshold=0): 537*9c5db199SXin Li """Trims a data by removing value that is too small in head and tail. 538*9c5db199SXin Li 539*9c5db199SXin Li Removes elements in head and tail whose absolute value is smaller than 540*9c5db199SXin Li or equal to threshold. 541*9c5db199SXin Li E.g. trim_data([0.0, 0.1, 0.2, 0.3, 0.2, 0.1, 0.0], 0.2) = 542*9c5db199SXin Li ([0.2, 0.3, 0.2], 2) 543*9c5db199SXin Li 544*9c5db199SXin Li @param data: A list of numbers. 545*9c5db199SXin Li @param threshold: The threshold to compare against. 546*9c5db199SXin Li 547*9c5db199SXin Li @returns: A tuple (trimmed_data, end_trimmed_length), where 548*9c5db199SXin Li end_trimmed_length is the length of original data being trimmed 549*9c5db199SXin Li from the end. 550*9c5db199SXin Li Returns ([], None) if there is no valid data. 551*9c5db199SXin Li """ 552*9c5db199SXin Li indice_valid = [ 553*9c5db199SXin Li i for i, j in enumerate(data) if abs(j) > threshold] 554*9c5db199SXin Li if not indice_valid: 555*9c5db199SXin Li logging.warning( 556*9c5db199SXin Li 'There is no element with absolute value greater ' 557*9c5db199SXin Li 'than threshold %f', threshold) 558*9c5db199SXin Li return [], None 559*9c5db199SXin Li logging.debug('Start and end of indice_valid: %d, %d', 560*9c5db199SXin Li indice_valid[0], indice_valid[-1]) 561*9c5db199SXin Li end_trimmed_length = len(data) - indice_valid[-1] - 1 562*9c5db199SXin Li logging.debug('Trimmed length in the end: %d', end_trimmed_length) 563*9c5db199SXin Li return (data[indice_valid[0] : indice_valid[-1] + 1], end_trimmed_length) 564*9c5db199SXin Li 565*9c5db199SXin Li 566*9c5db199SXin Lidef get_one_channel_correlation(test_data, golden_data): 567*9c5db199SXin Li """Gets max cross-correlation of test_data and golden_data. 568*9c5db199SXin Li 569*9c5db199SXin Li Trims test data and compute the max cross-correlation against golden_data. 570*9c5db199SXin Li Signal can be trimmed because those zero values in the head and tail of 571*9c5db199SXin Li a signal will not affect correlation computation. 572*9c5db199SXin Li 573*9c5db199SXin Li @param test_data: A list containing the data to compare against golden data. 574*9c5db199SXin Li @param golden_data: A list containing the golden data. 575*9c5db199SXin Li 576*9c5db199SXin Li @returns: A tuple (max cross-correlation, best_delay) if data is valid. 577*9c5db199SXin Li Otherwise returns (None, None). Refer to docstring of 578*9c5db199SXin Li get_max_cross_correlation. 579*9c5db199SXin Li """ 580*9c5db199SXin Li trimmed_test_data, end_trimmed_length = trim_data(test_data) 581*9c5db199SXin Li 582*9c5db199SXin Li def to_float(samples): 583*9c5db199SXin Li """Casts elements in the list to float. 584*9c5db199SXin Li 585*9c5db199SXin Li @param samples: A list of numbers. 586*9c5db199SXin Li 587*9c5db199SXin Li @returns: A list of original numbers casted to float. 588*9c5db199SXin Li """ 589*9c5db199SXin Li samples_float = [float(x) for x in samples] 590*9c5db199SXin Li return samples_float 591*9c5db199SXin Li 592*9c5db199SXin Li max_cross_correlation, best_delay = get_max_cross_correlation( 593*9c5db199SXin Li to_float(golden_data), 594*9c5db199SXin Li to_float(trimmed_test_data)) 595*9c5db199SXin Li 596*9c5db199SXin Li # The reason to add back the trimmed length in the end. 597*9c5db199SXin Li # E.g.: 598*9c5db199SXin Li # golden data: 599*9c5db199SXin Li # 600*9c5db199SXin Li # |-----------vvvv----------------| vvvv is the signal of interest. 601*9c5db199SXin Li # a b 602*9c5db199SXin Li # 603*9c5db199SXin Li # test data: 604*9c5db199SXin Li # 605*9c5db199SXin Li # |---x----vvvv--------x----------------| x is the place to trim. 606*9c5db199SXin Li # c d e f 607*9c5db199SXin Li # 608*9c5db199SXin Li # trimmed test data: 609*9c5db199SXin Li # 610*9c5db199SXin Li # |----vvvv--------| 611*9c5db199SXin Li # d e 612*9c5db199SXin Li # 613*9c5db199SXin Li # The first output of cross correlation computation : 614*9c5db199SXin Li # 615*9c5db199SXin Li # |-----------vvvv----------------| 616*9c5db199SXin Li # a b 617*9c5db199SXin Li # 618*9c5db199SXin Li # |----vvvv--------| 619*9c5db199SXin Li # d e 620*9c5db199SXin Li # 621*9c5db199SXin Li # The largest output of cross correlation computation happens at 622*9c5db199SXin Li # delay a + e. 623*9c5db199SXin Li # 624*9c5db199SXin Li # |-----------vvvv----------------| 625*9c5db199SXin Li # a b 626*9c5db199SXin Li # 627*9c5db199SXin Li # |----vvvv--------| 628*9c5db199SXin Li # d e 629*9c5db199SXin Li # 630*9c5db199SXin Li # Cross correlation starts computing by aligning the last sample 631*9c5db199SXin Li # of the trimmed test data to the first sample of golden data. 632*9c5db199SXin Li # The best delay calculated from trimmed test data and golden data 633*9c5db199SXin Li # cross correlation is e + a. But the real best delay that should be 634*9c5db199SXin Li # identical on two channel should be e + a + f. 635*9c5db199SXin Li # So we need to add back the length being trimmed in the end. 636*9c5db199SXin Li 637*9c5db199SXin Li if max_cross_correlation: 638*9c5db199SXin Li return max_cross_correlation, best_delay + end_trimmed_length 639*9c5db199SXin Li else: 640*9c5db199SXin Li return None, None 641*9c5db199SXin Li 642*9c5db199SXin Li 643*9c5db199SXin Lidef compare_one_channel_correlation(test_data, golden_data, parameters): 644*9c5db199SXin Li """Compares two one-channel data by correlation. 645*9c5db199SXin Li 646*9c5db199SXin Li @param test_data: A list containing the data to compare against golden data. 647*9c5db199SXin Li @param golden_data: A list containing the golden data. 648*9c5db199SXin Li @param parameters: A dict containing parameters for method. 649*9c5db199SXin Li 650*9c5db199SXin Li @returns: A dict containing: 651*9c5db199SXin Li index: The index of similarity where 1 means they are different 652*9c5db199SXin Li only by a positive scale. 653*9c5db199SXin Li best_delay: The best delay of test data in relative to golden 654*9c5db199SXin Li data. 655*9c5db199SXin Li equal: A bool containing comparing result. 656*9c5db199SXin Li """ 657*9c5db199SXin Li if 'correlation_threshold' in parameters: 658*9c5db199SXin Li threshold = parameters['correlation_threshold'] 659*9c5db199SXin Li else: 660*9c5db199SXin Li threshold = _CORRELATION_INDEX_THRESHOLD 661*9c5db199SXin Li 662*9c5db199SXin Li result_dict = dict() 663*9c5db199SXin Li max_cross_correlation, best_delay = get_one_channel_correlation( 664*9c5db199SXin Li test_data, golden_data) 665*9c5db199SXin Li result_dict['index'] = max_cross_correlation 666*9c5db199SXin Li result_dict['best_delay'] = best_delay 667*9c5db199SXin Li result_dict['equal'] = True if ( 668*9c5db199SXin Li max_cross_correlation and 669*9c5db199SXin Li max_cross_correlation > threshold) else False 670*9c5db199SXin Li logging.debug('result_dict: %r', result_dict) 671*9c5db199SXin Li return result_dict 672*9c5db199SXin Li 673*9c5db199SXin Li 674*9c5db199SXin Lidef compare_data_correlation(golden_data_binary, golden_data_format, 675*9c5db199SXin Li test_data_binary, test_data_format, 676*9c5db199SXin Li channel_map, parameters=None): 677*9c5db199SXin Li """Compares two raw data using correlation. 678*9c5db199SXin Li 679*9c5db199SXin Li @param golden_data_binary: The binary containing golden data. 680*9c5db199SXin Li @param golden_data_format: The data format of golden data. 681*9c5db199SXin Li @param test_data_binary: The binary containing test data. 682*9c5db199SXin Li @param test_data_format: The data format of test data. 683*9c5db199SXin Li @param channel_map: A list containing channel mapping. 684*9c5db199SXin Li E.g. [1, 0, None, None, None, None, None, None] means 685*9c5db199SXin Li channel 0 of test data should map to channel 1 of 686*9c5db199SXin Li golden data. Channel 1 of test data should map to 687*9c5db199SXin Li channel 0 of golden data. Channel 2 to 7 of test data 688*9c5db199SXin Li should be skipped. 689*9c5db199SXin Li @param parameters: A dict containing parameters for method, if needed. 690*9c5db199SXin Li 691*9c5db199SXin Li @raises: NotImplementedError if file type is not raw. 692*9c5db199SXin Li NotImplementedError if sampling rates of two data are not the same. 693*9c5db199SXin Li error.TestFail if golden data and test data are not equal. 694*9c5db199SXin Li """ 695*9c5db199SXin Li if parameters is None: 696*9c5db199SXin Li parameters = dict() 697*9c5db199SXin Li 698*9c5db199SXin Li if (golden_data_format['file_type'] != 'raw' or 699*9c5db199SXin Li test_data_format['file_type'] != 'raw'): 700*9c5db199SXin Li raise NotImplementedError('Only support raw data in compare_data.') 701*9c5db199SXin Li if (golden_data_format['rate'] != test_data_format['rate']): 702*9c5db199SXin Li raise NotImplementedError( 703*9c5db199SXin Li 'Only support comparing data with the same sampling rate') 704*9c5db199SXin Li golden_data = audio_data.AudioRawData( 705*9c5db199SXin Li binary=golden_data_binary, 706*9c5db199SXin Li channel=golden_data_format['channel'], 707*9c5db199SXin Li sample_format=golden_data_format['sample_format']) 708*9c5db199SXin Li test_data = audio_data.AudioRawData( 709*9c5db199SXin Li binary=test_data_binary, 710*9c5db199SXin Li channel=test_data_format['channel'], 711*9c5db199SXin Li sample_format=test_data_format['sample_format']) 712*9c5db199SXin Li compare_results = [] 713*9c5db199SXin Li for test_channel, golden_channel in enumerate(channel_map): 714*9c5db199SXin Li if golden_channel is None: 715*9c5db199SXin Li logging.info('Skipped channel %d', test_channel) 716*9c5db199SXin Li continue 717*9c5db199SXin Li test_data_one_channel = test_data.channel_data[test_channel] 718*9c5db199SXin Li golden_data_one_channel = golden_data.channel_data[golden_channel] 719*9c5db199SXin Li result_dict = dict(test_channel=test_channel, 720*9c5db199SXin Li golden_channel=golden_channel) 721*9c5db199SXin Li result_dict.update( 722*9c5db199SXin Li compare_one_channel_correlation( 723*9c5db199SXin Li test_data_one_channel, golden_data_one_channel, 724*9c5db199SXin Li parameters)) 725*9c5db199SXin Li compare_results.append(result_dict) 726*9c5db199SXin Li logging.info('compare_results: %r', compare_results) 727*9c5db199SXin Li for result in compare_results: 728*9c5db199SXin Li if not result['equal']: 729*9c5db199SXin Li error_msg = ('Failed on test channel %d and golden channel %d with ' 730*9c5db199SXin Li 'index %f') % ( 731*9c5db199SXin Li result['test_channel'], 732*9c5db199SXin Li result['golden_channel'], 733*9c5db199SXin Li result['index']) 734*9c5db199SXin Li logging.error(error_msg) 735*9c5db199SXin Li raise error.TestFail(error_msg) 736*9c5db199SXin Li # Also checks best delay are exactly the same. 737*9c5db199SXin Li best_delays = set([result['best_delay'] for result in compare_results]) 738*9c5db199SXin Li if len(best_delays) > 1: 739*9c5db199SXin Li error_msg = 'There are more than one best delay: %s' % best_delays 740*9c5db199SXin Li logging.error(error_msg) 741*9c5db199SXin Li raise error.TestFail(error_msg) 742*9c5db199SXin Li 743*9c5db199SXin Li 744*9c5db199SXin Lidef recorded_filesize_check(filesize, 745*9c5db199SXin Li duration, 746*9c5db199SXin Li channels=1, 747*9c5db199SXin Li rate=48000, 748*9c5db199SXin Li bits=16, 749*9c5db199SXin Li tolerant_ratio=0.1): 750*9c5db199SXin Li """Checks the recorded file size is correct. The check will pass if the 751*9c5db199SXin Li file size is larger than expected and smaller than our tolerant size. We 752*9c5db199SXin Li can tolerate size larger than expected as the way cras_test_client stop 753*9c5db199SXin Li recording base on duration is not always accurate but should record longer 754*9c5db199SXin Li than expected audio. 755*9c5db199SXin Li 756*9c5db199SXin Li @param filesize: Actually file size. 757*9c5db199SXin Li @param duration: Expected seconds to record. 758*9c5db199SXin Li @param channels: Number of channels to record. 759*9c5db199SXin Li @param rate: Recording sample rate. 760*9c5db199SXin Li @param bits: The sample bit width. 761*9c5db199SXin Li @param tolerant_ratio: The larger than expected file size ratio that we 762*9c5db199SXin Li regard as pass. 763*9c5db199SXin Li 764*9c5db199SXin Li @raises: TestFail if the size is less or larger than expect. 765*9c5db199SXin Li """ 766*9c5db199SXin Li expected = duration * channels * (bits // 8) * rate 767*9c5db199SXin Li ratio = abs(float(filesize) / expected) 768*9c5db199SXin Li if ratio < 1 or ratio > 1 + tolerant_ratio: 769*9c5db199SXin Li raise error.TestFail( 770*9c5db199SXin Li 'File size not correct: %d expect: %d' % (filesize, expected)) 771*9c5db199SXin Li 772*9c5db199SXin Li 773*9c5db199SXin Liclass _base_rms_test(test.test): 774*9c5db199SXin Li """Base class for all rms_test """ 775*9c5db199SXin Li 776*9c5db199SXin Li def postprocess(self): 777*9c5db199SXin Li super(_base_rms_test, self).postprocess() 778*9c5db199SXin Li 779*9c5db199SXin Li # Sum up the number of failed constraints in each iteration 780*9c5db199SXin Li if sum(len(x) for x in self.failed_constraints): 781*9c5db199SXin Li dump_audio_diagnostics(test.resultsdir) 782*9c5db199SXin Li 783*9c5db199SXin Li 784*9c5db199SXin Liclass chrome_rms_test(_base_rms_test): 785*9c5db199SXin Li """Base test class for audio RMS test with Chrome. 786*9c5db199SXin Li 787*9c5db199SXin Li The chrome instance can be accessed by self.chrome. 788*9c5db199SXin Li """ 789*9c5db199SXin Li def warmup(self): 790*9c5db199SXin Li super(chrome_rms_test, self).warmup() 791*9c5db199SXin Li 792*9c5db199SXin Li # Not all client of this file using telemetry. 793*9c5db199SXin Li # Just do the import here for those who really need it. 794*9c5db199SXin Li from autotest_lib.client.common_lib.cros import chrome 795*9c5db199SXin Li 796*9c5db199SXin Li self.chrome = chrome.Chrome(init_network_controller=True) 797*9c5db199SXin Li 798*9c5db199SXin Li # The audio configuration could be changed when we 799*9c5db199SXin Li # restart chrome. 800*9c5db199SXin Li try: 801*9c5db199SXin Li cras_rms_test_setup() 802*9c5db199SXin Li except Exception: 803*9c5db199SXin Li self.chrome.browser.Close() 804*9c5db199SXin Li raise 805*9c5db199SXin Li 806*9c5db199SXin Li 807*9c5db199SXin Li def cleanup(self, *args): 808*9c5db199SXin Li try: 809*9c5db199SXin Li self.chrome.close() 810*9c5db199SXin Li finally: 811*9c5db199SXin Li super(chrome_rms_test, self).cleanup() 812*9c5db199SXin Li 813*9c5db199SXin Liclass cras_rms_test(_base_rms_test): 814*9c5db199SXin Li """Base test class for CRAS audio RMS test.""" 815*9c5db199SXin Li 816*9c5db199SXin Li def warmup(self): 817*9c5db199SXin Li super(cras_rms_test, self).warmup() 818*9c5db199SXin Li # Stop ui to make sure there are not other streams. 819*9c5db199SXin Li utils.stop_service('ui', ignore_status=True) 820*9c5db199SXin Li cras_rms_test_setup() 821*9c5db199SXin Li 822*9c5db199SXin Li def cleanup(self, *args): 823*9c5db199SXin Li # Restart ui. 824*9c5db199SXin Li utils.start_service('ui', ignore_status=True) 825*9c5db199SXin Li 826*9c5db199SXin Li 827*9c5db199SXin Liclass alsa_rms_test(_base_rms_test): 828*9c5db199SXin Li """Base test class for ALSA audio RMS test. 829*9c5db199SXin Li 830*9c5db199SXin Li Note the warmup will take 10 seconds and the device cannot be used before it 831*9c5db199SXin Li returns. 832*9c5db199SXin Li """ 833*9c5db199SXin Li def warmup(self): 834*9c5db199SXin Li super(alsa_rms_test, self).warmup() 835*9c5db199SXin Li 836*9c5db199SXin Li cras_rms_test_setup() 837*9c5db199SXin Li # We need CRAS to initialize the volume and gain. 838*9c5db199SXin Li cras_utils.playback(playback_file="/dev/zero", duration=1) 839*9c5db199SXin Li # CRAS will release the device after 10 seconds. 840*9c5db199SXin Li time.sleep(11) 841