Я планирую реализовать процессор сигналов типа «DSP-like» в Python. Он должен захватывать небольшие фрагменты аудио через ALSA, обрабатывать их, а затем воспроизводить их через ALSA.Реализация обработки сигналов в реальном времени в Python - как непрерывно записывать звук?
Чтобы все началось, я написал следующий (очень простой) код.
import alsaaudio
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL)
inp.setchannels(1)
inp.setrate(96000)
inp.setformat(alsaaudio.PCM_FORMAT_U32_LE)
inp.setperiodsize(1920)
outp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
outp.setchannels(1)
outp.setrate(96000)
outp.setformat(alsaaudio.PCM_FORMAT_U32_LE)
outp.setperiodsize(1920)
while True:
l, data = inp.read()
# TODO: Perform some processing.
outp.write(data)
Проблема в том, что звук «заикается» и не безразличен. Я попытался поэкспериментировать с режимом PCM, установив его на PCM_ASYNC или PCM_NONBLOCK, но проблема остается. Я думаю, проблема заключается в том, что образцы «между» двумя последующими вызовами «inp.read()» теряются.
Есть ли способ записи звука «непрерывно» в Python (желательно без необходимости слишком «конкретных»/«нестандартных» библиотек)? Я бы хотел, чтобы сигнал всегда захватывался «в фоновом режиме» в некоторый буфер, из которого я мог читать некоторое «мгновенное состояние», в то время как звук также захватывается в буфер даже в течение времени, когда я выполняю свои операции чтения , Как я могу это достичь?
Даже если я использую выделенный процесс/поток для захвата аудио, этот процесс/поток всегда будет по крайней мере иметь (1) читать аудио из источника, (2) затем помещать его в некоторый буфер (из которого обработка/процесс обработки сигнала обрабатывается). Таким образом, эти две операции будут по-прежнему последовательными по времени и, следовательно, образцы будут потеряны. Как мне избежать этого?
Большое спасибо за ваш совет!
EDIT 2: Теперь у меня он работает.
import alsaaudio
from multiprocessing import Process, Queue
import numpy as np
import struct
"""
A class implementing buffered audio I/O.
"""
class Audio:
"""
Initialize the audio buffer.
"""
def __init__(self):
#self.__rate = 96000
self.__rate = 8000
self.__stride = 4
self.__pre_post = 4
self.__read_queue = Queue()
self.__write_queue = Queue()
"""
Reads audio from an ALSA audio device into the read queue.
Supposed to run in its own process.
"""
def __read(self):
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NORMAL)
inp.setchannels(1)
inp.setrate(self.__rate)
inp.setformat(alsaaudio.PCM_FORMAT_U32_BE)
inp.setperiodsize(self.__rate/50)
while True:
_, data = inp.read()
self.__read_queue.put(data)
"""
Writes audio to an ALSA audio device from the write queue.
Supposed to run in its own process.
"""
def __write(self):
outp = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
outp.setchannels(1)
outp.setrate(self.__rate)
outp.setformat(alsaaudio.PCM_FORMAT_U32_BE)
outp.setperiodsize(self.__rate/50)
while True:
data = self.__write_queue.get()
outp.write(data)
"""
Pre-post data into the output buffer to avoid buffer underrun.
"""
def __pre_post_data(self):
zeros = np.zeros(self.__rate/50, dtype = np.uint32)
for i in range(0, self.__pre_post):
self.__write_queue.put(zeros)
"""
Runs the read and write processes.
"""
def run(self):
self.__pre_post_data()
read_process = Process(target = self.__read)
write_process = Process(target = self.__write)
read_process.start()
write_process.start()
"""
Reads audio samples from the queue captured from the reading thread.
"""
def read(self):
return self.__read_queue.get()
"""
Writes audio samples to the queue to be played by the writing thread.
"""
def write(self, data):
self.__write_queue.put(data)
"""
Pseudonymize the audio samples from a binary string into an array of integers.
"""
def pseudonymize(self, s):
return struct.unpack(">" + ("I" * (len(s)/self.__stride)), s)
"""
Depseudonymize the audio samples from an array of integers into a binary string.
"""
def depseudonymize(self, a):
s = ""
for elem in a:
s += struct.pack(">I", elem)
return s
"""
Normalize the audio samples from an array of integers into an array of floats with unity level.
"""
def normalize(self, data, max_val):
data = np.array(data)
bias = int(0.5 * max_val)
fac = 1.0/(0.5 * max_val)
data = fac * (data - bias)
return data
"""
Denormalize the data from an array of floats with unity level into an array of integers.
"""
def denormalize(self, data, max_val):
bias = int(0.5 * max_val)
fac = 0.5 * max_val
data = np.array(data)
data = (fac * data).astype(np.int64) + bias
return data
debug = True
audio = Audio()
audio.run()
while True:
data = audio.read()
pdata = audio.pseudonymize(data)
if debug:
print "[PRE-PSEUDONYMIZED] Min: " + str(np.min(pdata)) + ", Max: " + str(np.max(pdata))
ndata = audio.normalize(pdata, 0xffffffff)
if debug:
print "[PRE-NORMALIZED] Min: " + str(np.min(ndata)) + ", Max: " + str(np.max(ndata))
print "[PRE-NORMALIZED] Level: " + str(int(10.0 * np.log10(np.max(np.absolute(ndata)))))
#ndata += 0.01 # When I comment in this line, it wreaks complete havoc!
if debug:
print "[POST-NORMALIZED] Level: " + str(int(10.0 * np.log10(np.max(np.absolute(ndata)))))
print "[POST-NORMALIZED] Min: " + str(np.min(ndata)) + ", Max: " + str(np.max(ndata))
pdata = audio.denormalize(ndata, 0xffffffff)
if debug:
print "[POST-PSEUDONYMIZED] Min: " + str(np.min(pdata)) + ", Max: " + str(np.max(pdata))
print ""
data = audio.depseudonymize(pdata)
audio.write(data)
Однако, когда я даже выполнять малейшие изменения в аудиоданные (е. Г. Комментарий этой строки в), я получаю много шума и экстремальных искажений на выходе. Похоже, я не правильно обрабатываю данные PCM. Странно то, что выход «измерителя уровня» и т. Д., Кажется, имеет смысл. Однако выход полностью искажен (но непрерывный), когда я слегка его компенсировал.
EDIT 3: Я только выяснил, что мои алгоритмы (не включены сюда) работают, когда я применяю их к волновым файлам. Таким образом, на самом деле проблема действительно сводится к API ALSA.
EDIT 4: Я, наконец, нашел проблемы. Они были следующими.
1-й - ALSA тихо «упал обратно» на PCM_FORMAT_U8_LE после запроса PCM_FORMAT_U32_LE, поэтому я неправильно интерпретировал данные, предположив, что каждый образец имеет ширину 4 байта. Он работает, когда я запрашиваю PCM_FORMAT_S32_LE.
второй - выход ALSA кажется ожидать размер периода в байтах , даже если они явно заявляют, что, как ожидается, в кадров в спецификации. Таким образом, вы должны установить размер периода в четыре раза выше для вывода, если вы используете 32-битную глубину образца.
3rd - Даже в Python (где есть «блокировка глобального интерпретатора») процессы медленны по сравнению с Threads. Вы можете значительно уменьшить время ожидания, переключившись на потоки, так как потоки ввода-вывода в основном не делают ничего интенсивного в вычислительном режиме.
Использование потока для чтения и публикации в очереди должно работать. 'PCM' имеет буфер, управляемый' setperiodsize' (по умолчанию он равен 32 кадрам), что дает вам время для публикации данных. – tdelaney
Я думаю, что проблема в том, что «read()» только читается с аудиоустройства во время его запуска. Если он вернется, операция чтения будет завершена (иначе он не сможет вернуть сколько-нибудь значимые данные).Даже если у меня есть второй поток, выполняющий «read()», а затем добавляя возвращенные данные в буфер, он не будет «читать()» при добавлении, и поэтому в захвате будет пробел. –
Ничего себе. Тогда этот интерфейс серьезно нарушен. Интерфейсы, которые имеют традиционные блокирующие/неблокирующие режимы, нуждаются в промежуточных буферах по той причине, которую вы описываете. Интерфейс реального времени требует буферов предварительного заполнения перед созданием данных. Но 'isaudio', похоже, не работает так. Я не могу представить, как этот модуль работал бы без буферизации. Итак ... вы уверены, что так оно и работает, или вы спекулируете? Я думаю, что он буферизирует X-фреймы за раз, и если вы не прочитаете его к тому времени, когда появится следующий X, тогда его потеряют. Просто догадаться с моей стороны! – tdelaney