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): 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 79 @returns: A new AudioTestData object with converted format and new path. 80 81 """ 82 original_path_without_ext, _ = os.path.splitext(self.path) 83 new_ext = '.' + data_format['file_type'] 84 # New path will be the composition of original name, new data format, 85 # and new file type as extension. 86 new_path = (original_path_without_ext + '_' + 87 '_'.join(str(x) for x in data_format.values()) + new_ext) 88 89 logging.debug('src data_format: %s', self.data_format) 90 logging.debug('dst data_format: %s', data_format) 91 92 # If source file has header, use that header. 93 if self.data_format['file_type'] != 'raw': 94 use_src_header = True 95 channels_src = None 96 rate_src = None 97 bits_src = None 98 else: 99 use_src_header = False 100 channels_src = self.data_format['channel'] 101 rate_src = self.data_format['rate'] 102 bits_src = audio_data.SAMPLE_FORMATS[ 103 self.data_format['sample_format']]['size_bytes'] * 8 104 105 # If dst file type is not raw, write file format into header of dst 106 # file. 107 use_dst_header = data_format['file_type'] != 'raw' 108 109 sox_utils.convert_format( 110 path_src=self.path, 111 channels_src=channels_src, 112 rate_src=rate_src, 113 bits_src=bits_src, 114 path_dst=new_path, 115 channels_dst=data_format['channel'], 116 rate_dst=data_format['rate'], 117 bits_dst=audio_data.SAMPLE_FORMATS[ 118 data_format['sample_format']]['size_bytes'] * 8, 119 volume_scale=volume_scale, 120 use_src_header=use_src_header, 121 use_dst_header=use_dst_header) 122 123 new_test_data = AudioTestData(path=new_path, 124 data_format=data_format) 125 126 return new_test_data 127 128 129 def delete(self): 130 """Deletes the file at self.path.""" 131 os.unlink(self.path) 132 133 134class FakeTestData(object): 135 def __init__(self, frequencies, url=None, duration_secs=None): 136 """A fake test data which contains properties but no real data. 137 138 This is useful when we need to pass an AudioTestData object into a test 139 or audio_test_utils.check_recorded_frequency. 140 141 @param frequencies: A list containing the frequency of each channel in 142 this file. Only applicable to data of sine tone. 143 @param url: The URL to the test file. 144 @param duration_secs: The duration of the file in seconds. 145 146 """ 147 self.frequencies = frequencies 148 self.url = url 149 self.duration_secs = duration_secs 150 151 152class AudioTestDataGenerateOnDemand(AudioTestData): 153 """AudioTestData that generates real data on demand.""" 154 def __init__(self, data_format=None, path=None, frequencies=None, 155 duration_secs=None): 156 """ 157 Initializes an audio test file that generate file on demand. 158 159 @param data_format: A dict containing data format including 160 file_type, sample_format, channel, and rate. 161 file_type: file type e.g. 'raw' or 'wav'. 162 sample_format: One of the keys in 163 audio_data.SAMPLE_FORMAT. 164 channel: number of channels. 165 rate: sampling rate. 166 @param path: The path to the file. 167 @param frequencies: A list containing the frequency of each channel in 168 this file. Only applicable to data of sine tone. 169 @param duration_secs: Duration of test file in seconds. 170 171 """ 172 self.data_format = data_format 173 self.path = path 174 self.frequencies = frequencies 175 self.duration_secs = duration_secs 176 177 178 def generate_file(self): 179 """Generates the data with specified format and frequencies.""" 180 sample_format = audio_data.SAMPLE_FORMATS[self.data_format['sample_format']] 181 bits = sample_format['size_bytes'] * 8 182 183 command = sox_utils.generate_sine_tone_cmd( 184 filename=self.path, 185 channels=self.data_format['channel'], 186 bits=bits, 187 rate=self.data_format['rate'], 188 duration=self.duration_secs, 189 frequencies=self.frequencies, 190 raw=(self.data_format['file_type'] == 'raw')) 191 192 subprocess.check_call(command) 193 194 195AUDIO_PATH = os.path.join(os.path.dirname(__file__)) 196 197""" 198This test data contains frequency sweep from 20Hz to 20000Hz in two channels. 199Left channel sweeps from 20Hz to 20000Hz, while right channel sweeps from 20020000Hz to 20Hz. The sweep duration is 2 seconds. The begin and end of the file 201is padded with 0.4 seconds of silence. The file is two-channel raw data with 202each sample being a signed 16-bit integer in little-endian with sampling rate 20348000 samples/sec. 204""" 205SWEEP_TEST_FILE = AudioTestData( 206 path=os.path.join(AUDIO_PATH, 'pad_sweep_pad_16.raw'), 207 data_format=dict(file_type='raw', 208 sample_format='S16_LE', 209 channel=2, 210 rate=48000)) 211 212""" 213This test data contains fixed frequency sine wave in two channels. 214Left channel is 2KHz, while right channel is 1KHz. The duration is 6 seconds. 215The file format is two-channel raw data with each sample being a signed 21616-bit integer in little-endian with sampling rate 48000 samples/sec. 217""" 218FREQUENCY_TEST_FILE = AudioTestData( 219 path=os.path.join(AUDIO_PATH, 'fix_2k_1k_16.raw'), 220 data_format=dict(file_type='raw', 221 sample_format='S16_LE', 222 channel=2, 223 rate=48000), 224 frequencies=[2000, 1000]) 225 226""" 227This test data contains fixed frequency sine wave in two channels. 228Left and right channel are both 440Hz. The duration is 10 seconds. 229The file format is two-channel raw data with each sample being a signed 23016-bit integer in little-endian with sampling rate 48000 samples/sec. 231The volume is 0.1. The small volume is to avoid distortion when played 232on Chameleon. 233""" 234SIMPLE_FREQUENCY_TEST_FILE = AudioTestData( 235 path=os.path.join(AUDIO_PATH, 'fix_440_16.raw'), 236 data_format=dict(file_type='raw', 237 sample_format='S16_LE', 238 channel=2, 239 rate=48000), 240 frequencies=[440, 440]) 241 242""" 243This test data contains fixed frequency sine wave in two channels. 244Left and right channel are both 1330 Hz. 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_1330_FILE = AudioTestData( 251 path=os.path.join(AUDIO_PATH, 'fix_1330_16.raw'), 252 data_format=dict(file_type='raw', 253 sample_format='S16_LE', 254 channel=2, 255 rate=48000), 256 frequencies=[1330, 1330]) 257 258""" 259This test data contains fixed frequency sine wave in two channels. 260Left and right channel are both 660Hz. The duration is 60 seconds. 261The file format is two-channel wav data with each sample being a signed 26216-bit integer in little-endian with sampling rate 48000 samples/sec. 263The volume is 1.0. 264""" 265SIMPLE_FREQUENCY_LOUD_WAVE_FILE = AudioTestDataGenerateOnDemand( 266 path=os.path.join(AUDIO_PATH, 'fix_660_16.wav'), 267 data_format=dict(file_type='wav', 268 sample_format='S16_LE', 269 channel=2, 270 rate=48000), 271 duration_secs=60, 272 frequencies=[660, 660]) 273 274 275""" 276This test data contains fixed frequency sine wave in one channel. 277Left channel is 440Hz. The duration is 10 seconds. 278The file format is two-channel raw data with each sample being a signed 27916-bit integer in little-endian with sampling rate 48000 samples/sec. 280The volume is 0.5. 281""" 282LEFT_CHANNEL_TEST_FILE = AudioTestDataGenerateOnDemand( 283 path=os.path.join(AUDIO_PATH, 'left_440_half.raw'), 284 data_format=dict(file_type='raw', 285 sample_format='S16_LE', 286 channel=2, 287 rate=48000), 288 duration_secs=10, 289 frequencies=[440, 0]) 290 291""" 292This test data contains fixed frequency sine wave in one channel. 293Right channel is 440Hz. The duration is 10 seconds. 294The file format is two-channel raw data with each sample being a signed 29516-bit integer in little-endian with sampling rate 48000 samples/sec. 296The volume is 0.5. 297""" 298RIGHT_CHANNEL_TEST_FILE = AudioTestDataGenerateOnDemand( 299 path=os.path.join(AUDIO_PATH, 'right_440_half.raw'), 300 data_format=dict(file_type='raw', 301 sample_format='S16_LE', 302 channel=2, 303 rate=48000), 304 duration_secs=10, 305 frequencies=[0, 440]) 306 307""" 308This test data contains fixed frequency sine wave in two channels. 309Left and right channel are both 440Hz. The duration is 10 seconds. 310The file format is two-channel raw data with each sample being a signed 31116-bit integer in little-endian with sampling rate 48000 samples/sec. 312The volume is 0.5. The larger volume is needed to test internal 313speaker of Cros device because the microphone of Chameleon is not sensitive 314enough. 315""" 316SIMPLE_FREQUENCY_SPEAKER_TEST_FILE = AudioTestData( 317 path=os.path.join(AUDIO_PATH, 'fix_440_16_half.raw'), 318 data_format=dict(file_type='raw', 319 sample_format='S16_LE', 320 channel=2, 321 rate=48000), 322 frequencies=[440, 440]) 323 324""" 325Media test verification for 256Hz frequency (headphone audio). 326""" 327MEDIA_HEADPHONE_TEST_FILE = FakeTestData(frequencies=[256, 256]) 328 329""" 330Media test verification for 512Hz frequency (onboard speakers). 331""" 332MEDIA_SPEAKER_TEST_FILE = FakeTestData(frequencies=[512, 512]) 333 334""" 335Test file for 10 min playback for headphone. Left frequency is 1350Hz, right 336frequency is 870 Hz, and amplitude is 0.85. 337""" 338HEADPHONE_10MIN_TEST_FILE = FakeTestData( 339 frequencies=[1350, 870], 340 url=('http://commondatastorage.googleapis.com/chromiumos-test-assets-' 341 'public/audio_test/chameleon/Headphone/L1350_R870_A085_10min.wav'), 342 duration_secs=600) 343