added V for voice chat, it outputs my laptop mic out the nao speaker

This commit is contained in:
Lucca Pirovano
2026-07-16 17:05:29 -04:00
parent 929687dea5
commit e1b5e3ab76
+139 -4
View File
@@ -14,6 +14,7 @@ import subprocess
import os
import json
import re
import socket
# Import the new music module
from naomusic import NaoMusicPlayer
@@ -25,6 +26,13 @@ try:
except ImportError:
HAS_AUDIO = False
# --- SSH for live mic relay (push-to-talk) ---
try:
import paramiko
HAS_SSH = True
except ImportError:
HAS_SSH = False
# ==========================================
# NAO MIC STREAM
# ==========================================
@@ -131,11 +139,113 @@ class SoundReceiver:
self.p.terminate()
except: pass
# ==========================================
# LIVE MIC RELAY (push-to-talk over SSH)
# ==========================================
class LiveMicRelay:
"""
Push-to-talk: streams the local mic straight to NAO's own speaker.
On start(), SSHes into the robot and launches `nc -l | aplay` there,
then opens a plain TCP socket to that listener and pipes raw mic
audio into it for as long as the key is held. Closing the socket on
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):
self.nao_ip = nao_ip
self.ssh_user = ssh_user
self.ssh_password = ssh_password
self.port = port
self.rate = rate
self.input_device_index = input_device_index
self.active = False
self._ssh_client = None
self._ssh_channel = None
self._sock = None
self._stream = None
self._thread = None
self._p = pyaudio.PyAudio() if HAS_AUDIO else None
def start(self):
if self.active:
return
if not HAS_AUDIO:
print("🎙️ Live mic needs pyaudio installed")
return
if not HAS_SSH:
print("🎙️ Live mic needs paramiko: pip install paramiko")
return
self.active = True
self._thread = threading.Thread(target=self._run, daemon=True)
self._thread.start()
def stop(self):
self.active = False
if self._sock:
try: self._sock.close()
except: pass
self._sock = None
if self._stream:
try:
self._stream.stop_stream()
self._stream.close()
except: pass
self._stream = None
if self._ssh_channel:
try: self._ssh_channel.close()
except: pass
self._ssh_channel = None
if self._ssh_client:
try: self._ssh_client.close()
except: pass
self._ssh_client = None
def _run(self):
try:
self._ssh_client = paramiko.SSHClient()
self._ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self._ssh_client.connect(self.nao_ip, username=self.ssh_user,
password=self.ssh_password, timeout=5)
# Raw 16-bit mono PCM in, straight to ALSA out. If pitch/speed
# sounds off, the robot's ALSA default rate doesn't match
# self.rate - pass --relay-rate to match it (try 48000).
remote_cmd = (
f"nc -l -p {self.port} | "
f"aplay -q -r {self.rate} -f S16_LE -c 1 -t raw -"
)
print("🎙️ Talk relay: starting remote listener...")
self._ssh_channel = self._ssh_client.get_transport().open_session()
self._ssh_channel.exec_command(remote_cmd)
time.sleep(0.4) # give nc a moment to bind before we connect
self._sock = socket.create_connection((self.nao_ip, self.port), timeout=5)
self._stream = self._p.open(format=pyaudio.paInt16, channels=1,
rate=self.rate, input=True,
input_device_index=self.input_device_index,
frames_per_buffer=1024)
print("🎙️ Talk relay: live")
while self.active:
try:
chunk = self._stream.read(1024, exception_on_overflow=False)
self._sock.sendall(chunk)
except Exception:
break
except Exception as e:
print(f"🎙️ Talk relay error: {e}")
finally:
self.stop()
print("🎙️ Talk relay: stopped")
# ==========================================
# MAIN TELEOP
# ==========================================
class NaoTeleop:
def __init__(self, session, nao_ip, audio_device_index=None, mic_channel=1, mic_gain=8.0, video_scale=2.0):
def __init__(self, session, nao_ip, audio_device_index=None, mic_channel=1, mic_gain=8.0, video_scale=2.0,
nao_ssh_user="nao", nao_ssh_password="nao", relay_port=5555, relay_rate=16000):
self.session = session
self.nao_ip = nao_ip
self.motion = session.service("ALMotion")
@@ -156,6 +266,10 @@ class NaoTeleop:
self.mic_channel = mic_channel
self.mic_gain = mic_gain
self.mic_relay = LiveMicRelay(nao_ip, ssh_user=nao_ssh_user, ssh_password=nao_ssh_password,
port=relay_port, rate=relay_rate)
self.ptt_active = False
self.running = True
self.head_yaw = 0.0
self.head_pitch = 0.0
@@ -284,7 +398,7 @@ class NaoTeleop:
if self.typing_mode: return
print("🖐️ SIX... SEVEN...")
try:
self.tts.say("Six, seven")
threading.Thread(target=self.tts.say, args=("six seven",), daemon=True).start()
self.motion.setStiffnesses(["RArm", "LArm"], 1.0)
@@ -387,7 +501,7 @@ class NaoTeleop:
self._safe("moveInit", self.motion.moveInit)
print("🦶 Walk engine ready")
print("\n🎮 Controls: WASD/Arrows=Walk | [/]=Speed | IJKL=Head | SPACE=Reset head | 1=Wave | 2=Cena | 3=6-7 | M=Music search | X=Stop | -/==Volume | T=TTS | ESC=Quit")
print("\n🎮 Controls: WASD/Arrows=Walk | [/]=Speed | IJKL=Head | SPACE=Reset head | 1=Wave | 2=Cena | 3=6-7 | M=Music search | X=Stop | -/==Volume | T=TTS | V=Push-to-talk (hold) | ESC=Quit")
try:
while self.running:
@@ -488,6 +602,13 @@ class NaoTeleop:
if not self.typing_mode and not self.music_search_mode:
keys = pygame.key.get_pressed()
if keys[pygame.K_v] and not self.ptt_active:
self.ptt_active = True
self.mic_relay.start()
elif not keys[pygame.K_v] and self.ptt_active:
self.ptt_active = False
self.mic_relay.stop()
speed_changed = False
if keys[pygame.K_LEFTBRACKET]:
self.walk_speed = max(0.1, self.walk_speed - 0.01)
@@ -592,6 +713,8 @@ class NaoTeleop:
print("🛑 Shutting down...")
self._safe("stopMove", self.motion.stopMove)
self._safe("music stop", self.music.stop)
if getattr(self, "mic_relay", None):
self._safe("mic relay stop", self.mic_relay.stop)
if self.audio_device:
self._safe("audio unsubscribe", lambda: self.audio_device.unsubscribe(self.audio_service_name))
if getattr(self, "sound_receiver", None):
@@ -612,6 +735,14 @@ if __name__ == "__main__":
parser.add_argument("--mic-channel", choices=MIC_CHANNELS.keys(), default="left")
parser.add_argument("--mic-gain", type=float, default=8.0)
parser.add_argument("--video-scale", type=float, default=2.0)
parser.add_argument("--nao-ssh-user", type=str, default="nao",
help="SSH login for the live mic relay (push-to-talk)")
parser.add_argument("--nao-ssh-password", type=str, default="nao",
help="SSH password for the live mic relay (push-to-talk)")
parser.add_argument("--relay-port", type=int, default=5555,
help="TCP port used to stream mic audio to the robot's nc listener")
parser.add_argument("--relay-rate", type=int, default=16000,
help="Sample rate for the live mic relay - try 48000 if audio sounds sped up/slow")
args = parser.parse_args()
session = qi.Session()
@@ -626,4 +757,8 @@ if __name__ == "__main__":
audio_device_index=args.audio_device_index,
mic_channel=MIC_CHANNELS[args.mic_channel],
mic_gain=args.mic_gain,
video_scale=args.video_scale).run()
video_scale=args.video_scale,
nao_ssh_user=args.nao_ssh_user,
nao_ssh_password=args.nao_ssh_password,
relay_port=args.relay_port,
relay_rate=args.relay_rate).run()