added auto mute when holding v so i dont hear myself

This commit is contained in:
Lucca Pirovano
2026-07-16 17:09:07 -04:00
parent e1b5e3ab76
commit f806fa8efc
+28 -6
View File
@@ -40,7 +40,7 @@ class SoundReceiver:
PREBUFFER_CHUNKS = 3 PREBUFFER_CHUNKS = 3
SILENCE_CHUNK = b"\x00" * 4096 SILENCE_CHUNK = b"\x00" * 4096
def __init__(self, output_device_index=None, mic_gain=8.0): def __init__(self, output_device_index=None, mic_gain=8.0, pyaudio_instance=None):
self.running = True self.running = True
self.underrun_count = 0 self.underrun_count = 0
self.chunks_received = 0 self.chunks_received = 0
@@ -48,9 +48,12 @@ class SoundReceiver:
self._peak_since_print = 0 self._peak_since_print = 0
self._last_status_print = 0 self._last_status_print = 0
self.mic_gain = float(mic_gain) self.mic_gain = float(mic_gain)
self.muted = False
self._owns_pyaudio = False
if HAS_AUDIO: if HAS_AUDIO:
self.p = pyaudio.PyAudio() self.p = pyaudio_instance if pyaudio_instance is not None else pyaudio.PyAudio()
self._owns_pyaudio = pyaudio_instance is None
print("🔈 Available output devices:") print("🔈 Available output devices:")
for i in range(self.p.get_device_count()): for i in range(self.p.get_device_count()):
info = self.p.get_device_info_by_index(i) info = self.p.get_device_info_by_index(i)
@@ -117,6 +120,9 @@ class SoundReceiver:
def _write_chunk(self, raw_bytes): def _write_chunk(self, raw_bytes):
try: try:
if self.muted:
self.stream.write(b"\x00" * len(raw_bytes))
return
samples = np.frombuffer(raw_bytes, dtype=np.int16).astype(np.float32) samples = np.frombuffer(raw_bytes, dtype=np.int16).astype(np.float32)
if samples.size: if samples.size:
amplified = np.clip(samples * self.mic_gain, -32768, 32767) amplified = np.clip(samples * self.mic_gain, -32768, 32767)
@@ -136,6 +142,7 @@ class SoundReceiver:
try: try:
self.stream.stop_stream() self.stream.stop_stream()
self.stream.close() self.stream.close()
if self._owns_pyaudio:
self.p.terminate() self.p.terminate()
except: pass except: pass
@@ -151,7 +158,7 @@ class LiveMicRelay:
stop() ends the remote nc process, which ends aplay with it. stop() ends the remote nc process, which ends aplay with it.
""" """
def __init__(self, nao_ip, ssh_user="nao", ssh_password="nao", def __init__(self, nao_ip, ssh_user="nao", ssh_password="nao",
port=5555, rate=16000, input_device_index=None): port=5555, rate=16000, input_device_index=None, pyaudio_instance=None):
self.nao_ip = nao_ip self.nao_ip = nao_ip
self.ssh_user = ssh_user self.ssh_user = ssh_user
self.ssh_password = ssh_password self.ssh_password = ssh_password
@@ -165,7 +172,10 @@ class LiveMicRelay:
self._sock = None self._sock = None
self._stream = None self._stream = None
self._thread = None self._thread = None
self._p = pyaudio.PyAudio() if HAS_AUDIO else None # Reuse the app's single PyAudio context - a second independent one
# opened from a background thread crashes PortAudio's PulseAudio
# backend (pa_atomic_load assertion).
self._p = pyaudio_instance if pyaudio_instance is not None else (pyaudio.PyAudio() if HAS_AUDIO else None)
def start(self): def start(self):
if self.active: if self.active:
@@ -266,8 +276,13 @@ class NaoTeleop:
self.mic_channel = mic_channel self.mic_channel = mic_channel
self.mic_gain = mic_gain self.mic_gain = mic_gain
# One shared PyAudio context for the whole app - SoundReceiver (mic
# playback) and LiveMicRelay (push-to-talk capture) both use it,
# since two separate contexts crash PortAudio's PulseAudio backend.
self._pyaudio = pyaudio.PyAudio() if HAS_AUDIO else None
self.mic_relay = LiveMicRelay(nao_ip, ssh_user=nao_ssh_user, ssh_password=nao_ssh_password, self.mic_relay = LiveMicRelay(nao_ip, ssh_user=nao_ssh_user, ssh_password=nao_ssh_password,
port=relay_port, rate=relay_rate) port=relay_port, rate=relay_rate, pyaudio_instance=self._pyaudio)
self.ptt_active = False self.ptt_active = False
self.running = True self.running = True
@@ -328,7 +343,8 @@ class NaoTeleop:
def _init_mic_stream(self): def _init_mic_stream(self):
if not self.audio_device: return if not self.audio_device: return
try: try:
self.sound_receiver = SoundReceiver(self.audio_device_index, self.mic_gain) self.sound_receiver = SoundReceiver(self.audio_device_index, self.mic_gain,
pyaudio_instance=self._pyaudio)
self.session.registerService(self.audio_service_name, self.sound_receiver) self.session.registerService(self.audio_service_name, self.sound_receiver)
time.sleep(0.5) time.sleep(0.5)
self.audio_device.setClientPreferences(self.audio_service_name, 16000, self.mic_channel, 0) self.audio_device.setClientPreferences(self.audio_service_name, 16000, self.mic_channel, 0)
@@ -605,9 +621,13 @@ class NaoTeleop:
if keys[pygame.K_v] and not self.ptt_active: if keys[pygame.K_v] and not self.ptt_active:
self.ptt_active = True self.ptt_active = True
self.mic_relay.start() self.mic_relay.start()
if getattr(self, "sound_receiver", None):
self.sound_receiver.muted = True
elif not keys[pygame.K_v] and self.ptt_active: elif not keys[pygame.K_v] and self.ptt_active:
self.ptt_active = False self.ptt_active = False
self.mic_relay.stop() self.mic_relay.stop()
if getattr(self, "sound_receiver", None):
self.sound_receiver.muted = False
speed_changed = False speed_changed = False
if keys[pygame.K_LEFTBRACKET]: if keys[pygame.K_LEFTBRACKET]:
@@ -721,6 +741,8 @@ class NaoTeleop:
self._safe("sound receiver close", self.sound_receiver.close) self._safe("sound receiver close", self.sound_receiver.close)
if hasattr(self, 'video') and self.video_client: if hasattr(self, 'video') and self.video_client:
self._safe("video unsubscribe", lambda: self.video.unsubscribe(self.video_client)) self._safe("video unsubscribe", lambda: self.video.unsubscribe(self.video_client))
if getattr(self, "_pyaudio", None):
self._safe("pyaudio terminate", self._pyaudio.terminate)
self._safe("rest", self.motion.rest) self._safe("rest", self.motion.rest)
pygame.quit() pygame.quit()