Home | History | Annotate | Download | only in audio
      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 
      8 import logging
      9 import os
     10 import subprocess
     11 
     12 from autotest_lib.client.cros.audio import audio_data
     13 from autotest_lib.client.cros.audio import sox_utils
     14 
     15 
     16 class AudioTestDataException(Exception):
     17     """Exception for audio test data."""
     18     pass
     19 
     20 
     21 class 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 
    141 class 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 
    159 def 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 
    211 AUDIO_PATH = os.path.join(os.path.dirname(__file__))
    212 
    213 """
    214 This test data contains frequency sweep from 20Hz to 20000Hz in two channels.
    215 Left channel sweeps from 20Hz to 20000Hz, while right channel sweeps from
    216 20000Hz to 20Hz. The sweep duration is 2 seconds. The begin and end of the file
    217 is padded with 0.4 seconds of silence. The file is two-channel raw data with
    218 each sample being a signed 16-bit integer in little-endian with sampling rate
    219 48000 samples/sec.
    220 """
    221 SWEEP_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 """
    229 This test data contains fixed frequency sine wave in two channels.
    230 Left channel is 2KHz, while right channel is 1KHz. The duration is 6 seconds.
    231 The file format is two-channel raw data with each sample being a signed
    232 16-bit integer in little-endian with sampling rate 48000 samples/sec.
    233 """
    234 FREQUENCY_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 """
    243 This test data contains fixed frequency sine wave in two channels.
    244 Left and right channel are both 440Hz. The duration is 10 seconds.
    245 The file format is two-channel raw data with each sample being a signed
    246 16-bit integer in little-endian with sampling rate 48000 samples/sec.
    247 The volume is 0.1. The small volume is to avoid distortion when played
    248 on Chameleon.
    249 """
    250 SIMPLE_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 """
    259 This test data contains fixed frequency sine wave in two channels.
    260 Left and right channel are both 1330 Hz. The duration is 10 seconds.
    261 The file format is two-channel raw data with each sample being a signed
    262 16-bit integer in little-endian with sampling rate 48000 samples/sec.
    263 The volume is 0.1. The small volume is to avoid distortion when played
    264 on Chameleon.
    265 """
    266 SIMPLE_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 """
    275 This test data contains fixed frequency sine wave in two channels.
    276 Left and right channel are both 440Hz. The duration is 10 seconds.
    277 The file format is two-channel raw data with each sample being a signed
    278 16-bit integer in little-endian with sampling rate 48000 samples/sec.
    279 The volume is 0.5. The larger volume is needed to test internal
    280 speaker of Cros device because the microphone of Chameleon is not sensitive
    281 enough.
    282 """
    283 SIMPLE_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 """
    292 This test data contains hotword - "Ok google" generated by google translate.
    293 The file format is two-channel raw data with each sample being a signed
    294 16-bit integer in little-endian with sampling rate 48000 samples/sec.
    295 """
    296 HOTWORD_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 """
    305 Media test verification for 256Hz frequency (headphone audio).
    306 """
    307 MEDIA_HEADPHONE_TEST_FILE = FakeTestData(frequencies=[256, 256])
    308 
    309 """
    310 Media test verification for 512Hz frequency (onboard speakers).
    311 """
    312 MEDIA_SPEAKER_TEST_FILE = FakeTestData(frequencies=[512, 512])
    313 
    314 """
    315 Test file for 10 min playback for headphone. Left frequency is 1350Hz, right
    316 frequency is 870 Hz, and amplitude is 0.85.
    317 """
    318 HEADPHONE_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