blob: 0fdb0c7ef40631ba046754c462ff4f515b154bbe [file] [log] [blame]
Hsinyu Chao4b8300e2011-11-15 13:07:32 -08001#!/usr/bin/python
2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +08006
Hsinyu Chao4b8300e2011-11-15 13:07:32 -08007import logging
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +08008import numpy
Hsinyu Chao4b8300e2011-11-15 13:07:32 -08009import os
Hsinyu Chaof80337a2012-04-07 18:02:29 +080010import re
Owen Lindae7a0d2013-12-05 13:34:06 +080011import tempfile
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080012import threading
Hsin-Yu Chaof272d8e2013-04-05 03:28:50 +080013import time
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080014
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +080015from glob import glob
Owen Lin942e04d2014-01-09 14:16:59 +080016from autotest_lib.client.bin import test, utils
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +080017from autotest_lib.client.bin.input.input_device import *
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080018from autotest_lib.client.common_lib import error
Chinyue Chend5292c72015-07-17 16:23:53 +080019from autotest_lib.client.cros.audio import alsa_utils
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +080020from autotest_lib.client.cros.audio import audio_data
Owen Lin7ab45a22013-11-19 17:26:33 +080021from autotest_lib.client.cros.audio import cmd_utils
Owen Lin56050862013-12-09 11:42:51 +080022from autotest_lib.client.cros.audio import cras_utils
Owen Lin7ab45a22013-11-19 17:26:33 +080023from autotest_lib.client.cros.audio import sox_utils
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080024
25LD_LIBRARY_PATH = 'LD_LIBRARY_PATH'
26
Owen Lin410840b2013-12-17 17:02:57 +080027_AUDIO_DIAGNOSTICS_PATH = '/usr/bin/audio_diagnostics'
28
Hsinyu Chaof80337a2012-04-07 18:02:29 +080029_DEFAULT_NUM_CHANNELS = 2
Dylan Reidbf9a5d42012-11-06 16:27:20 -080030_DEFAULT_REC_COMMAND = 'arecord -D hw:0,0 -d 10 -f dat'
Hsinyu Chaof80337a2012-04-07 18:02:29 +080031_DEFAULT_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L'
Owen Lin56050862013-12-09 11:42:51 +080032_DEFAULT_PLAYBACK_VOLUME = 100
33_DEFAULT_CAPTURE_GAIN = 2500
Chinyue Chend5292c72015-07-17 16:23:53 +080034_DEFAULT_ALSA_MAX_VOLUME = '100%'
35_DEFAULT_ALSA_CAPTURE_GAIN = '25dB'
Hsin-Yu Chao4be6d182013-04-19 14:07:56 +080036
37# Minimum RMS value to pass when checking recorded file.
38_DEFAULT_SOX_RMS_THRESHOLD = 0.08
Hsinyu Chaof80337a2012-04-07 18:02:29 +080039
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +080040_JACK_VALUE_ON_RE = re.compile('.*values=on')
41_HP_JACK_CONTROL_RE = re.compile('numid=(\d+).*Headphone\sJack')
42_MIC_JACK_CONTROL_RE = re.compile('numid=(\d+).*Mic\sJack')
43
Hsinyu Chaof80337a2012-04-07 18:02:29 +080044_SOX_RMS_AMPLITUDE_RE = re.compile('RMS\s+amplitude:\s+(.+)')
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +080045_SOX_ROUGH_FREQ_RE = re.compile('Rough\s+frequency:\s+(.+)')
Hsinyu Chaof80337a2012-04-07 18:02:29 +080046
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080047_AUDIO_NOT_FOUND_RE = r'Audio\snot\sdetected'
48_MEASURED_LATENCY_RE = r'Measured\sLatency:\s(\d+)\suS'
49_REPORTED_LATENCY_RE = r'Reported\sLatency:\s(\d+)\suS'
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080050
Hsin-Yu Chaof6bbc6c2013-08-20 19:22:05 +080051# Tools from platform/audiotest
52AUDIOFUNTEST_PATH = 'audiofuntest'
53AUDIOLOOP_PATH = 'looptest'
54LOOPBACK_LATENCY_PATH = 'loopback_latency'
55SOX_PATH = 'sox'
56TEST_TONES_PATH = 'test_tones'
57
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +080058_MINIMUM_NORM = 0.001
59_CORRELATION_INDEX_THRESHOLD = 0.999
Cheng-Yi Chiang17a25272014-11-28 19:05:13 +080060# The minimum difference of estimated frequencies between two sine waves.
Cheng-Yi Chiangb8728662015-05-05 11:18:23 -070061_FREQUENCY_DIFF_THRESHOLD = 20
Cheng-Yi Chiang17a25272014-11-28 19:05:13 +080062# The minimum RMS value of meaningful audio data.
63_MEANINGFUL_RMS_THRESHOLD = 0.001
Hsin-Yu Chaof6bbc6c2013-08-20 19:22:05 +080064
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080065def set_mixer_controls(mixer_settings={}, card='0'):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +080066 """Sets all mixer controls listed in the mixer settings on card.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080067
68 @param mixer_settings: Mixer settings to set.
69 @param card: Index of audio card to set mixer settings for.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +080070 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080071 logging.info('Setting mixer control values on %s', card)
72 for item in mixer_settings:
73 logging.info('Setting %s to %s on card %s',
74 item['name'], item['value'], card)
75 cmd = 'amixer -c %s cset name=%s %s'
76 cmd = cmd % (card, item['name'], item['value'])
77 try:
78 utils.system(cmd)
79 except error.CmdError:
80 # A card is allowed not to support all the controls, so don't
81 # fail the test here if we get an error.
82 logging.info('amixer command failed: %s', cmd)
83
84def set_volume_levels(volume, capture):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +080085 """Sets the volume and capture gain through cras_test_client.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080086
87 @param volume: The playback volume to set.
88 @param capture: The capture gain to set.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +080089 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080090 logging.info('Setting volume level to %d', volume)
Xiaochu Liuff57ecf2017-01-09 12:39:37 -080091 try:
92 utils.system('/usr/bin/cras_test_client --volume %d' % volume)
93 logging.info('Setting capture gain to %d', capture)
94 utils.system('/usr/bin/cras_test_client --capture_gain %d' % capture)
95 utils.system('/usr/bin/cras_test_client --dump_server_info')
96 utils.system('/usr/bin/cras_test_client --mute 0')
97 utils.system('amixer -c 0 contents')
98 except error.CmdError, e:
99 raise error.TestError(
100 '*** Can not tune volume through CRAS. *** (' + str(e) + ')')
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800101
102def loopback_latency_check(**args):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800103 """Checks loopback latency.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800104
105 @param args: additional arguments for loopback_latency.
106
107 @return A tuple containing measured and reported latency in uS.
108 Return None if no audio detected.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800109 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800110 noise_threshold = str(args['n']) if args.has_key('n') else '400'
111
112 cmd = '%s -n %s' % (LOOPBACK_LATENCY_PATH, noise_threshold)
113
114 output = utils.system_output(cmd, retain_output=True)
115
116 # Sleep for a short while to make sure device is not busy anymore
117 # after called loopback_latency.
118 time.sleep(.1)
119
120 measured_latency = None
121 reported_latency = None
122 for line in output.split('\n'):
123 match = re.search(_MEASURED_LATENCY_RE, line, re.I)
124 if match:
125 measured_latency = int(match.group(1))
126 continue
127 match = re.search(_REPORTED_LATENCY_RE, line, re.I)
128 if match:
129 reported_latency = int(match.group(1))
130 continue
131 if re.search(_AUDIO_NOT_FOUND_RE, line, re.I):
132 return None
133 if measured_latency and reported_latency:
134 return (measured_latency, reported_latency)
135 else:
136 # Should not reach here, just in case.
137 return None
138
139def get_mixer_jack_status(jack_reg_exp):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800140 """Gets the mixer jack status.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800141
142 @param jack_reg_exp: The regular expression to match jack control name.
143
144 @return None if the control does not exist, return True if jack control
145 is detected plugged, return False otherwise.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800146 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800147 output = utils.system_output('amixer -c0 controls', retain_output=True)
148 numid = None
149 for line in output.split('\n'):
150 m = jack_reg_exp.match(line)
151 if m:
152 numid = m.group(1)
153 break
154
155 # Proceed only when matched numid is not empty.
156 if numid:
157 output = utils.system_output('amixer -c0 cget numid=%s' % numid)
158 for line in output.split('\n'):
159 if _JACK_VALUE_ON_RE.match(line):
160 return True
161 return False
162 else:
163 return None
164
165def get_hp_jack_status():
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800166 """Gets the status of headphone jack."""
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800167 status = get_mixer_jack_status(_HP_JACK_CONTROL_RE)
168 if status is not None:
169 return status
170
171 # When headphone jack is not found in amixer, lookup input devices
172 # instead.
173 #
174 # TODO(hychao): Check hp/mic jack status dynamically from evdev. And
175 # possibly replace the existing check using amixer.
176 for evdev in glob('/dev/input/event*'):
177 device = InputDevice(evdev)
178 if device.is_hp_jack():
179 return device.get_headphone_insert()
180 else:
181 return None
182
183def get_mic_jack_status():
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800184 """Gets the status of mic jack."""
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800185 status = get_mixer_jack_status(_MIC_JACK_CONTROL_RE)
186 if status is not None:
187 return status
188
189 # When mic jack is not found in amixer, lookup input devices instead.
190 for evdev in glob('/dev/input/event*'):
191 device = InputDevice(evdev)
192 if device.is_mic_jack():
193 return device.get_microphone_insert()
194 else:
195 return None
196
Owen Lin56050862013-12-09 11:42:51 +0800197def log_loopback_dongle_status():
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800198 """Log the status of the loopback dongle to make sure it is equipped."""
Owen Lin56050862013-12-09 11:42:51 +0800199 dongle_status_ok = True
200
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800201 # Check Mic Jack
202 mic_jack_status = get_mic_jack_status()
Owen Lin56050862013-12-09 11:42:51 +0800203 logging.info('Mic jack status: %s', mic_jack_status)
Owen Lin14bec7a2014-04-21 11:39:40 +0800204 dongle_status_ok &= bool(mic_jack_status)
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800205
206 # Check Headphone Jack
207 hp_jack_status = get_hp_jack_status()
Owen Lin56050862013-12-09 11:42:51 +0800208 logging.info('Headphone jack status: %s', hp_jack_status)
Owen Lin14bec7a2014-04-21 11:39:40 +0800209 dongle_status_ok &= bool(hp_jack_status)
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800210
211 # Use latency check to test if audio can be captured through dongle.
212 # We only want to know the basic function of dongle, so no need to
213 # assert the latency accuracy here.
214 latency = loopback_latency_check(n=4000)
215 if latency:
216 logging.info('Got latency measured %d, reported %d',
217 latency[0], latency[1])
218 else:
Owen Lin56050862013-12-09 11:42:51 +0800219 logging.info('Latency check fail.')
220 dongle_status_ok = False
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800221
Owen Lin56050862013-12-09 11:42:51 +0800222 logging.info('audio loopback dongle test: %s',
223 'PASS' if dongle_status_ok else 'FAIL')
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800224
225# Functions to test audio palyback.
226def play_sound(duration_seconds=None, audio_file_path=None):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800227 """Plays a sound file found at |audio_file_path| for |duration_seconds|.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800228
229 If |audio_file_path|=None, plays a default audio file.
230 If |duration_seconds|=None, plays audio file in its entirety.
231
232 @param duration_seconds: Duration to play sound.
233 @param audio_file_path: Path to the audio file.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800234 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800235 if not audio_file_path:
236 audio_file_path = '/usr/local/autotest/cros/audio/sine440.wav'
237 duration_arg = ('-d %d' % duration_seconds) if duration_seconds else ''
238 utils.system('aplay %s %s' % (duration_arg, audio_file_path))
239
240def get_play_sine_args(channel, odev='default', freq=1000, duration=10,
241 sample_size=16):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800242 """Gets the command args to generate a sine wav to play to odev.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800243
244 @param channel: 0 for left, 1 for right; otherwize, mono.
245 @param odev: alsa output device.
246 @param freq: frequency of the generated sine tone.
247 @param duration: duration of the generated sine tone.
248 @param sample_size: output audio sample size. Default to 16.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800249 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800250 cmdargs = [SOX_PATH, '-b', str(sample_size), '-n', '-t', 'alsa',
251 odev, 'synth', str(duration)]
252 if channel == 0:
253 cmdargs += ['sine', str(freq), 'sine', '0']
254 elif channel == 1:
255 cmdargs += ['sine', '0', 'sine', str(freq)]
256 else:
257 cmdargs += ['sine', str(freq)]
258
259 return cmdargs
260
261def play_sine(channel, odev='default', freq=1000, duration=10,
262 sample_size=16):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800263 """Generates a sine wave and plays to odev.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800264
265 @param channel: 0 for left, 1 for right; otherwize, mono.
266 @param odev: alsa output device.
267 @param freq: frequency of the generated sine tone.
268 @param duration: duration of the generated sine tone.
269 @param sample_size: output audio sample size. Default to 16.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800270 """
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800271 cmdargs = get_play_sine_args(channel, odev, freq, duration, sample_size)
272 utils.system(' '.join(cmdargs))
273
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800274# Functions to compose customized sox command, execute it and process the
275# output of sox command.
276def get_sox_mixer_cmd(infile, channel,
277 num_channels=_DEFAULT_NUM_CHANNELS,
278 sox_format=_DEFAULT_SOX_FORMAT):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800279 """Gets sox mixer command to reduce channel.
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800280
281 @param infile: Input file name.
282 @param channel: The selected channel to take effect.
283 @param num_channels: The number of total channels to test.
284 @param sox_format: Format to generate sox command.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800285 """
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800286 # Build up a pan value string for the sox command.
287 if channel == 0:
288 pan_values = '1'
289 else:
290 pan_values = '0'
291 for pan_index in range(1, num_channels):
292 if channel == pan_index:
293 pan_values = '%s%s' % (pan_values, ',1')
294 else:
295 pan_values = '%s%s' % (pan_values, ',0')
296
297 return '%s -c 2 %s %s -c 1 %s - mixer %s' % (SOX_PATH,
298 sox_format, infile, sox_format, pan_values)
299
300def sox_stat_output(infile, channel,
301 num_channels=_DEFAULT_NUM_CHANNELS,
302 sox_format=_DEFAULT_SOX_FORMAT):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800303 """Executes sox stat command.
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800304
305 @param infile: Input file name.
306 @param channel: The selected channel.
307 @param num_channels: The number of total channels to test.
308 @param sox_format: Format to generate sox command.
309
310 @return The output of sox stat command
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800311 """
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800312 sox_mixer_cmd = get_sox_mixer_cmd(infile, channel,
313 num_channels, sox_format)
314 stat_cmd = '%s -c 1 %s - -n stat 2>&1' % (SOX_PATH, sox_format)
315 sox_cmd = '%s | %s' % (sox_mixer_cmd, stat_cmd)
316 return utils.system_output(sox_cmd, retain_output=True)
317
318def get_audio_rms(sox_output):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800319 """Gets the audio RMS value from sox stat output
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800320
321 @param sox_output: Output of sox stat command.
322
323 @return The RMS value parsed from sox stat output.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800324 """
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800325 for rms_line in sox_output.split('\n'):
326 m = _SOX_RMS_AMPLITUDE_RE.match(rms_line)
327 if m is not None:
328 return float(m.group(1))
329
330def get_rough_freq(sox_output):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800331 """Gets the rough audio frequency from sox stat output
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800332
333 @param sox_output: Output of sox stat command.
334
335 @return The rough frequency value parsed from sox stat output.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800336 """
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800337 for rms_line in sox_output.split('\n'):
338 m = _SOX_ROUGH_FREQ_RE.match(rms_line)
339 if m is not None:
340 return int(m.group(1))
341
342def check_audio_rms(sox_output, sox_threshold=_DEFAULT_SOX_RMS_THRESHOLD):
343 """Checks if the calculated RMS value is expected.
344
345 @param sox_output: The output from sox stat command.
346 @param sox_threshold: The threshold to test RMS value against.
347
348 @raises error.TestError if RMS amplitude can't be parsed.
349 @raises error.TestFail if the RMS amplitude of the recording isn't above
350 the threshold.
351 """
352 rms_val = get_audio_rms(sox_output)
353
354 # In case we don't get a valid RMS value.
355 if rms_val is None:
356 raise error.TestError(
357 'Failed to generate an audio RMS value from playback.')
358
359 logging.info('Got audio RMS value of %f. Minimum pass is %f.',
360 rms_val, sox_threshold)
361 if rms_val < sox_threshold:
362 raise error.TestFail(
363 'Audio RMS value %f too low. Minimum pass is %f.' %
364 (rms_val, sox_threshold))
365
366def noise_reduce_file(in_file, noise_file, out_file,
367 sox_format=_DEFAULT_SOX_FORMAT):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800368 """Runs the sox command to reduce noise.
369
370 Runs the sox command to noise-reduce in_file using the noise
371 profile from noise_file.
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800372
373 @param in_file: The file to noise reduce.
374 @param noise_file: The file containing the noise profile.
375 This can be created by recording silence.
376 @param out_file: The file contains the noise reduced sound.
377 @param sox_format: The sox format to generate sox command.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800378 """
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800379 prof_cmd = '%s -c 2 %s %s -n noiseprof' % (SOX_PATH,
380 sox_format, noise_file)
381 reduce_cmd = ('%s -c 2 %s %s -c 2 %s %s noisered' %
382 (SOX_PATH, sox_format, in_file, sox_format, out_file))
383 utils.system('%s | %s' % (prof_cmd, reduce_cmd))
384
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800385def record_sample(tmpfile, record_command=_DEFAULT_REC_COMMAND):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800386 """Records a sample from the default input device.
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800387
388 @param tmpfile: The file to record to.
389 @param record_command: The command to record audio.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800390 """
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800391 utils.system('%s %s' % (record_command, tmpfile))
392
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800393def create_wav_file(wav_dir, prefix=""):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800394 """Creates a unique name for wav file.
Adrian Li689d3ff2013-07-15 15:11:06 -0700395
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800396 The created file name will be preserved in autotest result directory
397 for future analysis.
398
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800399 @param wav_dir: The directory of created wav file.
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800400 @param prefix: specified file name prefix.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800401 """
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800402 filename = "%s-%s.wav" % (prefix, time.time())
403 return os.path.join(wav_dir, filename)
404
Owen Lin66cd9de2013-11-08 10:26:49 +0800405def run_in_parallel(*funs):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800406 """Runs methods in parallel.
407
408 @param funs: methods to run.
409 """
Owen Lin66cd9de2013-11-08 10:26:49 +0800410 threads = []
411 for f in funs:
412 t = threading.Thread(target=f)
413 t.start()
414 threads.append(t)
415
416 for t in threads:
417 t.join()
418
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800419def loopback_test_channels(noise_file_name, wav_dir,
Owen Lin66cd9de2013-11-08 10:26:49 +0800420 playback_callback=None,
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800421 check_recorded_callback=check_audio_rms,
422 preserve_test_file=True,
423 num_channels = _DEFAULT_NUM_CHANNELS,
Owen Lin66cd9de2013-11-08 10:26:49 +0800424 record_callback=record_sample,
425 mix_callback=None):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800426 """Tests loopback on all channels.
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800427
428 @param noise_file_name: Name of the file contains pre-recorded noise.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800429 @param wav_dir: The directory of created wav file.
Owen Lin66cd9de2013-11-08 10:26:49 +0800430 @param playback_callback: The callback to do the playback for
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800431 one channel.
Owen Lin66cd9de2013-11-08 10:26:49 +0800432 @param record_callback: The callback to do the recording.
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800433 @param check_recorded_callback: The callback to check recorded file.
434 @param preserve_test_file: Retain the recorded files for future debugging.
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800435 @param num_channels: The number of total channels to test.
436 @param mix_callback: The callback to do on the one-channel file.
437 """
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800438 for channel in xrange(num_channels):
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800439 record_file_name = create_wav_file(wav_dir,
440 "record-%d" % channel)
Owen Lin66cd9de2013-11-08 10:26:49 +0800441 functions = [lambda: record_callback(record_file_name)]
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800442
Owen Lin66cd9de2013-11-08 10:26:49 +0800443 if playback_callback:
444 functions.append(lambda: playback_callback(channel))
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800445
Owen Lin66cd9de2013-11-08 10:26:49 +0800446 if mix_callback:
447 mix_file_name = create_wav_file(wav_dir, "mix-%d" % channel)
448 functions.append(lambda: mix_callback(mix_file_name))
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800449
Owen Lin66cd9de2013-11-08 10:26:49 +0800450 run_in_parallel(*functions)
451
452 if mix_callback:
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800453 sox_output_mix = sox_stat_output(mix_file_name, channel)
454 rms_val_mix = get_audio_rms(sox_output_mix)
455 logging.info('Got mixed audio RMS value of %f.', rms_val_mix)
Adrian Li689d3ff2013-07-15 15:11:06 -0700456
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800457 sox_output_record = sox_stat_output(record_file_name, channel)
458 rms_val_record = get_audio_rms(sox_output_record)
459 logging.info('Got recorded audio RMS value of %f.', rms_val_record)
Adrian Li689d3ff2013-07-15 15:11:06 -0700460
Owen Lin66cd9de2013-11-08 10:26:49 +0800461 reduced_file_name = create_wav_file(wav_dir,
462 "reduced-%d" % channel)
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800463 noise_reduce_file(record_file_name, noise_file_name,
464 reduced_file_name)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800465
Owen Lin66cd9de2013-11-08 10:26:49 +0800466 sox_output_reduced = sox_stat_output(reduced_file_name, channel)
Adrian Li689d3ff2013-07-15 15:11:06 -0700467
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800468 if not preserve_test_file:
469 os.unlink(reduced_file_name)
470 os.unlink(record_file_name)
Owen Lin66cd9de2013-11-08 10:26:49 +0800471 if mix_callback:
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800472 os.unlink(mix_file_name)
Adrian Li689d3ff2013-07-15 15:11:06 -0700473
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800474 check_recorded_callback(sox_output_reduced)
Owen Lindae7a0d2013-12-05 13:34:06 +0800475
476
477def get_channel_sox_stat(
Owen Lin2013e462013-12-05 17:54:42 +0800478 input_audio, channel_index, channels=2, bits=16, rate=48000):
Owen Lindae7a0d2013-12-05 13:34:06 +0800479 """Gets the sox stat info of the selected channel in the input audio file.
480
481 @param input_audio: The input audio file to be analyzed.
482 @param channel_index: The index of the channel to be analyzed.
483 (1 for the first channel).
484 @param channels: The number of channels in the input audio.
485 @param bits: The number of bits of each audio sample.
486 @param rate: The sampling rate.
487 """
488 if channel_index <= 0 or channel_index > channels:
489 raise ValueError('incorrect channel_indexi: %d' % channel_index)
490
491 if channels == 1:
Owen Lin4a154c42013-12-26 11:03:20 +0800492 return sox_utils.get_stat(
493 input_audio, channels=channels, bits=bits, rate=rate)
Owen Lindae7a0d2013-12-05 13:34:06 +0800494
495 p1 = cmd_utils.popen(
496 sox_utils.extract_channel_cmd(
497 input_audio, '-', channel_index,
498 channels=channels, bits=bits, rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800499 stdout=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800500 p2 = cmd_utils.popen(
501 sox_utils.stat_cmd('-', channels=1, bits=bits, rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800502 stdin=p1.stdout, stderr=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800503 stat_output = p2.stderr.read()
504 cmd_utils.wait_and_check_returncode(p1, p2)
505 return sox_utils.parse_stat_output(stat_output)
506
507
Owen Lin942e04d2014-01-09 14:16:59 +0800508def get_rms(input_audio, channels=1, bits=16, rate=48000):
509 """Gets the RMS values of all channels of the input audio.
Owen Lin5bba6c02013-12-13 14:17:08 +0800510
Owen Lin5cbf5c32013-12-13 15:13:37 +0800511 @param input_audio: The input audio file to be checked.
Owen Lin5bba6c02013-12-13 14:17:08 +0800512 @param channels: The number of channels in the input audio.
513 @param bits: The number of bits of each audio sample.
514 @param rate: The sampling rate.
Owen Lin5bba6c02013-12-13 14:17:08 +0800515 """
516 stats = [get_channel_sox_stat(
517 input_audio, i + 1, channels=channels, bits=bits,
518 rate=rate) for i in xrange(channels)]
519
520 logging.info('sox stat: %s', [str(s) for s in stats])
Owen Lin942e04d2014-01-09 14:16:59 +0800521 return [s.rms for s in stats]
Owen Lin5bba6c02013-12-13 14:17:08 +0800522
523
Owen Lin942e04d2014-01-09 14:16:59 +0800524def reduce_noise_and_get_rms(
525 input_audio, noise_file, channels=1, bits=16, rate=48000):
526 """Reduces noise in the input audio by the given noise file and then gets
Owen Lin5cbf5c32013-12-13 15:13:37 +0800527 the RMS values of all channels of the input audio.
Owen Lin5bba6c02013-12-13 14:17:08 +0800528
529 @param input_audio: The input audio file to be analyzed.
530 @param noise_file: The noise file used to reduce noise in the input audio.
Owen Lin5bba6c02013-12-13 14:17:08 +0800531 @param channels: The number of channels in the input audio.
532 @param bits: The number of bits of each audio sample.
533 @param rate: The sampling rate.
Owen Lin5bba6c02013-12-13 14:17:08 +0800534 """
Owen Lindae7a0d2013-12-05 13:34:06 +0800535 with tempfile.NamedTemporaryFile() as reduced_file:
536 p1 = cmd_utils.popen(
537 sox_utils.noise_profile_cmd(
538 noise_file, '-', channels=channels, bits=bits,
539 rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800540 stdout=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800541 p2 = cmd_utils.popen(
542 sox_utils.noise_reduce_cmd(
543 input_audio, reduced_file.name, '-',
544 channels=channels, bits=bits, rate=rate),
545 stdin=p1.stdout)
546 cmd_utils.wait_and_check_returncode(p1, p2)
Owen Lin942e04d2014-01-09 14:16:59 +0800547 return get_rms(reduced_file.name, channels, bits, rate)
Owen Lin56050862013-12-09 11:42:51 +0800548
549
Rohit Makasana06446562014-01-03 11:39:03 -0800550def skip_devices_to_test(*boards):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800551 """Devices to skip due to hardware or test compatibility issues.
552
553 @param boards: the boards to skip testing.
554 """
Rohit Makasana06446562014-01-03 11:39:03 -0800555 # TODO(scottz): Remove this when crbug.com/220147 is fixed.
556 dut_board = utils.get_current_board()
557 if dut_board in boards:
558 raise error.TestNAError('This test is not available on %s' % dut_board)
559
560
Owen Lin56050862013-12-09 11:42:51 +0800561def cras_rms_test_setup():
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800562 """Setups for the cras_rms_tests.
Owen Lin56050862013-12-09 11:42:51 +0800563
564 To make sure the line_out-to-mic_in path is all green.
565 """
566 # TODO(owenlin): Now, the nodes are choosed by chrome.
567 # We should do it here.
Owen Lin56050862013-12-09 11:42:51 +0800568 cras_utils.set_system_volume(_DEFAULT_PLAYBACK_VOLUME)
Cheng-Yi Chiang79b9ada2015-05-27 14:46:56 +0800569 cras_utils.set_selected_output_node_volume(_DEFAULT_PLAYBACK_VOLUME)
Owen Lin56050862013-12-09 11:42:51 +0800570
571 cras_utils.set_capture_gain(_DEFAULT_CAPTURE_GAIN)
572
573 cras_utils.set_system_mute(False)
574 cras_utils.set_capture_mute(False)
575
576
Owen Lin63b214d2014-01-07 16:40:14 +0800577def generate_rms_postmortem():
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800578 """Generates postmortem for rms tests."""
Owen Lin63b214d2014-01-07 16:40:14 +0800579 try:
580 logging.info('audio postmortem report')
581 log_loopback_dongle_status()
Cheng-Yi Chiang88fdf252015-04-08 01:02:59 -0700582 logging.info(get_audio_diagnostics())
Owen Lin942e04d2014-01-09 14:16:59 +0800583 except Exception:
Owen Lin63b214d2014-01-07 16:40:14 +0800584 logging.exception('Error while generating postmortem report')
585
586
Cheng-Yi Chiang88fdf252015-04-08 01:02:59 -0700587def get_audio_diagnostics():
588 """Gets audio diagnostic results.
589
590 @returns: a string containing diagnostic results.
591
592 """
593 return cmd_utils.execute([_AUDIO_DIAGNOSTICS_PATH], stdout=cmd_utils.PIPE)
594
595
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800596def get_max_cross_correlation(signal_a, signal_b):
597 """Gets max cross-correlation and best time delay of two signals.
598
599 Computes cross-correlation function between two
600 signals and gets the maximum value and time delay.
601 The steps includes:
602 1. Compute cross-correlation function of X and Y and get Cxy.
603 The correlation function Cxy is an array where Cxy[k] is the
604 cross product of X and Y when Y is delayed by k.
605 Refer to manual of numpy.correlate for detail of correlation.
606 2. Find the maximum value C_max and index C_index in Cxy.
607 3. Compute L2 norm of X and Y to get norm(X) and norm(Y).
608 4. Divide C_max by norm(X)*norm(Y) to get max cross-correlation.
609
610 Max cross-correlation indicates the similarity of X and Y. The value
611 is 1 if X equals Y multiplied by a positive scalar.
612 The value is -1 if X equals Y multiplied by a negative scaler.
613 Any constant level shift will be regarded as distortion and will make
614 max cross-correlation value deviated from 1.
615 C_index is the best time delay of Y that make Y looks similar to X.
616 Refer to http://en.wikipedia.org/wiki/Cross-correlation.
617
618 @param signal_a: A list of numbers which contains the first signal.
619 @param signal_b: A list of numbers which contains the second signal.
620
621 @raises: ValueError if any number in signal_a or signal_b is not a float.
622 ValueError if norm of any array is less than _MINIMUM_NORM.
623
624 @returns: A tuple (correlation index, best delay). If there are more than
625 one best delay, just return the first one.
626 """
627 def check_list_contains_float(numbers):
628 """Checks the elements in a list are all float.
629
630 @param numbers: A list of numbers.
631
632 @raises: ValueError if there is any element which is not a float
633 in the list.
634 """
635 if any(not isinstance(x, float) for x in numbers):
636 raise ValueError('List contains number which is not a float')
637
638 check_list_contains_float(signal_a)
639 check_list_contains_float(signal_b)
640
641 norm_a = numpy.linalg.norm(signal_a)
642 norm_b = numpy.linalg.norm(signal_b)
643 logging.debug('norm_a: %f', norm_a)
644 logging.debug('norm_b: %f', norm_b)
645 if norm_a <= _MINIMUM_NORM or norm_b <= _MINIMUM_NORM:
646 raise ValueError('No meaningful data as norm is too small.')
647
648 correlation = numpy.correlate(signal_a, signal_b, 'full')
649 max_correlation = max(correlation)
650 best_delays = [i for i, j in enumerate(correlation) if j == max_correlation]
651 if len(best_delays) > 1:
652 logging.warning('There are more than one best delay: %r', best_delays)
653 return max_correlation / (norm_a * norm_b), best_delays[0]
654
655
656def trim_data(data, threshold=0):
657 """Trims a data by removing value that is too small in head and tail.
658
659 Removes elements in head and tail whose absolute value is smaller than
660 or equal to threshold.
661 E.g. trim_data([0.0, 0.1, 0.2, 0.3, 0.2, 0.1, 0.0], 0.2) =
662 ([0.2, 0.3, 0.2], 2)
663
664 @param data: A list of numbers.
665 @param threshold: The threshold to compare against.
666
Cheng-Yi Chiangd2d00e32015-08-11 18:40:15 +0800667 @returns: A tuple (trimmed_data, end_trimmed_length), where
668 end_trimmed_length is the length of original data being trimmed
669 from the end.
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800670 Returns ([], None) if there is no valid data.
671 """
672 indice_valid = [
673 i for i, j in enumerate(data) if abs(j) > threshold]
674 if not indice_valid:
675 logging.warning(
676 'There is no element with absolute value greater '
Cheng-Yi Chiang17a25272014-11-28 19:05:13 +0800677 'than threshold %f', threshold)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800678 return [], None
679 logging.debug('Start and end of indice_valid: %d, %d',
680 indice_valid[0], indice_valid[-1])
Cheng-Yi Chiangd2d00e32015-08-11 18:40:15 +0800681 end_trimmed_length = len(data) - indice_valid[-1] - 1
682 logging.debug('Trimmed length in the end: %d', end_trimmed_length)
683 return (data[indice_valid[0] : indice_valid[-1] + 1], end_trimmed_length)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800684
685
686def get_one_channel_correlation(test_data, golden_data):
687 """Gets max cross-correlation of test_data and golden_data.
688
689 Trims test data and compute the max cross-correlation against golden_data.
690 Signal can be trimmed because those zero values in the head and tail of
691 a signal will not affect correlation computation.
692
693 @param test_data: A list containing the data to compare against golden data.
694 @param golden_data: A list containing the golden data.
695
696 @returns: A tuple (max cross-correlation, best_delay) if data is valid.
697 Otherwise returns (None, None). Refer to docstring of
698 get_max_cross_correlation.
699 """
Cheng-Yi Chiangd2d00e32015-08-11 18:40:15 +0800700 trimmed_test_data, end_trimmed_length = trim_data(test_data)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800701
702 def to_float(samples):
703 """Casts elements in the list to float.
704
705 @param samples: A list of numbers.
706
707 @returns: A list of original numbers casted to float.
708 """
709 samples_float = [float(x) for x in samples]
710 return samples_float
711
712 max_cross_correlation, best_delay = get_max_cross_correlation(
713 to_float(golden_data),
714 to_float(trimmed_test_data))
715
Cheng-Yi Chiangd2d00e32015-08-11 18:40:15 +0800716 # The reason to add back the trimmed length in the end.
717 # E.g.:
718 # golden data:
719 #
720 # |-----------vvvv----------------| vvvv is the signal of interest.
721 # a b
722 #
723 # test data:
724 #
725 # |---x----vvvv--------x----------------| x is the place to trim.
726 # c d e f
727 #
728 # trimmed test data:
729 #
730 # |----vvvv--------|
731 # d e
732 #
733 # The first output of cross correlation computation :
734 #
735 # |-----------vvvv----------------|
736 # a b
737 #
738 # |----vvvv--------|
739 # d e
740 #
741 # The largest output of cross correlation computation happens at
742 # delay a + e.
743 #
744 # |-----------vvvv----------------|
745 # a b
746 #
747 # |----vvvv--------|
748 # d e
749 #
750 # Cross correlation starts computing by aligning the last sample
751 # of the trimmed test data to the first sample of golden data.
752 # The best delay calculated from trimmed test data and golden data
753 # cross correlation is e + a. But the real best delay that should be
754 # identical on two channel should be e + a + f.
755 # So we need to add back the length being trimmed in the end.
756
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800757 if max_cross_correlation:
Cheng-Yi Chiangd2d00e32015-08-11 18:40:15 +0800758 return max_cross_correlation, best_delay + end_trimmed_length
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800759 else:
760 return None, None
761
762
Selina Liu38c18b02015-09-04 19:00:23 +0800763def compare_one_channel_correlation(test_data, golden_data, parameters):
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800764 """Compares two one-channel data by correlation.
765
766 @param test_data: A list containing the data to compare against golden data.
767 @param golden_data: A list containing the golden data.
Selina Liu38c18b02015-09-04 19:00:23 +0800768 @param parameters: A dict containing parameters for method.
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800769
770 @returns: A dict containing:
771 index: The index of similarity where 1 means they are different
772 only by a positive scale.
773 best_delay: The best delay of test data in relative to golden
774 data.
775 equal: A bool containing comparing result.
776 """
Selina Liu38c18b02015-09-04 19:00:23 +0800777 if 'correlation_threshold' in parameters:
778 threshold = parameters['correlation_threshold']
779 else:
780 threshold = _CORRELATION_INDEX_THRESHOLD
781
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800782 result_dict = dict()
783 max_cross_correlation, best_delay = get_one_channel_correlation(
784 test_data, golden_data)
785 result_dict['index'] = max_cross_correlation
786 result_dict['best_delay'] = best_delay
787 result_dict['equal'] = True if (
788 max_cross_correlation and
Selina Liu38c18b02015-09-04 19:00:23 +0800789 max_cross_correlation > threshold) else False
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800790 logging.debug('result_dict: %r', result_dict)
791 return result_dict
792
793
Cheng-Yi Chiangf32c3d92016-06-22 18:29:35 +0800794def compare_data_correlation(golden_data_binary, golden_data_format,
795 test_data_binary, test_data_format,
796 channel_map, parameters=None):
797 """Compares two raw data using correlation.
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800798
799 @param golden_data_binary: The binary containing golden data.
800 @param golden_data_format: The data format of golden data.
801 @param test_data_binary: The binary containing test data.
802 @param test_data_format: The data format of test data.
803 @param channel_map: A list containing channel mapping.
804 E.g. [1, 0, None, None, None, None, None, None] means
805 channel 0 of test data should map to channel 1 of
806 golden data. Channel 1 of test data should map to
807 channel 0 of golden data. Channel 2 to 7 of test data
808 should be skipped.
Selina Liu38c18b02015-09-04 19:00:23 +0800809 @param parameters: A dict containing parameters for method, if needed.
810
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800811 @raises: NotImplementedError if file type is not raw.
Cheng-Yi Chiang17a25272014-11-28 19:05:13 +0800812 NotImplementedError if sampling rates of two data are not the same.
Cheng-Yi Chiangf32c3d92016-06-22 18:29:35 +0800813 error.TestFail if golden data and test data are not equal.
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800814 """
Selina Liu38c18b02015-09-04 19:00:23 +0800815 if parameters is None:
816 parameters = dict()
817
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800818 if (golden_data_format['file_type'] != 'raw' or
819 test_data_format['file_type'] != 'raw'):
820 raise NotImplementedError('Only support raw data in compare_data.')
Cheng-Yi Chiang17a25272014-11-28 19:05:13 +0800821 if (golden_data_format['rate'] != test_data_format['rate']):
822 raise NotImplementedError(
823 'Only support comparing data with the same sampling rate')
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800824 golden_data = audio_data.AudioRawData(
825 binary=golden_data_binary,
826 channel=golden_data_format['channel'],
827 sample_format=golden_data_format['sample_format'])
828 test_data = audio_data.AudioRawData(
829 binary=test_data_binary,
830 channel=test_data_format['channel'],
831 sample_format=test_data_format['sample_format'])
832 compare_results = []
833 for test_channel, golden_channel in enumerate(channel_map):
834 if golden_channel is None:
835 logging.info('Skipped channel %d', test_channel)
836 continue
837 test_data_one_channel = test_data.channel_data[test_channel]
838 golden_data_one_channel = golden_data.channel_data[golden_channel]
839 result_dict = dict(test_channel=test_channel,
840 golden_channel=golden_channel)
841 result_dict.update(
Cheng-Yi Chiangf32c3d92016-06-22 18:29:35 +0800842 compare_one_channel_correlation(
843 test_data_one_channel, golden_data_one_channel,
Selina Liu38c18b02015-09-04 19:00:23 +0800844 parameters))
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800845 compare_results.append(result_dict)
846 logging.info('compare_results: %r', compare_results)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800847 for result in compare_results:
848 if not result['equal']:
Cheng-Yi Chiangf32c3d92016-06-22 18:29:35 +0800849 error_msg = ('Failed on test channel %d and golden channel %d with '
850 'index %f') % (
851 result['test_channel'],
852 result['golden_channel'],
853 result['index'])
854 logging.error(error_msg)
855 raise error.TestFail(error_msg)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800856 # Also checks best delay are exactly the same.
Cheng-Yi Chiangf32c3d92016-06-22 18:29:35 +0800857 best_delays = set([result['best_delay'] for result in compare_results])
858 if len(best_delays) > 1:
859 error_msg = 'There are more than one best delay: %s' % best_delays
860 logging.error(error_msg)
861 raise error.TestFail(error_msg)
Cheng-Yi Chiang7e49fb82014-09-01 20:02:08 +0800862
863
Owen Lin942e04d2014-01-09 14:16:59 +0800864class _base_rms_test(test.test):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800865 """Base class for all rms_test """
Owen Lin56050862013-12-09 11:42:51 +0800866
Owen Lin942e04d2014-01-09 14:16:59 +0800867 def postprocess(self):
868 super(_base_rms_test, self).postprocess()
Owen Lin56050862013-12-09 11:42:51 +0800869
Owen Lin942e04d2014-01-09 14:16:59 +0800870 # Sum up the number of failed constraints in each iteration
871 if sum(len(x) for x in self.failed_constraints):
872 generate_rms_postmortem()
873
874
875class chrome_rms_test(_base_rms_test):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800876 """Base test class for audio RMS test with Chrome.
Owen Lin942e04d2014-01-09 14:16:59 +0800877
878 The chrome instance can be accessed by self.chrome.
Owen Lin56050862013-12-09 11:42:51 +0800879 """
Owen Lin942e04d2014-01-09 14:16:59 +0800880 def warmup(self):
881 skip_devices_to_test('x86-mario')
882 super(chrome_rms_test, self).warmup()
883
Owen Lin56050862013-12-09 11:42:51 +0800884 # Not all client of this file using telemetry.
885 # Just do the import here for those who really need it.
886 from autotest_lib.client.common_lib.cros import chrome
Owen Lin942e04d2014-01-09 14:16:59 +0800887
888 self.chrome = chrome.Chrome()
889
890 # The audio configuration could be changed when we
891 # restart chrome.
Owen Lin56050862013-12-09 11:42:51 +0800892 try:
Owen Lin942e04d2014-01-09 14:16:59 +0800893 cras_rms_test_setup()
894 except Exception:
895 self.chrome.browser.Close()
Owen Lin56050862013-12-09 11:42:51 +0800896 raise
Owen Lin56050862013-12-09 11:42:51 +0800897
898
Owen Lin942e04d2014-01-09 14:16:59 +0800899 def cleanup(self, *args):
900 try:
Ricky Liang2bfa5642016-11-09 10:18:37 +0800901 self.chrome.close()
Owen Lin942e04d2014-01-09 14:16:59 +0800902 finally:
903 super(chrome_rms_test, self).cleanup()
Owen Lin56050862013-12-09 11:42:51 +0800904
Owen Lin942e04d2014-01-09 14:16:59 +0800905class cras_rms_test(_base_rms_test):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800906 """Base test class for CRAS audio RMS test."""
Owen Lin942e04d2014-01-09 14:16:59 +0800907
908 def warmup(self):
Rohit Makasana06446562014-01-03 11:39:03 -0800909 skip_devices_to_test('x86-mario')
Owen Lin942e04d2014-01-09 14:16:59 +0800910 super(cras_rms_test, self).warmup()
Owen Lin56050862013-12-09 11:42:51 +0800911 cras_rms_test_setup()
Owen Lin56050862013-12-09 11:42:51 +0800912
913
Chinyue Chend5292c72015-07-17 16:23:53 +0800914def alsa_rms_test_setup():
915 """Setup for alsa_rms_test.
916
917 Different boards/chipsets have different set of mixer controls. Even
918 controls that have the same name on different boards might have different
919 capabilities. The following is a general idea to setup a given class of
920 boards, and some specialized setup for certain boards.
921 """
922 card_id = alsa_utils.get_first_soundcard_with_control('Mic Jack', 'Mic')
923 arch = utils.get_arch()
924 board = utils.get_board()
925 uses_max98090 = os.path.exists('/sys/module/snd_soc_max98090')
926 if board in ['daisy_spring', 'daisy_skate']:
927 # The MIC controls of the boards do not support dB syntax.
928 alsa_utils.mixer_cmd(card_id,
929 'sset Headphone ' + _DEFAULT_ALSA_MAX_VOLUME)
930 alsa_utils.mixer_cmd(card_id, 'sset MIC1 ' + _DEFAULT_ALSA_MAX_VOLUME)
931 alsa_utils.mixer_cmd(card_id, 'sset MIC2 ' + _DEFAULT_ALSA_MAX_VOLUME)
932 elif arch in ['armv7l', 'aarch64'] or uses_max98090:
933 # ARM platforms or Intel platforms that uses max98090 codec driver.
934 alsa_utils.mixer_cmd(card_id,
935 'sset Headphone ' + _DEFAULT_ALSA_MAX_VOLUME)
936 alsa_utils.mixer_cmd(card_id, 'sset MIC1 ' + _DEFAULT_ALSA_CAPTURE_GAIN)
937 alsa_utils.mixer_cmd(card_id, 'sset MIC2 ' + _DEFAULT_ALSA_CAPTURE_GAIN)
938 else:
939 # The rest of Intel platforms.
940 alsa_utils.mixer_cmd(card_id, 'sset Master ' + _DEFAULT_ALSA_MAX_VOLUME)
941 alsa_utils.mixer_cmd(card_id,
942 'sset Capture ' + _DEFAULT_ALSA_CAPTURE_GAIN)
943
944
Owen Lin942e04d2014-01-09 14:16:59 +0800945class alsa_rms_test(_base_rms_test):
Cheng-Yi Chiang9414ddb2015-06-15 15:14:18 +0800946 """Base test class for ALSA audio RMS test."""
Owen Lin56050862013-12-09 11:42:51 +0800947
Owen Lin942e04d2014-01-09 14:16:59 +0800948 def warmup(self):
Rohit Makasana06446562014-01-03 11:39:03 -0800949 skip_devices_to_test('x86-mario')
Owen Lin942e04d2014-01-09 14:16:59 +0800950 super(alsa_rms_test, self).warmup()
951
Chinyue Chend5292c72015-07-17 16:23:53 +0800952 alsa_rms_test_setup()