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