宇树机器人G1二次开发:语音对话完整功能实现(打断、停止、待命、激活、有线/无线话筒)
·
1. 引言
宇树机器人G1是一款功能强大的通用人形机器人,其开放的SDK为开发者提供了广阔的二次开发空间。语音交互是机器人实现自然、高效人机交互的核心能力。本文将详细介绍如何在宇树G1机器人上实现一套完整的语音对话系统,涵盖语音对话、打断对话、停止对话、进入待命、激活对话等核心功能,并提供有线话筒与无线话筒两种方案的完整代码实现。
通过本文,你将能够为你的G1机器人赋予“耳朵”和“嘴巴”,使其能够听懂指令、进行多轮对话,并灵活响应用户的交互意图。
2. 环境准备与依赖安装
2.1 硬件准备
- 宇树机器人G1:确保机器人系统已启动,网络连接正常。
- 音频输入设备:
- 有线话筒方案:USB接口的麦克风,直接插入机器人主控计算机的USB口。
- 无线话筒方案:支持蓝牙或2.4G无线连接的麦克风,需确保与机器人主控计算机配对成功。
- 音频输出设备:机器人内置扬声器或外接音箱。
2.2 软件环境
- 操作系统:Ubuntu 20.04/22.04 (推荐,与官方SDK兼容性最佳)
- Python版本:Python 3.8+
- 宇树G1 SDK:确保已安装并配置好
unitree_sdk2。
2.3 Python依赖包安装
在机器人主控计算机上执行以下命令安装必要的Python库:
pip install pyaudio wave numpy sounddevice soundfile
pip install openai-whisper # 可选,用于本地语音识别
pip install speechrecognition
pip install pydub
pip install pyttsx3
# 如果使用在线ASR服务(如百度、阿里云、Azure等),还需安装对应SDK
# pip install baidu-aip
3. 核心功能模块设计
整个语音对话系统可分为以下几个核心模块:
渲染错误: Mermaid 渲染失败: Lexical error on line 9. Unrecognized text.
...] subgraph “用户交互” H[用户说
---------------------^
4. 完整代码实现
4.1 主程序框架:voice_controller.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
宇树G1机器人语音对话控制器
功能:语音对话、打断、停止、待命、激活、支持有线/无线话筒
作者:CSDN博主
"""
import threading
import time
import queue
import json
from enum import Enum
import logging
# 配置日志
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class RobotState(Enum):
"""机器人对话状态枚举"""
STANDBY = "standby" # 待命状态,等待唤醒
LISTENING = "listening" # 聆听状态,正在接收语音
PROCESSING = "processing" # 处理中,ASR或NLU
SPEAKING = "speaking" # 播报状态,TTS输出中
STOPPED = "stopped" # 强制停止状态
class VoiceController:
"""语音对话主控制器"""
def __init__(self, mic_type='wired'):
"""
初始化语音控制器
:param mic_type: 话筒类型,'wired' 或 'wireless'
"""
self.state = RobotState.STANDBY
self.mic_type = mic_type
self.audio_queue = queue.Queue(maxsize=10)
self.stop_event = threading.Event()
self.interrupt_event = threading.Event()
# 初始化各模块
self.audio_input = self._init_audio_input(mic_type)
self.vad_detector = VADetector()
self.asr_engine = ASREngine()
self.tts_engine = TTSEngine()
self.dialog_manager = DialogManager()
# 唤醒词和打断词
self.wake_words = ["小宇", "机器人", "嗨G1"]
self.interrupt_words = ["停", "停止", "闭嘴", "打断"]
self.standby_words = ["待命", "休息", "退下"]
logger.info(f"语音控制器初始化完成,话筒类型: {mic_type}")
def _init_audio_input(self, mic_type):
"""初始化音频输入设备"""
if mic_type == 'wired':
return WiredMicrophone()
elif mic_type == 'wireless':
return WirelessMicrophone()
else:
raise ValueError(f"不支持的话筒类型: {mic_type}")
def start(self):
"""启动语音对话系统"""
logger.info("启动语音对话系统...")
self.stop_event.clear()
self.interrupt_event.clear()
# 启动音频采集线程
audio_thread = threading.Thread(target=self._audio_capture_loop, daemon=True)
audio_thread.start()
# 启动主处理线程
main_thread = threading.Thread(target=self._main_loop, daemon=True)
main_thread.start()
logger.info("语音对话系统已启动,等待唤醒...")
return audio_thread, main_thread
def stop(self):
"""停止整个语音系统"""
logger.info("停止语音对话系统...")
self.stop_event.set()
self.state = RobotState.STOPPED
self.tts_engine.stop() # 停止当前播报
def interrupt(self):
"""打断当前对话"""
logger.info("收到打断指令")
self.interrupt_event.set()
if self.state == RobotState.SPEAKING:
self.tts_engine.interrupt()
self.state = RobotState.STANDBY
self.tts_engine.speak("已打断")
def enter_standby(self):
"""进入待命状态"""
logger.info("进入待命状态")
self.state = RobotState.STANDBY
self.tts_engine.speak("已进入待命模式")
def activate(self):
"""激活对话(从待命状态唤醒)"""
logger.info("激活对话")
self.state = RobotState.LISTENING
self.tts_engine.speak("我在,请说")
def _audio_capture_loop(self):
"""音频采集循环"""
try:
self.audio_input.start_stream()
logger.info("音频采集已开始")
while not self.stop_event.is_set():
audio_data = self.audio_input.read_chunk()
if audio_data is not None:
# 简单的VAD检测,避免空音频入队
if self.vad_detector.is_speech(audio_data):
self.audio_queue.put(audio_data)
time.sleep(0.01)
except Exception as e:
logger.error(f"音频采集异常: {e}")
finally:
self.audio_input.stop_stream()
def _main_loop(self):
"""主处理循环"""
while not self.stop_event.is_set():
try:
# 状态机处理
if self.state == RobotState.STANDBY:
self._handle_standby()
elif self.state == RobotState.LISTENING:
self._handle_listening()
elif self.state == RobotState.PROCESSING:
self._handle_processing()
elif self.state == RobotState.SPEAKING:
self._handle_speaking()
time.sleep(0.05)
except Exception as e:
logger.error(f"主循环异常: {e}")
time.sleep(1)
def _handle_standby(self):
"""待命状态处理:检测唤醒词"""
if not self.audio_queue.empty():
audio_data = self.audio_queue.get()
text = self.asr_engine.recognize(audio_data)
if text and any(word in text for word in self.wake_words):
logger.info(f"检测到唤醒词: {text}")
self.activate()
def _handle_listening(self):
"""聆听状态处理:收集语音并识别"""
audio_chunks = []
silence_count = 0
max_silence = 20 # 静音帧数阈值
while silence_count < max_silence and not self.interrupt_event.is_set():
if not self.audio_queue.empty():
audio_data = self.audio_queue.get()
if self.vad_detector.is_speech(audio_data):
audio_chunks.append(audio_data)
silence_count = 0
else:
silence_count += 1
time.sleep(0.01)
if audio_chunks and not self.interrupt_event.is_set():
# 合并音频并识别
full_audio = b''.join(audio_chunks)
text = self.asr_engine.recognize(full_audio)
if text:
logger.info(f"识别结果: {text}")
self.state = RobotState.PROCESSING
self._process_user_input(text)
def _process_user_input(self, text):
"""处理用户输入文本"""
# 检查是否为控制指令
if any(word in text for word in self.interrupt_words):
self.interrupt()
return
if any(word in text for word in self.standby_words):
self.enter_standby()
return
# 正常对话处理
response = self.dialog_manager.get_response(text)
if response:
self.state = RobotState.SPEAKING
self.tts_engine.speak(response)
# 播报完成后回到聆听状态(除非被打断)
if not self.interrupt_event.is_set():
self.state = RobotState.LISTENING
else:
self.state = RobotState.LISTENING
def _handle_processing(self):
"""处理中状态:短暂等待"""
time.sleep(0.1)
def _handle_speaking(self):
"""播报状态:检查是否被打断"""
if self.interrupt_event.is_set():
self.interrupt_event.clear()
self.state = RobotState.STANDBY
# 主程序入口
if __name__ == "__main__":
# 创建控制器(可选择话筒类型)
controller = VoiceController(mic_type='wired') # 或 'wireless'
try:
# 启动系统
threads = controller.start()
# 保持主线程运行
while True:
cmd = input("输入命令 (stop/exit退出): ").strip().lower()
if cmd in ['stop', 'exit', 'quit']:
controller.stop()
break
elif cmd == 'interrupt':
controller.interrupt()
elif cmd == 'standby':
controller.enter_standby()
elif cmd == 'activate':
controller.activate()
time.sleep(0.1)
except KeyboardInterrupt:
logger.info("收到中断信号,停止程序...")
controller.stop()
except Exception as e:
logger.error(f"程序运行异常: {e}")
finally:
logger.info("程序结束")
4.2 音频输入模块:audio_input.py
import pyaudio
import numpy as np
import sounddevice as sd
import threading
import time
class WiredMicrophone:
"""有线话筒采集(基于PyAudio)"""
def __init__(self, rate=16000, chunksize=1024, channels=1):
self.rate = rate
self.chunksize = chunksize
self.channels = channels
self.p = pyaudio.PyAudio()
self.stream = None
def start_stream(self):
"""开始音频流"""
self.stream = self.p.open(
format=pyaudio.paInt16,
channels=self.channels,
rate=self.rate,
input=True,
frames_per_buffer=self.chunksize
)
def read_chunk(self):
"""读取一个音频块"""
if self.stream:
try:
data = self.stream.read(self.chunksize, exception_on_overflow=False)
return data
except Exception as e:
print(f"读取音频数据失败: {e}")
return None
return None
def stop_stream(self):
"""停止音频流"""
if self.stream:
self.stream.stop_stream()
self.stream.close()
class WirelessMicrophone:
"""无线话筒采集(基于sounddevice,支持蓝牙)"""
def __init__(self, rate=16000, chunksize=1024):
self.rate = rate
self.chunksize = chunksize
self.buffer = []
self.lock = threading.Lock()
self.callback_running = False
def _audio_callback(self, indata, frames, time, status):
"""音频回调函数"""
if status:
print(f"音频流状态: {status}")
with self.lock:
self.buffer.append(indata.copy())
def start_stream(self):
"""开始音频流"""
self.buffer = []
self.callback_running = True
# 查找无线音频设备
devices = sd.query_devices()
wireless_device = None
for i, dev in enumerate(devices):
if 'bluetooth' in dev['name'].lower() or 'wireless' in dev['name'].lower():
wireless_device = i
break
if wireless_device is None:
print("警告: 未找到无线音频设备,使用默认设备")
wireless_device = sd.default.device[0]
# 启动音频流
self.stream = sd.InputStream(
device=wireless_device,
samplerate=self.rate,
channels=1,
callback=self._audio_callback,
blocksize=self.chunksize
)
self.stream.start()
def read_chunk(self):
"""读取一个音频块"""
with self.lock:
if len(self.buffer) > 0:
# 合并缓冲区中的数据
if len(self.buffer) > 1:
data = np.concatenate(self.buffer, axis=0)
else:
data = self.buffer[0]
self.buffer = []
# 转换为字节数据
audio_bytes = (data * 32767).astype(np.int16).tobytes()
return audio_bytes
return None
def stop_stream(self):
"""停止音频流"""
if hasattr(self, 'stream') and self.stream:
self.stream.stop()
self.stream.close()
self.callback_running = False
4.3 语音活动检测:vad.py
import numpy as np
import webrtcvad
import collections
class VADetector:
"""语音活动检测器"""
def __init__(self, mode=3, sample_rate=16000):
"""
:param mode: 0-3,越大越激进(3最严格)
:param sample_rate: 采样率,支持8000, 16000, 32000, 48000
"""
self.vad = webrtcvad.Vad(mode)
self.sample_rate = sample_rate
self.frame_duration = 30 # 毫秒
self.frame_size = int(sample_rate * self.frame_duration / 1000)
def is_speech(self, audio_bytes):
"""检测音频中是否包含语音"""
if len(audio_bytes) < self.frame_size * 2:
return False
# 将音频分割成帧
frames = self._frame_generator(audio_bytes)
# 统计语音帧比例
speech_frames = 0
total_frames = 0
for frame in frames:
is_speech = self.vad.is_speech(frame, self.sample_rate)
if is_speech:
speech_frames += 1
total_frames += 1
# 如果超过30%的帧是语音,则认为包含语音
return total_frames > 0 and (speech_frames / total_frames) > 0.3
def _frame_generator(self, audio_bytes):
"""生成音频帧"""
n = len(audio_bytes)
offset = 0
while offset + self.frame_size * 2 <= n:
yield audio_bytes[offset:offset + self.frame_size * 2]
offset += self.frame_size * 2
4.4 语音识别模块:asr.py
import speech_recognition as sr
import whisper
import io
import wave
class ASREngine:
"""语音识别引擎(支持多种后端)"""
def __init__(self, engine_type='google'):
"""
:param engine_type: 'google'|'whisper'|'baidu'|'azure'
"""
self.engine_type = engine_type
self.recognizer = sr.Recognizer()
if engine_type == 'whisper':
# 加载Whisper模型(首次使用会自动下载)
self.whisper_model = whisper.load_model("base")
def recognize(self, audio_bytes):
"""识别音频数据"""
if not audio_bytes:
return None
try:
if self.engine_type == 'google':
return self._recognize_google(audio_bytes)
elif self.engine_type == 'whisper':
return self._recognize_whisper(audio_bytes)
elif self.engine_type == 'baidu':
return self._recognize_baidu(audio_bytes)
else:
#
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)