def _read_wave_binary(self):
     """Reads in samples in wave file."""
     self._binary = self._wave_reader.readframes(self._n_frames)
     format_str = 'S%d_LE' % self._sample_width_bits
     self.raw_data = audio_data.AudioRawData(binary=self._binary,
                                             channel=self._n_channels,
                                             sample_format=format_str)
def read_audio_file(args):
    """Reads audio file.

    @param args: The namespace parsed from command line arguments.

    @returns: A tuple (raw_data, rate) where raw_data is
              audio_data.AudioRawData, rate is sampling rate.

    """
    if args.filename.endswith('.wav'):
        wavefile = WaveFile(args.filename)
        raw_data = wavefile.raw_data
        rate = wavefile.rate
    elif args.filename.endswith('.raw'):
        binary = None
        with open(args.filename, 'r') as f:
            binary = f.read()

        raw_data = audio_data.AudioRawData(binary=binary,
                                           channel=args.channel,
                                           sample_format='S%d_LE' %
                                           args.bit_width)
        rate = args.rate
    else:
        raise CheckQualityError('File format for %s is not supported' %
                                args.filename)

    return raw_data, rate
Beispiel #3
0
def get_one_channel_stat(data, data_format):
    """Gets statistic information of data.

    @param data: A list containing one channel data.
    @param data_format: A dict containing data format of data.

    @return: The sox stat parsed result. An object containing
             sameple_count: An int. Samples read.
             length: A float. Length in seconds.
             rms: A float. RMS amplitude.
             rough_frequency: A float. Rough frequency.
    """
    if not data:
        raise ValueError('Data is empty. Can not get stat')
    raw_data = audio_data.AudioRawData(
        binary=None, channel=1, sample_format=data_format['sample_format'])
    raw_data.copy_channel_data([data])
    with tempfile.NamedTemporaryFile() as raw_data_file:
        raw_data_path = raw_data_file.name
        raw_data.write_to_file(raw_data_path)

        bits = 8 * (audio_data.SAMPLE_FORMATS[data_format['sample_format']]
                    ['size_bytes'])
        stat = sox_utils.get_stat(raw_data_path,
                                  channels=1,
                                  bits=bits,
                                  rate=data_format['rate'])
        return stat
 def testSpectralAnalysisRealData(self):
     """This unittest checks the spectral analysis works on real data."""
     binary = open('client/cros/audio/test_data/1k_2k.raw', 'r').read()
     data = audio_data.AudioRawData(binary, 2, 'S32_LE')
     saturate_value = audio_data.get_maximum_value_from_sample_format(
             'S32_LE')
     golden_frequency = [1000, 2000]
     for channel in [0, 1]:
         normalized_signal = audio_analysis.normalize_signal(
                 data.channel_data[channel],saturate_value)
         spectral = audio_analysis.spectral_analysis(
                 normalized_signal, 48000, 0.02)
         logging.debug('channel %s: %s', channel, spectral)
         self.assertTrue(abs(spectral[0][0] - golden_frequency[channel]) < 5,
                         'Dominant frequency is not correct')
