From f806fa8efcb6e15df881c39875d1c7b0e7d04718 Mon Sep 17 00:00:00 2001 From: Lucca Pirovano Date: Thu, 16 Jul 2026 17:09:07 -0400 Subject: [PATCH] added auto mute when holding v so i dont hear myself --- naowalk.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/naowalk.py b/naowalk.py index d8921f8..c786028 100644 --- a/naowalk.py +++ b/naowalk.py @@ -40,7 +40,7 @@ class SoundReceiver: PREBUFFER_CHUNKS = 3 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.underrun_count = 0 self.chunks_received = 0 @@ -48,9 +48,12 @@ class SoundReceiver: self._peak_since_print = 0 self._last_status_print = 0 self.mic_gain = float(mic_gain) + self.muted = False + self._owns_pyaudio = False 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:") for i in range(self.p.get_device_count()): info = self.p.get_device_info_by_index(i) @@ -117,6 +120,9 @@ class SoundReceiver: def _write_chunk(self, raw_bytes): try: + if self.muted: + self.stream.write(b"\x00" * len(raw_bytes)) + return samples = np.frombuffer(raw_bytes, dtype=np.int16).astype(np.float32) if samples.size: amplified = np.clip(samples * self.mic_gain, -32768, 32767) @@ -136,7 +142,8 @@ class SoundReceiver: try: self.stream.stop_stream() self.stream.close() - self.p.terminate() + if self._owns_pyaudio: + self.p.terminate() except: pass # ========================================== @@ -151,7 +158,7 @@ class LiveMicRelay: stop() ends the remote nc process, which ends aplay with it. """ 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.ssh_user = ssh_user self.ssh_password = ssh_password @@ -165,7 +172,10 @@ class LiveMicRelay: self._sock = None self._stream = 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): if self.active: @@ -266,8 +276,13 @@ class NaoTeleop: self.mic_channel = mic_channel 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, - port=relay_port, rate=relay_rate) + port=relay_port, rate=relay_rate, pyaudio_instance=self._pyaudio) self.ptt_active = False self.running = True @@ -328,7 +343,8 @@ class NaoTeleop: def _init_mic_stream(self): if not self.audio_device: return 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) time.sleep(0.5) 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: self.ptt_active = True 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: self.ptt_active = False self.mic_relay.stop() + if getattr(self, "sound_receiver", None): + self.sound_receiver.muted = False speed_changed = False if keys[pygame.K_LEFTBRACKET]: @@ -721,6 +741,8 @@ class NaoTeleop: self._safe("sound receiver close", self.sound_receiver.close) if hasattr(self, 'video') and 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) pygame.quit()