跳到主要内容
版本:V2.0.4.x

8.9 语音

8.9.1 ASR 拾音

  • 说明:语音识别接口,适用于天工行者·无界和天工行者·无疆。

  • 接收方式:topic

  • 话题名称:/xunfei/aiui_msg

  • 数据类型:std_msgs::msg::String

  • 数据格式:JSON

    {
    "type": "aiui_event",
    "content": {
    "eventType": 1,
    "info": {
    "data": [
    {
    "params": { "sub": "iat" },
    "content": [{ "dte": "utf8", "dtf": "json", "cnt_id": "0" } ]
    }
    ]
    },
    "result": {
    "text": {
    "bg": 0, "sn": 1, "ws": [
    {"bg": 0,"cw": [{"w": "叫","sc": 0}]},
    {"bg": 0,"cw": [{"w": "什么","sc": 0}]},
    {"bg": 0,"cw": [{"w": "名字","sc": 0}]}
    ],
    "ls": false, "ed": 0
    }
    }
    }
    }

在 192.168.41.2 的orin板上先启动讯飞服务,可用tmux:

. ~/voice_ws/install/setup.bash
ros2 launch xunfei_dev_socket xunfei_dev_all.launch.py

启动服务后,站在天工行者面前说话,ros2 topic echo /xunfei/aiui_msg 有输出,但是文本看不完整,可以用python代码来尝试,参考代码如下(仅供参考):

#!/usr/bin/env python3
from datetime import datetime
import glob
import json
import os
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

MAX_FILES = 100
SAVE_DIR = 'json_data'

class ASRMonitor(Node):
def __init__(self):
super().__init__('asr_monitor')
self.topic_name = '/xunfei/aiui_msg'
self.subscription = self.create_subscription(
String,
self.topic_name,
self.msgs_callback,
10
)

os.makedirs(SAVE_DIR, exist_ok=True)
self.get_logger().info(f"已订阅话题:{self.topic_name}")

def msgs_callback(self, msg: String):
json_data = None
try:
json_data = json.loads(msg.data)
except json.JSONDecodeError:
self.get_logger().warn("Received invalid JSON. Skipping.")
return

# 如果在这里保存就所有消息都保存了,其实没有必要
# self.try_save_json_file(json_data)

self.cleanup_old_files()

self.try_parse_and_print(json_data)

def try_save_json_file(self, json_data):
try:
os.makedirs(SAVE_DIR,exist_ok=True)
timestamp = datetime.now().strftime('%H%M%S%f')[:-3] # 时分秒+毫秒(保留3位)
filename = f"{timestamp}.json"
filepath = os.path.join(SAVE_DIR, filename)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(json_data, f, ensure_ascii=False, indent=2)
self.get_logger().info(f"Saved JSON to {filepath}")
except Exception as e:
self.get_logger().error(f"Failed to write JSON file: {e}")

def try_parse_and_print(self, json_data):
if not json_data:
return
if "content" not in json_data or "result" not in json_data.get("content"):
return

result = json_data.get("content").get("result")
if not result:
return

if "cbm_meta" not in result:
return


if not result.get("cbm_meta"):
return
cbm_meta = result.get("cbm_meta")
if "text" not in cbm_meta:
return

text_data = json.loads(cbm_meta.get("text"))
if not text_data:
return
key = next(iter(text_data))
if key not in result:
return
result_text = result.get(key).get("text")
try:
res_data = json.loads(result_text)
print(json.dumps(res_data, indent=2, ensure_ascii=False))

except json.JSONDecodeError:
self.get_logger().info(f"content.result.{key}.text: ")
print(result_text)

self.try_save_json_file(json_data)

if result_text:
print("-" * 20 + "\r\n")

def cleanup_old_files(self):
files = sorted(
glob.glob(os.path.join(SAVE_DIR, '*.json')),
key=os.path.getmtime
)
if len(files) > MAX_FILES:
to_delete = files[:len(files) - MAX_FILES]
for f in to_delete:
try:
os.remove(f)
self.get_logger().info(f"Deleted old file: {f}")
except Exception as e:
self.get_logger().warn(f"Failed to delete {f}: {e}")

def main(args=None):
rclpy.init(args=args)
node = ASRMonitor()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()

if __name__ == '__main__':
main()

8.9.2 语音播放

  • 说明:语音播放接口,适用于天工行者·无界和天工行者·无疆。
  • 接收方式:topic
  • 话题名称:/xunfei/tts_play
  • 数据类型:std_msgs::msg::String
  • 数据格式:JSON
  1. 开始播放

    {
    "file": "path/to/audio/file.mp3"
    }
  2. 停止播放

    {
    "cmd": "stop"
    }
  3. 持续播放

    {
    "cmd": "append",
    "file": "path/to/audio/file.mp3"
    }
  • 示例命令
    . ~/voice_ws/install/setup.bash
    ros2 launch xunfei_dev_socket xunfei_dev_all.launch.py
    # 可尝试播放如下.mp3音频文件,播放前请确保这些文件存在,也可自行获取音频文件进行播放,请注意这里传的文件路径是orin板上的文件路径,也就是说音频文件需要放到orin板上,放到x86主控板上是无法播放的
    ros2 topic pub /xunfei/tts_play std_msgs/msg/String "{data: '{\"file\": \"/home/nvidia/data/speech/chenggong.mp3\"}'}"
    ros2 topic pub /xunfei/tts_play std_msgs/msg/String "{data: '{\"file\": \"/home/nvidia/data/speech/guzhang.mp3\"}'}"
    ros2 topic pub /xunfei/tts_play std_msgs/msg/String "{data: '{\"file\": \"/home/nvidia/data/speech/didianliang.wav\"}'}"
    ros2 topic pub /xunfei/tts_play std_msgs/msg/String "{data: '{\"file\": \"/home/nvidia/data/speech/kaishichongdian.mp3\"}'}"
    ros2 topic pub /xunfei/tts_play std_msgs/msg/String "{data: '{\"file\": \"/home/nvidia/data/speech/anjianyin.mp3\"}'}"