def compare_data_correlation(golden_data_binary,
                             golden_data_format,
                             test_data_binary,
                             test_data_format,
                             channel_map,
                             parameters=None):
    """Compares two raw data using correlation.

    @param golden_data_binary: The binary containing golden data.
    @param golden_data_format: The data format of golden data.
    @param test_data_binary: The binary containing test data.
    @param test_data_format: The data format of test data.
    @param channel_map: A list containing channel mapping.
                        E.g. [1, 0, None, None, None, None, None, None] means
                        channel 0 of test data should map to channel 1 of
                        golden data. Channel 1 of test data should map to
                        channel 0 of golden data. Channel 2 to 7 of test data
                        should be skipped.
    @param parameters: A dict containing parameters for method, if needed.

    @raises: NotImplementedError if file type is not raw.
             NotImplementedError if sampling rates of two data are not the same.
             error.TestFail if golden data and test data are not equal.
    """
    if parameters is None:
        parameters = dict()

    if (golden_data_format['file_type'] != 'raw'
            or test_data_format['file_type'] != 'raw'):
        raise NotImplementedError('Only support raw data in compare_data.')
    if (golden_data_format['rate'] != test_data_format['rate']):
        raise NotImplementedError(
            'Only support comparing data with the same sampling rate')
    golden_data = audio_data.AudioRawData(
        binary=golden_data_binary,
        channel=golden_data_format['channel'],
        sample_format=golden_data_format['sample_format'])
    test_data = audio_data.AudioRawData(
        binary=test_data_binary,
        channel=test_data_format['channel'],
        sample_format=test_data_format['sample_format'])
    compare_results = []
    for test_channel, golden_channel in enumerate(channel_map):
        if golden_channel is None:
            logging.info('Skipped channel %d', test_channel)
            continue
        test_data_one_channel = test_data.channel_data[test_channel]
        golden_data_one_channel = golden_data.channel_data[golden_channel]
        result_dict = dict(test_channel=test_channel,
                           golden_channel=golden_channel)
        result_dict.update(
            compare_one_channel_correlation(test_data_one_channel,
                                            golden_data_one_channel,
                                            parameters))
        compare_results.append(result_dict)
    logging.info('compare_results: %r', compare_results)
    for result in compare_results:
        if not result['equal']:
            error_msg = (
                'Failed on test channel %d and golden channel %d with '
                'index %f') % (result['test_channel'],
                               result['golden_channel'], result['index'])
            logging.error(error_msg)
            raise error.TestFail(error_msg)
    # Also checks best delay are exactly the same.
    best_delays = set([result['best_delay'] for result in compare_results])
    if len(best_delays) > 1:
        error_msg = 'There are more than one best delay: %s' % best_delays
        logging.error(error_msg)
        raise error.TestFail(error_msg)
