Как реализовать обратный вызов Pyaudio с внутренне сгенерированным вводом - PullRequest
0 голосов
/ 03 октября 2019

У меня в качестве входных звуковых сигналов на разных частотах в течение разных промежутков времени. Я пытаюсь нарезать эти входные данные на фрагменты (одинакового размера независимо от входной частоты) и четко воспроизвести фрагменты на выходе. Когда я использую метод блокировки, например stream.write (), я получаю чистый вывод, но не хватает большой вычислительной мощности для выполнения других задач. Итак, я преобразовал код в неблокирующий подход с помощью функции callback (), как показано ниже.

Я попытался создать шаблон своего кода после этого поста: https://stackoverflow.com/a/22354593/12076263, но этоне работает вообще.

# Generate a 250 Hz tone in chunks amounting to 100 chunks per second,
# using a non-blocking method.
#
# How does one fit a 250 Hz wave into 100 Hz packets?
# Slice up a sine wave into segments of two-and-a-half waves apiece:
#
#               |              |              |              |
#  _     _     _|    _     _   | _     _     _|    _     _   | 
# / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   
#    \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/
#               |              |              |              |
#  <----X1----> | <----X2----> | <----X1----> | <----X2----> |
#              
# Play back each segment in alternating fashion.
# This simulates continuous audio where zero crossings do not necessarily
# occur at the edge of a chunk.

import numpy as np
import pyaudio
import time

# based on examples from https://people.csail.mit.edu/hubert/pyaudio/docs/
def callback(in_data, frame_count, time_info, status):
    waveData = Y.tobytes()
    return (waveData, pyaudio.paContinue)

Y = np.zeros((441), dtype = np.int16)   # initialize audio array Y
p = pyaudio.PyAudio()
stream = p.open(format = 8,             # 8 is code for int16 format
           channels = 1,
           rate = 44100,
           frames_per_buffer = 441,
           stream_callback = callback, # !! COMMENT OUT to enable blocking
           output = True)

stream.start_stream()

X1 = np.linspace(0, 5 * np.pi, num = 441, endpoint = False)
Y1 = (5000 * np.sin(X1)).astype(np.int16) # Generate two-and-a-half waves

X2 = np.linspace(5 * np.pi, 10 * np.pi, num = 441, endpoint = False)
Y2 = (5000 * np.sin(X2)).astype(np.int16) # The "other" two-and-a-half waves

# Play the two wave segments one after the other
# Result should be a clean 250 Hz tone
while True:
    Y = Y1                              # First set of waves
    time.sleep(0.0015)                  # This 1.5 millisecond delay
                                        #   simulates other stuff happening
                                        #   within the While loop.
    #stream.write(Y.tobytes())          # !! UNCOMMENT to use blocking method
    Y = Y2                              # Second set of waves
    time.sleep(0.0015)                  # More miscellaneous delays
    #stream.write(Y.tobytes())          # !! UNCOMMENT to use blocking method

Ожидается, что синусоида без искажений (как цифровой звук идет), и получил сильно искаженный звук, как рев гигантского кино монстра. Как правило, сообщений об ошибках нет, но если значения time.sleep увеличены, может произойти опустошение. Также проблема исчезнет, ​​если операторы time.sleep будут удалены полностью, но полное приложение, в котором это будет использоваться, будет иметь нагрузку обработки не менее 1,5 миллисекунды для каждого блока.

Ответы [ 2 ]

0 голосов
/ 08 октября 2019

Другая часть моего вопроса - как ускорить выполнение, потому что большая часть обработки происходит внутри цикла while. Однако что-то замедляет критичный ко времени код примерно в 5 раз. Раздел кода while был заменен приведенным ниже кодом. t2 минус t1 идет в 5 раз быстрее, если оператор qu.put закомментирован, что странно, потому что qu.put находится вне интервала измерения кода, критичного ко времени.

import time
from multiprocessing import Process

def whileLoop():
    while True:
        qu.put(Y1)
        t1 = time.time()
        # time-critical processing code goes here
        # must run in < 10ms to prevent audio underrun
        t2 = time.time()

        qu.put(Y2)
        t3 = time.time()
        # more time-critical processing code goes here
        # must run in < 10ms to prevent audio underrun
        t4 = time.time()

if __name__ == '__main__':
    pr = Process(target = whileLoop)
    pr.start()
    pr.join()
0 голосов
/ 05 октября 2019

Чтобы частично ответить на мой собственный вопрос, ключом является использование очереди для передачи данных между основным циклом и функцией обратного вызова. Теперь звук не искажается. Однако блокировка, по-видимому, оказывает существенное влияние на время выполнения каждого оператора, выполняемого в цикле while. Конечно, общий цикл замедляется, но он выполняется с правильной частотой обновления. Я бы хотел видеть операторы в цикле, работающем с нормальной скоростью, но каждый из них работает примерно на 1/5 от нормальной скорости.

# Generate a 250 Hz tone in chunks amounting to 100 chunks per second,
# using a non-blocking method.
#
# How does one fit a 250 Hz wave into 100 Hz packets?
# Slice up a sine wave into segments of two-and-a-half waves apiece:
#
#               |              |              |              |
#  _     _     _|    _     _   | _     _     _|    _     _   | 
# / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   
#    \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/   \_/
#               |              |              |              |
#  <----X1----> | <----X2----> | <----X1----> | <----X2----> |
#              
# Play back each segment in alternating fashion.
# This simulates continuous audio where zero crossings do not necessarily
# occur at the edge of a chunk.

import numpy as np
import pyaudio
import time
import queue

# based on examples from https://people.csail.mit.edu/hubert/pyaudio/docs/
def callback(in_data, frame_count, time_info, status):
    Y = qu.get()
    waveData = Y.tobytes()
    return (waveData, pyaudio.paContinue)

qu = queue.Queue(maxsize = 1)           # 1 complete buffer (of length 441)
Y = np.zeros((441), dtype = np.int16)   # initialize audio array Y
p = pyaudio.PyAudio()
stream = p.open(format = 8,             # 8 is code for int16 format
           channels = 1,
           rate = 44100,
           frames_per_buffer = 441,
           stream_callback = callback,
           output = True)

stream.start_stream()

X1 = np.linspace(0, 5 * np.pi, num = 441, endpoint = False)
Y1 = (5000 * np.sin(X1)).astype(np.int16) # Generate two-and-a-half waves

X2 = np.linspace(5 * np.pi, 10 * np.pi, num = 441, endpoint = False)
Y2 = (5000 * np.sin(X2)).astype(np.int16) # The "other" two-and-a-half waves

# Play the two wave segments one after the other
# Result should be a clean 250 Hz tone
while True:
    qu.put(Y1)                          # First set of waves
    time.sleep(0.0015)                  # This 1.5 millisecond delay
                                        #   simulates other stuff happening
                                        #   within the While loop.
    qu.put(Y2)                              # Second set of waves
    time.sleep(0.0015)                  # More miscellaneous delays
...