xref: /aosp_15_r20/external/autotest/client/cros/audio/sox_utils.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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