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