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