blob: a4471863f1168482bd1bbf0dffb83565b2ae9783 [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
Hsinyu Chaof80337a2012-04-07 18:02:29 +08008import re
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +08009import threading
Hsin-Yu Chaof272d8e2013-04-05 03:28:50 +080010import time
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080011
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +080012from glob import glob
13
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080014from autotest_lib.client.bin import utils
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +080015from autotest_lib.client.bin.input.input_device import *
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080016from autotest_lib.client.common_lib import error
17
18LD_LIBRARY_PATH = 'LD_LIBRARY_PATH'
19
Hsinyu Chaof80337a2012-04-07 18:02:29 +080020_DEFAULT_NUM_CHANNELS = 2
Dylan Reidbf9a5d42012-11-06 16:27:20 -080021_DEFAULT_REC_COMMAND = 'arecord -D hw:0,0 -d 10 -f dat'
Hsinyu Chaof80337a2012-04-07 18:02:29 +080022_DEFAULT_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L'
Hsin-Yu Chao4be6d182013-04-19 14:07:56 +080023
24# Minimum RMS value to pass when checking recorded file.
25_DEFAULT_SOX_RMS_THRESHOLD = 0.08
Hsinyu Chaof80337a2012-04-07 18:02:29 +080026
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +080027_JACK_VALUE_ON_RE = re.compile('.*values=on')
28_HP_JACK_CONTROL_RE = re.compile('numid=(\d+).*Headphone\sJack')
29_MIC_JACK_CONTROL_RE = re.compile('numid=(\d+).*Mic\sJack')
30
Hsinyu Chaof80337a2012-04-07 18:02:29 +080031_SOX_RMS_AMPLITUDE_RE = re.compile('RMS\s+amplitude:\s+(.+)')
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +080032_SOX_ROUGH_FREQ_RE = re.compile('Rough\s+frequency:\s+(.+)')
Hsinyu Chaof80337a2012-04-07 18:02:29 +080033_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L'
34
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080035_AUDIO_NOT_FOUND_RE = r'Audio\snot\sdetected'
36_MEASURED_LATENCY_RE = r'Measured\sLatency:\s(\d+)\suS'
37_REPORTED_LATENCY_RE = r'Reported\sLatency:\s(\d+)\suS'
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080038
39class RecordSampleThread(threading.Thread):
40 '''Wraps the execution of arecord in a thread.'''
41 def __init__(self, audio, recordfile):
42 threading.Thread.__init__(self)
43 self._audio = audio
44 self._recordfile = recordfile
45
46 def run(self):
47 self._audio.record_sample(self._recordfile)
48
49
Adrian Li689d3ff2013-07-15 15:11:06 -070050class RecordMixThread(threading.Thread):
51 '''
52 Wraps the execution of recording the mixed loopback stream in
53 cras_test_client in a thread.
54 '''
55 def __init__(self, audio, recordfile):
56 threading.Thread.__init__(self)
57 self._audio = audio
58 self._recordfile = recordfile
59
60 def run(self):
61 self._audio.record_mix(self._recordfile)
62
63
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080064class AudioHelper(object):
65 '''
66 A helper class contains audio related utility functions.
67 '''
Dylan Reidbf9a5d42012-11-06 16:27:20 -080068 def __init__(self, test,
69 sox_format = _DEFAULT_SOX_FORMAT,
Dylan Reid51f289c2012-11-06 17:16:24 -080070 sox_threshold = _DEFAULT_SOX_RMS_THRESHOLD,
Dylan Reidbf9a5d42012-11-06 16:27:20 -080071 record_command = _DEFAULT_REC_COMMAND,
Adrian Li689d3ff2013-07-15 15:11:06 -070072 num_channels = _DEFAULT_NUM_CHANNELS,
73 mix_command = None):
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080074 self._test = test
Dylan Reid51f289c2012-11-06 17:16:24 -080075 self._sox_threshold = sox_threshold
Hsinyu Chaof80337a2012-04-07 18:02:29 +080076 self._sox_format = sox_format
Dylan Reidbf9a5d42012-11-06 16:27:20 -080077 self._rec_cmd = record_command
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080078 self._num_channels = num_channels
Adrian Li689d3ff2013-07-15 15:11:06 -070079 self._mix_cmd = mix_command
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080080
81 def setup_deps(self, deps):
82 '''
83 Sets up audio related dependencies.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +080084
85 @param deps: List of dependencies to set up.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080086 '''
87 for dep in deps:
88 if dep == 'test_tones':
89 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
90 self._test.job.install_pkg(dep, 'dep', dep_dir)
91 self.test_tones_path = os.path.join(dep_dir, 'src', dep)
92 elif dep == 'audioloop':
93 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
94 self._test.job.install_pkg(dep, 'dep', dep_dir)
95 self.audioloop_path = os.path.join(dep_dir, 'src',
96 'looptest')
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080097 self.loopback_latency_path = os.path.join(dep_dir, 'src',
98 'loopback_latency')
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080099 elif dep == 'sox':
100 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
101 self._test.job.install_pkg(dep, 'dep', dep_dir)
102 self.sox_path = os.path.join(dep_dir, 'bin', dep)
103 self.sox_lib_path = os.path.join(dep_dir, 'lib')
104 if os.environ.has_key(LD_LIBRARY_PATH):
105 paths = os.environ[LD_LIBRARY_PATH].split(':')
106 if not self.sox_lib_path in paths:
107 paths.append(self.sox_lib_path)
108 os.environ[LD_LIBRARY_PATH] = ':'.join(paths)
109 else:
110 os.environ[LD_LIBRARY_PATH] = self.sox_lib_path
111
112 def cleanup_deps(self, deps):
113 '''
114 Cleans up environments which has been setup for dependencies.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800115
116 @param deps: List of dependencies to clean up.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800117 '''
118 for dep in deps:
119 if dep == 'sox':
120 if (os.environ.has_key(LD_LIBRARY_PATH)
121 and hasattr(self, 'sox_lib_path')):
122 paths = filter(lambda x: x != self.sox_lib_path,
123 os.environ[LD_LIBRARY_PATH].split(':'))
124 os.environ[LD_LIBRARY_PATH] = ':'.join(paths)
125
Derek Basehoree973ce42012-07-10 23:38:32 -0700126 def set_volume_levels(self, volume, capture):
127 '''
128 Sets the volume and capture gain through cras_test_client
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800129
130 @param volume: The playback volume to set.
131 @param capture: The capture gain to set.
Derek Basehoree973ce42012-07-10 23:38:32 -0700132 '''
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800133 logging.info('Setting volume level to %d', volume)
Derek Basehoree973ce42012-07-10 23:38:32 -0700134 utils.system('/usr/bin/cras_test_client --volume %d' % volume)
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800135 logging.info('Setting capture gain to %d', capture)
Derek Basehoree973ce42012-07-10 23:38:32 -0700136 utils.system('/usr/bin/cras_test_client --capture_gain %d' % capture)
137 utils.system('/usr/bin/cras_test_client --dump_server_info')
Dylan Reidc264e012012-11-06 18:26:12 -0800138 utils.system('/usr/bin/cras_test_client --mute 0')
Derek Basehoree973ce42012-07-10 23:38:32 -0700139 utils.system('amixer -c 0 contents')
140
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800141 def get_mixer_jack_status(self, jack_reg_exp):
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800142 '''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800143 Gets the mixer jack status.
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800144
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800145 @param jack_reg_exp: The regular expression to match jack control name.
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800146
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800147 @return None if the control does not exist, return True if jack control
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800148 is detected plugged, return False otherwise.
149 '''
150 output = utils.system_output('amixer -c0 controls', retain_output=True)
151 numid = None
152 for line in output.split('\n'):
153 m = jack_reg_exp.match(line)
154 if m:
155 numid = m.group(1)
156 break
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800157
158 # Proceed only when matched numid is not empty.
159 if numid:
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800160 output = utils.system_output('amixer -c0 cget numid=%s' % numid)
161 for line in output.split('\n'):
162 if _JACK_VALUE_ON_RE.match(line):
163 return True
164 return False
165 else:
166 return None
167
168 def get_hp_jack_status(self):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800169 '''Gets the status of headphone jack'''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800170 status = self.get_mixer_jack_status(_HP_JACK_CONTROL_RE)
171 if status is not None:
172 return status
173
174 # When headphone jack is not found in amixer, lookup input devices
175 # instead.
176 #
177 # TODO(hychao): Check hp/mic jack status dynamically from evdev. And
178 # possibly replace the existing check using amixer.
179 for evdev in glob('/dev/input/event*'):
180 device = InputDevice(evdev)
181 if device.is_hp_jack():
182 return device.get_headphone_insert()
183 else:
184 return None
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800185
186 def get_mic_jack_status(self):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800187 '''Gets the status of mic jack'''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800188 status = self.get_mixer_jack_status(_MIC_JACK_CONTROL_RE)
189 if status is not None:
190 return status
191
192 # When mic jack is not found in amixer, lookup input devices instead.
193 for evdev in glob('/dev/input/event*'):
194 device = InputDevice(evdev)
195 if device.is_mic_jack():
196 return device.get_microphone_insert()
197 else:
198 return None
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800199
200 def check_loopback_dongle(self):
201 '''
202 Checks if loopback dongle is equipped correctly.
203 '''
204 # Check Mic Jack
205 mic_jack_status = self.get_mic_jack_status()
206 if mic_jack_status is None:
207 logging.warning('Found no Mic Jack control, skip check.')
208 elif not mic_jack_status:
209 logging.info('Mic jack is not plugged.')
210 return False
211 else:
212 logging.info('Mic jack is plugged.')
213
214 # Check Headphone Jack
215 hp_jack_status = self.get_hp_jack_status()
216 if hp_jack_status is None:
217 logging.warning('Found no Headphone Jack control, skip check.')
218 elif not hp_jack_status:
219 logging.info('Headphone jack is not plugged.')
220 return False
221 else:
222 logging.info('Headphone jack is plugged.')
223
Hsin-Yu Chao3953f522012-11-12 18:39:39 +0800224 # Use latency check to test if audio can be captured through dongle.
225 # We only want to know the basic function of dongle, so no need to
226 # assert the latency accuracy here.
227 latency = self.loopback_latency_check(n=4000)
228 if latency:
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800229 logging.info('Got latency measured %d, reported %d',
230 latency[0], latency[1])
Hsin-Yu Chao3953f522012-11-12 18:39:39 +0800231 else:
232 logging.warning('Latency check fail.')
233 return False
234
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800235 return True
236
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800237 def set_mixer_controls(self, mixer_settings={}, card='0'):
238 '''
239 Sets all mixer controls listed in the mixer settings on card.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800240
241 @param mixer_settings: Mixer settings to set.
242 @param card: Index of audio card to set mixer settings for.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800243 '''
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800244 logging.info('Setting mixer control values on %s', card)
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800245 for item in mixer_settings:
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800246 logging.info('Setting %s to %s on card %s',
247 item['name'], item['value'], card)
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800248 cmd = 'amixer -c %s cset name=%s %s'
249 cmd = cmd % (card, item['name'], item['value'])
250 try:
251 utils.system(cmd)
252 except error.CmdError:
253 # A card is allowed not to support all the controls, so don't
254 # fail the test here if we get an error.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800255 logging.info('amixer command failed: %s', cmd)
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800256
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800257 def sox_stat_output(self, infile, channel):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800258 '''Executes sox stat command.
259
260 @param infile: Input file name.
261 @param channel: The selected channel.
262
263 @return The output of sox stat command
264 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800265 sox_mixer_cmd = self.get_sox_mixer_cmd(infile, channel)
266 stat_cmd = '%s -c 1 %s - -n stat 2>&1' % (self.sox_path,
267 self._sox_format)
268 sox_cmd = '%s | %s' % (sox_mixer_cmd, stat_cmd)
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800269 return utils.system_output(sox_cmd, retain_output=True)
270
271 def get_audio_rms(self, sox_output):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800272 '''Gets the audio RMS value from sox stat output
273
274 @param sox_output: Output of sox stat command.
275
276 @return The RMS value parsed from sox stat output.
277 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800278 for rms_line in sox_output.split('\n'):
279 m = _SOX_RMS_AMPLITUDE_RE.match(rms_line)
280 if m is not None:
281 return float(m.group(1))
282
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800283 def get_rough_freq(self, sox_output):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800284 '''Gets the rough audio frequency from sox stat output
285
286 @param sox_output: Output of sox stat command.
287
288 @return The rough frequency value parsed from sox stat output.
289 '''
290
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800291 for rms_line in sox_output.split('\n'):
292 m = _SOX_ROUGH_FREQ_RE.match(rms_line)
293 if m is not None:
294 return int(m.group(1))
295
296
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800297 def get_sox_mixer_cmd(self, infile, channel):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800298 '''Gets sox mixer command to reduce channel.
299
300 @param infile: Input file name.
301 @param channel: The selected channel to take effect.
302 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800303 # Build up a pan value string for the sox command.
304 if channel == 0:
305 pan_values = '1'
306 else:
307 pan_values = '0'
308 for pan_index in range(1, self._num_channels):
309 if channel == pan_index:
310 pan_values = '%s%s' % (pan_values, ',1')
311 else:
312 pan_values = '%s%s' % (pan_values, ',0')
313
314 return '%s -c 2 %s %s -c 1 %s - mixer %s' % (self.sox_path,
315 self._sox_format, infile, self._sox_format, pan_values)
316
317 def noise_reduce_file(self, in_file, noise_file, out_file):
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800318 '''Runs the sox command to noise-reduce in_file using
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800319 the noise profile from noise_file.
320
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800321 @param in_file: The file to noise reduce.
322 @param noise_file: The file containing the noise profile.
323 This can be created by recording silence.
324 @param out_file: The file contains the noise reduced sound.
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800325
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800326 @return The name of the file containing the noise-reduced data.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800327 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800328 prof_cmd = '%s -c 2 %s %s -n noiseprof' % (self.sox_path,
329 _SOX_FORMAT, noise_file)
330 reduce_cmd = ('%s -c 2 %s %s -c 2 %s %s noisered' %
331 (self.sox_path, _SOX_FORMAT, in_file, _SOX_FORMAT, out_file))
332 utils.system('%s | %s' % (prof_cmd, reduce_cmd))
333
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800334 def record_sample(self, tmpfile):
335 '''Records a sample from the default input device.
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800336
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800337 @param tmpfile: The file to record to.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800338 '''
Dylan Reidbf9a5d42012-11-06 16:27:20 -0800339 cmd_rec = self._rec_cmd + ' %s' % tmpfile
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800340 logging.info('Command %s recording now', cmd_rec)
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800341 utils.system(cmd_rec)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800342
Adrian Li689d3ff2013-07-15 15:11:06 -0700343 def record_mix(self, tmpfile):
344 '''Records a sample from the mixed loopback stream in cras_test_client.
345
346 @param tmpfile: The file to record to.
347 '''
348 cmd_mix = self._mix_cmd + ' %s' % tmpfile
349 logging.info('Command %s recording now', cmd_mix)
350 utils.system(cmd_mix)
351
Rohit Makasana8ef9e292013-05-02 19:02:15 -0700352 def loopback_test_channels(self, noise_file_name,
353 loopback_callback=None,
354 check_recorded_callback=None,
355 preserve_test_file=True):
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800356 '''Tests loopback on all channels.
357
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800358 @param noise_file_name: Name of the file contains pre-recorded noise.
359 @param loopback_callback: The callback to do the loopback for
360 one channel.
361 @param check_recorded_callback: The callback to check recorded file.
Rohit Makasana8ef9e292013-05-02 19:02:15 -0700362 @param preserve_test_file: Retain the recorded files for future debugging.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800363 '''
364 for channel in xrange(self._num_channels):
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800365 reduced_file_name = self.create_wav_file("reduced-%d" % channel)
366 record_file_name = self.create_wav_file("record-%d" % channel)
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800367 record_thread = RecordSampleThread(self, record_file_name)
368 record_thread.start()
Adrian Li689d3ff2013-07-15 15:11:06 -0700369
370 if self._mix_cmd != None:
371 mix_file_name = self.create_wav_file("mix-%d" % channel)
372 mix_thread = RecordMixThread(self, mix_file_name)
373 mix_thread.start()
374
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800375 if loopback_callback:
376 loopback_callback(channel)
Adrian Li689d3ff2013-07-15 15:11:06 -0700377
378 if self._mix_cmd != None:
379 mix_thread.join()
380 sox_output_mix = self.sox_stat_output(mix_file_name, channel)
381 rms_val_mix = self.get_audio_rms(sox_output_mix)
382 logging.info('Got mixed audio RMS value of %f.', rms_val_mix)
383
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800384 record_thread.join()
Adrian Li689d3ff2013-07-15 15:11:06 -0700385 sox_output_record = self.sox_stat_output(record_file_name, channel)
386 rms_val_record = self.get_audio_rms(sox_output_record)
387 logging.info('Got recorded audio RMS value of %f.', rms_val_record)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800388
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800389 self.noise_reduce_file(record_file_name, noise_file_name,
Adrian Li689d3ff2013-07-15 15:11:06 -0700390 reduced_file_name)
Hsin-Yu Chao11041d32012-11-13 15:46:13 +0800391
Adrian Li689d3ff2013-07-15 15:11:06 -0700392 sox_output_reduced = self.sox_stat_output(reduced_file_name,
393 channel)
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800394
Rohit Makasana8ef9e292013-05-02 19:02:15 -0700395 if not preserve_test_file:
396 os.unlink(reduced_file_name)
397 os.unlink(record_file_name)
Adrian Li689d3ff2013-07-15 15:11:06 -0700398 if self._mix_cmd != None:
399 os.unlink(mix_file_name)
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800400 # Use injected check recorded callback if any.
401 if check_recorded_callback:
Adrian Li689d3ff2013-07-15 15:11:06 -0700402 check_recorded_callback(sox_output_reduced)
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800403 else:
Adrian Li689d3ff2013-07-15 15:11:06 -0700404 self.check_recorded(sox_output_reduced)
Dylan Reid51f289c2012-11-06 17:16:24 -0800405
406 def check_recorded(self, sox_output):
407 """Checks if the calculated RMS value is expected.
408
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800409 @param sox_output: The output from sox stat command.
Dylan Reid51f289c2012-11-06 17:16:24 -0800410
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800411 @raises error.TestError if RMS amplitude can't be parsed.
412 @raises error.TestFail if the RMS amplitude of the recording isn't above
Dylan Reid51f289c2012-11-06 17:16:24 -0800413 the threshold.
414 """
415 rms_val = self.get_audio_rms(sox_output)
416
417 # In case we don't get a valid RMS value.
418 if rms_val is None:
419 raise error.TestError(
420 'Failed to generate an audio RMS value from playback.')
421
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800422 logging.info('Got audio RMS value of %f. Minimum pass is %f.',
423 rms_val, self._sox_threshold)
Dylan Reid51f289c2012-11-06 17:16:24 -0800424 if rms_val < self._sox_threshold:
Hsin-Yu Chao84e86d22013-04-03 00:43:01 +0800425 raise error.TestFail(
Dylan Reid51f289c2012-11-06 17:16:24 -0800426 'Audio RMS value %f too low. Minimum pass is %f.' %
427 (rms_val, self._sox_threshold))
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800428
429 def loopback_latency_check(self, **args):
430 '''
431 Checks loopback latency.
432
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800433 @param args: additional arguments for loopback_latency.
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800434
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800435 @return A tuple containing measured and reported latency in uS.
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800436 Return None if no audio detected.
437 '''
438 noise_threshold = str(args['n']) if args.has_key('n') else '400'
439
440 cmd = '%s -n %s' % (self.loopback_latency_path, noise_threshold)
441
Hsin-Yu Chaod73dfc12013-04-15 18:26:27 +0800442 output = utils.system_output(cmd, retain_output=True)
Hsin-Yu Chaof272d8e2013-04-05 03:28:50 +0800443
444 # Sleep for a short while to make sure device is not busy anymore
445 # after called loopback_latency.
446 time.sleep(.1)
447
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800448 measured_latency = None
449 reported_latency = None
450 for line in output.split('\n'):
451 match = re.search(_MEASURED_LATENCY_RE, line, re.I)
452 if match:
453 measured_latency = int(match.group(1))
454 continue
455 match = re.search(_REPORTED_LATENCY_RE, line, re.I)
456 if match:
457 reported_latency = int(match.group(1))
458 continue
459 if re.search(_AUDIO_NOT_FOUND_RE, line, re.I):
460 return None
461 if measured_latency and reported_latency:
462 return (measured_latency, reported_latency)
463 else:
464 # Should not reach here, just in case.
465 return None
Simon Quea4be3442012-11-14 16:36:56 -0800466
467 def play_sound(self, duration_seconds=None, audio_file_path=None):
468 '''
469 Plays a sound file found at |audio_file_path| for |duration_seconds|.
470
471 If |audio_file_path|=None, plays a default audio file.
472 If |duration_seconds|=None, plays audio file in its entirety.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800473
474 @param duration_seconds: Duration to play sound.
475 @param audio_file_path: Path to the audio file.
Simon Quea4be3442012-11-14 16:36:56 -0800476 '''
477 if not audio_file_path:
478 audio_file_path = '/usr/local/autotest/cros/audio/sine440.wav'
479 duration_arg = ('-d %d' % duration_seconds) if duration_seconds else ''
480 utils.system('aplay %s %s' % (duration_arg, audio_file_path))
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800481
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800482 def get_play_sine_args(self, channel, odev='default', freq=1000, duration=10,
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800483 sample_size=16):
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800484 '''Gets the command args to generate a sine wav to play to odev.
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800485
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800486 @param channel: 0 for left, 1 for right; otherwize, mono.
487 @param odev: alsa output device.
488 @param freq: frequency of the generated sine tone.
489 @param duration: duration of the generated sine tone.
490 @param sample_size: output audio sample size. Default to 16.
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800491 '''
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800492 cmdargs = [self.sox_path, '-b', str(sample_size), '-n', '-t', 'alsa',
493 odev, 'synth', str(duration)]
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800494 if channel == 0:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800495 cmdargs += ['sine', str(freq), 'sine', '0']
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800496 elif channel == 1:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800497 cmdargs += ['sine', '0', 'sine', str(freq)]
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800498 else:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800499 cmdargs += ['sine', str(freq)]
500
501 return cmdargs
502
503 def play_sine(self, channel, odev='default', freq=1000, duration=10,
504 sample_size=16):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800505 '''Generates a sine wave and plays to odev.
506
507 @param channel: 0 for left, 1 for right; otherwize, mono.
508 @param odev: alsa output device.
509 @param freq: frequency of the generated sine tone.
510 @param duration: duration of the generated sine tone.
511 @param sample_size: output audio sample size. Default to 16.
512 '''
Hsin-Yu Chao1b641072013-03-25 18:23:44 +0800513 cmdargs = self.get_play_sine_args(channel, odev, freq, duration, sample_size)
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800514 utils.system(' '.join(cmdargs))
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800515
516 def create_wav_file(self, prefix=""):
517 '''Creates a unique name for wav file.
518
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800519 The created file name will be preserved in autotest result directory
520 for future analysis.
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800521
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800522 @param prefix: specified file name prefix.
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800523 '''
524 filename = "%s-%s.wav" % (prefix, time.time())
525 return os.path.join(self._test.resultsdir, filename)