Я хочу выполнить низкочастотную фильтрацию аудиоданных в Python и воспроизвести их одновременно.Я ищу совет по улучшению моего кода, и я поделюсь своим текущим, но очень неполным решением проблемы.Хотя я и прошу совета по его улучшению, я не буду полностью переписывать весь код.Я хочу использовать это как возможность изучить низкоуровневые основы обработки сигналов, и в то же время лучше узнать Python 3.В целом я доволен синтаксисом Python, но, скорее всего, я упустил немало способов повысить эффективность работы.
Ниже я приведу наиболее важные фрагменты кода программы.Мой код по крайней мере слабо основан на ответах, которые я читаю здесь, а также на некоторых моих собственных идеях (таких как кольцевой буфер).Хотя моя основная цель - получить помощь, есть еще одна причина, по которой я пишу это.В качестве благодарности за информацию, которую это сообщество предоставило мне, я верну то, что знаю сейчас, в надежде, что все это в одном месте поможет кому-то другому, желающему достичь той же или аналогичной цели.Сценарий подробно описан ниже.
Сначала загружаются необходимые встроенные модули.
import sys, wave, math, subprocess
Несколько глобальных переменных объявлены и инициализированы.Важно, чтобы они были глобальными, потому что данные должны сохраняться между вызовами функции фильтра.Функция зажима ОЧЕНЬ важна, потому что без нее преобразование обратно в s16le из подписанного int завершится с ошибкой переполнения.Мне также нужно загрузить aplay как подпроцесс, чтобы отправить необработанные образцы s16le после обработки.Я выбрал aplay в качестве метода вывода звука, потому что он удобно буферизует сами данные и очень прост в использовании, потому что вы просто передаете данные на него.
aplay=subprocess.Popen(('aplay','-f','cd'),stdin=subprocess.PIPE)
source=wave.open(sys.argv[1],"rb")
frameRate=source.getframerate()
frequencyRatio=(int(sys.argv[2])/frameRate)
global windowSize
windowSize=int(math.sqrt(0.196196+frequencyRatio**2)/frequencyRatio)
global bufferIndex0; bufferIndex0=0
global bufferL0; bufferL0=[]
global bufferR0; bufferR0=[]
for _ in range(windowSize+1):
bufferL0.append(0); bufferR0.append(0
clamp = lambda n, minn, maxn: max(min(maxn, n), minn)
Далее, волновой файл загружается в память как«данные» и «данные» разрываются и разделяются на отдельные кадры, хранящиеся в виде массива frame [].Объект / переменная «data» теперь бесполезен и, вероятно, занимает 1 ГБ или более ОЗУ, поэтому он получает значение «del data».Затем данные кадра зацикливаются и преобразуются в 16-разрядные целые числа со знаком.Он передается функции скользящего среднего.Как вы можете видеть, у меня есть три копии, чтобы достичь желаемой частоты среза.Я также избегал максимально возможного хранения данных в переменных, и это значительно уменьшило потребление памяти и значительно ускорило мой код.Это перешло от заикания к плавному воспроизведению, но при этом потребляя почти всю доступную вычислительную мощность на одном ядре.
if __name__=="__main__":
length=source.getnframes()
data=source.readframes(length)
frame=[data[_:_+4] for _ in range(0,len(data),4)]
del data
channel=[]
for _ in range(length):
channel=rollingAverage_stage2(rollingAverage_stage1(rollingAverage_stage0([int.from_bytes(frame[_][:2], byteorder='little', signed=True),int.from_bytes(frame[_][2:], byteorder='little', signed=True)])))
aplay.stdin.write(bytearray(channel[0].to_bytes(2, byteorder='little', signed=True)+channel[1].to_bytes(2, byteorder='little', signed=True)))
Функция "rollAverage" подробно описана здесь.Я, очевидно, показываю только одну копию, поскольку они все одинаковые, за исключением имен переменных.Дополнительные глобальные переменные ringIndex1, ringIndex2, bufferL1, bufferL2, bufferR1 и bufferR2 каждый объявляется в начале скрипта и в соответствующих функциях.Возможно, было бы лучше создать динамические переменные и некоторые экземпляры класса «RollingAverage» на основе входного параметра с числом проходов, а не три фиксированных копии.
def rollingAverage_stage0(channel):
global bufferL0; global bufferR0; global ringIndex0
bufferL0[ringIndex0],bufferR0[ringIndex0]=channel[0],channel[1]
channel=[clamp(int(sum(bufferL0)/windowSize),-32768,32767),clamp(int(sum(bufferR0)/windowSize),-32768,32767)]
if ringIndex0==windowSize:
ringIndex0=0
else:
ringIndex0+=1
return channel
Это суммирует код.Он работает на удивление хорошо для низких частот среза (например, 500), но немного обрезает звук при использовании с частотой среза более 5000 Гц.Не проблема для моего использования, так как я намереваюсь обрезать частоты до полосы голоса, которая составляет 3000 Гц и ниже, и я могу перейти на 2000 Гц и ниже.Мой последний скрипт будет читать необработанные кадры из rtl_sdr, используя канал подпроцесса.Программа будет использоваться следующим образом:
lowfilter.py <parameters>
rtl_sdr - это команда, которая контролирует и собирает данные с радиоуправляемых программ на базе USB Realtek RTL2832.Я мог бы также добавить подпроцесс sox в сценарий для выполнения шумоподавления.
Как я упоминал ранее, я, скорее всего, не буду полностью переписывать программу, но тяжелая модификация, которая близка к полной перезаписи, определеннов плане.Я потратил на это несколько дней и многому научился.
Звук, который я собираюсь обработать, будет представлять собой погодное радио NOAA и аналогичные FM-передачи с низкой пропускной способностью.Я также буду реализовывать какую-то автоматическую регулировку усиления и сжатие динамического диапазона, если это возможно, но сейчас фильтрация достаточно хороша, и я собираюсь сохранить свою цель.Это то, чего я хочу достичь, и это причина узнать больше о Python.
Может быть, некоторые вещи могут быть адаптированы к сопрограммам и генераторам для меньшего использования ресурсов.Я их плохо понимаю, если вообще, но готов учиться.Кажется, они могут быть весьма применимы к этому приложению.Любые изменения, которые я делаю, предпочтительнее без дополнительных библиотек, кроме, возможно, numpy и чего-то для многопоточности.Я тоже не понимаю numpy, так что это было бы совершенно новым для меня.Ссылки на лучшие ресурсы для начинающих могут помочь, так как даже некоторые из примеров, которые я нашел для фильтрации нижних частот и тому подобного, были ПУТЬ за пределы того, что я мог понять.Вот почему я пишу свою собственную фильтрацию низких частот.Numpy, вероятно, намного быстрее, чем встроенная математическая библиотека, поэтому это мой второй фокус.
Мой текущий и основной фокус - оптимизация и балансировка нагрузки.Разделив мой скрипт на разные файлы и запустив их с помощью subprocess.Popen, я могу запустить их как отдельные процессы.Это неизбежно приведет к тому, что ядро поместит их в отдельные ядра.Это означает, что я могу отправлять большие двоичные данные на стандартный ввод, и сразу же после получения последнего большого двоичного объекта данных подпроцесс сбрасывает на стандартный вывод и передает его следующему подпроцессу.Это можно сделать даже в сценарии bash вместо основного сценария python.Результат такой оптимизации будет означать, что основной сценарий тратит большую часть своего времени, просто перетасовывая данные, и приведет к значительному увеличению производительности.
Какой бы совет вы ни посоветовали, я бы хотел его услышать.
(Примечание для тех, кто предлагал правки: Спасибо за помощь в улучшении грамматики и правописания в моем вопросе. Обратите внимание, что имя rollAverage должно быть сохранено как есть, потому что это имя функции и подпроцесс.между пропусками нет пропусков.)