blob: 9634c37531acad13f3e30204cd4cc31727fe3baf [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'
Dylan Reid51f289c2012-11-06 17:16:24 -080023_DEFAULT_SOX_RMS_THRESHOLD = 0.5
Hsinyu Chaof80337a2012-04-07 18:02:29 +080024
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +080025_JACK_VALUE_ON_RE = re.compile('.*values=on')
26_HP_JACK_CONTROL_RE = re.compile('numid=(\d+).*Headphone\sJack')
27_MIC_JACK_CONTROL_RE = re.compile('numid=(\d+).*Mic\sJack')
28
Hsinyu Chaof80337a2012-04-07 18:02:29 +080029_SOX_RMS_AMPLITUDE_RE = re.compile('RMS\s+amplitude:\s+(.+)')
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +080030_SOX_ROUGH_FREQ_RE = re.compile('Rough\s+frequency:\s+(.+)')
Hsinyu Chaof80337a2012-04-07 18:02:29 +080031_SOX_FORMAT = '-t raw -b 16 -e signed -r 48000 -L'
32
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080033_AUDIO_NOT_FOUND_RE = r'Audio\snot\sdetected'
34_MEASURED_LATENCY_RE = r'Measured\sLatency:\s(\d+)\suS'
35_REPORTED_LATENCY_RE = r'Reported\sLatency:\s(\d+)\suS'
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080036
37class RecordSampleThread(threading.Thread):
38 '''Wraps the execution of arecord in a thread.'''
39 def __init__(self, audio, recordfile):
40 threading.Thread.__init__(self)
41 self._audio = audio
42 self._recordfile = recordfile
43
44 def run(self):
45 self._audio.record_sample(self._recordfile)
46
47
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080048class AudioHelper(object):
49 '''
50 A helper class contains audio related utility functions.
51 '''
Dylan Reidbf9a5d42012-11-06 16:27:20 -080052 def __init__(self, test,
53 sox_format = _DEFAULT_SOX_FORMAT,
Dylan Reid51f289c2012-11-06 17:16:24 -080054 sox_threshold = _DEFAULT_SOX_RMS_THRESHOLD,
Dylan Reidbf9a5d42012-11-06 16:27:20 -080055 record_command = _DEFAULT_REC_COMMAND,
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080056 num_channels = _DEFAULT_NUM_CHANNELS):
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080057 self._test = test
Dylan Reid51f289c2012-11-06 17:16:24 -080058 self._sox_threshold = sox_threshold
Hsinyu Chaof80337a2012-04-07 18:02:29 +080059 self._sox_format = sox_format
Dylan Reidbf9a5d42012-11-06 16:27:20 -080060 self._rec_cmd = record_command
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +080061 self._num_channels = num_channels
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080062
63 def setup_deps(self, deps):
64 '''
65 Sets up audio related dependencies.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +080066
67 @param deps: List of dependencies to set up.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080068 '''
69 for dep in deps:
70 if dep == 'test_tones':
71 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
72 self._test.job.install_pkg(dep, 'dep', dep_dir)
73 self.test_tones_path = os.path.join(dep_dir, 'src', dep)
74 elif dep == 'audioloop':
75 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
76 self._test.job.install_pkg(dep, 'dep', dep_dir)
77 self.audioloop_path = os.path.join(dep_dir, 'src',
78 'looptest')
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +080079 self.loopback_latency_path = os.path.join(dep_dir, 'src',
80 'loopback_latency')
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080081 elif dep == 'sox':
82 dep_dir = os.path.join(self._test.autodir, 'deps', dep)
83 self._test.job.install_pkg(dep, 'dep', dep_dir)
84 self.sox_path = os.path.join(dep_dir, 'bin', dep)
85 self.sox_lib_path = os.path.join(dep_dir, 'lib')
86 if os.environ.has_key(LD_LIBRARY_PATH):
87 paths = os.environ[LD_LIBRARY_PATH].split(':')
88 if not self.sox_lib_path in paths:
89 paths.append(self.sox_lib_path)
90 os.environ[LD_LIBRARY_PATH] = ':'.join(paths)
91 else:
92 os.environ[LD_LIBRARY_PATH] = self.sox_lib_path
93
94 def cleanup_deps(self, deps):
95 '''
96 Cleans up environments which has been setup for dependencies.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +080097
98 @param deps: List of dependencies to clean up.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -080099 '''
100 for dep in deps:
101 if dep == 'sox':
102 if (os.environ.has_key(LD_LIBRARY_PATH)
103 and hasattr(self, 'sox_lib_path')):
104 paths = filter(lambda x: x != self.sox_lib_path,
105 os.environ[LD_LIBRARY_PATH].split(':'))
106 os.environ[LD_LIBRARY_PATH] = ':'.join(paths)
107
Derek Basehoree973ce42012-07-10 23:38:32 -0700108 def set_volume_levels(self, volume, capture):
109 '''
110 Sets the volume and capture gain through cras_test_client
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800111
112 @param volume: The playback volume to set.
113 @param capture: The capture gain to set.
Derek Basehoree973ce42012-07-10 23:38:32 -0700114 '''
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800115 logging.info('Setting volume level to %d', volume)
Derek Basehoree973ce42012-07-10 23:38:32 -0700116 utils.system('/usr/bin/cras_test_client --volume %d' % volume)
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800117 logging.info('Setting capture gain to %d', capture)
Derek Basehoree973ce42012-07-10 23:38:32 -0700118 utils.system('/usr/bin/cras_test_client --capture_gain %d' % capture)
119 utils.system('/usr/bin/cras_test_client --dump_server_info')
Dylan Reidc264e012012-11-06 18:26:12 -0800120 utils.system('/usr/bin/cras_test_client --mute 0')
Derek Basehoree973ce42012-07-10 23:38:32 -0700121 utils.system('amixer -c 0 contents')
122
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800123 def get_mixer_jack_status(self, jack_reg_exp):
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800124 '''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800125 Gets the mixer jack status.
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800126
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800127 @param jack_reg_exp: The regular expression to match jack control name.
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800128
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800129 @return None if the control does not exist, return True if jack control
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800130 is detected plugged, return False otherwise.
131 '''
132 output = utils.system_output('amixer -c0 controls', retain_output=True)
133 numid = None
134 for line in output.split('\n'):
135 m = jack_reg_exp.match(line)
136 if m:
137 numid = m.group(1)
138 break
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800139
140 # Proceed only when matched numid is not empty.
141 if numid:
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800142 output = utils.system_output('amixer -c0 cget numid=%s' % numid)
143 for line in output.split('\n'):
144 if _JACK_VALUE_ON_RE.match(line):
145 return True
146 return False
147 else:
148 return None
149
150 def get_hp_jack_status(self):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800151 '''Gets the status of headphone jack'''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800152 status = self.get_mixer_jack_status(_HP_JACK_CONTROL_RE)
153 if status is not None:
154 return status
155
156 # When headphone jack is not found in amixer, lookup input devices
157 # instead.
158 #
159 # TODO(hychao): Check hp/mic jack status dynamically from evdev. And
160 # possibly replace the existing check using amixer.
161 for evdev in glob('/dev/input/event*'):
162 device = InputDevice(evdev)
163 if device.is_hp_jack():
164 return device.get_headphone_insert()
165 else:
166 return None
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800167
168 def get_mic_jack_status(self):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800169 '''Gets the status of mic jack'''
Hsin-Yu Chao084e9da2012-11-07 15:56:26 +0800170 status = self.get_mixer_jack_status(_MIC_JACK_CONTROL_RE)
171 if status is not None:
172 return status
173
174 # When mic jack is not found in amixer, lookup input devices instead.
175 for evdev in glob('/dev/input/event*'):
176 device = InputDevice(evdev)
177 if device.is_mic_jack():
178 return device.get_microphone_insert()
179 else:
180 return None
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800181
182 def check_loopback_dongle(self):
183 '''
184 Checks if loopback dongle is equipped correctly.
185 '''
186 # Check Mic Jack
187 mic_jack_status = self.get_mic_jack_status()
188 if mic_jack_status is None:
189 logging.warning('Found no Mic Jack control, skip check.')
190 elif not mic_jack_status:
191 logging.info('Mic jack is not plugged.')
192 return False
193 else:
194 logging.info('Mic jack is plugged.')
195
196 # Check Headphone Jack
197 hp_jack_status = self.get_hp_jack_status()
198 if hp_jack_status is None:
199 logging.warning('Found no Headphone Jack control, skip check.')
200 elif not hp_jack_status:
201 logging.info('Headphone jack is not plugged.')
202 return False
203 else:
204 logging.info('Headphone jack is plugged.')
205
Hsin-Yu Chao3953f522012-11-12 18:39:39 +0800206 # Use latency check to test if audio can be captured through dongle.
207 # We only want to know the basic function of dongle, so no need to
208 # assert the latency accuracy here.
209 latency = self.loopback_latency_check(n=4000)
210 if latency:
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800211 logging.info('Got latency measured %d, reported %d',
212 latency[0], latency[1])
Hsin-Yu Chao3953f522012-11-12 18:39:39 +0800213 else:
214 logging.warning('Latency check fail.')
215 return False
216
Hsin-Yu Chao8d093f42012-11-05 18:43:22 +0800217 return True
218
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800219 def set_mixer_controls(self, mixer_settings={}, card='0'):
220 '''
221 Sets all mixer controls listed in the mixer settings on card.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800222
223 @param mixer_settings: Mixer settings to set.
224 @param card: Index of audio card to set mixer settings for.
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800225 '''
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800226 logging.info('Setting mixer control values on %s', card)
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800227 for item in mixer_settings:
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800228 logging.info('Setting %s to %s on card %s',
229 item['name'], item['value'], card)
Hsinyu Chao4b8300e2011-11-15 13:07:32 -0800230 cmd = 'amixer -c %s cset name=%s %s'
231 cmd = cmd % (card, item['name'], item['value'])
232 try:
233 utils.system(cmd)
234 except error.CmdError:
235 # A card is allowed not to support all the controls, so don't
236 # fail the test here if we get an error.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800237 logging.info('amixer command failed: %s', cmd)
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800238
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800239 def sox_stat_output(self, infile, channel):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800240 '''Executes sox stat command.
241
242 @param infile: Input file name.
243 @param channel: The selected channel.
244
245 @return The output of sox stat command
246 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800247 sox_mixer_cmd = self.get_sox_mixer_cmd(infile, channel)
248 stat_cmd = '%s -c 1 %s - -n stat 2>&1' % (self.sox_path,
249 self._sox_format)
250 sox_cmd = '%s | %s' % (sox_mixer_cmd, stat_cmd)
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800251 return utils.system_output(sox_cmd, retain_output=True)
252
253 def get_audio_rms(self, sox_output):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800254 '''Gets the audio RMS value from sox stat output
255
256 @param sox_output: Output of sox stat command.
257
258 @return The RMS value parsed from sox stat output.
259 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800260 for rms_line in sox_output.split('\n'):
261 m = _SOX_RMS_AMPLITUDE_RE.match(rms_line)
262 if m is not None:
263 return float(m.group(1))
264
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800265 def get_rough_freq(self, sox_output):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800266 '''Gets the rough audio frequency from sox stat output
267
268 @param sox_output: Output of sox stat command.
269
270 @return The rough frequency value parsed from sox stat output.
271 '''
272
Hsinyu Chao2d64e1f2012-05-21 11:18:53 +0800273 for rms_line in sox_output.split('\n'):
274 m = _SOX_ROUGH_FREQ_RE.match(rms_line)
275 if m is not None:
276 return int(m.group(1))
277
278
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800279 def get_sox_mixer_cmd(self, infile, channel):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800280 '''Gets sox mixer command to reduce channel.
281
282 @param infile: Input file name.
283 @param channel: The selected channel to take effect.
284 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800285 # Build up a pan value string for the sox command.
286 if channel == 0:
287 pan_values = '1'
288 else:
289 pan_values = '0'
290 for pan_index in range(1, self._num_channels):
291 if channel == pan_index:
292 pan_values = '%s%s' % (pan_values, ',1')
293 else:
294 pan_values = '%s%s' % (pan_values, ',0')
295
296 return '%s -c 2 %s %s -c 1 %s - mixer %s' % (self.sox_path,
297 self._sox_format, infile, self._sox_format, pan_values)
298
299 def noise_reduce_file(self, in_file, noise_file, out_file):
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800300 '''Runs the sox command to noise-reduce in_file using
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800301 the noise profile from noise_file.
302
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800303 @param in_file: The file to noise reduce.
304 @param noise_file: The file containing the noise profile.
305 This can be created by recording silence.
306 @param out_file: The file contains the noise reduced sound.
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800307
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800308 @return The name of the file containing the noise-reduced data.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800309 '''
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800310 prof_cmd = '%s -c 2 %s %s -n noiseprof' % (self.sox_path,
311 _SOX_FORMAT, noise_file)
312 reduce_cmd = ('%s -c 2 %s %s -c 2 %s %s noisered' %
313 (self.sox_path, _SOX_FORMAT, in_file, _SOX_FORMAT, out_file))
314 utils.system('%s | %s' % (prof_cmd, reduce_cmd))
315
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800316 def record_sample(self, tmpfile):
317 '''Records a sample from the default input device.
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800318
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800319 @param duration: How long to record in seconds.
320 @param tmpfile: The file to record to.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800321 '''
Dylan Reidbf9a5d42012-11-06 16:27:20 -0800322 cmd_rec = self._rec_cmd + ' %s' % tmpfile
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800323 logging.info('Command %s recording now', cmd_rec)
Hsinyu Chaof80337a2012-04-07 18:02:29 +0800324 utils.system(cmd_rec)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800325
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800326 def loopback_test_channels(self, noise_file_name, loopback_callback=None,
Hsin-Yu Chao11041d32012-11-13 15:46:13 +0800327 check_recorded_callback=None):
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800328 '''Tests loopback on all channels.
329
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800330 @param noise_file_name: Name of the file contains pre-recorded noise.
331 @param loopback_callback: The callback to do the loopback for
332 one channel.
333 @param check_recorded_callback: The callback to check recorded file.
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800334 '''
335 for channel in xrange(self._num_channels):
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800336 reduced_file_name = self.create_wav_file("reduced-%d" % channel)
337 record_file_name = self.create_wav_file("record-%d" % channel)
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800338
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800339 record_thread = RecordSampleThread(self, record_file_name)
340 record_thread.start()
341 if loopback_callback:
342 loopback_callback(channel)
343 record_thread.join()
Hsinyu Chao2a7e2f22012-04-18 16:46:43 +0800344
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800345 self.noise_reduce_file(record_file_name, noise_file_name,
346 reduced_file_name)
Hsin-Yu Chao11041d32012-11-13 15:46:13 +0800347
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800348 sox_output = self.sox_stat_output(reduced_file_name, channel)
349
350 # Use injected check recorded callback if any.
351 if check_recorded_callback:
352 check_recorded_callback(sox_output)
353 else:
354 self.check_recorded(sox_output)
Dylan Reid51f289c2012-11-06 17:16:24 -0800355
356 def check_recorded(self, sox_output):
357 """Checks if the calculated RMS value is expected.
358
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800359 @param sox_output: The output from sox stat command.
Dylan Reid51f289c2012-11-06 17:16:24 -0800360
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800361 @raises error.TestError if RMS amplitude can't be parsed.
362 @raises error.TestFail if the RMS amplitude of the recording isn't above
Dylan Reid51f289c2012-11-06 17:16:24 -0800363 the threshold.
364 """
365 rms_val = self.get_audio_rms(sox_output)
366
367 # In case we don't get a valid RMS value.
368 if rms_val is None:
369 raise error.TestError(
370 'Failed to generate an audio RMS value from playback.')
371
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800372 logging.info('Got audio RMS value of %f. Minimum pass is %f.',
373 rms_val, self._sox_threshold)
Dylan Reid51f289c2012-11-06 17:16:24 -0800374 if rms_val < self._sox_threshold:
Hsin-Yu Chao84e86d22013-04-03 00:43:01 +0800375 raise error.TestFail(
Dylan Reid51f289c2012-11-06 17:16:24 -0800376 'Audio RMS value %f too low. Minimum pass is %f.' %
377 (rms_val, self._sox_threshold))
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800378
379 def loopback_latency_check(self, **args):
380 '''
381 Checks loopback latency.
382
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800383 @param args: additional arguments for loopback_latency.
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800384
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800385 @return A tuple containing measured and reported latency in uS.
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800386 Return None if no audio detected.
387 '''
388 noise_threshold = str(args['n']) if args.has_key('n') else '400'
389
390 cmd = '%s -n %s' % (self.loopback_latency_path, noise_threshold)
391
392 output = utils.system_output(cmd)
Hsin-Yu Chaof272d8e2013-04-05 03:28:50 +0800393
394 # Sleep for a short while to make sure device is not busy anymore
395 # after called loopback_latency.
396 time.sleep(.1)
397
Hsin-Yu Chao95ee3512012-11-05 20:43:10 +0800398 measured_latency = None
399 reported_latency = None
400 for line in output.split('\n'):
401 match = re.search(_MEASURED_LATENCY_RE, line, re.I)
402 if match:
403 measured_latency = int(match.group(1))
404 continue
405 match = re.search(_REPORTED_LATENCY_RE, line, re.I)
406 if match:
407 reported_latency = int(match.group(1))
408 continue
409 if re.search(_AUDIO_NOT_FOUND_RE, line, re.I):
410 return None
411 if measured_latency and reported_latency:
412 return (measured_latency, reported_latency)
413 else:
414 # Should not reach here, just in case.
415 return None
Simon Quea4be3442012-11-14 16:36:56 -0800416
417 def play_sound(self, duration_seconds=None, audio_file_path=None):
418 '''
419 Plays a sound file found at |audio_file_path| for |duration_seconds|.
420
421 If |audio_file_path|=None, plays a default audio file.
422 If |duration_seconds|=None, plays audio file in its entirety.
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800423
424 @param duration_seconds: Duration to play sound.
425 @param audio_file_path: Path to the audio file.
Simon Quea4be3442012-11-14 16:36:56 -0800426 '''
427 if not audio_file_path:
428 audio_file_path = '/usr/local/autotest/cros/audio/sine440.wav'
429 duration_arg = ('-d %d' % duration_seconds) if duration_seconds else ''
430 utils.system('aplay %s %s' % (duration_arg, audio_file_path))
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800431
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800432 def get_play_sine_args(self, channel, odev='default', freq=1000, duration=10,
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800433 sample_size=16):
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800434 '''Gets the command args to generate a sine wav to play to odev.
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800435
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800436 @param channel: 0 for left, 1 for right; otherwize, mono.
437 @param odev: alsa output device.
438 @param freq: frequency of the generated sine tone.
439 @param duration: duration of the generated sine tone.
440 @param sample_size: output audio sample size. Default to 16.
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800441 '''
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800442 cmdargs = [self.sox_path, '-b', str(sample_size), '-n', '-t', 'alsa',
443 odev, 'synth', str(duration)]
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800444 if channel == 0:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800445 cmdargs += ['sine', str(freq), 'sine', '0']
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800446 elif channel == 1:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800447 cmdargs += ['sine', '0', 'sine', str(freq)]
Hsin-Yu Chaob443f5d2013-03-12 18:36:18 +0800448 else:
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800449 cmdargs += ['sine', str(freq)]
450
451 return cmdargs
452
453 def play_sine(self, channel, odev='default', freq=1000, duration=10,
454 sample_size=16):
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800455 '''Generates a sine wave and plays to odev.
456
457 @param channel: 0 for left, 1 for right; otherwize, mono.
458 @param odev: alsa output device.
459 @param freq: frequency of the generated sine tone.
460 @param duration: duration of the generated sine tone.
461 @param sample_size: output audio sample size. Default to 16.
462 '''
Hsin-Yu Chao1b641072013-03-25 18:23:44 +0800463 cmdargs = self.get_play_sine_args(channel, odev, freq, duration, sample_size)
Hsin-Yu Chaoe6bb7932013-03-22 14:08:54 +0800464 utils.system(' '.join(cmdargs))
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800465
466 def create_wav_file(self, prefix=""):
467 '''Creates a unique name for wav file.
468
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800469 The created file name will be preserved in autotest result directory
470 for future analysis.
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800471
Hsin-Yu Chao2ecbfe62013-04-06 05:49:27 +0800472 @param prefix: specified file name prefix.
Hsin-Yu Chao78c44b22013-04-06 05:33:58 +0800473 '''
474 filename = "%s-%s.wav" % (prefix, time.time())
475 return os.path.join(self._test.resultsdir, filename)