added V for voice chat, it outputs my laptop mic out the nao speaker
This commit is contained in:
+140
-5
@@ -14,6 +14,7 @@ import subprocess
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import socket
|
||||||
|
|
||||||
# Import the new music module
|
# Import the new music module
|
||||||
from naomusic import NaoMusicPlayer
|
from naomusic import NaoMusicPlayer
|
||||||
@@ -25,6 +26,13 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_AUDIO = False
|
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
|
# NAO MIC STREAM
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -131,11 +139,113 @@ class SoundReceiver:
|
|||||||
self.p.terminate()
|
self.p.terminate()
|
||||||
except: pass
|
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
|
# MAIN TELEOP
|
||||||
# ==========================================
|
# ==========================================
|
||||||
class NaoTeleop:
|
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.session = session
|
||||||
self.nao_ip = nao_ip
|
self.nao_ip = nao_ip
|
||||||
self.motion = session.service("ALMotion")
|
self.motion = session.service("ALMotion")
|
||||||
@@ -156,6 +266,10 @@ class NaoTeleop:
|
|||||||
self.mic_channel = mic_channel
|
self.mic_channel = mic_channel
|
||||||
self.mic_gain = mic_gain
|
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.running = True
|
||||||
self.head_yaw = 0.0
|
self.head_yaw = 0.0
|
||||||
self.head_pitch = 0.0
|
self.head_pitch = 0.0
|
||||||
@@ -284,7 +398,7 @@ class NaoTeleop:
|
|||||||
if self.typing_mode: return
|
if self.typing_mode: return
|
||||||
print("🖐️ SIX... SEVEN...")
|
print("🖐️ SIX... SEVEN...")
|
||||||
try:
|
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)
|
self.motion.setStiffnesses(["RArm", "LArm"], 1.0)
|
||||||
|
|
||||||
@@ -387,7 +501,7 @@ class NaoTeleop:
|
|||||||
self._safe("moveInit", self.motion.moveInit)
|
self._safe("moveInit", self.motion.moveInit)
|
||||||
print("🦶 Walk engine ready")
|
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:
|
try:
|
||||||
while self.running:
|
while self.running:
|
||||||
@@ -487,7 +601,14 @@ class NaoTeleop:
|
|||||||
|
|
||||||
if not self.typing_mode and not self.music_search_mode:
|
if not self.typing_mode and not self.music_search_mode:
|
||||||
keys = pygame.key.get_pressed()
|
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
|
speed_changed = False
|
||||||
if keys[pygame.K_LEFTBRACKET]:
|
if keys[pygame.K_LEFTBRACKET]:
|
||||||
self.walk_speed = max(0.1, self.walk_speed - 0.01)
|
self.walk_speed = max(0.1, self.walk_speed - 0.01)
|
||||||
@@ -592,6 +713,8 @@ class NaoTeleop:
|
|||||||
print("🛑 Shutting down...")
|
print("🛑 Shutting down...")
|
||||||
self._safe("stopMove", self.motion.stopMove)
|
self._safe("stopMove", self.motion.stopMove)
|
||||||
self._safe("music stop", self.music.stop)
|
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:
|
if self.audio_device:
|
||||||
self._safe("audio unsubscribe", lambda: self.audio_device.unsubscribe(self.audio_service_name))
|
self._safe("audio unsubscribe", lambda: self.audio_device.unsubscribe(self.audio_service_name))
|
||||||
if getattr(self, "sound_receiver", None):
|
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-channel", choices=MIC_CHANNELS.keys(), default="left")
|
||||||
parser.add_argument("--mic-gain", type=float, default=8.0)
|
parser.add_argument("--mic-gain", type=float, default=8.0)
|
||||||
parser.add_argument("--video-scale", type=float, default=2.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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
session = qi.Session()
|
session = qi.Session()
|
||||||
@@ -626,4 +757,8 @@ if __name__ == "__main__":
|
|||||||
audio_device_index=args.audio_device_index,
|
audio_device_index=args.audio_device_index,
|
||||||
mic_channel=MIC_CHANNELS[args.mic_channel],
|
mic_channel=MIC_CHANNELS[args.mic_channel],
|
||||||
mic_gain=args.mic_gain,
|
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user