def check_recorded_frequency(
        golden_file,
        recorder,
        second_peak_ratio=_DEFAULT_SECOND_PEAK_RATIO,
        frequency_diff_threshold=DEFAULT_FREQUENCY_DIFF_THRESHOLD,
        ignore_frequencies=None,
        check_anomaly=False,
        check_artifacts=False,
        mute_durations=None,
        volume_changes=None,
        tolerant_noise_level=DEFAULT_TOLERANT_NOISE_LEVEL):
    """Checks if the recorded data contains sine tone of golden frequency.

    @param golden_file: An AudioTestData object that serves as golden data.
    @param recorder: An AudioWidget used in the test to record data.
    @param second_peak_ratio: The test fails when the second dominant
                              frequency has coefficient larger than this
                              ratio of the coefficient of first dominant
                              frequency.
    @param frequency_diff_threshold: The maximum difference between estimated
                                     frequency of test signal and golden
                                     frequency. This value should be small for
                                     signal passed through line.
    @param ignore_frequencies: A list of frequencies to be ignored. The
                               component in the spectral with frequency too
                               close to the frequency in the list will be
                               ignored. The comparison of frequencies uses
                               frequency_diff_threshold as well.
    @param check_anomaly: True to check anomaly in the signal.
    @param check_artifacts: True to check artifacts in the signal.
    @param mute_durations: Each duration of mute in seconds in the signal.
    @param volume_changes: A list containing alternative -1 for decreasing
                           volume and +1 for increasing volume.
    @param tolerant_noise_level: The maximum noise level can be tolerated

    @returns: A list containing tuples of (dominant_frequency, coefficient) for
              valid channels. Coefficient can be a measure of signal magnitude
              on that dominant frequency. Invalid channels where golden_channel
              is None are ignored.

    @raises error.TestFail if the recorded data does not contain sine tone of
            golden frequency.

    """
    if not ignore_frequencies:
        ignore_frequencies = []

    # Also ignore harmonics of ignore frequencies.
    ignore_frequencies_harmonics = []
    for ignore_freq in ignore_frequencies:
        ignore_frequencies_harmonics += [ignore_freq * n for n in xrange(1, 4)]

    data_format = recorder.data_format
    recorded_data = audio_data.AudioRawData(
        binary=recorder.get_binary(),
        channel=data_format['channel'],
        sample_format=data_format['sample_format'])

    errors = []
    dominant_spectrals = []

    for test_channel, golden_channel in enumerate(recorder.channel_map):
        if golden_channel is None:
            logging.info('Skipped channel %d', test_channel)
            continue

        signal = recorded_data.channel_data[test_channel]
        saturate_value = audio_data.get_maximum_value_from_sample_format(
            data_format['sample_format'])
        logging.debug('Channel %d max signal: %f', test_channel, max(signal))
        normalized_signal = audio_analysis.normalize_signal(
            signal, saturate_value)
        logging.debug('saturate_value: %f', saturate_value)
        logging.debug('max signal after normalized: %f',
                      max(normalized_signal))
        spectral = audio_analysis.spectral_analysis(normalized_signal,
                                                    data_format['rate'])
        logging.debug('spectral: %s', spectral)

        if not spectral:
            errors.append('Channel %d: Can not find dominant frequency.' %
                          test_channel)

        golden_frequency = golden_file.frequencies[golden_channel]
        logging.debug('Checking channel %s spectral %s against frequency %s',
                      test_channel, spectral, golden_frequency)

        dominant_frequency = spectral[0][0]

        if (abs(dominant_frequency - golden_frequency) >
                frequency_diff_threshold):
            errors.append(
                'Channel %d: Dominant frequency %s is away from golden %s' %
                (test_channel, dominant_frequency, golden_frequency))

        if check_anomaly:
            detected_anomaly = audio_analysis.anomaly_detection(
                signal=normalized_signal,
                rate=data_format['rate'],
                freq=golden_frequency)
            if detected_anomaly:
                errors.append(
                    'Channel %d: Detect anomaly near these time: %s' %
                    (test_channel, detected_anomaly))
            else:
                logging.info(
                    'Channel %d: Quality is good as there is no anomaly',
                    test_channel)

        if check_artifacts or mute_durations or volume_changes:
            result = audio_quality_measurement.quality_measurement(
                normalized_signal,
                data_format['rate'],
                dominant_frequency=dominant_frequency)
            logging.debug('Quality measurement result:\n%s',
                          pprint.pformat(result))
            if check_artifacts:
                if len(result['artifacts']['noise_before_playback']) > 0:
                    errors.append(
                        'Channel %d: Detects artifacts before playing near'
                        ' these time and duration: %s' %
                        (test_channel,
                         str(result['artifacts']['noise_before_playback'])))

                if len(result['artifacts']['noise_after_playback']) > 0:
                    errors.append(
                        'Channel %d: Detects artifacts after playing near'
                        ' these time and duration: %s' %
                        (test_channel,
                         str(result['artifacts']['noise_after_playback'])))

            if mute_durations:
                delays = result['artifacts']['delay_during_playback']
                delay_durations = []
                for x in delays:
                    delay_durations.append(x[1])
                mute_matched, delay_matched = longest_common_subsequence(
                    mute_durations, delay_durations,
                    DEFAULT_EQUIVALENT_THRESHOLD)

                # updated delay list
                new_delays = [
                    delays[i] for i in delay_matched if not delay_matched[i]
                ]

                result['artifacts']['delay_during_playback'] = new_delays

                unmatched_mutes = [
                    mute_durations[i] for i in mute_matched
                    if not mute_matched[i]
                ]

                if len(unmatched_mutes) > 0:
                    errors.append('Channel %d: Unmatched mute duration: %s' %
                                  (test_channel, unmatched_mutes))

            if check_artifacts:
                if len(result['artifacts']['delay_during_playback']) > 0:
                    errors.append(
                        'Channel %d: Detects delay during playing near'
                        ' these time and duration: %s' %
                        (test_channel,
                         result['artifacts']['delay_during_playback']))

                if len(result['artifacts']['burst_during_playback']) > 0:
                    errors.append(
                        'Channel %d: Detects burst/pop near these time: %s' %
                        (test_channel,
                         result['artifacts']['burst_during_playback']))

                if result['equivalent_noise_level'] > tolerant_noise_level:
                    errors.append(
                        'Channel %d: noise level is higher than tolerant'
                        ' noise level: %f > %f' %
                        (test_channel, result['equivalent_noise_level'],
                         tolerant_noise_level))

            if volume_changes:
                matched = True
                volume_changing = result['volume_changes']
                if len(volume_changing) != len(volume_changes):
                    matched = False
                else:
                    for i in xrange(len(volume_changing)):
                        if volume_changing[i][1] != volume_changes[i]:
                            matched = False
                            break
                if not matched:
                    errors.append(
                        'Channel %d: volume changing is not as expected, '
                        'found changing time and events are: %s while '
                        'expected changing events are %s' %
                        (test_channel, volume_changing, volume_changes))

        # Filter out the harmonics resulted from imperfect sin wave.
        # This list is different for different channels.
        harmonics = [dominant_frequency * n for n in xrange(2, 10)]

        def should_be_ignored(frequency):
            """Checks if frequency is close to any frequency in ignore list.

            The ignore list is harmonics of frequency to be ignored
            (like power noise), plus harmonics of dominant frequencies,
            plus DC.

            @param frequency: The frequency to be tested.

            @returns: True if the frequency should be ignored. False otherwise.

            """
            for ignore_frequency in (ignore_frequencies_harmonics + harmonics +
                                     [0.0]):
                if (abs(frequency - ignore_frequency) <
                        frequency_diff_threshold):
                    logging.debug('Ignore frequency: %s', frequency)
                    return True

        # Checks DC is small enough.
        for freq, coeff in spectral:
            if freq < _DC_FREQ_THRESHOLD and coeff > _DC_COEFF_THRESHOLD:
                errors.append('Channel %d: Found large DC coefficient: '
                              '(%f Hz, %f)' % (test_channel, freq, coeff))

        # Filter out the frequencies to be ignored.
        spectral = [x for x in spectral if not should_be_ignored(x[0])]

        if len(spectral) > 1:
            first_coeff = spectral[0][1]
            second_coeff = spectral[1][1]
            if second_coeff > first_coeff * second_peak_ratio:
                errors.append(
                    'Channel %d: Found large second dominant frequencies: '
                    '%s' % (test_channel, spectral))

        dominant_spectrals.append(spectral[0])

    if errors:
        raise error.TestFail(', '.join(errors))

    return dominant_spectrals
