def test_not_meaningful_data(self): """Checks that sepectral analysis handles un-meaningful data.""" rate = 48000 length_in_secs = 0.5 samples = length_in_secs * rate noise_amplitude = audio_analysis.MEANINGFUL_RMS_THRESHOLD * 0.5 noise = numpy.random.standard_normal(int(samples)) * noise_amplitude results = audio_analysis.spectral_analysis(noise, rate) self.assertEqual([(0, 0)], results)
def test_spectral_snalysis_real_data(self): """This unittest checks the spectral analysis works on real data.""" file_path = os.path.join( os.path.dirname(__file__), 'test_data', '1k_2k.raw') binary = open(file_path, 'rb').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 test_spectral_analysis(self): rate = 48000 length_in_secs = 0.5 freq_1 = 490.0 freq_2 = 60.0 coeff_1 = 1 coeff_2 = 0.3 samples = length_in_secs * rate noise = numpy.random.standard_normal(int(samples)) * 0.005 x = numpy.linspace(0.0, (samples - 1) * 1.0 / rate, samples) y = (coeff_1 * numpy.sin(freq_1 * 2.0 * numpy.pi * x) + coeff_2 * numpy.sin(freq_2 * 2.0 * numpy.pi * x)) + noise results = audio_analysis.spectral_analysis(y, rate) # Results should contains # [(490, 1*k), (60, 0.3*k), (0, 0.1*k)] where 490Hz is the dominant # frequency with coefficient 1, 60Hz is the second dominant frequency # with coefficient 0.3, 0Hz is from Gaussian noise with coefficient # around 0.1. The k constant is resulted from window function. logging.debug('Results: %s', results) self.assertTrue(abs(results[0][0] - freq_1) < 1) self.assertTrue(abs(results[1][0] - freq_2) < 1) self.assertTrue( abs(results[0][1] / results[1][1] - coeff_1 / coeff_2) < 0.01)
def do_spectral_analysis(self, ignore_high_freq, check_quality, quality_params): """Gets the spectral_analysis result. Args: ignore_high_freq: Ignore high frequencies above this threshold. check_quality: Check quality of each channel. quality_params: A QualityParams object for quality measurement. """ self.has_data() for channel_idx in range(self._raw_data.channel): signal = self._raw_data.channel_data[channel_idx] max_abs = max(numpy.abs(signal)) logging.debug('Channel %d max abs signal: %f', channel_idx, max_abs) if max_abs == 0: logging.info('No data on channel %d, skip this channel', channel_idx) continue saturate_value = audio_data.get_maximum_value_from_sample_format( self._raw_data.sample_format) 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, self._rate) logging.debug('Channel %d spectral:\n%s', channel_idx, pprint.pformat(spectral)) # Ignore high frequencies above the threshold. spectral = [(f, c) for (f, c) in spectral if f < ignore_high_freq] logging.info('Channel %d spectral after ignoring high frequencies ' 'above %f:\n%s', channel_idx, ignore_high_freq, pprint.pformat(spectral)) try: if check_quality: quality = audio_quality_measurement.quality_measurement( signal=normalized_signal, rate=self._rate, dominant_frequency=spectral[0][0], block_size_secs=quality_params.block_size_secs, frequency_error_threshold=quality_params. frequency_error_threshold, delay_amplitude_threshold=quality_params. delay_amplitude_threshold, noise_amplitude_threshold=quality_params. noise_amplitude_threshold, burst_amplitude_threshold=quality_params. burst_amplitude_threshold) logging.debug('Channel %d quality:\n%s', channel_idx, pprint.pformat(quality)) self._quality_result.append(quality) self._spectrals.append(spectral) except Exception as error: logging.warning( "Failed to analyze channel {} with error: {}".format( channel_idx, error))
def quality_measurement( signal, rate, dominant_frequency=None, block_size_secs=DEFAULT_BLOCK_SIZE_SECS, frequency_error_threshold=DEFAULT_FREQUENCY_ERROR, delay_amplitude_threshold=DEFAULT_DELAY_AMPLITUDE_THRESHOLD, noise_amplitude_threshold=DEFAULT_NOISE_AMPLITUDE_THRESHOLD, burst_amplitude_threshold=DEFAULT_BURST_AMPLITUDE_THRESHOLD, volume_changing_amplitude_threshold=DEFAULT_VOLUME_CHANGE_AMPLITUDE): """Detects several artifacts and estimates the noise level. This method detects artifact before playing, after playing, and delay during playing. Also, it estimates the noise level of the signal. To avoid the influence of noise, it calculates amplitude and frequency block by block. Args: signal: A list of numbers for one-channel PCM data. The data should be normalized to [-1,1]. rate: Sampling rate in samples per second. Example inputs: 44100, 48000 dominant_frequency: Dominant frequency of signal. Set None to recalculate the frequency in this function. block_size_secs: Block size in seconds. The measurement will be done block-by-block using average amplitude and frequency in each block to avoid noise. frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR. delay_amplitude_threshold: If the average amplitude of a block is lower than average amplitude of the wave times delay_amplitude_threshold, it will be considered as delay. Also refer to delay_detection and DEFAULT_DELAY_AMPLITUDE_THRESHOLD. noise_amplitude_threshold: If the average amplitude of a block is higher than average amplitude of the wave times noise_amplitude_threshold, it will be considered as noise before/after playback. Also refer to noise_detection and DEFAULT_NOISE_AMPLITUDE_THRESHOLD. burst_amplitude_threshold: If the average amplitude of a block is higher than average amplitude of its left block and its right block times burst_amplitude_threshold. It will be considered as a burst. Also refer to burst_detection and DEFAULT_BURST_AMPLITUDE_THRESHOLD. volume_changing_amplitude_threshold: If the average amplitude of right block is higher or lower than that of left one times this value, it will be considered as a volume change. Also refer to changing_volume_detection and DEFAULT_VOLUME_CHANGE_AMPLITUDE Returns: A dictoinary of detection/estimation: {'artifacts': {'noise_before_playback': [(time_1, duration_1), (time_2, duration_2), ...], 'noise_after_playback': [(time_1, duration_1), (time_2, duration_2), ...], 'delay_during_playback': [(time_1, duration_1), (time_2, duration_2), ...], 'burst_during_playback': [time_1, time_2, ...] }, 'volume_changes': [(time_1, flag_1), (time_2, flag_2), ...], 'equivalent_noise_level': level } where durations and time points are in seconds. And, equivalence_noise_level is the quotient of noise and wave which refers to DEFAULT_STANDARD_NOISE. volume_changes is a list of tuples containing time stamps and decreasing/increasing flags for volume change events. """ # Calculates the block size, from seconds to samples. block_size = int(block_size_secs * rate) signal = numpy.concatenate( (numpy.zeros(int(rate * APPEND_ZEROS_SECS)), signal, numpy.zeros(int(rate * APPEND_ZEROS_SECS)))) signal = numpy.array(signal, dtype=float) length = len(signal) # Calculates the amplitude and frequency. amplitude, frequency = hilbert_analysis(signal, rate, block_size) # Finds the dominant frequency. if not dominant_frequency: dominant_frequency = audio_analysis.spectral_analysis(signal, rate)[0][0] # Finds the array which contains absolute difference between dominant # frequency and frequency at each time point. frequency_delta = abs(frequency - dominant_frequency) # Computes average amplitude of each type of block res = find_block_average_value(amplitude, block_size * 2, block_size) left_block_amplitude, right_block_amplitude, block_amplitude = res # Computes average absolute difference of frequency and dominant frequency # of the block of each index _, _, block_frequency_delta = find_block_average_value( frequency_delta, block_size * 2, block_size) # Finds start and end index of sine wave. start_index, end_index = find_start_end_index(dominant_frequency, block_frequency_delta, block_size, frequency_error_threshold) if start_index > end_index: raise SineWaveNotFound('No sine wave found in signal') logging.debug('Found sine wave: start: %s, end: %s', float(start_index) / rate - APPEND_ZEROS_SECS, float(end_index) / rate - APPEND_ZEROS_SECS) sum_of_amplitude = float(sum(amplitude[int(start_index):int(end_index)])) # Finds average amplitude of sine wave. average_amplitude = sum_of_amplitude / (end_index - start_index) # Finds noise before and/or after playback. noise_before_playing, noise_after_playing = noise_detection( start_index, end_index, block_amplitude, average_amplitude, rate, noise_amplitude_threshold) # Finds delay during playback. delays = delay_detection(start_index, end_index, block_amplitude, average_amplitude, dominant_frequency, rate, left_block_amplitude, right_block_amplitude, block_frequency_delta, delay_amplitude_threshold, frequency_error_threshold) # Finds burst during playback. burst_time_points = burst_detection( start_index, end_index, block_amplitude, average_amplitude, dominant_frequency, rate, left_block_amplitude, right_block_amplitude, block_frequency_delta, burst_amplitude_threshold, frequency_error_threshold) # Finds volume changing during playback. volume_changes = changing_volume_detection( start_index, end_index, average_amplitude, rate, left_block_amplitude, right_block_amplitude, volume_changing_amplitude_threshold) # Calculates the average teager value. teager_value = average_teager_value( signal[int(start_index):int(end_index)], average_amplitude) # Finds out the noise level. noise = noise_level(average_amplitude, dominant_frequency, rate, teager_value) return { 'artifacts': { 'noise_before_playback': noise_before_playing, 'noise_after_playback': noise_after_playing, 'delay_during_playback': delays, 'burst_during_playback': burst_time_points }, 'volume_changes': volume_changes, 'equivalent_noise_level': noise }
def testEmptyData(self): """Checks that sepectral analysis rejects empty data.""" with self.assertRaises(audio_analysis.EmptyDataError): results = audio_analysis.spectral_analysis([], 100)