• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This module provides audio test data."""
7
8import logging
9import os
10import subprocess
11
12from autotest_lib.client.cros.audio import audio_data
13from autotest_lib.client.cros.audio import sox_utils
14
15
16class AudioTestDataException(Exception):
17    """Exception for audio test data."""
18    pass
19
20
21class AudioTestData(object):
22    """Class to represent audio test data."""
23    def __init__(self, data_format=None, path=None, frequencies=None,
24                 duration_secs=None):
25        """
26        Initializes an audio test file.
27
28        @param data_format: A dict containing data format including
29                            file_type, sample_format, channel, and rate.
30                            file_type: file type e.g. 'raw' or 'wav'.
31                            sample_format: One of the keys in
32                                           audio_data.SAMPLE_FORMAT.
33                            channel: number of channels.
34                            rate: sampling rate.
35        @param path: The path to the file.
36        @param frequencies: A list containing the frequency of each channel in
37                            this file. Only applicable to data of sine tone.
38        @param duration_secs: Duration of test file in seconds.
39
40        @raises: AudioTestDataException if the path does not exist.
41
42        """
43        self.data_format = data_format
44        if not os.path.exists(path):
45            raise AudioTestDataException('Can not find path %s' % path)
46        self.path = path
47        self.frequencies = frequencies
48        self.duration_secs = duration_secs
49
50
51    def get_binary(self):
52        """The binary of test data.
53
54        @returns: The binary of test data.
55
56        """
57        with open(self.path, 'rb') as f:
58            return f.read()
59
60
61    def convert(self, data_format, volume_scale, path=None):
62        """Converts the data format and returns a new AudioTestData object.
63
64        Converts the source file at self.path to a new data format.
65        The destination file path is self.path with a suffix. E.g.
66        original_path = '/tmp/test.raw'
67        data_format = dict(file_type='raw', sample_format='S32_LE',
68                           channel=2, rate=48000)
69        new_path = '/tmp/test_raw_48000_S32_LE_2.raw'
70
71        This method returns a new AudioTestData object so the original object is
72        not changed.
73
74        @param data_format: A dict containing new data format.
75        @param volume_scale: A float for volume scale used in sox command.
76                              E.g. 1.0 is the same. 0.5 to scale volume by
77                              half. -1.0 to invert the data.
78        @param path: The path to the file of new AudioTestData. If this is None,
79                     this function will add the suffix described above to the
80                     path of the source file.
81
82        @returns: A new AudioTestData object with converted format and new path.
83
84        """
85        if path:
86            new_path = path
87        else:
88            original_path_without_ext, _ = os.path.splitext(self.path)
89            new_ext = '.' + data_format['file_type']
90            # New path will be the composition of original name, new data
91            # format, and new file type as extension.
92            new_path = (original_path_without_ext + '_' +
93                        '_'.join(str(x) for x in data_format.values()) +
94                        new_ext)
95
96        logging.debug('src data_format: %s', self.data_format)
97        logging.debug('dst data_format: %s', data_format)
98
99        # If source file has header, use that header.
100        if self.data_format['file_type'] != 'raw':
101            use_src_header = True
102            channels_src = None
103            rate_src = None
104            bits_src = None
105        else:
106            use_src_header = False
107            channels_src = self.data_format['channel']
108            rate_src = self.data_format['rate']
109            bits_src = audio_data.SAMPLE_FORMATS[
110                    self.data_format['sample_format']]['size_bytes'] * 8
111
112        # If dst file type is not raw, write file format into header of dst
113        # file.
114        use_dst_header = data_format['file_type'] != 'raw'
115
116        sox_utils.convert_format(
117                path_src=self.path,
118                channels_src=channels_src,
119                rate_src=rate_src,
120                bits_src=bits_src,
121                path_dst=new_path,
122                channels_dst=data_format['channel'],
123                rate_dst=data_format['rate'],
124                bits_dst=audio_data.SAMPLE_FORMATS[
125                        data_format['sample_format']]['size_bytes'] * 8,
126                volume_scale=volume_scale,
127                use_src_header=use_src_header,
128                use_dst_header=use_dst_header)
129
130        new_test_data = AudioTestData(path=new_path,
131                                      data_format=data_format)
132
133        return new_test_data
134
135
136    def delete(self):
137        """Deletes the file at self.path."""
138        os.unlink(self.path)
139
140
141class FakeTestData(object):
142    def __init__(self, frequencies, url=None, duration_secs=None):
143        """A fake test data which contains properties but no real data.
144
145        This is useful when we need to pass an AudioTestData object into a test
146        or audio_test_utils.check_recorded_frequency.
147
148        @param frequencies: A list containing the frequency of each channel in
149                            this file. Only applicable to data of sine tone.
150        @param url: The URL to the test file.
151        @param duration_secs: The duration of the file in seconds.
152
153        """
154        self.frequencies = frequencies
155        self.url = url
156        self.duration_secs = duration_secs
157
158
159def GenerateAudioTestData(data_format, path, frequencies=None,
160            duration_secs=None, volume_scale=None):
161    """Generates audio test data with specified format and frequencies.
162
163    @param data_format: A dict containing data format including
164                        file_type, sample_format, channel, and rate.
165                        file_type: file type e.g. 'raw' or 'wav'.
166                        sample_format: One of the keys in
167                                       audio_data.SAMPLE_FORMAT.
168                        channel: number of channels.
169                        rate: sampling rate.
170    @param path: The path to the file.
171    @param frequencies: A list containing the frequency of each channel in
172                        this file. Only applicable to data of sine tone.
173    @param duration_secs: Duration of test file in seconds.
174    @param volume_scale: A float for volume scale used in sox command.
175                         E.g. 0.5 to scale volume by half. -1.0 to invert.
176
177    @returns an AudioTestData object.
178    """
179    sample_format = audio_data.SAMPLE_FORMATS[data_format['sample_format']]
180    bits = sample_format['size_bytes'] * 8
181
182    if volume_scale:
183        path_without_ext, ext = os.path.splitext(path)
184        sox_file_path = os.path.join(path_without_ext + "_temp" + ext)
185    else:
186        sox_file_path = path
187
188    command = sox_utils.generate_sine_tone_cmd(
189            filename=sox_file_path,
190            channels=data_format['channel'],
191            bits=bits,
192            rate=data_format['rate'],
193            duration=duration_secs,
194            frequencies=frequencies,
195            raw=(data_format['file_type'] == 'raw'))
196
197    logging.info(' '.join(command))
198    subprocess.check_call(command)
199
200    test_data = AudioTestData(data_format=data_format, path=sox_file_path,
201            frequencies=frequencies, duration_secs=duration_secs)
202
203    if volume_scale:
204        converted_test_data = test_data.convert(data_format, volume_scale, path)
205        test_data.delete()
206        return converted_test_data
207    else:
208        return test_data
209
210
211AUDIO_PATH = os.path.join(os.path.dirname(__file__))
212
213"""
214This test data contains frequency sweep from 20Hz to 20000Hz in two channels.
215Left channel sweeps from 20Hz to 20000Hz, while right channel sweeps from
21620000Hz to 20Hz. The sweep duration is 2 seconds. The begin and end of the file
217is padded with 0.4 seconds of silence. The file is two-channel raw data with
218each sample being a signed 16-bit integer in little-endian with sampling rate
21948000 samples/sec.
220"""
221SWEEP_TEST_FILE = AudioTestData(
222        path=os.path.join(AUDIO_PATH, 'pad_sweep_pad_16.raw'),
223        data_format=dict(file_type='raw',
224                         sample_format='S16_LE',
225                         channel=2,
226                         rate=48000))
227
228"""
229This test data contains fixed frequency sine wave in two channels.
230Left channel is 2KHz, while right channel is 1KHz. The duration is 6 seconds.
231The file format is two-channel raw data with each sample being a signed
23216-bit integer in little-endian with sampling rate 48000 samples/sec.
233"""
234FREQUENCY_TEST_FILE = AudioTestData(
235        path=os.path.join(AUDIO_PATH, 'fix_2k_1k_16.raw'),
236        data_format=dict(file_type='raw',
237                         sample_format='S16_LE',
238                         channel=2,
239                         rate=48000),
240        frequencies=[2000, 1000])
241
242"""
243This test data contains fixed frequency sine wave in two channels.
244Left and right channel are both 440Hz. The duration is 10 seconds.
245The file format is two-channel raw data with each sample being a signed
24616-bit integer in little-endian with sampling rate 48000 samples/sec.
247The volume is 0.1. The small volume is to avoid distortion when played
248on Chameleon.
249"""
250SIMPLE_FREQUENCY_TEST_FILE = AudioTestData(
251        path=os.path.join(AUDIO_PATH, 'fix_440_16.raw'),
252        data_format=dict(file_type='raw',
253                         sample_format='S16_LE',
254                         channel=2,
255                         rate=48000),
256        frequencies=[440, 440])
257
258"""
259This test data contains fixed frequency sine wave in two channels.
260Left and right channel are both 1330 Hz. The duration is 10 seconds.
261The file format is two-channel raw data with each sample being a signed
26216-bit integer in little-endian with sampling rate 48000 samples/sec.
263The volume is 0.1. The small volume is to avoid distortion when played
264on Chameleon.
265"""
266SIMPLE_FREQUENCY_TEST_1330_FILE = AudioTestData(
267        path=os.path.join(AUDIO_PATH, 'fix_1330_16.raw'),
268        data_format=dict(file_type='raw',
269                         sample_format='S16_LE',
270                         channel=2,
271                         rate=48000),
272        frequencies=[1330, 1330])
273
274"""
275This test data contains fixed frequency sine wave in two channels.
276Left and right channel are both 440Hz. The duration is 10 seconds.
277The file format is two-channel raw data with each sample being a signed
27816-bit integer in little-endian with sampling rate 48000 samples/sec.
279The volume is 0.5. The larger volume is needed to test internal
280speaker of Cros device because the microphone of Chameleon is not sensitive
281enough.
282"""
283SIMPLE_FREQUENCY_SPEAKER_TEST_FILE = AudioTestData(
284        path=os.path.join(AUDIO_PATH, 'fix_440_16_half.raw'),
285        data_format=dict(file_type='raw',
286                         sample_format='S16_LE',
287                         channel=2,
288                         rate=48000),
289        frequencies=[440, 440])
290
291"""
292This test data contains hotword - "Ok google" generated by google translate.
293The file format is two-channel raw data with each sample being a signed
29416-bit integer in little-endian with sampling rate 48000 samples/sec.
295"""
296HOTWORD_TEST_FILE = AudioTestData(
297        path=os.path.join(AUDIO_PATH, 'hotword_16.raw'),
298        data_format=dict(file_type='raw',
299                         sample_format='S16_LE',
300                         channel=2,
301                         rate=48000),
302        duration_secs=1.0)
303
304"""
305Media test verification for 256Hz frequency (headphone audio).
306"""
307MEDIA_HEADPHONE_TEST_FILE = FakeTestData(frequencies=[256, 256])
308
309"""
310Media test verification for 512Hz frequency (onboard speakers).
311"""
312MEDIA_SPEAKER_TEST_FILE = FakeTestData(frequencies=[512, 512])
313
314"""
315Test file for 10 min playback for headphone. Left frequency is 1350Hz, right
316frequency is 870 Hz, and amplitude is 0.85.
317"""
318HEADPHONE_10MIN_TEST_FILE = FakeTestData(
319        frequencies=[1350, 870],
320        url=('http://commondatastorage.googleapis.com/chromiumos-test-assets-'
321             'public/audio_test/chameleon/Headphone/L1350_R870_A085_10min.wav'),
322        duration_secs=600)
323