Как я буду использовать pyaudio для обнаружения внезапного шума при нажатии на живой микрофон?
Обнаружение касания с помощью pyaudio из живого микрофона
Ответ 1
Один способ, которым я сделал это:
- читать блок сэмплов за раз, скажем, за 0,05 секунды
- вычислить среднеквадратичную амплитуду блока (квадратный корень из среднего квадратов отдельных выборок)
- если среднеквадратичная амплитуда блока больше порогового значения, это "шумный блок", иначе это "тихий блок"
- внезапный стук будет тихим блоком, сопровождаемым небольшим количеством шумных блоков, сопровождаемых тихим блоком
- если вы никогда не получите тихий блок, ваш порог слишком низок
- если вы никогда не получите шумный блок, ваш порог слишком высок
Мое приложение записывало "интересные" звуки без присмотра, поэтому оно записывалось до тех пор, пока были шумные блоки. Он умножил бы порог на 1,1, если бы был 15-секундный шумовой период ("прикрывая уши"), и умножил порог на 0,9, если был 15-минутный тихий период ("слушая сильнее"). Ваше приложение будет иметь разные потребности.
Кроме того, только что заметил некоторые комментарии в моем коде относительно наблюдаемых значений RMS. На встроенном микрофоне в Macbook Pro с нормализованным диапазоном аудиоданных + / - 1,0, с максимальной входной громкостью, некоторые точки данных:
- 0,003-0,006 (-50dB - -44dB) ужасно громкий вентилятор центрального отопления в моем доме
- 0,010-0,40 (-40dB - -8dB) печатать на том же ноутбуке
- 0,10 (-20dB) мягко щелкая пальцами на расстоянии 1 '
- 0,60 (-4.4 дБ) громко щелкая пальцами в 1 '
Обновление: здесь образец, чтобы вы начали.
#!/usr/bin/python
# open a microphone in pyAudio and listen for taps
import pyaudio
import struct
import math
INITIAL_TAP_THRESHOLD = 0.010
FORMAT = pyaudio.paInt16
SHORT_NORMALIZE = (1.0/32768.0)
CHANNELS = 2
RATE = 44100
INPUT_BLOCK_TIME = 0.05
INPUT_FRAMES_PER_BLOCK = int(RATE*INPUT_BLOCK_TIME)
# if we get this many noisy blocks in a row, increase the threshold
OVERSENSITIVE = 15.0/INPUT_BLOCK_TIME
# if we get this many quiet blocks in a row, decrease the threshold
UNDERSENSITIVE = 120.0/INPUT_BLOCK_TIME
# if the noise was longer than this many blocks, it not a 'tap'
MAX_TAP_BLOCKS = 0.15/INPUT_BLOCK_TIME
def get_rms( block ):
# RMS amplitude is defined as the square root of the
# mean over time of the square of the amplitude.
# so we need to convert this string of bytes into
# a string of 16-bit samples...
# we will get one short out for each
# two chars in the string.
count = len(block)/2
format = "%dh"%(count)
shorts = struct.unpack( format, block )
# iterate over the block.
sum_squares = 0.0
for sample in shorts:
# sample is a signed short in +/- 32768.
# normalize it to 1.0
n = sample * SHORT_NORMALIZE
sum_squares += n*n
return math.sqrt( sum_squares / count )
class TapTester(object):
def __init__(self):
self.pa = pyaudio.PyAudio()
self.stream = self.open_mic_stream()
self.tap_threshold = INITIAL_TAP_THRESHOLD
self.noisycount = MAX_TAP_BLOCKS+1
self.quietcount = 0
self.errorcount = 0
def stop(self):
self.stream.close()
def find_input_device(self):
device_index = None
for i in range( self.pa.get_device_count() ):
devinfo = self.pa.get_device_info_by_index(i)
print( "Device %d: %s"%(i,devinfo["name"]) )
for keyword in ["mic","input"]:
if keyword in devinfo["name"].lower():
print( "Found an input: device %d - %s"%(i,devinfo["name"]) )
device_index = i
return device_index
if device_index == None:
print( "No preferred input found; using default input device." )
return device_index
def open_mic_stream( self ):
device_index = self.find_input_device()
stream = self.pa.open( format = FORMAT,
channels = CHANNELS,
rate = RATE,
input = True,
input_device_index = device_index,
frames_per_buffer = INPUT_FRAMES_PER_BLOCK)
return stream
def tapDetected(self):
print("Tap!")
def listen(self):
try:
block = self.stream.read(INPUT_FRAMES_PER_BLOCK)
except IOError as e:
# dammit.
self.errorcount += 1
print( "(%d) Error recording: %s"%(self.errorcount,e) )
self.noisycount = 1
return
amplitude = get_rms( block )
if amplitude > self.tap_threshold:
# noisy block
self.quietcount = 0
self.noisycount += 1
if self.noisycount > OVERSENSITIVE:
# turn down the sensitivity
self.tap_threshold *= 1.1
else:
# quiet block.
if 1 <= self.noisycount <= MAX_TAP_BLOCKS:
self.tapDetected()
self.noisycount = 0
self.quietcount += 1
if self.quietcount > UNDERSENSITIVE:
# turn up the sensitivity
self.tap_threshold *= 0.9
if __name__ == "__main__":
tt = TapTester()
for i in range(1000):
tt.listen()
Ответ 2
упрощенная версия вышеуказанного кода...
import pyaudio
import struct
import math
INITIAL_TAP_THRESHOLD = 0.010
FORMAT = pyaudio.paInt16
SHORT_NORMALIZE = (1.0/32768.0)
CHANNELS = 2
RATE = 44100
INPUT_BLOCK_TIME = 0.05
INPUT_FRAMES_PER_BLOCK = int(RATE*INPUT_BLOCK_TIME)
OVERSENSITIVE = 15.0/INPUT_BLOCK_TIME
UNDERSENSITIVE = 120.0/INPUT_BLOCK_TIME # if we get this many quiet blocks in a row, decrease the threshold
MAX_TAP_BLOCKS = 0.15/INPUT_BLOCK_TIME # if the noise was longer than this many blocks, it not a 'tap'
def get_rms(block):
# RMS amplitude is defined as the square root of the
# mean over time of the square of the amplitude.
# so we need to convert this string of bytes into
# a string of 16-bit samples...
# we will get one short out for each
# two chars in the string.
count = len(block)/2
format = "%dh"%(count)
shorts = struct.unpack( format, block )
# iterate over the block.
sum_squares = 0.0
for sample in shorts:
# sample is a signed short in +/- 32768.
# normalize it to 1.0
n = sample * SHORT_NORMALIZE
sum_squares += n*n
return math.sqrt( sum_squares / count )
pa = pyaudio.PyAudio() #]
#|
stream = pa.open(format = FORMAT, #|
channels = CHANNELS, #|---- You always use this in pyaudio...
rate = RATE, #|
input = True, #|
frames_per_buffer = INPUT_FRAMES_PER_BLOCK) #]
tap_threshold = INITIAL_TAP_THRESHOLD #]
noisycount = MAX_TAP_BLOCKS+1 #|---- Variables for noise detector...
quietcount = 0 #|
errorcount = 0 #]
for i in range(1000):
try: #]
block = stream.read(INPUT_FRAMES_PER_BLOCK) #|
except IOError, e: #|---- just in case there is an error!
errorcount += 1 #|
print( "(%d) Error recording: %s"%(errorcount,e) ) #|
noisycount = 1 #]
amplitude = get_rms(block)
if amplitude > tap_threshold: # if its to loud...
quietcount = 0
noisycount += 1
if noisycount > OVERSENSITIVE:
tap_threshold *= 1.1 # turn down the sensitivity
else: # if its to quiet...
if 1 <= noisycount <= MAX_TAP_BLOCKS:
print 'tap!'
noisycount = 0
quietcount += 1
if quietcount > UNDERSENSITIVE:
tap_threshold *= 0.9 # turn up the sensitivity