Beispiel #7
0
def compare_data(golden_data_binary,
                 golden_data_format,
                 test_data_binary,
                 test_data_format,
                 channel_map,
                 method,
                 parameters=None):
    """Compares two raw data.

    @param golden_data_binary: The binary containing golden data.
    @param golden_data_format: The data format of golden data.
    @param test_data_binary: The binary containing test data.
    @param test_data_format: The data format of test data.
    @param channel_map: A list containing channel mapping.
                        E.g. [1, 0, None, None, None, None, None, None] means
                        channel 0 of test data should map to channel 1 of
                        golden data. Channel 1 of test data should map to
                        channel 0 of golden data. Channel 2 to 7 of test data
                        should be skipped.
    @param method: The method to compare data. Use 'correlation' to compare
                   general data. Use 'frequency' to compare data containing
                   sine wave.

    @param parameters: A dict containing parameters for method, if needed.

    @returns: A boolean for compare result.

    @raises: NotImplementedError if file type is not raw.
             NotImplementedError if sampling rates of two data are not the same.
    """
    if parameters is None:
        parameters = dict()

    if (golden_data_format['file_type'] != 'raw'
            or test_data_format['file_type'] != 'raw'):
        raise NotImplementedError('Only support raw data in compare_data.')
    if (golden_data_format['rate'] != test_data_format['rate']):
        raise NotImplementedError(
            'Only support comparing data with the same sampling rate')
    golden_data = audio_data.AudioRawData(
        binary=golden_data_binary,
        channel=golden_data_format['channel'],
        sample_format=golden_data_format['sample_format'])
    test_data = audio_data.AudioRawData(
        binary=test_data_binary,
        channel=test_data_format['channel'],
        sample_format=test_data_format['sample_format'])
    compare_results = []
    for test_channel, golden_channel in enumerate(channel_map):
        if golden_channel is None:
            logging.info('Skipped channel %d', test_channel)
            continue
        test_data_one_channel = test_data.channel_data[test_channel]
        golden_data_one_channel = golden_data.channel_data[golden_channel]
        result_dict = dict(test_channel=test_channel,
                           golden_channel=golden_channel)
        result_dict.update(
            compare_one_channel_data(test_data_one_channel, test_data_format,
                                     golden_data_one_channel,
                                     golden_data_format, method, parameters))
        compare_results.append(result_dict)
    logging.info('compare_results: %r', compare_results)
    return_value = False if not compare_results else True
    for result in compare_results:
        if not result['equal']:
            logging.error('Failed on test channel %d and golden channel %d',
                          result['test_channel'], result['golden_channel'])
            return_value = False
    # Also checks best delay are exactly the same.
    if method == 'correlation':
        best_delays = set([result['best_delay'] for result in compare_results])
        if len(best_delays) > 1:
            logging.error('There are more than one best delay.')
            return_value = False
    return return_value
