2022-05-25 22:27:33 +00:00
|
|
|
"""
|
|
|
|
Gather information about audio devices.
|
|
|
|
"""
|
2022-05-11 22:10:59 +00:00
|
|
|
import atexit
|
2022-02-17 13:25:22 +00:00
|
|
|
import multiprocessing
|
2022-05-11 22:10:59 +00:00
|
|
|
|
2022-09-19 21:17:07 +00:00
|
|
|
import crcengine
|
2022-03-24 19:49:13 +00:00
|
|
|
import sounddevice as sd
|
2022-06-30 12:05:57 +00:00
|
|
|
import structlog
|
2022-03-24 19:49:13 +00:00
|
|
|
|
2022-04-10 09:37:09 +00:00
|
|
|
atexit.register(sd._terminate)
|
2022-03-04 15:50:32 +00:00
|
|
|
|
2022-09-18 15:40:11 +00:00
|
|
|
log = structlog.get_logger("audio")
|
2022-06-30 12:05:57 +00:00
|
|
|
|
2022-10-05 14:01:58 +00:00
|
|
|
# crc algorithm for unique audio device names
|
|
|
|
crc_algorithm = crcengine.new("crc16-ccitt-false") # load crc16 library
|
|
|
|
|
2022-05-26 01:23:30 +00:00
|
|
|
|
2022-04-11 09:03:54 +00:00
|
|
|
def get_audio_devices():
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
|
|
|
return list of input and output audio devices in own process to avoid crashes of portaudio on raspberry pi
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-03-04 15:50:32 +00:00
|
|
|
also uses a process data manager
|
|
|
|
"""
|
2022-05-26 01:23:30 +00:00
|
|
|
# we need to run this on Windows for multiprocessing support
|
2022-02-19 19:45:57 +00:00
|
|
|
# multiprocessing.freeze_support()
|
2022-05-26 01:23:30 +00:00
|
|
|
# multiprocessing.get_context("spawn")
|
2022-02-17 13:25:22 +00:00
|
|
|
|
2022-04-08 09:35:13 +00:00
|
|
|
# we need to reset and initialize sounddevice before running the multiprocessing part.
|
|
|
|
# If we are not doing this at this early point, not all devices will be displayed
|
|
|
|
sd._terminate()
|
|
|
|
sd._initialize()
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-11-08 08:45:26 +00:00
|
|
|
# log.debug("[AUD] get_audio_devices")
|
2022-02-19 19:45:57 +00:00
|
|
|
with multiprocessing.Manager() as manager:
|
|
|
|
proxy_input_devices = manager.list()
|
|
|
|
proxy_output_devices = manager.list()
|
2022-05-26 01:23:30 +00:00
|
|
|
# print(multiprocessing.get_start_method())
|
|
|
|
proc = multiprocessing.Process(
|
|
|
|
target=fetch_audio_devices, args=(proxy_input_devices, proxy_output_devices)
|
|
|
|
)
|
|
|
|
proc.start()
|
|
|
|
proc.join()
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-11-08 08:45:26 +00:00
|
|
|
# additional logging for audio devices
|
|
|
|
# log.debug("[AUD] get_audio_devices: input_devices:", list=f"{proxy_input_devices}")
|
|
|
|
# log.debug("[AUD] get_audio_devices: output_devices:", list=f"{proxy_output_devices}")
|
2022-06-25 20:25:12 +00:00
|
|
|
return list(proxy_input_devices), list(proxy_output_devices)
|
2022-02-17 15:52:11 +00:00
|
|
|
|
2022-05-26 01:23:30 +00:00
|
|
|
|
2022-09-20 22:24:22 +00:00
|
|
|
def device_crc(device) -> str:
|
2022-10-05 14:01:58 +00:00
|
|
|
|
2022-09-20 22:24:22 +00:00
|
|
|
crc_hwid = crc_algorithm(bytes(f"{device}", encoding="utf-8"))
|
2022-09-19 21:17:07 +00:00
|
|
|
crc_hwid = crc_hwid.to_bytes(2, byteorder="big")
|
|
|
|
crc_hwid = crc_hwid.hex()
|
2022-11-08 08:08:44 +00:00
|
|
|
|
|
|
|
hostapi_name = sd.query_hostapis(device['hostapi'])['name']
|
|
|
|
|
|
|
|
return f"{device['name']} [{hostapi_name}] [{crc_hwid}]"
|
2022-09-19 21:17:07 +00:00
|
|
|
|
|
|
|
|
2022-02-17 15:52:11 +00:00
|
|
|
def fetch_audio_devices(input_devices, output_devices):
|
2022-03-04 15:50:32 +00:00
|
|
|
"""
|
|
|
|
get audio devices from portaudio
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-03-04 15:50:32 +00:00
|
|
|
Args:
|
|
|
|
input_devices: proxy variable for input devices
|
2022-05-26 01:23:30 +00:00
|
|
|
output_devices: proxy variable for output devices
|
2022-03-04 15:50:32 +00:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
2022-03-24 19:49:13 +00:00
|
|
|
devices = sd.query_devices(device=None, kind=None)
|
2022-09-18 19:33:46 +00:00
|
|
|
|
2022-10-05 14:01:58 +00:00
|
|
|
for index, device in enumerate(devices):
|
2022-05-11 22:10:59 +00:00
|
|
|
# Use a try/except block because Windows doesn't have an audio device range
|
2022-01-22 19:39:37 +00:00
|
|
|
try:
|
2022-03-24 19:49:13 +00:00
|
|
|
name = device["name"]
|
2023-06-10 16:54:03 +00:00
|
|
|
# Ignore some Flex Radio devices to make device selection simpler
|
|
|
|
if name.startswith("DAX RESERVED") or name.startswith("DAX IQ"):
|
2023-06-11 06:29:31 +00:00
|
|
|
continue
|
2022-05-09 00:41:49 +00:00
|
|
|
|
2022-05-26 01:23:30 +00:00
|
|
|
max_output_channels = device["max_output_channels"]
|
|
|
|
max_input_channels = device["max_input_channels"]
|
2022-03-24 19:49:13 +00:00
|
|
|
|
2022-06-25 20:43:23 +00:00
|
|
|
except KeyError:
|
|
|
|
continue
|
2022-05-26 01:23:30 +00:00
|
|
|
except Exception as err:
|
|
|
|
print(err)
|
|
|
|
max_input_channels = 0
|
|
|
|
max_output_channels = 0
|
2022-01-22 19:39:37 +00:00
|
|
|
|
2022-05-26 01:23:30 +00:00
|
|
|
if max_input_channels > 0:
|
2022-10-05 14:01:58 +00:00
|
|
|
new_input_device = {"id": index, "name": device_crc(device)}
|
|
|
|
# check if device not in device list
|
|
|
|
if new_input_device not in input_devices:
|
|
|
|
input_devices.append(new_input_device)
|
2022-06-25 20:25:12 +00:00
|
|
|
|
2022-10-05 14:01:58 +00:00
|
|
|
if max_output_channels > 0:
|
|
|
|
new_output_device = {"id": index, "name": device_crc(device)}
|
|
|
|
# check if device not in device list
|
|
|
|
if new_output_device not in output_devices:
|
|
|
|
output_devices.append(new_output_device)
|