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
|
# Patch sounddevice.InputStream globally to use low-latency settings
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
from collections import deque
|
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):
|
class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
||||||
"""Patched SoundDeviceAudioInput with low-latency capture and adaptive resampling."""
|
"""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.
|
# Target ~2 ms blocksize (48 kHz -> 96 frames). For other rates, keep ~2 ms.
|
||||||
_sr = int(self._pcm_format.sample_rate)
|
_sr = int(self._pcm_format.sample_rate)
|
||||||
|
|
||||||
self.log_counter0=0
|
self.counter=0
|
||||||
self._runavg_samps = deque(maxlen=30)
|
|
||||||
self._runavg = 0
|
|
||||||
self.max_avail=0
|
self.max_avail=0
|
||||||
self.logfile_name="available_samples.txt"
|
self.logfile_name="available_samples.txt"
|
||||||
self.blocksize = 480
|
self.blocksize = 120
|
||||||
self._frames_offset = 0
|
|
||||||
|
|
||||||
if os.path.exists(self.logfile_name):
|
if os.path.exists(self.logfile_name):
|
||||||
os.remove(self.logfile_name)
|
os.remove(self.logfile_name)
|
||||||
@@ -111,7 +99,7 @@ class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
|||||||
channels=self._pcm_format.channels,
|
channels=self._pcm_format.channels,
|
||||||
dtype='int16',
|
dtype='int16',
|
||||||
blocksize=self.blocksize,
|
blocksize=self.blocksize,
|
||||||
latency=0.005,
|
latency=0.004,
|
||||||
)
|
)
|
||||||
self._stream.start()
|
self._stream.start()
|
||||||
|
|
||||||
@@ -123,50 +111,43 @@ class ModSoundDeviceAudioInput(audio_io.SoundDeviceAudioInput):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _read(self, frame_size: int) -> bytes:
|
def _read(self, frame_size: int) -> bytes:
|
||||||
"""Generate frames while minimally adjusting frames_offset to keep _runavg ~ blocksize/2."""
|
"""Read PCM samples from the stream."""
|
||||||
# Persist frames_offset across calls; clamp within 1% of blocksize
|
|
||||||
|
|
||||||
max_offset = 30
|
#if self.counter % 50 == 0:
|
||||||
target = 0.5*self.blocksize
|
frame_size = frame_size + 1 # consume samples a little faster to avoid latency akkumulation
|
||||||
deadband = 2
|
|
||||||
|
|
||||||
pcm_buffer, overflowed = self._stream.read(frame_size + self._frames_offset)
|
pcm_buffer, overflowed = self._stream.read(frame_size)
|
||||||
if overflowed:
|
if overflowed:
|
||||||
logging.warning("SoundDeviceAudioInput: overflowed")
|
logging.warning("SoundDeviceAudioInput: overflowed")
|
||||||
|
|
||||||
available = self._stream.read_available
|
n_available = self._stream.read_available
|
||||||
if available > 0:
|
|
||||||
self.max_avail = max(self.max_avail, available)
|
|
||||||
|
|
||||||
self._runavg_samps.append(available)
|
# adapt = n_available > 20
|
||||||
self._runavg = max(self._runavg_samps) # statistics.median(self._runavg_samps)
|
# 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")
|
||||||
|
|
||||||
|
# out = bytes(pcm_buffer) + bytes(pcm_extra)
|
||||||
|
# else:
|
||||||
|
out = bytes(pcm_buffer)
|
||||||
|
|
||||||
# Minimal feedback: nudge frames_offset by 1 to target blocksize/2 with small deadband
|
self.max_avail = max(self.max_avail, n_available)
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#Diagnostics
|
#Diagnostics
|
||||||
with open(self.logfile_name, "a", encoding="utf-8") as f:
|
#with open(self.logfile_name, "a", encoding="utf-8") as f:
|
||||||
f.write(f"{available}, {round(self._runavg, 2)}, {self._frames_offset}, {overflowed}\n")
|
# 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(
|
logging.info(
|
||||||
"read available=%d, max=%d, runavg=%.3f, frames_offset=%d (max %d)",
|
"read available=%d, max=%d, latency:%d",
|
||||||
available, self.max_avail, round(self._runavg, 2), self._frames_offset, max_offset
|
n_available, self.max_avail, self._stream.latency
|
||||||
)
|
)
|
||||||
self.max_avail = 0
|
self.max_avail = 0
|
||||||
|
|
||||||
self.log_counter0 += 1
|
self.counter += 1
|
||||||
return bytes(pcm_buffer)
|
return out
|
||||||
|
|
||||||
audio_io.SoundDeviceAudioInput = ModSoundDeviceAudioInput
|
audio_io.SoundDeviceAudioInput = ModSoundDeviceAudioInput
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ pcm.ch1 {
|
|||||||
channels 2
|
channels 2
|
||||||
rate 48000
|
rate 48000
|
||||||
format S16_LE
|
format S16_LE
|
||||||
period_size 480 # 2.5ms
|
period_size 120
|
||||||
buffer_size 960
|
buffer_size 240
|
||||||
}
|
}
|
||||||
bindings.0 0
|
bindings.0 0
|
||||||
}
|
}
|
||||||
@@ -21,8 +21,8 @@ pcm.ch2 {
|
|||||||
channels 2
|
channels 2
|
||||||
rate 48000
|
rate 48000
|
||||||
format S16_LE
|
format S16_LE
|
||||||
period_size 480
|
period_size 120
|
||||||
buffer_size 960
|
buffer_size 240
|
||||||
}
|
}
|
||||||
bindings.0 1
|
bindings.0 1
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user