Files
VRCT/src-python/device_manager.py
misyaguziya 4ddcad1e3e [bugfix] DeviceManager : Deviceを接続していない場合に発生する問題を修正
- 関数設定されていない変数をコールする問題を修正
- Deviceソート時にindexを参照しているため、NoDeviceにindex=-1を設定するように変更
2024-11-02 06:39:05 +09:00

327 lines
14 KiB
Python

from typing import Callable
from time import sleep
from threading import Thread
import comtypes
from pyaudiowpatch import PyAudio, paWASAPI
from pycaw.callbacks import MMNotificationClient
from pycaw.utils import AudioUtilities
from utils import printLog
class Client(MMNotificationClient):
def __init__(self):
super().__init__()
self.loop = True
def on_default_device_changed(self, flow, flow_id, role, role_id, default_device_id):
self.loop = False
def on_device_added(self, added_device_id):
self.loop = False
def on_device_removed(self, removed_device_id):
self.loop = False
def on_device_state_changed(self, device_id, state):
self.loop = False
# def on_property_value_changed(self, device_id, key):
# self.loop = False
class DeviceManager:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(DeviceManager, cls).__new__(cls)
cls._instance.init()
return cls._instance
def init(self):
self.mic_devices = {"NoHost": [{"index": -1, "name": "NoDevice"}]}
self.default_mic_device = {"host": {"index": -1, "name": "NoHost"}, "device": {"index": -1, "name": "NoDevice"}}
self.speaker_devices = [{"index": -1, "name": "NoDevice"}]
self.default_speaker_device = {"device": {"index": -1, "name": "NoDevice"}}
self.update()
self.prev_mic_host = [host for host in self.mic_devices]
self.prev_mic_devices = self.mic_devices
self.prev_default_mic_device = self.default_mic_device
self.prev_speaker_devices = self.speaker_devices
self.prev_default_speaker_device = self.default_speaker_device
self.update_flag_default_mic_device = False
self.update_flag_default_speaker_device = False
self.update_flag_host_list = False
self.update_flag_mic_device_list = False
self.update_flag_speaker_device_list = False
self.callback_default_mic_device = None
self.callback_default_speaker_device = None
self.callback_host_list = None
self.callback_mic_device_list = None
self.callback_speaker_device_list = None
self.callback_process_before_update_devices = None
self.callback_process_after_update_devices = None
self.monitoring_flag = False
self.startMonitoring()
def update(self):
buffer_mic_devices = {}
buffer_default_mic_device = {"host": {"index": -1, "name": "NoHost"}, "device": {"index": -1, "name": "NoDevice"}}
buffer_speaker_devices = []
buffer_default_speaker_device = {"device": {"index": -1, "name": "NoDevice"}}
with PyAudio() as p:
for host_index in range(p.get_host_api_count()):
host = p.get_host_api_info_by_index(host_index)
device_count = host.get('deviceCount', 0)
for device_index in range(device_count):
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if device.get("maxInputChannels", 0) > 0 and not device.get("isLoopbackDevice", True):
buffer_mic_devices.setdefault(host["name"], []).append(device)
if not buffer_mic_devices:
buffer_mic_devices = {"NoHost": [{"index": -1, "name": "NoDevice"}]}
api_info = p.get_default_host_api_info()
default_mic_device = api_info["defaultInputDevice"]
for host_index in range(p.get_host_api_count()):
host = p.get_host_api_info_by_index(host_index)
device_count = host.get('deviceCount', 0)
for device_index in range(device_count):
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if device["index"] == default_mic_device:
buffer_default_mic_device = {"host": host, "device": device}
break
else:
continue
break
speaker_devices = []
wasapi_info = p.get_host_api_info_by_type(paWASAPI)
wasapi_name = wasapi_info["name"]
for host_index in range(p.get_host_api_count()):
host = p.get_host_api_info_by_index(host_index)
if host["name"] == wasapi_name:
device_count = host.get('deviceCount', 0)
for device_index in range(device_count):
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if not device.get("isLoopbackDevice", True):
for loopback in p.get_loopback_device_info_generator():
if device["name"] in loopback["name"]:
speaker_devices.append(loopback)
speaker_devices = [dict(t) for t in {tuple(d.items()) for d in speaker_devices}] or [{"index": -1, "name": "NoDevice"}]
buffer_speaker_devices = sorted(speaker_devices, key=lambda d: d['index'])
wasapi_info = p.get_host_api_info_by_type(paWASAPI)
default_speaker_device_index = wasapi_info["defaultOutputDevice"]
for host_index in range(p.get_host_api_count()):
host_info = p.get_host_api_info_by_index(host_index)
device_count = host_info.get('deviceCount', 0)
for device_index in range(0, device_count):
device = p.get_device_info_by_host_api_device_index(host_index, device_index)
if device["index"] == default_speaker_device_index:
default_speakers = device
if not default_speakers.get("isLoopbackDevice", True):
for loopback in p.get_loopback_device_info_generator():
if default_speakers["name"] in loopback["name"]:
buffer_default_speaker_device = {"device": loopback}
break
break
if buffer_default_speaker_device["device"]["name"] != "NoDevice":
break
self.mic_devices = buffer_mic_devices
self.default_mic_device = buffer_default_mic_device
self.speaker_devices = buffer_speaker_devices
self.default_speaker_device = buffer_default_speaker_device
def checkUpdate(self):
if self.prev_default_mic_device["device"]["name"] != self.default_mic_device["device"]["name"]:
self.update_flag_default_mic_device = True
self.prev_default_mic_device = self.default_mic_device
if self.prev_default_speaker_device["device"]["name"] != self.default_speaker_device["device"]["name"]:
self.update_flag_default_speaker_device = True
self.prev_default_speaker_device = self.default_speaker_device
if self.prev_mic_host != [host for host in self.mic_devices]:
self.update_flag_host_list = True
self.prev_mic_host = [host for host in self.mic_devices]
if {key: [device['name'] for device in devices] for key, devices in self.prev_mic_devices.items()} != {key: [device['name'] for device in devices] for key, devices in self.mic_devices.items()}:
self.update_flag_mic_device_list = True
self.prev_mic_devices = self.mic_devices
if [device['name'] for device in self.prev_speaker_devices] != [device['name'] for device in self.speaker_devices]:
self.update_flag_speaker_device_list = True
self.prev_speaker_devices = self.speaker_devices
update_flag = (
self.update_flag_default_mic_device or
self.update_flag_default_speaker_device or
self.update_flag_host_list or
self.update_flag_mic_device_list or
self.update_flag_speaker_device_list
)
return update_flag
def monitoring(self):
try:
while self.monitoring_flag is True:
try:
comtypes.CoInitialize()
cb = Client()
enumerator = AudioUtilities.GetDeviceEnumerator()
enumerator.RegisterEndpointNotificationCallback(cb)
while cb.loop is True:
sleep(1)
enumerator.UnregisterEndpointNotificationCallback(cb)
comtypes.CoUninitialize()
self.runProcessBeforeUpdateDevices()
sleep(2)
for _ in range(10):
self.update()
if self.checkUpdate():
break
sleep(2)
self.noticeUpdateDevices()
self.runProcessAfterUpdateDevices()
except Exception as e:
printLog("Device Monitoring: ", e)
finally:
pass
except Exception as e:
printLog("Device Monitoring End Exception: ", e)
def startMonitoring(self):
self.monitoring_flag = True
self.th_monitoring = Thread(target=self.monitoring)
self.th_monitoring.daemon = True
self.th_monitoring.start()
def stopMonitoring(self):
self.monitoring_flag = False
self.th_monitoring.join()
def setCallbackDefaultMicDevice(self, callback):
self.callback_default_mic_device = callback
def clearCallbackDefaultMicDevice(self):
self.callback_default_mic_device = None
def setCallbackDefaultSpeakerDevice(self, callback):
self.callback_default_speaker_device = callback
def clearCallbackDefaultSpeakerDevice(self):
self.callback_default_speaker_device = None
def setCallbackHostList(self, callback):
self.callback_host_list = callback
def clearCallbackHostList(self):
self.callback_host_list = None
def setCallbackMicDeviceList(self, callback):
self.callback_mic_device_list = callback
def clearCallbackMicDeviceList(self):
self.callback_mic_device_list = None
def setCallbackSpeakerDeviceList(self, callback):
self.callback_speaker_device_list = callback
def clearCallbackSpeakerDeviceList(self):
self.callback_speaker_device_list = None
def setCallbackProcessBeforeUpdateDevices(self, callback):
self.callback_process_before_update_devices = callback
def clearCallbackProcessBeforeUpdateDevices(self):
self.callback_process_before_update_devices = None
def runProcessBeforeUpdateDevices(self):
if isinstance(self.callback_process_before_update_devices, Callable):
self.callback_process_before_update_devices()
def setCallbackProcessAfterUpdateDevices(self, callback):
self.callback_process_after_update_devices = callback
def clearCallbackProcessAfterUpdateDevices(self):
self.callback_process_after_update_devices = None
def runProcessAfterUpdateDevices(self):
if isinstance(self.callback_process_after_update_devices, Callable):
self.callback_process_after_update_devices()
def noticeUpdateDevices(self):
if self.update_flag_default_mic_device is True:
self.setMicDefaultDevice()
if self.update_flag_default_speaker_device is True:
self.setSpeakerDefaultDevice()
if self.update_flag_host_list is True:
self.setMicHostList()
if self.update_flag_mic_device_list is True:
self.setMicDeviceList()
if self.update_flag_speaker_device_list is True:
self.setSpeakerDeviceList()
self.update_flag_default_mic_device = False
self.update_flag_default_speaker_device = False
self.update_flag_host_list = False
self.update_flag_mic_device_list = False
self.update_flag_speaker_device_list = False
def setMicDefaultDevice(self):
if isinstance(self.callback_default_mic_device, Callable):
self.callback_default_mic_device(self.default_mic_device["host"]["name"], self.default_mic_device["device"]["name"])
def setSpeakerDefaultDevice(self):
if isinstance(self.callback_default_speaker_device, Callable):
self.callback_default_speaker_device(self.default_speaker_device["device"]["name"])
def setMicHostList(self):
if isinstance(self.callback_host_list, Callable):
self.callback_host_list()
def setMicDeviceList(self):
if isinstance(self.callback_mic_device_list, Callable):
self.callback_mic_device_list()
def setSpeakerDeviceList(self):
if isinstance(self.callback_speaker_device_list, Callable):
self.callback_speaker_device_list()
def getMicDevices(self):
return self.mic_devices
def getDefaultMicDevice(self):
return self.default_mic_device
def getSpeakerDevices(self):
return self.speaker_devices
def getDefaultSpeakerDevice(self):
return self.default_speaker_device
def forceUpdateAndSetMicDevices(self):
self.update()
self.setMicHostList()
self.setMicDeviceList()
self.setMicDefaultDevice()
def forceUpdateAndSetSpeakerDevices(self):
self.update()
self.setSpeakerDeviceList()
self.setSpeakerDefaultDevice()
device_manager = DeviceManager()
if __name__ == "__main__":
# print("getMicDevices()", device_manager.getMicDevices())
# print("getDefaultMicDevice()", device_manager.getDefaultMicDevice())
# print("getSpeakerDevices()", device_manager.getSpeakerDevices())
# print("getDefaultSpeakerDevice()", device_manager.getDefaultSpeakerDevice())
while True:
sleep(1)