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