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