def check_recorded_frequency(
        golden_file,
        recorder,
        second_peak_ratio=DEFAULT_SECOND_PEAK_RATIO,
        frequency_diff_threshold=DEFAULT_FREQUENCY_DIFF_THRESHOLD,
        ignore_frequencies=None,
        check_anomaly=False):
    """Checks if the recorded data contains sine tone of golden frequency.

    @param golden_file: An AudioTestData object that serves as golden data.
    @param recorder: An AudioWidget used in the test to record data.
    @param second_peak_ratio: The test fails when the second dominant
                              frequency has coefficient larger than this
                              ratio of the coefficient of first dominant
                              frequency.
    @param frequency_diff_threshold: The maximum difference between estimated
                                     frequency of test signal and golden
                                     frequency. This value should be small for
                                     signal passed through line.
    @param ignore_frequencies: A list of frequencies to be ignored. The
                               component in the spectral with frequency too
                               close to the frequency in the list will be
                               ignored. The comparison of frequencies uses
                               frequency_diff_threshold as well.
    @param check_anomaly: True to check anomaly in the signal.

    @raises error.TestFail if the recorded data does not contain sine tone of
            golden frequency.

    """
    data_format = recorder.data_format
    recorded_data = audio_data.AudioRawData(
        binary=recorder.get_binary(),
        channel=data_format['channel'],
        sample_format=data_format['sample_format'])

    errors = []

    for test_channel, golden_channel in enumerate(recorder.channel_map):
        if golden_channel is None:
            logging.info('Skipped channel %d', test_channel)
            continue

        signal = recorded_data.channel_data[test_channel]
        saturate_value = audio_data.get_maximum_value_from_sample_format(
            data_format['sample_format'])
        normalized_signal = audio_analysis.normalize_signal(
            signal, saturate_value)
        spectral = audio_analysis.spectral_analysis(normalized_signal,
                                                    data_format['rate'])

        if not spectral:
            errors.append('Channel %d: Can not find dominant frequency.' %
                          test_channel)

        golden_frequency = golden_file.frequencies[golden_channel]
        logging.debug('Checking channel %s spectral %s against frequency %s',
                      test_channel, spectral, golden_frequency)

        dominant_frequency = spectral[0][0]

        if (abs(dominant_frequency - golden_frequency) >
                frequency_diff_threshold):
            errors.append(
                'Channel %d: Dominant frequency %s is away from golden %s' %
                (test_channel, dominant_frequency, golden_frequency))

        if check_anomaly:
            detected_anomaly = audio_analysis.anomaly_detection(
                signal=normalized_signal,
                rate=data_format['rate'],
                freq=golden_frequency)
            if detected_anomaly:
                errors.append(
                    'Channel %d: Detect anomaly near these time: %s' %
                    (test_channel, detected_anomaly))
            else:
                logging.info(
                    'Channel %d: Quality is good as there is no anomaly',
                    test_channel)

        def should_be_ignored(frequency):
            """Checks if frequency is close to any frequency in ignore list.

            @param frequency: The frequency to be tested.

            @returns: True if the frequency should be ignored. False otherwise.

            """
            for ignore_frequency in ignore_frequencies:
                if (abs(frequency - ignore_frequency) <
                        frequency_diff_threshold):
                    logging.debug('Ignore frequency: %s', frequency)
                    return True

        # Filter out the frequencies to be ignored.
        if ignore_frequencies:
            spectral = [x for x in spectral if not should_be_ignored(x[0])]

        if len(spectral) > 1:
            first_coeff = spectral[0][1]
            second_coeff = spectral[1][1]
            if second_coeff > first_coeff * second_peak_ratio:
                errors.append(
                    'Channel %d: Found large second dominant frequencies: '
                    '%s' % (test_channel, spectral))

    if errors:
        raise error.TestFail(', '.join(errors))