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