Как получить точное время с помощью микрофона в Python - PullRequest
0 голосов
/ 01 ноября 2018

Я пытаюсь сделать обнаружение ударов с помощью микрофона ПК, а затем с помощью временной метки вычислить расстояние между несколькими последовательными ударами. Я выбрал python, потому что там много материала и он быстро развивается. При поиске в интернете я пришел к этому простому коду (без расширенного обнаружения пиков или чего-либо еще, это произойдет позже, если потребуется):

import pyaudio
import struct
import math
import time


SHORT_NORMALIZE = (1.0/32768.0)


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)


CHUNK = 32
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

elapsed_time = 0
prev_detect_time = 0

while True:
    data = stream.read(CHUNK)
    amplitude = get_rms(data)
    if amplitude > 0.05:  # value set by observing graphed data captured from mic
        elapsed_time = time.perf_counter() - prev_detect_time
        if elapsed_time > 0.1:  # guard against multiple spikes at beat point
            print(elapsed_time)
            prev_detect_time = time.perf_counter()

def close_stream():
  stream.stop_stream()
  stream.close()
  p.terminate()

Код работает довольно хорошо в тишине, и я был очень доволен первыми двумя моментами, когда я его запускал, но потом я попробовал, насколько точным он был, и я был немного менее доволен. Чтобы проверить это, я использовал два метода: телефон с метрономом, установленным на 60 ударов в минуту (издает тик-ток в микрофон), и Arduino, подключенный к звуковому сигналу, который запускается с частотой 1 Гц с помощью точного RTC Chronodot. Звуковой сигнал подает сигнал в микрофон, вызывая обнаружение. При обоих методах результаты выглядят одинаково (числа представляют расстояние между двумя обнаружениями ударов в секундах):

0.9956681643835616
1.0056331689497717
0.9956100091324198
1.0058207853881278
0.9953449497716891
1.0052103013698623
1.0049350136986295
0.9859074337899543
1.004996383561644
0.9954095342465745
1.0061518904109583
0.9953025753424658
1.0051235068493156
1.0057199634703196
0.984839305936072
1.00610396347032
0.9951862648401821
1.0053146301369864
0.9960100821917806
1.0053391780821919
0.9947373881278523
1.0058608219178105
1.0056580091324214
0.9852110319634697
1.0054473059360731
0.9950465753424638
1.0058237077625556
0.995704694063928
1.0054566575342463
0.9851026118721435
1.0059882374429243
1.0052523835616398
0.9956161461187207
1.0050863926940607
0.9955758173515932
1.0058052968036577
0.9953960913242028
1.0048014611872205
1.006336876712325
0.9847434520547935
1.0059712876712297

Теперь я вполне уверен, что по крайней мере Arduino с точностью до 1 мс (что является целевой точностью). Результаты, как правило, отклоняются на + - 5 мсек, но время от времени даже на 15 мсек, что недопустимо. Есть ли способ добиться большей точности или это ограничение python / soundcard / что-то еще? Спасибо!

EDIT: После включения в код предложений tom10 и Барни код выглядит следующим образом:

import pyaudio
import struct
import math
import psutil
import os


def set_high_priority():
    p = psutil.Process(os.getpid())
    p.nice(psutil.HIGH_PRIORITY_CLASS)


SHORT_NORMALIZE = (1.0/32768.0)


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)


CHUNK = 4096
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
RUNTIME_SECONDS = 10

set_high_priority()

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

elapsed_time = 0
prev_detect_time = 0
TIME_PER_CHUNK = 1000 / RATE * CHUNK
SAMPLE_GROUP_SIZE = 32  # 1 sample = 2 bytes, group is closest to 1 msec elapsing
TIME_PER_GROUP = 1000 / RATE * SAMPLE_GROUP_SIZE

for i in range(0, int(RATE / CHUNK * RUNTIME_SECONDS)):
    data = stream.read(CHUNK)
    time_in_chunk = 0
    group_index = 0
    for j in range(0, len(data), (SAMPLE_GROUP_SIZE * 2)):
        group = data[j:(j + (SAMPLE_GROUP_SIZE * 2))]
        amplitude = get_rms(group)
        amplitudes.append(amplitude)
        if amplitude > 0.02:
            current_time = (elapsed_time + time_in_chunk)
            time_since_last_beat = current_time - prev_detect_time
            if time_since_last_beat > 500:
                print(time_since_last_beat)
                prev_detect_time = current_time
        time_in_chunk = (group_index+1) * TIME_PER_GROUP
        group_index += 1
    elapsed_time = (i+1) * TIME_PER_CHUNK

stream.stop_stream()
stream.close()
p.terminate()

С помощью этого кода я достиг следующих результатов (единицы измерения - это миллисекунды вместо секунд):

999.909297052154
999.9092970521542
999.9092970521542
999.9092970521542
999.9092970521542
1000.6349206349205
999.9092970521551
999.9092970521524
999.9092970521542
999.909297052156
999.9092970521542
999.9092970521542
999.9092970521524
999.9092970521542

Который, если я не ошибся, выглядит намного лучше, чем раньше, и достиг точности до миллисекунды. Я благодарю Tom10 и Барни за помощь.

1 Ответ

0 голосов
/ 01 ноября 2018

Причина, по которой вы не получаете правильное время для ударов, заключается в том, что вы пропускаете фрагменты аудиоданных. То есть фрагменты читаются звуковой картой, но вы не собираете данные, пока они не будут перезаписаны следующим фрагментом .

Во-первых, однако, для этой проблемы вам необходимо различать идеи точности синхронизации и реакции в реальном времени .

Точность синхронизации звуковой карты должна быть очень хорошей, намного лучше, чем мс, и вы должны быть в состоянии уловить всю эту точность в данных, которые вы читаете со звуковой карты. Реакция операционной системы вашего компьютера в реальном времени должна быть очень плохой, намного хуже, чем мс. То есть вы должны легко иметь возможность идентифицировать звуковые события (например, удары) с точностью до мс, но не идентифицировать их во время, когда они происходят (вместо этого, через 30-200 мс в зависимости от вашей системы). Эта схема обычно работает для компьютеров, потому что общее восприятие человеком хронометража событий намного больше, чем мс (за исключением редких специализированных перцептуальных систем, таких как сравнение слуховых событий между двумя ушами и т. Д.).

Конкретная проблема с вашим кодом заключается в том, что CHUNKS слишком мал для ОС, чтобы запрашивать звуковую карту для каждого семпла. Он установлен на 32, то есть на 44100 Гц, ОС должна получать доступ к звуковой карте каждые 0,7 мс, что слишком мало для компьютера, которому поручено выполнять множество других задач. Если ваша ОС не получает чанк до того, как появится следующий, исходный чанк перезаписывается и теряется.

Чтобы это работало так, чтобы оно соответствовало приведенным выше ограничениям, сделайте CHUNKS намного больше, чем 32, и больше похоже на 1024 (как в примерах PyAudio). В зависимости от вашего компьютера и от того, что он делает, даже если он не будет достаточно длинным.

Если такой подход не будет работать для вас, вам, вероятно, понадобится выделенная система реального времени, такая как Arduino. (Однако, как правило, в этом нет необходимости, поэтому подумайте дважды, прежде чем решите, что вам нужно использовать Arduino. Обычно, когда я видел, что люди нуждаются в истинном реальном времени, это происходит при попытке сделать что-то очень количественное интерактивное с человеком например, мигать, попросить человека нажать на кнопку, включить другую лампочку, попросить человека нажать другую кнопку и т. д., чтобы измерить время отклика.)

...