blob: c50e738aad152d493c4d6cbcd0b06320075fc219 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2017 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module provides utilities to detect some artifacts and measure the
quality of audio."""
import logging
import math
import numpy
import acts_contrib.test_utils.audio_analysis_lib.audio_analysis as audio_analysis
# The input signal should be one sine wave with fixed frequency which
# can have silence before and/or after sine wave.
# For example:
# silence sine wave silence
# -----------|VVVVVVVVVVVVV|-----------
# (a) (b) (c)
# This module detects these artifacts:
# 1. Detect noise in (a) and (c).
# 2. Detect delay in (b).
# 3. Detect burst in (b).
# Assume the transitions between (a)(b) and (b)(c) are smooth and
# amplitude increases/decreases linearly.
# This module will detect artifacts in the sine wave.
# This module also estimates the equivalent noise level by teager operator.
# This module also detects volume changes in the sine wave. However, volume
# changes may be affected by delay or burst.
# Some artifacts may cause each other.
# In this module, amplitude and frequency are derived from Hilbert transform.
# Both amplitude and frequency are a function of time.
# To detect each artifact, each point will be compared with
# average amplitude of its block. The block size will be 1.5 ms.
# Using average amplitude can mitigate the error caused by
# Hilbert transform and noise.
# In some case, for more accuracy, the block size may be modified
# to other values.
DEFAULT_BLOCK_SIZE_SECS = 0.0015
# If the difference between average frequency of this block and
# dominant frequency of full signal is less than 0.5 times of
# dominant frequency, this block is considered to be within the
# sine wave. In most cases, if there is no sine wave(only noise),
# average frequency will be much greater than 5 times of
# dominant frequency.
# Also, for delay during playback, the frequency will be about 0
# in perfect situation or much greater than 5 times of dominant
# frequency if it's noised.
DEFAULT_FREQUENCY_ERROR = 0.5
# If the amplitude of some sample is less than 0.6 times of the
# average amplitude of its left/right block, it will be considered
# as a delay during playing.
DEFAULT_DELAY_AMPLITUDE_THRESHOLD = 0.6
# If the average amplitude of the block before or after playing
# is more than 0.5 times to the average amplitude of the wave,
# it will be considered as a noise artifact.
DEFAULT_NOISE_AMPLITUDE_THRESHOLD = 0.5
# In the sine wave, if the amplitude is more than 1.4 times of
# its left side and its right side, it will be considered as
# a burst.
DEFAULT_BURST_AMPLITUDE_THRESHOLD = 1.4
# When detecting burst, if the amplitude is lower than 0.5 times
# average amplitude, we ignore it.
DEFAULT_BURST_TOO_SMALL = 0.5
# For a signal which is the combination of sine wave with fixed frequency f and
# amplitude 1 and standard noise with amplitude k, the average teager value is
# nearly linear to the noise level k.
# Given frequency f, we simulate a sine wave with default noise level and
# calculate its average teager value. Then, we can estimate the equivalent
# noise level of input signal by the average teager value of input signal.
DEFAULT_STANDARD_NOISE = 0.005
# For delay, burst, volume increasing/decreasing, if two delay(
# burst, volume increasing/decreasing) happen within
# DEFAULT_SAME_EVENT_SECS seconds, we consider they are the
# same event.
DEFAULT_SAME_EVENT_SECS = 0.001
# When detecting increasing/decreasing volume of signal, if the amplitude
# is lower than 0.1 times average amplitude, we ignore it.
DEFAULT_VOLUME_CHANGE_TOO_SMALL = 0.1
# If average amplitude of right block is less/more than average
# amplitude of left block times DEFAULT_VOLUME_CHANGE_AMPLITUDE, it will be
# considered as decreasing/increasing on volume.
DEFAULT_VOLUME_CHANGE_AMPLITUDE = 0.1
# If the increasing/decreasing volume event is too close to the start or the end
# of sine wave, we consider its volume change as part of rising/falling phase in
# the start/end.
NEAR_START_OR_END_SECS = 0.01
# After applying Hilbert transform, the resulting amplitude and frequency may be
# extremely large in the start and/or the end part. Thus, we will append zeros
# before and after the whole wave for 0.1 secs.
APPEND_ZEROS_SECS = 0.1
# If the noise event is too close to the start or the end of the data, we
# consider its noise as part of artifacts caused by edge effect of Hilbert
# transform.
# For example, originally, the data duration is 10 seconds.
# We append 0.1 seconds of zeros in the beginning and the end of the data, so
# the data becomes 10.2 seocnds long.
# Then, we apply Hilbert transform to 10.2 seconds of data.
# Near 0.1 seconds and 10.1 seconds, there will be edge effect of Hilbert
# transform. We do not want these be treated as noise.
# If NEAR_DATA_START_OR_END_SECS is set to 0.01, then the noise happened
# at [0, 0.11] and [10.09, 10.1] will be ignored.
NEAR_DATA_START_OR_END_SECS = 0.01
# If the noise event is too close to the start or the end of the sine wave in
# the data, we consider its noise as part of artifacts caused by edge effect of
# Hilbert transform.
# A |-------------|vvvvvvvvvvvvvvvvvvvvvvv|-------------|
# B |ooooooooo| d | | d |ooooooooo|
#
# A is full signal. It contains a sine wave and silence before and after sine
# wave.
# In B, |oooo| shows the parts that we are going to check for noise before/after
# sine wave. | d | is determined by NEAR_SINE_START_OR_END_SECS.
NEAR_SINE_START_OR_END_SECS = 0.01
class SineWaveNotFound(Exception):
"""Error when there's no sine wave found in the signal"""
def hilbert(x):
"""Hilbert transform copied from scipy.
More information can be found here:
http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.hilbert.html
Args:
x: Real signal data to transform.
Returns:
Analytic signal of x, we can further extract amplitude and
frequency from it.
"""
x = numpy.asarray(x)
if numpy.iscomplexobj(x):
raise ValueError("x must be real.")
axis = -1
N = x.shape[axis]
if N <= 0:
raise ValueError("N must be positive.")
Xf = numpy.fft.fft(x, N, axis=axis)
h = numpy.zeros(N)
if N % 2 == 0:
h[0] = h[N // 2] = 1
h[1:N // 2] = 2
else:
h[0] = 1
h[1:(N + 1) // 2] = 2
if len(x.shape) > 1:
ind = [newaxis] * x.ndim
ind[axis] = slice(None)
h = h[ind]
x = numpy.fft.ifft(Xf * h, axis=axis)
return x
def noised_sine_wave(frequency, rate, noise_level):
"""Generates a sine wave of 2 second with specified noise level.
Args:
frequency: Frequency of sine wave.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
noise_level: Required noise level.
Returns:
A sine wave with specified noise level.
"""
wave = []
for index in range(0, rate * 2):
sample = 2.0 * math.pi * frequency * float(index) / float(rate)
sine_wave = math.sin(sample)
noise = noise_level * numpy.random.standard_normal()
wave.append(sine_wave + noise)
return wave
def average_teager_value(wave, amplitude):
"""Computes the normalized average teager value.
After averaging the teager value, we will normalize the value by
dividing square of amplitude.
Args:
wave: Wave to apply teager operator.
amplitude: Average amplitude of given wave.
Returns:
Average teager value.
"""
teager_value, length = 0, len(wave)
for i in range(1, length - 1):
ith_teager_value = abs(wave[i] * wave[i] - wave[i - 1] * wave[i + 1])
ith_teager_value *= max(1, abs(wave[i]))
teager_value += ith_teager_value
teager_value = (float(teager_value) / length) / (amplitude**2)
return teager_value
def noise_level(amplitude, frequency, rate, teager_value_of_input):
"""Computes the noise level compared with standard_noise.
For a signal which is the combination of sine wave with fixed frequency f
and amplitude 1 and standard noise with amplitude k, the average teager
value is nearly linear to the noise level k.
Thus, we can compute the average teager value of a sine wave with
standard_noise. Then, we can estimate the noise level of given input.
Args:
amplitude: Amplitude of input audio.
frequency: Dominant frequency of input audio.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
teager_value_of_input: Average teager value of input audio.
Returns:
A float value denotes the audio is equivalent to have how many times of
noise compared with its amplitude.For example, 0.02 denotes that the
wave has a noise which has standard distribution with standard
deviation being 0.02 times the amplitude of the wave.
"""
standard_noise = DEFAULT_STANDARD_NOISE
# Generates the standard sine wave with stdandard_noise level of noise.
standard_wave = noised_sine_wave(frequency, rate, standard_noise)
# Calculates the average teager value.
teager_value_of_std_wave = average_teager_value(standard_wave, amplitude)
return (teager_value_of_input / teager_value_of_std_wave) * standard_noise
def error(f1, f2):
"""Calculates the relative error between f1 and f2.
Args:
f1: Exact value.
f2: Test value.
Returns:
Relative error between f1 and f2.
"""
return abs(float(f1) - float(f2)) / float(f1)
def hilbert_analysis(signal, rate, block_size):
"""Finds amplitude and frequency of each time of signal by Hilbert transform.
Args:
signal: The wave to analyze.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
block_size: The size of block to transform.
Returns:
A tuple of list: (amplitude, frequency) composed of amplitude and
frequency of each time.
"""
# To apply Hilbert transform, the wave will be transformed
# segment by segment. For each segment, its size will be
# block_size and we will only take middle part of it.
# Thus, each segment looks like: |-----|=====|=====|-----|.
# "=...=" part will be taken while "-...-" part will be ignored.
#
# The whole size of taken part will be half of block_size
# which will be hilbert_block.
# The size of each ignored part will be half of hilbert_block
# which will be half_hilbert_block.
hilbert_block = block_size // 2
half_hilbert_block = hilbert_block // 2
# As mentioned above, for each block, we will only take middle
# part of it. Thus, the whole transformation will be completed as:
# |=====|=====|-----| |-----|=====|=====|-----|
# |-----|=====|=====|-----| |-----|=====|=====|
# |-----|=====|=====|-----|
# Specially, beginning and ending part may not have ignored part.
length = len(signal)
result = []
for left_border in range(0, length, hilbert_block):
right_border = min(length, left_border + hilbert_block)
temp_left_border = max(0, left_border - half_hilbert_block)
temp_right_border = min(length, right_border + half_hilbert_block)
temp = hilbert(signal[temp_left_border:temp_right_border])
for index in range(left_border, right_border):
result.append(temp[index - temp_left_border])
result = numpy.asarray(result)
amplitude = numpy.abs(result)
phase = numpy.unwrap(numpy.angle(result))
frequency = numpy.diff(phase) / (2.0 * numpy.pi) * rate
#frequency.append(frequency[len(frequency)-1])
frequecny = numpy.append(frequency, frequency[len(frequency) - 1])
return (amplitude, frequency)
def find_block_average_value(arr, side_block_size, block_size):
"""For each index, finds average value of its block, left block, right block.
It will find average value for each index in the range.
For each index, the range of its block is
[max(0, index - block_size / 2), min(length - 1, index + block_size / 2)]
For each index, the range of its left block is
[max(0, index - size_block_size), index]
For each index, the range of its right block is
[index, min(length - 1, index + side_block_size)]
Args:
arr: The array to be computed.
side_block_size: the size of the left_block and right_block.
block_size: the size of the block.
Returns:
A tuple of lists: (left_block_average_array,
right_block_average_array,
block_average_array)
"""
length = len(arr)
left_border, right_border = 0, 1
left_block_sum = arr[0]
right_block_sum = arr[0]
left_average_array = numpy.zeros(length)
right_average_array = numpy.zeros(length)
block_average_array = numpy.zeros(length)
for index in range(0, length):
while left_border < index - side_block_size:
left_block_sum -= arr[left_border]
left_border += 1
while right_border < min(length, index + side_block_size):
right_block_sum += arr[right_border]
right_border += 1
left_average_value = float(left_block_sum) / (index - left_border + 1)
right_average_value = float(right_block_sum) / (right_border - index)
left_average_array[index] = left_average_value
right_average_array[index] = right_average_value
if index + 1 < length:
left_block_sum += arr[index + 1]
right_block_sum -= arr[index]
left_border, right_border = 0, 1
block_sum = 0
for index in range(0, length):
while left_border < index - block_size / 2:
block_sum -= arr[left_border]
left_border += 1
while right_border < min(length, index + block_size / 2):
block_sum += arr[right_border]
right_border += 1
average_value = float(block_sum) / (right_border - left_border)
block_average_array[index] = average_value
return (left_average_array, right_average_array, block_average_array)
def find_start_end_index(dominant_frequency, block_frequency_delta, block_size,
frequency_error_threshold):
"""Finds start and end index of sine wave.
For each block with size of block_size, we check that whether its frequency
is close enough to the dominant_frequency. If yes, we will consider this
block to be within the sine wave.
Then, it will return the start and end index of sine wave indicating that
sine wave is between [start_index, end_index)
It's okay if the whole signal only contains sine wave.
Args:
dominant_frequency: Dominant frequency of signal.
block_frequency_delta: Average absolute difference between dominant
frequency and frequency of each block. For
each index, its block is
[max(0, index - block_size / 2),
min(length - 1, index + block_size / 2)]
block_size: Block size in samples.
Returns:
A tuple composed of (start_index, end_index)
"""
length = len(block_frequency_delta)
# Finds the start/end time index of playing based on dominant frequency
start_index, end_index = length - 1, 0
for index in range(0, length):
left_border = max(0, index - block_size / 2)
right_border = min(length - 1, index + block_size / 2)
frequency_error = block_frequency_delta[index] / dominant_frequency
if frequency_error < frequency_error_threshold:
start_index = min(start_index, left_border)
end_index = max(end_index, right_border + 1)
return (start_index, end_index)
def noise_detection(start_index, end_index, block_amplitude, average_amplitude,
rate, noise_amplitude_threshold):
"""Detects noise before/after sine wave.
If average amplitude of some sample's block before start of wave or after
end of wave is more than average_amplitude times noise_amplitude_threshold,
it will be considered as a noise.
Args:
start_index: Start index of sine wave.
end_index: End index of sine wave.
block_amplitude: An array for average amplitude of each block, where
amplitude is computed from Hilbert transform.
average_amplitude: Average amplitude of sine wave.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
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.
Returns:
A tuple of lists indicating the time that noise happens:
(noise_before_playing, noise_after_playing).
"""
length = len(block_amplitude)
amplitude_threshold = average_amplitude * noise_amplitude_threshold
same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
# Detects noise before playing.
noise_time_point = []
last_noise_end_time_point = []
previous_noise_index = None
times = 0
for index in range(0, length):
# Ignore noise too close to the beginning or the end of sine wave.
# Check the docstring of NEAR_SINE_START_OR_END_SECS.
if ((start_index - rate * NEAR_SINE_START_OR_END_SECS) <= index
and (index < end_index + rate * NEAR_SINE_START_OR_END_SECS)):
continue
# Ignore noise too close to the beginning or the end of original data.
# Check the docstring of NEAR_DATA_START_OR_END_SECS.
if (float(index) / rate <=
NEAR_DATA_START_OR_END_SECS + APPEND_ZEROS_SECS):
continue
if (float(length - index) / rate <=
NEAR_DATA_START_OR_END_SECS + APPEND_ZEROS_SECS):
continue
if block_amplitude[index] > amplitude_threshold:
same_event = False
if previous_noise_index:
same_event = (index -
previous_noise_index) < same_event_samples
if not same_event:
index_start_sec = float(index) / rate - APPEND_ZEROS_SECS
index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
noise_time_point.append(index_start_sec)
last_noise_end_time_point.append(index_end_sec)
times += 1
index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
last_noise_end_time_point[times - 1] = index_end_sec
previous_noise_index = index
noise_before_playing, noise_after_playing = [], []
for i in range(times):
duration = last_noise_end_time_point[i] - noise_time_point[i]
if noise_time_point[i] < float(start_index) / rate - APPEND_ZEROS_SECS:
noise_before_playing.append((noise_time_point[i], duration))
else:
noise_after_playing.append((noise_time_point[i], duration))
return (noise_before_playing, noise_after_playing)
def 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):
"""Detects delay during playing.
For each sample, we will check whether the average amplitude of its block
is less than average amplitude of its left block and its right block times
delay_amplitude_threshold. Also, we will check whether the frequency of
its block is far from the dominant frequency.
If at least one constraint fulfilled, it will be considered as a delay.
Args:
start_index: Start index of sine wave.
end_index: End index of sine wave.
block_amplitude: An array for average amplitude of each block, where
amplitude is computed from Hilbert transform.
average_amplitude: Average amplitude of sine wave.
dominant_frequency: Dominant frequency of signal.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
left_block_amplitude: Average amplitude of left block of each index.
Ref to find_block_average_value function.
right_block_amplitude: Average amplitude of right block of each index.
Ref to find_block_average_value function.
block_frequency_delta: Average absolute difference frequency to
dominant frequency of block of each index.
Ref to find_block_average_value function.
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.
frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR
Returns:
List of delay occurrence:
[(time_1, duration_1), (time_2, duration_2), ...],
where time and duration are in seconds.
"""
delay_time_points = []
last_delay_end_time_points = []
previous_delay_index = None
times = 0
same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
start_time = float(start_index) / rate - APPEND_ZEROS_SECS
end_time = float(end_index) / rate - APPEND_ZEROS_SECS
for index in range(int(start_index), int(end_index)):
if block_amplitude[
index] > average_amplitude * delay_amplitude_threshold:
continue
now_time = float(index) / rate - APPEND_ZEROS_SECS
if abs(now_time - start_time) < NEAR_START_OR_END_SECS:
continue
if abs(now_time - end_time) < NEAR_START_OR_END_SECS:
continue
# If amplitude less than its left/right side and small enough,
# it will be considered as a delay.
amp_threshold = average_amplitude * delay_amplitude_threshold
left_threshold = delay_amplitude_threshold * left_block_amplitude[index]
amp_threshold = min(amp_threshold, left_threshold)
right_threshold = delay_amplitude_threshold * right_block_amplitude[
index]
amp_threshold = min(amp_threshold, right_threshold)
frequency_error = block_frequency_delta[index] / dominant_frequency
amplitude_too_small = block_amplitude[index] < amp_threshold
frequency_not_match = frequency_error > frequency_error_threshold
if amplitude_too_small or frequency_not_match:
same_event = False
if previous_delay_index:
same_event = (index -
previous_delay_index) < same_event_samples
if not same_event:
index_start_sec = float(index) / rate - APPEND_ZEROS_SECS
index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
delay_time_points.append(index_start_sec)
last_delay_end_time_points.append(index_end_sec)
times += 1
previous_delay_index = index
index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
last_delay_end_time_points[times - 1] = index_end_sec
delay_list = []
for i in range(len(delay_time_points)):
duration = last_delay_end_time_points[i] - delay_time_points[i]
delay_list.append((delay_time_points[i], duration))
return delay_list
def 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):
"""Detects burst during playing.
For each sample, we will check whether the average amplitude of its block is
more than average amplitude of its left block and its right block times
burst_amplitude_threshold. Also, we will check whether the frequency of
its block is not compatible to the dominant frequency.
If at least one constraint fulfilled, it will be considered as a burst.
Args:
start_index: Start index of sine wave.
end_index: End index of sine wave.
block_amplitude: An array for average amplitude of each block, where
amplitude is computed from Hilbert transform.
average_amplitude: Average amplitude of sine wave.
dominant_frequency: Dominant frequency of signal.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
left_block_amplitude: Average amplitude of left block of each index.
Ref to find_block_average_value function.
right_block_amplitude: Average amplitude of right block of each index.
Ref to find_block_average_value function.
block_frequency_delta: Average absolute difference frequency to
dominant frequency of block of each index.
burst_amplitude_threshold: If the amplitude is higher than average
amplitude of its left block and its right block
times burst_amplitude_threshold. It will be
considered as a burst.
frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR
Returns:
List of burst occurence: [time_1, time_2, ...],
where time is in seconds.
"""
burst_time_points = []
previous_burst_index = None
same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
for index in range(int(start_index), int(end_index)):
# If amplitude higher than its left/right side and large enough,
# it will be considered as a burst.
if block_amplitude[
index] <= average_amplitude * DEFAULT_BURST_TOO_SMALL:
continue
if abs(index - start_index) < rate * NEAR_START_OR_END_SECS:
continue
if abs(index - end_index) < rate * NEAR_START_OR_END_SECS:
continue
amp_threshold = average_amplitude * DEFAULT_BURST_TOO_SMALL
left_threshold = burst_amplitude_threshold * left_block_amplitude[index]
amp_threshold = max(amp_threshold, left_threshold)
right_threshold = burst_amplitude_threshold * right_block_amplitude[
index]
amp_threshold = max(amp_threshold, right_threshold)
frequency_error = block_frequency_delta[index] / dominant_frequency
amplitude_too_large = block_amplitude[index] > amp_threshold
frequency_not_match = frequency_error > frequency_error_threshold
if amplitude_too_large or frequency_not_match:
same_event = False
if previous_burst_index:
same_event = index - previous_burst_index < same_event_samples
if not same_event:
burst_time_points.append(
float(index) / rate - APPEND_ZEROS_SECS)
previous_burst_index = index
return burst_time_points
def changing_volume_detection(start_index, end_index, average_amplitude, rate,
left_block_amplitude, right_block_amplitude,
volume_changing_amplitude_threshold):
"""Finds volume changing during playback.
For each index, we will compare average amplitude of its left block and its
right block. If average amplitude of right block is more than average
amplitude of left block times (1 + DEFAULT_VOLUME_CHANGE_AMPLITUDE), it will
be considered as an increasing volume. If the one of right block is less
than that of left block times (1 - DEFAULT_VOLUME_CHANGE_AMPLITUDE), it will
be considered as a decreasing volume.
Args:
start_index: Start index of sine wave.
end_index: End index of sine wave.
average_amplitude: Average amplitude of sine wave.
rate: Sampling rate in samples per second. Example inputs: 44100,
48000
left_block_amplitude: Average amplitude of left block of each index.
Ref to find_block_average_value function.
right_block_amplitude: Average amplitude of right block of each index.
Ref to find_block_average_value function.
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
DEFAULT_VOLUME_CHANGE_AMPLITUDE
Returns:
List of volume changing composed of 1 for increasing and -1 for
decreasing.
"""
length = len(left_block_amplitude)
# Detects rising and/or falling volume.
previous_rising_index, previous_falling_index = None, None
changing_time = []
changing_events = []
amplitude_threshold = average_amplitude * DEFAULT_VOLUME_CHANGE_TOO_SMALL
same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
for index in range(int(start_index), int(end_index)):
# Skips if amplitude is too small.
if left_block_amplitude[index] < amplitude_threshold:
continue
if right_block_amplitude[index] < amplitude_threshold:
continue
# Skips if changing is from start or end time
if float(abs(start_index - index)) / rate < NEAR_START_OR_END_SECS:
continue
if float(abs(end_index - index)) / rate < NEAR_START_OR_END_SECS:
continue
delta_margin = volume_changing_amplitude_threshold
if left_block_amplitude[index] > 0:
delta_margin *= left_block_amplitude[index]
increasing_threshold = left_block_amplitude[index] + delta_margin
decreasing_threshold = left_block_amplitude[index] - delta_margin
if right_block_amplitude[index] > increasing_threshold:
same_event = False
if previous_rising_index:
same_event = index - previous_rising_index < same_event_samples
if not same_event:
changing_time.append(float(index) / rate - APPEND_ZEROS_SECS)
changing_events.append(+1)
previous_rising_index = index
if right_block_amplitude[index] < decreasing_threshold:
same_event = False
if previous_falling_index:
same_event = index - previous_falling_index < same_event_samples
if not same_event:
changing_time.append(float(index) / rate - APPEND_ZEROS_SECS)
changing_events.append(-1)
previous_falling_index = index
# Combines consecutive increasing/decreasing event.
combined_changing_events, prev = [], 0
for i in range(len(changing_events)):
if changing_events[i] == prev:
continue
combined_changing_events.append((changing_time[i], changing_events[i]))
prev = changing_events[i]
return combined_changing_events
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
}