1
0
forked from zbw/yiliao2026
Files
yiliao2026/src/vlm_detect/vlm_backup/tts_node.py

62 lines
2.4 KiB
Python

#!/usr/bin/env python3
import rclpy, subprocess, os, wave
from rclpy.node import Node
from std_msgs.msg import String
from piper import PiperVoice
from piper.config import SynthesisConfig
MODEL_PATH = '/home/sunrise/tts_model/zh_CN-huayan-medium.onnx'
class TTSNode(Node):
def __init__(self):
super().__init__('tts_node')
self.declare_parameter('audio_sink', 'alsa_output.usb-C-Media_Electronics_Inc._USB_Audio_Device-00.analog-stereo')
self.declare_parameter('result_topic', '/vlm_result')
self.declare_parameter('tts_speed', 1.5)
self.audio_sink = self.get_parameter('audio_sink').value
result_topic = self.get_parameter('result_topic').value
tts_speed = self.get_parameter('tts_speed').value
self.length_scale = 1.0 / tts_speed
self.espeak_speed = int(175 * tts_speed) # espeak default=175wpm, scale with tts_speed
self.sub = self.create_subscription(String, result_topic, self.callback, 10)
self.get_logger().info(f'Piper TTS started | voice=zh_CN-huayan | speed={tts_speed}x | sink={self.audio_sink}')
self.get_logger().info(f'Loading model: {MODEL_PATH}')
self.voice = PiperVoice.load(MODEL_PATH)
self.get_logger().info('Model loaded OK')
def callback(self, msg):
text = msg.data.strip()
if not text:
return
self.get_logger().info(f'TTS: {text}')
try:
syn_config = SynthesisConfig(length_scale=self.length_scale)
wav_path = '/tmp/tts_out.wav'
with wave.open(wav_path, 'wb') as wf:
self.voice.synthesize_wav(text, wf, syn_config=syn_config)
subprocess.Popen(
['paplay', f'--device={self.audio_sink}', wav_path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except Exception as e:
self.get_logger().error(f'Piper TTS failed, fallback espeak: {e}')
subprocess.Popen(
['espeak-ng', '-v', 'cmn', '-s', str(self.espeak_speed), text],
env={**os.environ, 'PULSE_SINK': self.audio_sink},
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def main(args=None):
rclpy.init(args=args)
node = TTSNode()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()