mirror of
https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git
synced 2026-09-01 19:48:26 +02:00
169 lines
6.6 KiB
Python
169 lines
6.6 KiB
Python
import numpy as np
|
|
from multiprocessing import Process, Value, Event
|
|
from multiprocessing.shared_memory import SharedMemory
|
|
import sounddevice as sd
|
|
import signal
|
|
|
|
|
|
class AudioIoProcess(Process):
|
|
def __init__(self,
|
|
input_device,
|
|
output_device,
|
|
input_audio_block_size: int,
|
|
sample_rate: int,
|
|
channel_num: int = 2,
|
|
is_device_combined: bool = True,
|
|
is_input_wasapi_exclusive: bool = False,
|
|
is_output_wasapi_exclusive: bool = False
|
|
):
|
|
super().__init__()
|
|
self.in_dev = input_device
|
|
self.out_dev = output_device
|
|
self.block_size: int = input_audio_block_size
|
|
self.buf_size: int = self.block_size << 1 # 双缓冲
|
|
self.sample_rate: int = sample_rate
|
|
self.channels: int = channel_num
|
|
self.is_device_combined: bool = is_device_combined
|
|
self.is_input_wasapi_exclusive: bool = is_input_wasapi_exclusive
|
|
self.is_output_wasapi_exclusive: bool = is_output_wasapi_exclusive
|
|
|
|
self.__rec_ptr = 0
|
|
self.in_ptr = Value('i', 0) # 当收满一个block时由本进程设置
|
|
self.out_ptr = Value('i', 0) # 由主进程设置,指示下一次预期写入位置
|
|
self.play_ptr = Value('i', 0) # 由本进程设置,指示当前音频已经播放到哪里
|
|
self.in_evt = Event() # 当收满一个block时由本进程设置
|
|
self.stop_evt = Event() # 当主进程停止音频活动时由主进程设置
|
|
|
|
self.latency = Value('d', 114514.1919810)
|
|
|
|
self.buf_shape: tuple = (self.buf_size, self.channels)
|
|
self.buf_dtype: np.dtype = np.float32
|
|
self.buf_nbytes: int = int(
|
|
np.prod(self.buf_shape) * np.dtype(self.buf_dtype).itemsize)
|
|
|
|
self.in_mem = SharedMemory(create=True, size=self.buf_nbytes)
|
|
self.out_mem = SharedMemory(create=True, size=self.buf_nbytes)
|
|
self.in_mem_name: str = self.in_mem.name
|
|
self.out_mem_name: str = self.out_mem.name
|
|
|
|
self.in_buf = None
|
|
self.out_buf = None
|
|
|
|
def get_in_mem_name(self) -> str:
|
|
return self.in_mem_name
|
|
|
|
def get_out_mem_name(self) -> str:
|
|
return self.out_mem_name
|
|
|
|
def get_np_shape(self) -> tuple:
|
|
return self.buf_shape
|
|
|
|
def get_np_dtype(self) -> np.dtype:
|
|
return self.buf_dtype
|
|
|
|
def get_ptrs_and_events(self):
|
|
return self.in_ptr, \
|
|
self.out_ptr,\
|
|
self.play_ptr,\
|
|
self.in_evt, \
|
|
self.stop_evt\
|
|
|
|
def get_latency(self) -> float:
|
|
return self.latency.value
|
|
|
|
def run(self):
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
|
|
in_mem = SharedMemory(name=self.in_mem_name)
|
|
self.in_buf = np.ndarray(
|
|
self.buf_shape, dtype=self.buf_dtype, buffer=in_mem.buf, order='C')
|
|
self.in_buf.fill(0.0)
|
|
|
|
out_mem = SharedMemory(name=self.out_mem_name)
|
|
self.out_buf = np.ndarray(
|
|
self.buf_shape, dtype=self.buf_dtype, buffer=out_mem.buf, order='C')
|
|
self.out_buf.fill(0.0)
|
|
|
|
exclusive_settings = sd.WasapiSettings(exclusive=True)
|
|
|
|
sd.default.device = (self.in_dev, self.out_dev)
|
|
|
|
def output_callback(outdata, frames, time_info, status):
|
|
play_ptr = self.play_ptr.value
|
|
end_ptr = play_ptr + frames
|
|
|
|
if end_ptr <= self.buf_size:
|
|
outdata[:] = self.out_buf[play_ptr:end_ptr]
|
|
else:
|
|
first = self.buf_size - play_ptr
|
|
second = end_ptr - self.buf_size
|
|
outdata[:first] = self.out_buf[play_ptr:]
|
|
outdata[first:] = self.out_buf[:second]
|
|
|
|
self.play_ptr.value = end_ptr % self.buf_size
|
|
|
|
def input_callback(indata, frames, time_info, status):
|
|
# 收录输入数据
|
|
end_ptr = self.__rec_ptr + frames
|
|
if end_ptr <= self.buf_size: # 整块拷贝
|
|
self.in_buf[self.__rec_ptr:end_ptr] = indata
|
|
else: # 处理回绕
|
|
first = self.buf_size - self.__rec_ptr
|
|
second = end_ptr - self.buf_size
|
|
self.in_buf[self.__rec_ptr:] = indata[:first]
|
|
self.in_buf[:second] = indata[first:]
|
|
write_pos = self.__rec_ptr
|
|
self.__rec_ptr = end_ptr % self.buf_size
|
|
|
|
# 设置信号
|
|
if write_pos < self.block_size and self.__rec_ptr >= self.block_size:
|
|
self.in_ptr.value = 0
|
|
self.in_evt.set() # 通知主线程来取甲缓冲
|
|
elif write_pos < self.buf_size and self.__rec_ptr < write_pos:
|
|
self.in_ptr.value = self.block_size
|
|
self.in_evt.set() # 通知主线程来取乙缓冲
|
|
|
|
def combined_callback(indata, outdata, frames, time_info, status):
|
|
output_callback(outdata, frames, time_info, status) # 优先出声
|
|
input_callback(indata, frames, time_info, status)
|
|
|
|
if self.is_device_combined:
|
|
with sd.Stream(
|
|
samplerate=self.sample_rate,
|
|
channels=self.channels,
|
|
dtype=self.buf_dtype,
|
|
latency='low',
|
|
extra_settings=exclusive_settings if
|
|
self.is_input_wasapi_exclusive and
|
|
self.is_output_wasapi_exclusive else None,
|
|
callback=combined_callback
|
|
) as s:
|
|
self.latency.value = s.latency[-1]
|
|
self.stop_evt.wait()
|
|
self.out_buf.fill(0.0)
|
|
else:
|
|
with sd.InputStream(
|
|
samplerate=self.sample_rate,
|
|
channels=self.channels,
|
|
dtype=self.buf_dtype,
|
|
latency='low',
|
|
extra_settings=exclusive_settings if self.is_input_wasapi_exclusive else None,
|
|
callback=input_callback
|
|
) as si, sd.OutputStream(
|
|
samplerate=self.sample_rate,
|
|
channels=self.channels,
|
|
dtype=self.buf_dtype,
|
|
latency='low',
|
|
extra_settings=exclusive_settings if self.is_output_wasapi_exclusive else None,
|
|
callback=output_callback
|
|
) as so:
|
|
self.latency.value = si.latency[-1] + so.latency[-1]
|
|
self.stop_evt.wait()
|
|
self.out_buf.fill(0.0)
|
|
|
|
# 清理共享内存
|
|
in_mem.close()
|
|
out_mem.close()
|
|
in_mem.unlink()
|
|
out_mem.unlink()
|