Звук в DIY проектах

от автора

Если ваше хобби/DIY, как и моё, связано с компьютером, то на каком то этапе вам захочется использовать звук. Предлагаю поговорить о звуке и обменяться опытом. Конкретно говорить будем, про запись и воспроизведение звука на компьютере. Возьмем компьютер под управлением Linux, но и под Windows должно работать. Язык для программирования предпочитаю Python. Как известно в Linux есть ALSA и для нее есть python библиотека pyalsaaudio. В каком то проекте голосового помощника я видел ее использовали для регулировки громкости. Про громкость поговорим потом. Т.к. библиотека не является системонезависимой поэтому возьмем другую хорошо известную — sounddevice. Библиотека основано на кросс платформенной C/C++ библиотеке portaudio.

Необходимый минимум для использования sounddevice есть в описании и он весьма прост:

import sounddevice as sd sd.play(myarray, fs)

здесь myarray — массив со звуком, надо сказать, что sounddevice поддерживает массивы numpy. fs — битрейт записи или частота дискретизации. Откуда это все брать? Нужна еще библиотека, например soundfile.

import sounddevice as sd import soundfile as sf  myarray, fs = sf.read('my-file.wav') sd.play(myarray, fs) sd.wait() #  ждем конца воспроизведения

Можно создать свой сигнал, например синус, таким образом для каких то нужд можно получить генератор любой звуковой частоты:

duration=1 длительность сигнала сек  frequency = 1000 # частота Гц  samplerate = 24000 # битрейт  amp = 10000 # амплитуда  #############  #берем numpy  import numpy as np  t = np.arange(duration * samplerate) / samplerate  signal = amp*np.sin(2 * np.pi * frequency * t)  sd.play(signal) sd.wait()

Естественно выбор не ограничен синусом. Не намного сложнее запись:

import sounddevice as sd  import soundfile as sf  fs = 48000 # битрейт записи  duration=3 # длительность записи  rec = sd.rec(int(duration * fs), samplerate=fs, channels=1, blocking=True)  sd.wait()  sf.write('my-file.wav', rec, fs)

Есть еще функция для одновременной записи и воспроизведя. Какие то подробности можно посмотреть в описании. Но это на мой взгляд мало интересно, т.к. недостаточно гибкости… Переходим к потокам. Про потоки в описании тоже ест и даже с примерами, поэтому отмечу, на мой взгляд, интересные моменты. Создаем поток:

stream = sd.Stream(device=(dev_in, dev_out),             samplerate=41000,             blocksize=blocksize,             dtype="int16",             channels=(1,2),             callback=callback_fun)

Данный тип потока поддерживает и запись и воспроизведение. В библиотеке есть и раздельные, но этот мне показался наиболее интересным. В параметре device, поэтому содержится указание на на эти устройства. Где же их брать? Воспользуемся функцией sd.query_devices():

device_info = sd.query_devices()

в результате получим список с доступными звуковыми устройствами. Например такой:

0 sof-hda-dsp: - (hw:0,0), ALSA (2 in, 0 out)  1 sof-hda-dsp: - (hw:0,3), ALSA (0 in, 2 out) 2 sof-hda-dsp: - (hw:0,4), ALSA (0 in, 2 out) 3 sof-hda-dsp: - (hw:0,5), ALSA (0 in, 2 out) 4 sof-hda-dsp: - (hw:0,6), ALSA (4 in, 0 out) 5 sof-hda-dsp: - (hw:0,7), ALSA (4 in, 0 out) 6 sysdefault, ALSA (128 in, 0 out) 7 samplerate, ALSA (128 in, 0 out) 8 speexrate, ALSA (128 in, 0 out) 9 pulse, ALSA (32 in, 32 out) 10 upmix, ALSA (8 in, 0 out)  11 vdownmix, ALSA (6 in, 0 out) 12 default, ALSA (32 in, 32 out)

в параметр device можно записать или номер или имя, например device = (12,12). Я пробовал в параметр вписать номер от микрофона USB камеры, программа работает через раз — вылетает ошибка с указанием на portaudio, как я понял такая ошибка не только у меня. C default все работает. Так можно получить имя устройства:

dev_in = sd.query_devices(kind="input")["name"] dev_out = sd.query_devices(kind="output")["name"]

Для работы библиотека предлагает, и это удобно, использовать callback функцию.

def callback_fun(indata, outdata, frames, time, status):   if status:      print(status)    outdata[:] = indata

Она вызывается функцией потока. Здесь, в функции видно, что входной сигнал подается прямо на выход. Например сигнал с микрофона можно сразу услышать. Или если кто то делает голосового ассистента, то сигнал с микрофона надо направить на распознавание голоса, а сигнал с синтезатора голоса на выход. В этом случае удобно использовать очереди — queue. Для примера, как проиграть WAV фай с диска? Для этого можно использовать такие функции:

import queue  fifo = queue.Queue()  inp_fifo = queue.Queue()  ############# def play(file):     with sf.SoundFile(file,mode="r") as f:         data = f.read(blocksize,dtype='int16')         while len(data):             if not fifo.full():                 data = f.read(blocksize,dtype='int16')                 fifo.put(data) ################# def callback_fun(indata, outdata, frames, time, status):     inp_fifo.put(bytes(indata.copy())) # для обработки сигнала с микрофона     if status:         print(status)     inp = np.zeros(len(outdata), dtype=np.int16)     if not fifo.empty() :         bf = np.frombuffer(fifo.get(), dtype=np.int16)         inp[:bf.shape[0]] = bf #     outdata[:] = inp.reshape(-1,1)

Отмечу, если помните, при создании потока был параметр channels=(1,2), который значит, что на вход у нас 1 — моно сигнал, а на выход 2 — стерео. Если вы подаете на выход моно сигнал в поток, то он автоматически разведется на два канала.

Пойдем далее. Возможно, что кто то уже догадался у нас массивы — вот он шанс сделать регулировку громкости, т. к. в sounddevice таких специальных функций нет.
Заводим переменную, например vol = 1, а далее магия циф в строке:

 inp[:bf.shape[0]] = bf // vol

нам остается только правильно менять vol. Если взять vol = 1000, то можно практически
занулить звук. Можно пойти дальше… Если у нас есть, например, две входные очереди мы можем смешать звук от двух источников:

# см. В callback_fun if not fifo1.empty() :         bf1 = np.frombuffer(fifo1.get(), dtype=np.int16)         inp1[:bf1.shape[0]] = bf1 # if not fifo2.empty() :         bf2= np.frombuffer(fifo2.get(), dtype=np.int16)         inp2[:bf2.shape[0]] = bf2 # inp = (inp1 + inp2)//2 outdata[:] = inp.reshape(-1,1)

Или один сигнал в левое уха, а другой в правое:

# см. В callback_fun inp = np.column_stack((inp1,inp2))     outdata[:] = inp

На мой взгляд тут широкое поле для творчества. Если кто то захочет повторить мои эксперименты смотрите внимательно, я не профессиональный программист, могут быть ошибки. Ссылка на документацию sounddevice.

Надеюсь эта информация была полезна.


ссылка на оригинал статьи https://habr.com/ru/post/661037/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *