1# Copyright (c) 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import re 7import subprocess 8 9from autotest_lib.client.cros.audio import cmd_utils 10 11SOX_PATH = 'sox' 12 13def _raw_format_args(channels, bits, rate): 14 """Gets raw format args used in sox. 15 16 @param channels: Number of channels. 17 @param bits: Bit length for a sample. 18 @param rate: Sampling rate. 19 20 @returns: A list of args. 21 22 """ 23 args = ['-t', 'raw', '-e', 'signed'] 24 args += _format_args(channels, bits, rate) 25 return args 26 27 28def _format_args(channels, bits, rate): 29 """Gets format args used in sox. 30 31 @param channels: Number of channels. 32 @param bits: Bit length for a sample. 33 @param rate: Sampling rate. 34 35 @returns: A list of args. 36 37 """ 38 return ['-c', str(channels), '-b', str(bits), '-r', str(rate)] 39 40 41def generate_sine_tone_cmd( 42 filename, channels=2, bits=16, rate=48000, duration=None, frequencies=440, 43 gain=None, vol=None, raw=True): 44 """Gets a command to generate sine tones at specified ferquencies. 45 46 @param filename: The name of the file to store the sine wave in. 47 @param channels: The number of channels. 48 @param bits: The number of bits of each sample. 49 @param rate: The sampling rate. 50 @param duration: The length of the generated sine tone (in seconds). 51 @param frequencies: The frequencies of the sine wave. Pass a number or a 52 list to specify frequency for each channel. 53 @param gain: The gain (in db). 54 @param vol: A float for volume scale used in sox command. 55 E.g. 1.0 is the same. 0.5 to scale volume by 56 half. -1.0 to invert the data. 57 @param raw: True to use raw data format. False to use what filename specifies. 58 59 """ 60 args = [SOX_PATH, '-n'] 61 if raw: 62 args += _raw_format_args(channels, bits, rate) 63 else: 64 args += _format_args(channels, bits, rate) 65 args.append(filename) 66 args.append('synth') 67 if duration is not None: 68 args.append(str(duration)) 69 if not isinstance(frequencies, list): 70 frequencies = [frequencies] 71 for freq in frequencies: 72 args += ['sine', str(freq)] 73 if gain is not None: 74 args += ['gain', str(gain)] 75 if vol is not None: 76 args += ['vol', str(vol)] 77 return args 78 79 80def noise_profile(*args, **kwargs): 81 """A helper function to execute the noise_profile_cmd.""" 82 return cmd_utils.execute(noise_profile_cmd(*args, **kwargs)) 83 84 85def noise_profile_cmd(input, output, channels=1, bits=16, rate=48000): 86 """Gets the noise profile of the input audio. 87 88 @param input: The input audio. 89 @param output: The file where the output profile will be stored in. 90 @param channels: The number of channels. 91 @param bits: The number of bits of each sample. 92 @param rate: The sampling rate. 93 """ 94 args = [SOX_PATH] 95 args += _raw_format_args(channels, bits, rate) 96 args += [input, '-n', 'noiseprof', output] 97 return args 98 99 100def noise_reduce(*args, **kwargs): 101 """A helper function to execute the noise_reduce_cmd.""" 102 return cmd_utils.execute(noise_reduce_cmd(*args, **kwargs)) 103 104 105def noise_reduce_cmd( 106 input, output, noise_profile, channels=1, bits=16, rate=48000): 107 """Reduce noise in the input audio by the given noise profile. 108 109 @param input: The input audio file. 110 @param output: The output file in which the noise reduced audio is stored. 111 @param noise_profile: The noise profile. 112 @param channels: The number of channels. 113 @param bits: The number of bits of each sample. 114 @param rate: The sampling rate. 115 """ 116 args = [SOX_PATH] 117 format_args = _raw_format_args(channels, bits, rate) 118 args += format_args 119 args.append(input) 120 # Uses the same format for output. 121 args += format_args 122 args.append(output) 123 args.append('noisered') 124 args.append(noise_profile) 125 return args 126 127 128def extract_channel_cmd( 129 input, output, channel_index, channels=2, bits=16, rate=48000): 130 """Extract the specified channel data from the given input audio file. 131 132 @param input: The input audio file. 133 @param output: The output file to which the extracted channel is stored 134 @param channel_index: The index of the channel to be extracted. 135 Note: 1 for the first channel. 136 @param channels: The number of channels. 137 @param bits: The number of bits of each sample. 138 @param rate: The sampling rate. 139 """ 140 args = [SOX_PATH] 141 args += _raw_format_args(channels, bits, rate) 142 args.append(input) 143 args += ['-t', 'raw', output] 144 args += ['remix', str(channel_index)] 145 return args 146 147 148def stat_cmd(input, channels=1, bits=16, rate=44100): 149 """Get statistical information about the input audio data. 150 151 The statistics will be output to standard error. 152 153 @param input: The input audio file. 154 @param channels: The number of channels. 155 @param bits: The number of bits of each sample. 156 @param rate: The sampling rate. 157 """ 158 args = [SOX_PATH] 159 args += _raw_format_args(channels, bits, rate) 160 args += [input, '-n', 'stat'] 161 return args 162 163 164def get_stat(*args, **kargs): 165 """A helper function to execute the stat_cmd. 166 167 It returns the statistical information (in text) read from the standard 168 error. 169 """ 170 p = cmd_utils.popen(stat_cmd(*args, **kargs), stderr=subprocess.PIPE) 171 172 #The output is read from the stderr instead of stdout 173 stat_output = p.stderr.read() 174 cmd_utils.wait_and_check_returncode(p) 175 return parse_stat_output(stat_output) 176 177 178_SOX_STAT_ATTR_MAP = { 179 'Samples read': ('sameple_count', int), 180 'Length (seconds)': ('length', float), 181 'RMS amplitude': ('rms', float), 182 'Rough frequency': ('rough_frequency', float)} 183 184_RE_STAT_LINE = re.compile('(.*):(.*)') 185 186class _SOX_STAT: 187 def __str__(self): 188 return str(vars(self)) 189 190 191def _remove_redundant_spaces(value): 192 return ' '.join(value.split()).strip() 193 194 195def parse_stat_output(stat_output): 196 """A helper function to parses the stat_cmd's output to get a python object 197 for easy access to the statistics. 198 199 It returns a python object with the following attributes: 200 .sample_count: The number of the audio samples. 201 .length: The length of the audio (in seconds). 202 .rms: The RMS value of the audio. 203 .rough_frequency: The rough frequency of the audio (in Hz). 204 205 @param stat_output: The statistics ouput to be parsed. 206 """ 207 stat = _SOX_STAT() 208 209 for line in stat_output.splitlines(): 210 match = _RE_STAT_LINE.match(line) 211 if not match: 212 continue 213 key, value = (_remove_redundant_spaces(x) for x in match.groups()) 214 attr, convfun = _SOX_STAT_ATTR_MAP.get(key, (None, None)) 215 if attr: 216 setattr(stat, attr, convfun(value)) 217 218 if not all(hasattr(stat, x[0]) for x in _SOX_STAT_ATTR_MAP.values()): 219 logging.error('stat_output: %s', stat_output) 220 raise RuntimeError('missing entries: ' + str(stat)) 221 222 return stat 223 224 225def convert_raw_file(path_src, channels_src, bits_src, rate_src, 226 path_dst): 227 """Converts a raw file to a new format. 228 229 @param path_src: The path to the source file. 230 @param channels_src: The channel number of the source file. 231 @param bits_src: The size of sample in bits of the source file. 232 @param rate_src: The sampling rate of the source file. 233 @param path_dst: The path to the destination file. The file name determines 234 the new file format. 235 236 """ 237 sox_cmd = [SOX_PATH] 238 sox_cmd += _raw_format_args(channels_src, bits_src, rate_src) 239 sox_cmd += [path_src] 240 sox_cmd += [path_dst] 241 cmd_utils.execute(sox_cmd) 242 243 244def convert_format(path_src, channels_src, bits_src, rate_src, 245 path_dst, channels_dst, bits_dst, rate_dst, 246 volume_scale, use_src_header=False, use_dst_header=False): 247 """Converts a raw file to a new format. 248 249 @param path_src: The path to the source file. 250 @param channels_src: The channel number of the source file. 251 @param bits_src: The size of sample in bits of the source file. 252 @param rate_src: The sampling rate of the source file. 253 @param path_dst: The path to the destination file. 254 @param channels_dst: The channel number of the destination file. 255 @param bits_dst: The size of sample in bits of the destination file. 256 @param rate_dst: The sampling rate of the destination file. 257 @param volume_scale: A float for volume scale used in sox command. 258 E.g. 1.0 is the same. 0.5 to scale volume by 259 half. -1.0 to invert the data. 260 @param use_src_header: True to use header from source file and skip 261 specifying channel, sample format, and rate for 262 source. False otherwise. 263 @param use_dst_header: True to use header for dst file. False to treat 264 dst file as a raw file. 265 266 """ 267 sox_cmd = [SOX_PATH] 268 269 if not use_src_header: 270 sox_cmd += _raw_format_args(channels_src, bits_src, rate_src) 271 sox_cmd += ['-v', '%f' % volume_scale] 272 sox_cmd += [path_src] 273 274 if not use_dst_header: 275 sox_cmd += _raw_format_args(channels_dst, bits_dst, rate_dst) 276 else: 277 sox_cmd += _format_args(channels_dst, bits_dst, rate_dst) 278 sox_cmd += [path_dst] 279 280 cmd_utils.execute(sox_cmd) 281 282 283def lowpass_filter(path_src, channels_src, bits_src, rate_src, 284 path_dst, frequency): 285 """Passes a raw file to a lowpass filter. 286 287 @param path_src: The path to the source file. 288 @param channels_src: The channel number of the source file. 289 @param bits_src: The size of sample in bits of the source file. 290 @param rate_src: The sampling rate of the source file. 291 @param path_dst: The path to the destination file. 292 @param frequency: A float for frequency used in sox command. The 3dB 293 frequency of the lowpass filter. Checks manual of sox 294 command for detail. 295 296 """ 297 sox_cmd = [SOX_PATH] 298 sox_cmd += _raw_format_args(channels_src, bits_src, rate_src) 299 sox_cmd += [path_src] 300 sox_cmd += _raw_format_args(channels_src, bits_src, rate_src) 301 sox_cmd += [path_dst] 302 sox_cmd += ['lowpass', '-2', str(frequency)] 303 cmd_utils.execute(sox_cmd) 304 305 306def trim_silence_from_wav_file(path_src, path_dst, new_duration, volume=1, 307 duration_threshold=0): 308 """Trim silence from beginning of a file. 309 310 Trim silence from beginning of file, and trim remaining audio to 311 new_duration seconds in length. 312 313 @param path_src: The path to the source file. 314 @oaram path_dst: The path to the destination file. 315 @param new_duration: The new duration of the destination file in seconds. 316 @param volume: [Optional] A float indicating the volume in percent, below 317 which sox will consider silence, defaults to 1 (1%). 318 @param duration_threshold: [Optional] A float of the duration in seconds of 319 sound above volume parameter required to consider 320 end of silence. Defaults to 0 (0 seconds). 321 """ 322 mins, secs = divmod(new_duration, 60) 323 hrs, mins = divmod(mins, 60) 324 length_str = '{:d}:{:02d}:{:.3f}'.format(int(hrs), int(mins), float(secs)) 325 326 sox_cmd = [SOX_PATH] 327 sox_cmd += ['-G', path_src, path_dst] 328 sox_cmd += ['silence', '1', str(duration_threshold), '{}%'.format(volume)] 329 sox_cmd += ['trim', '0', length_str] 330 331 cmd_utils.execute(sox_cmd) 332 333 334def get_file_length(file_path, channels, bits, rate): 335 """Get the length in seconds of an audio file. 336 337 @param file_path: Path to audio file. 338 @param channels: The number of channels. 339 @param bits: The number of bits of each sample. 340 @param rate: The sampling rate. 341 342 @returns: float length in seconds 343 """ 344 return get_stat(file_path, channels, bits, rate).length 345