tune consumption of samples
This commit is contained in:
@@ -58,17 +58,8 @@ from auracast.utils.webrtc_audio_input import WebRTCAudioInput
|
||||
# Patch sounddevice.InputStream globally to use low-latency settings
|
||||
import sounddevice as sd
|
||||
from collections import deque
|
||||
import statistics
|
||||
|
||||
|
||||
def popleft_n(d: deque, n: int) -> bytes:
|
||||
# pops up to n items (handles short deques)
|
||||
# if the deque was too short, fill with 0s and print a warning
|
||||
it = (d.popleft() for _ in range(min(n, len(d))))
|
||||
if len(d) < n:
|
||||
logging.warning("SoundDeviceAudioInput: deque was too short, requested %d, got %d", n, len(d))
|
||||
return b"".join(it)
|
||||
|
||||
class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
||||
"""Patched SoundDeviceAudioInput with low-latency capture and adaptive resampling."""
|
||||
|
||||
@@ -94,13 +85,10 @@ class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
||||
# Target ~2 ms blocksize (48 kHz -> 96 frames). For other rates, keep ~2 ms.
|
||||
_sr = int(self._pcm_format.sample_rate)
|
||||
|
||||
self.log_counter0=0
|
||||
self._runavg_samps = deque(maxlen=30)
|
||||
self._runavg = 0
|
||||
self.counter=0
|
||||
self.max_avail=0
|
||||
self.logfile_name="available_samples.txt"
|
||||
self.blocksize = 480
|
||||
self._frames_offset = 0
|
||||
self.blocksize = 120
|
||||
|
||||
if os.path.exists(self.logfile_name):
|
||||
os.remove(self.logfile_name)
|
||||
@@ -111,7 +99,7 @@ class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
||||
channels=self._pcm_format.channels,
|
||||
dtype='int16',
|
||||
blocksize=self.blocksize,
|
||||
latency=0.005,
|
||||
latency=0.004,
|
||||
)
|
||||
self._stream.start()
|
||||
|
||||
@@ -123,50 +111,43 @@ class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
||||
)
|
||||
|
||||
def _read(self, frame_size: int) -> bytes:
|
||||
"""Generate frames while minimally adjusting frames_offset to keep _runavg ~ blocksize/2."""
|
||||
# Persist frames_offset across calls; clamp within 1% of blocksize
|
||||
"""Read PCM samples from the stream."""
|
||||
|
||||
max_offset = 30
|
||||
target = 0.5*self.blocksize
|
||||
deadband = 2
|
||||
#if self.counter % 50 == 0:
|
||||
frame_size = frame_size + 1 # consume samples a little faster to avoid latency akkumulation
|
||||
|
||||
pcm_buffer, overflowed = self._stream.read(frame_size + self._frames_offset)
|
||||
pcm_buffer, overflowed = self._stream.read(frame_size)
|
||||
if overflowed:
|
||||
logging.warning("SoundDeviceAudioInput: overflowed")
|
||||
|
||||
available = self._stream.read_available
|
||||
if available > 0:
|
||||
self.max_avail = max(self.max_avail, available)
|
||||
n_available = self._stream.read_available
|
||||
|
||||
self._runavg_samps.append(available)
|
||||
self._runavg = max(self._runavg_samps) # statistics.median(self._runavg_samps)
|
||||
# adapt = n_available > 20
|
||||
# if adapt:
|
||||
# pcm_extra, overflowed = self._stream.read(3)
|
||||
# logging.info('consuming extra samples, available was %d', n_available)
|
||||
# if overflowed:
|
||||
# logging.warning("SoundDeviceAudioInput: overflowed")
|
||||
|
||||
# Minimal feedback: nudge frames_offset by 1 to target blocksize/2 with small deadband
|
||||
# out = bytes(pcm_buffer) + bytes(pcm_extra)
|
||||
# else:
|
||||
out = bytes(pcm_buffer)
|
||||
|
||||
increment = self._runavg > (target + deadband) or overflowed
|
||||
decrement = self._runavg < (target - deadband)
|
||||
clipped_upper = self._frames_offset >= max_offset
|
||||
clipped_lower = self._frames_offset <= -1 * max_offset
|
||||
|
||||
self._frames_offset = 0
|
||||
if increment and not clipped_upper:
|
||||
self._frames_offset += 1
|
||||
elif decrement and not clipped_lower:
|
||||
self._frames_offset -= 1
|
||||
self.max_avail = max(self.max_avail, n_available)
|
||||
|
||||
#Diagnostics
|
||||
with open(self.logfile_name, "a", encoding="utf-8") as f:
|
||||
f.write(f"{available}, {round(self._runavg, 2)}, {self._frames_offset}, {overflowed}\n")
|
||||
#with open(self.logfile_name, "a", encoding="utf-8") as f:
|
||||
# f.write(f"{n_available}, {adapt}, {round(self._runavg, 2)}, {overflowed}\n")
|
||||
|
||||
if self.log_counter0 % 500 == 0:
|
||||
if self.counter % 500 == 0:
|
||||
logging.info(
|
||||
"read available=%d, max=%d, runavg=%.3f, frames_offset=%d (max %d)",
|
||||
available, self.max_avail, round(self._runavg, 2), self._frames_offset, max_offset
|
||||
"read available=%d, max=%d, latency:%d",
|
||||
n_available, self.max_avail, self._stream.latency
|
||||
)
|
||||
self.max_avail = 0
|
||||
|
||||
self.log_counter0 += 1
|
||||
return bytes(pcm_buffer)
|
||||
self.counter += 1
|
||||
return out
|
||||
|
||||
audio_io.SoundDeviceAudioInput = ModSoundDeviceAudioInput
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ pcm.ch1 {
|
||||
channels 2
|
||||
rate 48000
|
||||
format S16_LE
|
||||
period_size 480 # 2.5ms
|
||||
buffer_size 960
|
||||
period_size 120
|
||||
buffer_size 240
|
||||
}
|
||||
bindings.0 0
|
||||
}
|
||||
@@ -21,8 +21,8 @@ pcm.ch2 {
|
||||
channels 2
|
||||
rate 48000
|
||||
format S16_LE
|
||||
period_size 480
|
||||
buffer_size 960
|
||||
period_size 120
|
||||
buffer_size 240
|
||||
}
|
||||
bindings.0 1
|
||||
}
|
||||
Reference in New Issue
Block a user