blob: a2f005954f16b968fa34c3b655e92f7bae123a62 [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
6import logging
7import os
Owen Linca365f82013-11-08 16:52:28 +08008import pipes
Hsinyu Chaof80337a2012-04-07 18:02:29 +08009import re
Owen Lin62492b02013-11-01 19:00:24 +080010import shlex
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
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080016from autotest_lib.client.bin import 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
Owen Lin56050862013-12-09 11:42:51 +080019from autotest_lib.client.cros.audio import alsa_utils
Owen Lin7ab45a22013-11-19 17:26:33 +080020from autotest_lib.client.cros.audio import cmd_utils
Owen Lin56050862013-12-09 11:42:51 +080021from autotest_lib.client.cros.audio import cras_utils
Owen Lin7ab45a22013-11-19 17:26:33 +080022from autotest_lib.client.cros.audio import sox_utils
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080023
24LD_LIBRARY_PATH = 'LD_LIBRARY_PATH'
25
Owen Lin410840b2013-12-17 17:02:57 +080026_AUDIO_DIAGNOSTICS_PATH = '/usr/bin/audio_diagnostics'
27
Hsinyu Chaof80337a2012-04-07 18:02:29 +080028_DEFAULT_NUM_CHANNELS = 2
Dylan Reidbf9a5d42012-11-06 16:27:20 -080029_DEFAULT_REC_COMMAND = 'arecord -D hw:0,0 -d 10 -f dat'
Hsinyu Chaof80337a2012-04-07 18:02:29 +080030_DEFAULT_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L'
Owen Lin56050862013-12-09 11:42:51 +080031_DEFAULT_PLAYBACK_VOLUME = 100
32_DEFAULT_CAPTURE_GAIN = 2500
Hsin-Yu Chao4be6d182013-04-19 14:07:56 +080033
34# Minimum RMS value to pass when checking recorded file.
35_DEFAULT_SOX_RMS_THRESHOLD = 0.08
Hsinyu Chaof80337a2012-04-07 18:02:29 +080036
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +080037_JACK_VALUE_ON_RE = re.compile('.*values=on')
38_HP_JACK_CONTROL_RE = re.compile('numid=(\d+).*Headphone\sJack')
39_MIC_JACK_CONTROL_RE = re.compile('numid=(\d+).*Mic\sJack')
40
Hsinyu Chaof80337a2012-04-07 18:02:29 +080041_SOX_RMS_AMPLITUDE_RE = re.compile('RMS\s+amplitude:\s+(.+)')
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +080042_SOX_ROUGH_FREQ_RE = re.compile('Rough\s+frequency:\s+(.+)')
Hsinyu Chaof80337a2012-04-07 18:02:29 +080043
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080044_AUDIO_NOT_FOUND_RE = r'Audio\snot\sdetected'
45_MEASURED_LATENCY_RE = r'Measured\sLatency:\s(\d+)\suS'
46_REPORTED_LATENCY_RE = r'Reported\sLatency:\s(\d+)\suS'
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080047
Hsin-Yu Chaof6bbc6c2013-08-20 19:22:05 +080048# Tools from platform/audiotest
49AUDIOFUNTEST_PATH = 'audiofuntest'
50AUDIOLOOP_PATH = 'looptest'
51LOOPBACK_LATENCY_PATH = 'loopback_latency'
52SOX_PATH = 'sox'
53TEST_TONES_PATH = 'test_tones'
54
55
Hsin-Yu Chao30561af2013-09-06 14:03:56 +080056def set_mixer_controls(mixer_settings={}, card='0'):
57 '''
58 Sets all mixer controls listed in the mixer settings on card.
59
60 @param mixer_settings: Mixer settings to set.
61 @param card: Index of audio card to set mixer settings for.
62 '''
63 logging.info('Setting mixer control values on %s', card)
64 for item in mixer_settings:
65 logging.info('Setting %s to %s on card %s',
66 item['name'], item['value'], card)
67 cmd = 'amixer -c %s cset name=%s %s'
68 cmd = cmd % (card, item['name'], item['value'])
69 try:
70 utils.system(cmd)
71 except error.CmdError:
72 # A card is allowed not to support all the controls, so don't
73 # fail the test here if we get an error.
74 logging.info('amixer command failed: %s', cmd)
75
76def set_volume_levels(volume, capture):
77 '''
78 Sets the volume and capture gain through cras_test_client
79
80 @param volume: The playback volume to set.
81 @param capture: The capture gain to set.
82 '''
83 logging.info('Setting volume level to %d', volume)
84 utils.system('/usr/bin/cras_test_client --volume %d' % volume)
85 logging.info('Setting capture gain to %d', capture)
86 utils.system('/usr/bin/cras_test_client --capture_gain %d' % capture)
87 utils.system('/usr/bin/cras_test_client --dump_server_info')
88 utils.system('/usr/bin/cras_test_client --mute 0')
89 utils.system('amixer -c 0 contents')
90
91def loopback_latency_check(**args):
92 '''
93 Checks loopback latency.
94
95 @param args: additional arguments for loopback_latency.
96
97 @return A tuple containing measured and reported latency in uS.
98 Return None if no audio detected.
99 '''
100 noise_threshold = str(args['n']) if args.has_key('n') else '400'
101
102 cmd = '%s -n %s' % (LOOPBACK_LATENCY_PATH, noise_threshold)
103
104 output = utils.system_output(cmd, retain_output=True)
105
106 # Sleep for a short while to make sure device is not busy anymore
107 # after called loopback_latency.
108 time.sleep(.1)
109
110 measured_latency = None
111 reported_latency = None
112 for line in output.split('\n'):
113 match = re.search(_MEASURED_LATENCY_RE, line, re.I)
114 if match:
115 measured_latency = int(match.group(1))
116 continue
117 match = re.search(_REPORTED_LATENCY_RE, line, re.I)
118 if match:
119 reported_latency = int(match.group(1))
120 continue
121 if re.search(_AUDIO_NOT_FOUND_RE, line, re.I):
122 return None
123 if measured_latency and reported_latency:
124 return (measured_latency, reported_latency)
125 else:
126 # Should not reach here, just in case.
127 return None
128
129def get_mixer_jack_status(jack_reg_exp):
130 '''
131 Gets the mixer jack status.
132
133 @param jack_reg_exp: The regular expression to match jack control name.
134
135 @return None if the control does not exist, return True if jack control
136 is detected plugged, return False otherwise.
137 '''
138 output = utils.system_output('amixer -c0 controls', retain_output=True)
139 numid = None
140 for line in output.split('\n'):
141 m = jack_reg_exp.match(line)
142 if m:
143 numid = m.group(1)
144 break
145
146 # Proceed only when matched numid is not empty.
147 if numid:
148 output = utils.system_output('amixer -c0 cget numid=%s' % numid)
149 for line in output.split('\n'):
150 if _JACK_VALUE_ON_RE.match(line):
151 return True
152 return False
153 else:
154 return None
155
156def get_hp_jack_status():
157 '''Gets the status of headphone jack'''
158 status = get_mixer_jack_status(_HP_JACK_CONTROL_RE)
159 if status is not None:
160 return status
161
162 # When headphone jack is not found in amixer, lookup input devices
163 # instead.
164 #
165 # TODO(hychao): Check hp/mic jack status dynamically from evdev. And
166 # possibly replace the existing check using amixer.
167 for evdev in glob('/dev/input/event*'):
168 device = InputDevice(evdev)
169 if device.is_hp_jack():
170 return device.get_headphone_insert()
171 else:
172 return None
173
174def get_mic_jack_status():
175 '''Gets the status of mic jack'''
176 status = get_mixer_jack_status(_MIC_JACK_CONTROL_RE)
177 if status is not None:
178 return status
179
180 # When mic jack is not found in amixer, lookup input devices instead.
181 for evdev in glob('/dev/input/event*'):
182 device = InputDevice(evdev)
183 if device.is_mic_jack():
184 return device.get_microphone_insert()
185 else:
186 return None
187
Owen Lin56050862013-12-09 11:42:51 +0800188def log_loopback_dongle_status():
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800189 '''
Owen Lin56050862013-12-09 11:42:51 +0800190 Log the status of the loopback dongle to make sure it is equipped correctly.
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800191 '''
Owen Lin56050862013-12-09 11:42:51 +0800192 dongle_status_ok = True
193
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800194 # Check Mic Jack
195 mic_jack_status = get_mic_jack_status()
Owen Lin56050862013-12-09 11:42:51 +0800196 logging.info('Mic jack status: %s', mic_jack_status)
197 dongle_status_ok &= mic_jack_status
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800198
199 # Check Headphone Jack
200 hp_jack_status = get_hp_jack_status()
Owen Lin56050862013-12-09 11:42:51 +0800201 logging.info('Headphone jack status: %s', hp_jack_status)
202 dongle_status_ok &= hp_jack_status
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800203
204 # Use latency check to test if audio can be captured through dongle.
205 # We only want to know the basic function of dongle, so no need to
206 # assert the latency accuracy here.
207 latency = loopback_latency_check(n=4000)
208 if latency:
209 logging.info('Got latency measured %d, reported %d',
210 latency[0], latency[1])
211 else:
Owen Lin56050862013-12-09 11:42:51 +0800212 logging.info('Latency check fail.')
213 dongle_status_ok = False
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800214
Owen Lin56050862013-12-09 11:42:51 +0800215 logging.info('audio loopback dongle test: %s',
216 'PASS' if dongle_status_ok else 'FAIL')
Hsin-Yu Chao30561af2013-09-06 14:03:56 +0800217
218# Functions to test audio palyback.
219def play_sound(duration_seconds=None, audio_file_path=None):
220 '''
221 Plays a sound file found at |audio_file_path| for |duration_seconds|.
222
223 If |audio_file_path|=None, plays a default audio file.
224 If |duration_seconds|=None, plays audio file in its entirety.
225
226 @param duration_seconds: Duration to play sound.
227 @param audio_file_path: Path to the audio file.
228 '''
229 if not audio_file_path:
230 audio_file_path = '/usr/local/autotest/cros/audio/sine440.wav'
231 duration_arg = ('-d %d' % duration_seconds) if duration_seconds else ''
232 utils.system('aplay %s %s' % (duration_arg, audio_file_path))
233
234def get_play_sine_args(channel, odev='default', freq=1000, duration=10,
235 sample_size=16):
236 '''Gets the command args to generate a sine wav to play to odev.
237
238 @param channel: 0 for left, 1 for right; otherwize, mono.
239 @param odev: alsa output device.
240 @param freq: frequency of the generated sine tone.
241 @param duration: duration of the generated sine tone.
242 @param sample_size: output audio sample size. Default to 16.
243 '''
244 cmdargs = [SOX_PATH, '-b', str(sample_size), '-n', '-t', 'alsa',
245 odev, 'synth', str(duration)]
246 if channel == 0:
247 cmdargs += ['sine', str(freq), 'sine', '0']
248 elif channel == 1:
249 cmdargs += ['sine', '0', 'sine', str(freq)]
250 else:
251 cmdargs += ['sine', str(freq)]
252
253 return cmdargs
254
255def play_sine(channel, odev='default', freq=1000, duration=10,
256 sample_size=16):
257 '''Generates a sine wave and plays to odev.
258
259 @param channel: 0 for left, 1 for right; otherwize, mono.
260 @param odev: alsa output device.
261 @param freq: frequency of the generated sine tone.
262 @param duration: duration of the generated sine tone.
263 @param sample_size: output audio sample size. Default to 16.
264 '''
265 cmdargs = get_play_sine_args(channel, odev, freq, duration, sample_size)
266 utils.system(' '.join(cmdargs))
267
Hsin-Yu Chao22bebdc2013-09-06 16:32:51 +0800268# Functions to compose customized sox command, execute it and process the
269# output of sox command.
270def get_sox_mixer_cmd(infile, channel,
271 num_channels=_DEFAULT_NUM_CHANNELS,
272 sox_format=_DEFAULT_SOX_FORMAT):
273 '''Gets sox mixer command to reduce channel.
274
275 @param infile: Input file name.
276 @param channel: The selected channel to take effect.
277 @param num_channels: The number of total channels to test.
278 @param sox_format: Format to generate sox command.
279 '''
280 # Build up a pan value string for the sox command.
281 if channel == 0:
282 pan_values = '1'
283 else:
284 pan_values = '0'
285 for pan_index in range(1, num_channels):
286 if channel == pan_index:
287 pan_values = '%s%s' % (pan_values, ',1')
288 else:
289 pan_values = '%s%s' % (pan_values, ',0')
290
291 return '%s -c 2 %s %s -c 1 %s - mixer %s' % (SOX_PATH,
292 sox_format, infile, sox_format, pan_values)
293
294def sox_stat_output(infile, channel,
295 num_channels=_DEFAULT_NUM_CHANNELS,
296 sox_format=_DEFAULT_SOX_FORMAT):
297 '''Executes sox stat command.
298
299 @param infile: Input file name.
300 @param channel: The selected channel.
301 @param num_channels: The number of total channels to test.
302 @param sox_format: Format to generate sox command.
303
304 @return The output of sox stat command
305 '''
306 sox_mixer_cmd = get_sox_mixer_cmd(infile, channel,
307 num_channels, sox_format)
308 stat_cmd = '%s -c 1 %s - -n stat 2>&1' % (SOX_PATH, sox_format)
309 sox_cmd = '%s | %s' % (sox_mixer_cmd, stat_cmd)
310 return utils.system_output(sox_cmd, retain_output=True)
311
312def get_audio_rms(sox_output):
313 '''Gets the audio RMS value from sox stat output
314
315 @param sox_output: Output of sox stat command.
316
317 @return The RMS value parsed from sox stat output.
318 '''
319 for rms_line in sox_output.split('\n'):
320 m = _SOX_RMS_AMPLITUDE_RE.match(rms_line)
321 if m is not None:
322 return float(m.group(1))
323
324def get_rough_freq(sox_output):
325 '''Gets the rough audio frequency from sox stat output
326
327 @param sox_output: Output of sox stat command.
328
329 @return The rough frequency value parsed from sox stat output.
330 '''
331 for rms_line in sox_output.split('\n'):
332 m = _SOX_ROUGH_FREQ_RE.match(rms_line)
333 if m is not None:
334 return int(m.group(1))
335
336def check_audio_rms(sox_output, sox_threshold=_DEFAULT_SOX_RMS_THRESHOLD):
337 """Checks if the calculated RMS value is expected.
338
339 @param sox_output: The output from sox stat command.
340 @param sox_threshold: The threshold to test RMS value against.
341
342 @raises error.TestError if RMS amplitude can't be parsed.
343 @raises error.TestFail if the RMS amplitude of the recording isn't above
344 the threshold.
345 """
346 rms_val = get_audio_rms(sox_output)
347
348 # In case we don't get a valid RMS value.
349 if rms_val is None:
350 raise error.TestError(
351 'Failed to generate an audio RMS value from playback.')
352
353 logging.info('Got audio RMS value of %f. Minimum pass is %f.',
354 rms_val, sox_threshold)
355 if rms_val < sox_threshold:
356 raise error.TestFail(
357 'Audio RMS value %f too low. Minimum pass is %f.' %
358 (rms_val, sox_threshold))
359
360def noise_reduce_file(in_file, noise_file, out_file,
361 sox_format=_DEFAULT_SOX_FORMAT):
362 '''Runs the sox command to noise-reduce in_file using
363 the noise profile from noise_file.
364
365 @param in_file: The file to noise reduce.
366 @param noise_file: The file containing the noise profile.
367 This can be created by recording silence.
368 @param out_file: The file contains the noise reduced sound.
369 @param sox_format: The sox format to generate sox command.
370 '''
371 prof_cmd = '%s -c 2 %s %s -n noiseprof' % (SOX_PATH,
372 sox_format, noise_file)
373 reduce_cmd = ('%s -c 2 %s %s -c 2 %s %s noisered' %
374 (SOX_PATH, sox_format, in_file, sox_format, out_file))
375 utils.system('%s | %s' % (prof_cmd, reduce_cmd))
376
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800377def record_sample(tmpfile, record_command=_DEFAULT_REC_COMMAND):
378 '''Records a sample from the default input device.
379
380 @param tmpfile: The file to record to.
381 @param record_command: The command to record audio.
382 '''
383 utils.system('%s %s' % (record_command, tmpfile))
384
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800385def create_wav_file(wav_dir, prefix=""):
386 '''Creates a unique name for wav file.
Adrian Li689d3ff2013-07-15 15:11:06 -0700387
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800388 The created file name will be preserved in autotest result directory
389 for future analysis.
390
391 @param prefix: specified file name prefix.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800392 '''
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800393 filename = "%s-%s.wav" % (prefix, time.time())
394 return os.path.join(wav_dir, filename)
395
Owen Lin66cd9de2013-11-08 10:26:49 +0800396def run_in_parallel(*funs):
397 threads = []
398 for f in funs:
399 t = threading.Thread(target=f)
400 t.start()
401 threads.append(t)
402
403 for t in threads:
404 t.join()
405
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800406def loopback_test_channels(noise_file_name, wav_dir,
Owen Lin66cd9de2013-11-08 10:26:49 +0800407 playback_callback=None,
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800408 check_recorded_callback=check_audio_rms,
409 preserve_test_file=True,
410 num_channels = _DEFAULT_NUM_CHANNELS,
Owen Lin66cd9de2013-11-08 10:26:49 +0800411 record_callback=record_sample,
412 mix_callback=None):
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800413 '''Tests loopback on all channels.
414
415 @param noise_file_name: Name of the file contains pre-recorded noise.
Owen Lin66cd9de2013-11-08 10:26:49 +0800416 @param playback_callback: The callback to do the playback for
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800417 one channel.
Owen Lin66cd9de2013-11-08 10:26:49 +0800418 @param record_callback: The callback to do the recording.
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800419 @param check_recorded_callback: The callback to check recorded file.
420 @param preserve_test_file: Retain the recorded files for future debugging.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800421 '''
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800422 for channel in xrange(num_channels):
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800423 record_file_name = create_wav_file(wav_dir,
424 "record-%d" % channel)
Owen Lin66cd9de2013-11-08 10:26:49 +0800425 functions = [lambda: record_callback(record_file_name)]
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800426
Owen Lin66cd9de2013-11-08 10:26:49 +0800427 if playback_callback:
428 functions.append(lambda: playback_callback(channel))
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800429
Owen Lin66cd9de2013-11-08 10:26:49 +0800430 if mix_callback:
431 mix_file_name = create_wav_file(wav_dir, "mix-%d" % channel)
432 functions.append(lambda: mix_callback(mix_file_name))
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800433
Owen Lin66cd9de2013-11-08 10:26:49 +0800434 run_in_parallel(*functions)
435
436 if mix_callback:
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800437 sox_output_mix = sox_stat_output(mix_file_name, channel)
438 rms_val_mix = get_audio_rms(sox_output_mix)
439 logging.info('Got mixed audio RMS value of %f.', rms_val_mix)
Adrian Li689d3ff2013-07-15 15:11:06 -0700440
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800441 sox_output_record = sox_stat_output(record_file_name, channel)
442 rms_val_record = get_audio_rms(sox_output_record)
443 logging.info('Got recorded audio RMS value of %f.', rms_val_record)
Adrian Li689d3ff2013-07-15 15:11:06 -0700444
Owen Lin66cd9de2013-11-08 10:26:49 +0800445 reduced_file_name = create_wav_file(wav_dir,
446 "reduced-%d" % channel)
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800447 noise_reduce_file(record_file_name, noise_file_name,
448 reduced_file_name)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800449
Owen Lin66cd9de2013-11-08 10:26:49 +0800450 sox_output_reduced = sox_stat_output(reduced_file_name, channel)
Adrian Li689d3ff2013-07-15 15:11:06 -0700451
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800452 if not preserve_test_file:
453 os.unlink(reduced_file_name)
454 os.unlink(record_file_name)
Owen Lin66cd9de2013-11-08 10:26:49 +0800455 if mix_callback:
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800456 os.unlink(mix_file_name)
Adrian Li689d3ff2013-07-15 15:11:06 -0700457
Hsin-Yu Chao4be66782013-09-06 13:45:54 +0800458 check_recorded_callback(sox_output_reduced)
Owen Lindae7a0d2013-12-05 13:34:06 +0800459
460
461def get_channel_sox_stat(
Owen Lin2013e462013-12-05 17:54:42 +0800462 input_audio, channel_index, channels=2, bits=16, rate=48000):
Owen Lindae7a0d2013-12-05 13:34:06 +0800463 """Gets the sox stat info of the selected channel in the input audio file.
464
465 @param input_audio: The input audio file to be analyzed.
466 @param channel_index: The index of the channel to be analyzed.
467 (1 for the first channel).
468 @param channels: The number of channels in the input audio.
469 @param bits: The number of bits of each audio sample.
470 @param rate: The sampling rate.
471 """
472 if channel_index <= 0 or channel_index > channels:
473 raise ValueError('incorrect channel_indexi: %d' % channel_index)
474
475 if channels == 1:
Owen Lin4a154c42013-12-26 11:03:20 +0800476 return sox_utils.get_stat(
477 input_audio, channels=channels, bits=bits, rate=rate)
Owen Lindae7a0d2013-12-05 13:34:06 +0800478
479 p1 = cmd_utils.popen(
480 sox_utils.extract_channel_cmd(
481 input_audio, '-', channel_index,
482 channels=channels, bits=bits, rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800483 stdout=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800484 p2 = cmd_utils.popen(
485 sox_utils.stat_cmd('-', channels=1, bits=bits, rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800486 stdin=p1.stdout, stderr=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800487 stat_output = p2.stderr.read()
488 cmd_utils.wait_and_check_returncode(p1, p2)
489 return sox_utils.parse_stat_output(stat_output)
490
491
Owen Lin5bba6c02013-12-13 14:17:08 +0800492def check_rms(
493 input_audio, rms_threshold=_DEFAULT_SOX_RMS_THRESHOLD,
494 channels=1, bits=16, rate=48000):
Owen Lin5cbf5c32013-12-13 15:13:37 +0800495 """Checks the RMS values of all channels of the input audio.
Owen Lin5bba6c02013-12-13 14:17:08 +0800496
Owen Lin5cbf5c32013-12-13 15:13:37 +0800497 @param input_audio: The input audio file to be checked.
498 @param rms_threshold: The minimal requirement for RMS values of
499 all channels.
Owen Lin5bba6c02013-12-13 14:17:08 +0800500 @param channels: The number of channels in the input audio.
501 @param bits: The number of bits of each audio sample.
502 @param rate: The sampling rate.
503 @raise TestFail: If the RMS of any channel is less than the threshold.
504 """
505 stats = [get_channel_sox_stat(
506 input_audio, i + 1, channels=channels, bits=bits,
507 rate=rate) for i in xrange(channels)]
508
509 logging.info('sox stat: %s', [str(s) for s in stats])
510
511 if any(s.rms < rms_threshold for s in stats):
512 raise error.TestFail('RMS: %s' % [s.rms for s in stats])
513
514
Owen Lindae7a0d2013-12-05 13:34:06 +0800515def reduce_noise_and_check_rms(
516 input_audio, noise_file, rms_threshold=_DEFAULT_SOX_RMS_THRESHOLD,
517 channels=1, bits=16, rate=48000):
Owen Lin5cbf5c32013-12-13 15:13:37 +0800518 """Reduces noise in the input audio by the given noise file and then checks
519 the RMS values of all channels of the input audio.
Owen Lin5bba6c02013-12-13 14:17:08 +0800520
521 @param input_audio: The input audio file to be analyzed.
522 @param noise_file: The noise file used to reduce noise in the input audio.
523 @param rms_threshold: The minimal requirement for RMS of all channels.
524 @param channels: The number of channels in the input audio.
525 @param bits: The number of bits of each audio sample.
526 @param rate: The sampling rate.
527 @raise TestFail: If the RMS of any channel is less than the threshold.
528 """
Owen Lindae7a0d2013-12-05 13:34:06 +0800529 with tempfile.NamedTemporaryFile() as reduced_file:
530 p1 = cmd_utils.popen(
531 sox_utils.noise_profile_cmd(
532 noise_file, '-', channels=channels, bits=bits,
533 rate=rate),
Owen Linad6610a2013-12-13 11:20:48 +0800534 stdout=cmd_utils.PIPE)
Owen Lindae7a0d2013-12-05 13:34:06 +0800535 p2 = cmd_utils.popen(
536 sox_utils.noise_reduce_cmd(
537 input_audio, reduced_file.name, '-',
538 channels=channels, bits=bits, rate=rate),
539 stdin=p1.stdout)
540 cmd_utils.wait_and_check_returncode(p1, p2)
Owen Lin5bba6c02013-12-13 14:17:08 +0800541 check_rms(reduced_file.name, rms_threshold, channels, bits, rate)
Owen Lin56050862013-12-09 11:42:51 +0800542
543
544def cras_rms_test_setup():
545 """ Setups for the cras_rms_tests.
546
547 To make sure the line_out-to-mic_in path is all green.
548 """
549 # TODO(owenlin): Now, the nodes are choosed by chrome.
550 # We should do it here.
551 output_node, _ = cras_utils.get_selected_nodes()
552
553 cras_utils.set_system_volume(_DEFAULT_PLAYBACK_VOLUME)
554 cras_utils.set_node_volume(output_node, _DEFAULT_PLAYBACK_VOLUME)
555
556 cras_utils.set_capture_gain(_DEFAULT_CAPTURE_GAIN)
557
558 cras_utils.set_system_mute(False)
559 cras_utils.set_capture_mute(False)
560
561
562def chrome_rms_test(run_once):
563 """ An annotation used for RMS audio test with Chrome.
564
565 The first parameter for run_once will be replaced by a chrome instance.
566
567 @param run_once: the function which this annotation will be applied.
568 """
569 def wrapper(self, cr=None, *args, **kargs):
570 # Not all client of this file using telemetry.
571 # Just do the import here for those who really need it.
572 from autotest_lib.client.common_lib.cros import chrome
573 try:
574 with chrome.Chrome() as chrome_instance:
575 # The audio configuration could be changed when we
576 # restart chrome.
577 cras_rms_test_setup()
578 run_once(self, chrome_instance, *args, **kargs)
579 except error.TestFail:
580 logging.info('audio postmortem report')
Owen Lin56050862013-12-09 11:42:51 +0800581 log_loopback_dongle_status()
Owen Lin410840b2013-12-17 17:02:57 +0800582 logging.info(cmd_utils.execute(
583 [_AUDIO_DIAGNOSTICS_PATH], stdout=cmd_utils.PIPE))
Owen Lin56050862013-12-09 11:42:51 +0800584 raise
585 return wrapper
586
587
588def cras_rms_test(run_once):
589 """ An annotation used for RMS audio test with CRAS.
590
591 @param run_once: the function which this annotation will be applied.
592 """
593 def wrapper(*args, **kargs):
594 cras_rms_test_setup()
595 try:
596 run_once(*args, **kargs)
597 except error.TestFail:
598 logging.info('audio postmortem report')
Owen Lin56050862013-12-09 11:42:51 +0800599 log_loopback_dongle_status()
Owen Lin410840b2013-12-17 17:02:57 +0800600 logging.info(cmd_utils.execute(
601 [_AUDIO_DIAGNOSTICS_PATH], stdout=cmd_utils.PIPE))
Owen Lin56050862013-12-09 11:42:51 +0800602 raise
603 return wrapper
604
605
606def alsa_rms_test(run_once):
607 """ An annotation used for RMS audio test with ALSA.
608
609 @param run_once: the function which this annotation to be applied.
610 """
611 def wrapper(*args, **kargs):
612 # TODO(owenlin): Don't use CRAS for setup.
613 cras_rms_test_setup()
614
615 # CRAS does not apply the volume and capture gain to ALSA util
616 # streams are added. Do that to ensure the values have been set.
Owen Linbd415142013-12-18 10:38:58 +0800617 cras_utils.playback('/dev/zero', duration=0.1)
Owen Lin56050862013-12-09 11:42:51 +0800618 cras_utils.capture('/dev/null', duration=0.1)
619 try:
620 run_once(*args, **kargs)
621 except error.TestFail:
622 logging.info('audio postmortem report')
623 log_loopback_dongle_status()
Owen Lin410840b2013-12-17 17:02:57 +0800624 logging.info(cmd_utils.execute(
625 [_AUDIO_DIAGNOSTICS_PATH], stdout=cmd_utils.PIPE))
Owen Lin56050862013-12-09 11:42:51 +0800626 raise
627 return wrapper