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