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