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