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 os
9
10from autotest_lib.client.cros.audio import audio_data
11from autotest_lib.client.cros.audio import sox_utils
12
13
14class AudioTestDataException(Exception):
15    """Exception for audio test data."""
16    pass
17
18
19class AudioTestData(object):
20    """Class to represent audio test data."""
21    def __init__(self, data_format=None, path=None, frequencies=None):
22        """
23        Initializes an audio test file.
24
25        @param data_format: A dict containing data format including
26                            file_type, sample_format, channel, and rate.
27                            file_type: file type e.g. 'raw' or 'wav'.
28                            sample_format: One of the keys in
29                                           audio_data.SAMPLE_FORMAT.
30                            channel: number of channels.
31                            rate: sampling rate.
32        @param path: The path to the file.
33        @param frequencies: A list containing the frequency of each channel in
34                            this file. Only applicable to data of sine tone.
35
36        @raises: AudioTestDataException if the path does not exist.
37
38        """
39        self.data_format = data_format
40        if not os.path.exists(path):
41            raise AudioTestDataException('Can not find path %s' % path)
42        self.path = path
43        self.frequencies = frequencies
44
45
46    def get_binary(self):
47        """The binary of test data.
48
49        @returns: The binary of test data.
50
51        """
52        with open(self.path, 'rb') as f:
53            return f.read()
54
55
56    def convert(self, data_format, volume_scale):
57        """Converts the data format and returns a new AudioTestData object.
58
59        Converts the source file at self.path to a new data format.
60        The destination file path is self.path with a suffix. E.g.
61        original_path = '/tmp/test.raw'
62        data_format = dict(file_type='raw', sample_format='S32_LE',
63                           channel=2, rate=48000)
64        new_path = '/tmp/test_raw_48000_S32_LE_2.raw'
65
66        This method returns a new AudioTestData object so the original object is
67        not changed.
68
69        @param data_format: A dict containing new data format.
70        @param volume_scale: A float for volume scale used in sox command.
71                              E.g. 1.0 is the same. 0.5 to scale volume by
72                              half. -1.0 to invert the data.
73
74        @returns: A new AudioTestData object with converted format and new path.
75
76        """
77        original_path_without_ext, ext = os.path.splitext(self.path)
78        new_path = (original_path_without_ext + '_' +
79                    '_'.join(str(x) for x in data_format.values()) + ext)
80
81        sox_utils.convert_format(
82                path_src=self.path,
83                channels_src=self.data_format['channel'],
84                rate_src=self.data_format['rate'],
85                bits_src=audio_data.SAMPLE_FORMATS[
86                        self.data_format['sample_format']]['size_bytes'] * 8,
87                path_dst=new_path,
88                channels_dst=data_format['channel'],
89                rate_dst=data_format['rate'],
90                bits_dst=audio_data.SAMPLE_FORMATS[
91                        data_format['sample_format']]['size_bytes'] * 8,
92                volume_scale=volume_scale)
93
94        new_test_data = AudioTestData(path=new_path,
95                                      data_format=data_format)
96
97        return new_test_data
98
99
100    def delete(self):
101        """Deletes the file at self.path."""
102        os.unlink(self.path)
103
104
105class FakeTestData(object):
106    def __init__(self, frequencies):
107        """A fake test data which contains properties but no real data.
108
109        This is useful when we need to pass an AudioTestData object into a test
110        or audio_test_utils.check_recorded_frequency.
111
112        @param frequencies: A list containing the frequency of each channel in
113                            this file. Only applicable to data of sine tone.
114
115        """
116        self.frequencies = frequencies
117
118
119AUDIO_PATH = os.path.join(os.path.dirname(__file__))
120
121"""
122This test data contains frequency sweep from 20Hz to 20000Hz in two channels.
123Left channel sweeps from 20Hz to 20000Hz, while right channel sweeps from
12420000Hz to 20Hz. The sweep duration is 2 seconds. The begin and end of the file
125is padded with 0.2 seconds of silence. The file is two-channel raw data with
126each sample being a signed 16-bit integer in little-endian with sampling rate
12748000 samples/sec.
128"""
129SWEEP_TEST_FILE = AudioTestData(
130        path=os.path.join(AUDIO_PATH, 'pad_sweep_pad_16.raw'),
131        data_format=dict(file_type='raw',
132                         sample_format='S16_LE',
133                         channel=2,
134                         rate=48000))
135
136"""
137This test data contains fixed frequency sine wave in two channels.
138Left channel is 2KHz, while right channel is 1KHz. The duration is 6 seconds.
139The file format is two-channel raw data with each sample being a signed
14016-bit integer in little-endian with sampling rate 48000 samples/sec.
141"""
142FREQUENCY_TEST_FILE = AudioTestData(
143        path=os.path.join(AUDIO_PATH, 'fix_2k_1k_16.raw'),
144        data_format=dict(file_type='raw',
145                         sample_format='S16_LE',
146                         channel=2,
147                         rate=48000),
148        frequencies=[2000, 1000])
149
150
151"""
152This test data contains fixed frequency sine wave in two channels.
153Left and right channel are both 440Hz. The duration is 10 seconds.
154The file format is two-channel raw data with each sample being a signed
15516-bit integer in little-endian with sampling rate 48000 samples/sec.
156The volume is 0.1. The small volume is to avoid distortion when played
157on Chameleon.
158"""
159SIMPLE_FREQUENCY_TEST_FILE = AudioTestData(
160        path=os.path.join(AUDIO_PATH, 'fix_440_16.raw'),
161        data_format=dict(file_type='raw',
162                         sample_format='S16_LE',
163                         channel=2,
164                         rate=48000),
165        frequencies=[440, 440])
166
167"""
168This test data contains fixed frequency sine wave in two channels.
169Left and right channel are both 440Hz. The duration is 10 seconds.
170The file format is two-channel raw data with each sample being a signed
17116-bit integer in little-endian with sampling rate 48000 samples/sec.
172The volume is 0.5. The larger volume is needed to test internal
173speaker of Cros device because the microphone of Chameleon is not sensitive
174enough.
175"""
176SIMPLE_FREQUENCY_SPEAKER_TEST_FILE = AudioTestData(
177        path=os.path.join(AUDIO_PATH, 'fix_440_16_half.raw'),
178        data_format=dict(file_type='raw',
179                         sample_format='S16_LE',
180                         channel=2,
181                         rate=48000),
182        frequencies=[440, 440])
183
184"""
185Media test verification for 256Hz frequency (headphone audio).
186"""
187MEDIA_HEADPHONE_TEST_FILE = FakeTestData(frequencies=[256, 256])
188
189"""
190Media test verification for 512Hz frequency (onboard speakers).
191"""
192MEDIA_SPEAKER_TEST_FILE = FakeTestData(frequencies=[512, 512])
193